home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / IOBinding.py < prev    next >
Text File  |  2003-12-30  |  20KB  |  575 lines

  1. # changes by dscherer@cmu.edu
  2. #   - IOBinding.open() replaces the current window with the opened file,
  3. #     if the current window is both unmodified and unnamed
  4. #   - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
  5. #     end-of-line conventions, instead of relying on the standard library,
  6. #     which will only understand the local convention.
  7.  
  8. import os
  9. import types
  10. import sys
  11. import codecs
  12. import tempfile
  13. import tkFileDialog
  14. import tkMessageBox
  15. import re
  16. from Tkinter import *
  17. from SimpleDialog import SimpleDialog
  18.  
  19. from configHandler import idleConf
  20.  
  21. try:
  22.     from codecs import BOM_UTF8
  23. except ImportError:
  24.     # only available since Python 2.3
  25.     BOM_UTF8 = '\xef\xbb\xbf'
  26.  
  27. # Try setting the locale, so that we can find out
  28. # what encoding to use
  29. try:
  30.     import locale
  31.     locale.setlocale(locale.LC_CTYPE, "")
  32. except (ImportError, locale.Error):
  33.     pass
  34.  
  35. encoding = "ascii"
  36. if sys.platform == 'win32':
  37.     # On Windows, we could use "mbcs". However, to give the user
  38.     # a portable encoding name, we need to find the code page
  39.     try:
  40.         encoding = locale.getdefaultlocale()[1]
  41.         codecs.lookup(encoding)
  42.     except LookupError:
  43.         pass
  44. else:
  45.     try:
  46.         # Different things can fail here: the locale module may not be
  47.         # loaded, it may not offer nl_langinfo, or CODESET, or the
  48.         # resulting codeset may be unknown to Python. We ignore all
  49.         # these problems, falling back to ASCII
  50.         encoding = locale.nl_langinfo(locale.CODESET)
  51.         if encoding is None:
  52.             # situation occurs on Mac OS X
  53.             encoding = 'ascii'
  54.         codecs.lookup(encoding)
  55.     except (NameError, AttributeError, LookupError):
  56.         # Try getdefaultlocale well: it parses environment variables,
  57.         # which may give a clue. Unfortunately, getdefaultlocale has
  58.         # bugs that can cause ValueError.
  59.         try:
  60.             encoding = locale.getdefaultlocale()[1]
  61.             if encoding is None:
  62.                 # situation occurs on Mac OS X
  63.                 encoding = 'ascii'
  64.             codecs.lookup(encoding)
  65.         except (ValueError, LookupError):
  66.             pass
  67.  
  68. encoding = encoding.lower()
  69.  
  70. coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
  71.  
  72. class EncodingMessage(SimpleDialog):
  73.     "Inform user that an encoding declaration is needed."
  74.     def __init__(self, master, enc):
  75.         self.should_edit = False
  76.  
  77.         self.root = top = Toplevel(master)
  78.         top.bind("<Return>", self.return_event)
  79.         top.bind("<Escape>", self.do_ok)
  80.         top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
  81.         top.wm_title("I/O Warning")
  82.         top.wm_iconname("I/O Warning")
  83.         self.top = top
  84.  
  85.         l1 = Label(top,
  86.             text="Non-ASCII found, yet no encoding declared. Add a line like")
  87.         l1.pack(side=TOP, anchor=W)
  88.         l2 = Entry(top, font="courier")
  89.         l2.insert(0, "# -*- coding: %s -*-" % enc)
  90.         # For some reason, the text is not selectable anymore if the
  91.         # widget is disabled.
  92.         # l2['state'] = DISABLED
  93.         l2.pack(side=TOP, anchor = W, fill=X)
  94.         l3 = Label(top, text="to your file\n"
  95.                    "Choose OK to save this file as %s\n"
  96.                    "Edit your general options to silence this warning" % enc)
  97.         l3.pack(side=TOP, anchor = W)
  98.  
  99.         buttons = Frame(top)
  100.         buttons.pack(side=TOP, fill=X)
  101.         # Both return and cancel mean the same thing: do nothing
  102.         self.default = self.cancel = 0
  103.         b1 = Button(buttons, text="Ok", default="active",
  104.                     command=self.do_ok)
  105.         b1.pack(side=LEFT, fill=BOTH, expand=1)
  106.         b2 = Button(buttons, text="Edit my file",
  107.                     command=self.do_edit)
  108.         b2.pack(side=LEFT, fill=BOTH, expand=1)
  109.  
  110.         self._set_transient(master)
  111.  
  112.     def do_ok(self):
  113.         self.done(0)
  114.  
  115.     def do_edit(self):
  116.         self.done(1)
  117.  
  118. def coding_spec(str):
  119.     """Return the encoding declaration according to PEP 263.
  120.  
  121.     Raise LookupError if the encoding is declared but unknown.
  122.     """
  123.     # Only consider the first two lines
  124.     str = str.split("\n")[:2]
  125.     str = "\n".join(str)
  126.  
  127.     match = coding_re.search(str)
  128.     if not match:
  129.         return None
  130.     name = match.group(1)
  131.     # Check whether the encoding is known
  132.     import codecs
  133.     try:
  134.         codecs.lookup(name)
  135.     except LookupError:
  136.         # The standard encoding error does not indicate the encoding
  137.         raise LookupError, "Unknown encoding "+name
  138.     return name
  139.  
  140.  
  141. class IOBinding:
  142.  
  143.     def __init__(self, editwin):
  144.         self.editwin = editwin
  145.         self.text = editwin.text
  146.         self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
  147.         self.__id_save = self.text.bind("<<save-window>>", self.save)
  148.         self.__id_saveas = self.text.bind("<<save-window-as-file>>",
  149.                                           self.save_as)
  150.         self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
  151.                                             self.save_a_copy)
  152.         self.fileencoding = None
  153.         self.__id_print = self.text.bind("<<print-window>>", self.print_window)
  154.  
  155.     def close(self):
  156.         # Undo command bindings
  157.         self.text.unbind("<<open-window-from-file>>", self.__id_open)
  158.         self.text.unbind("<<save-window>>", self.__id_save)
  159.         self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
  160.         self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
  161.         self.text.unbind("<<print-window>>", self.__id_print)
  162.         # Break cycles
  163.         self.editwin = None
  164.         self.text = None
  165.         self.filename_change_hook = None
  166.  
  167.     def get_saved(self):
  168.         return self.editwin.get_saved()
  169.  
  170.     def set_saved(self, flag):
  171.         self.editwin.set_saved(flag)
  172.  
  173.     def reset_undo(self):
  174.         self.editwin.reset_undo()
  175.  
  176.     filename_change_hook = None
  177.  
  178.     def set_filename_change_hook(self, hook):
  179.         self.filename_change_hook = hook
  180.  
  181.     filename = None
  182.     dirname = None
  183.  
  184.     def set_filename(self, filename):
  185.         if filename and os.path.isdir(filename):
  186.             self.filename = None
  187.             self.dirname = filename
  188.         else:
  189.             self.filename = filename
  190.             self.dirname = None
  191.             self.set_saved(1)
  192.             if self.filename_change_hook:
  193.                 self.filename_change_hook()
  194.  
  195.     def open(self, event=None, editFile=None):
  196.         if self.editwin.flist:
  197.             if not editFile:
  198.                 filename = self.askopenfile()
  199.             else:
  200.                 filename=editFile
  201.             if filename:
  202.                 # If the current window has no filename and hasn't been
  203.                 # modified, we replace its contents (no loss).  Otherwise
  204.                 # we open a new window.  But we won't replace the
  205.                 # shell window (which has an interp(reter) attribute), which
  206.                 # gets set to "not modified" at every new prompt.
  207.                 try:
  208.                     interp = self.editwin.interp
  209.                 except:
  210.                     interp = None
  211.                 if not self.filename and self.get_saved() and not interp:
  212.                     self.editwin.flist.open(filename, self.loadfile)
  213.                 else:
  214.                     self.editwin.flist.open(filename)
  215.             else:
  216.                 self.text.focus_set()
  217.             return "break"
  218.         #
  219.         # Code for use outside IDLE:
  220.         if self.get_saved():
  221.             reply = self.maybesave()
  222.             if reply == "cancel":
  223.                 self.text.focus_set()
  224.                 return "break"
  225.         if not editFile:
  226.             filename = self.askopenfile()
  227.         else:
  228.             filename=editFile
  229.         if filename:
  230.             self.loadfile(filename)
  231.         else:
  232.             self.text.focus_set()
  233.         return "break"
  234.  
  235.     eol = r"(\r\n)|\n|\r"  # \r\n (Windows), \n (UNIX), or \r (Mac)
  236.     eol_re = re.compile(eol)
  237.     eol_convention = os.linesep # Default
  238.  
  239.     def loadfile(self, filename):
  240.         try:
  241.             # open the file in binary mode so that we can handle
  242.             #   end-of-line convention ourselves.
  243.             f = open(filename,'rb')
  244.             chars = f.read()
  245.             f.close()
  246.         except IOError, msg:
  247.             tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
  248.             return False
  249.  
  250.         chars = self.decode(chars)
  251.         # We now convert all end-of-lines to '\n's
  252.         firsteol = self.eol_re.search(chars)
  253.         if firsteol:
  254.             self.eol_convention = firsteol.group(0)
  255.             if isinstance(self.eol_convention, unicode):
  256.                 # Make sure it is an ASCII string
  257.                 self.eol_convention = self.eol_convention.encode("ascii")
  258.             chars = self.eol_re.sub(r"\n", chars)
  259.  
  260.         self.text.delete("1.0", "end")
  261.         self.set_filename(None)
  262.         self.text.insert("1.0", chars)
  263.         self.reset_undo()
  264.         self.set_filename(filename)
  265.         self.text.mark_set("insert", "1.0")
  266.         self.text.see("insert")
  267.         self.updaterecentfileslist(filename)
  268.         return True
  269.  
  270.     def decode(self, chars):
  271.         """Create a Unicode string
  272.  
  273.         If that fails, let Tcl try its best
  274.         """
  275.         # Check presence of a UTF-8 signature first
  276.         if chars.startswith(BOM_UTF8):
  277.             try:
  278.                 chars = chars[3:].decode("utf-8")
  279.             except UnicodeError:
  280.                 # has UTF-8 signature, but fails to decode...
  281.                 return chars
  282.             else:
  283.                 # Indicates that this file originally had a BOM
  284.                 self.fileencoding = BOM_UTF8
  285.                 return chars
  286.         # Next look for coding specification
  287.         try:
  288.             enc = coding_spec(chars)
  289.         except LookupError, name:
  290.             tkMessageBox.showerror(
  291.                 title="Error loading the file",
  292.                 message="The encoding '%s' is not known to this Python "\
  293.                 "installation. The file may not display correctly" % name,
  294.                 master = self.text)
  295.             enc = None
  296.         if enc:
  297.             try:
  298.                 return unicode(chars, enc)
  299.             except UnicodeError:
  300.                 pass
  301.         # If it is ASCII, we need not to record anything
  302.         try:
  303.             return unicode(chars, 'ascii')
  304.         except UnicodeError:
  305.             pass
  306.         # Finally, try the locale's encoding. This is deprecated;
  307.         # the user should declare a non-ASCII encoding
  308.         try:
  309.             chars = unicode(chars, encoding)
  310.             self.fileencoding = encoding
  311.         except UnicodeError:
  312.             pass
  313.         return chars
  314.  
  315.     def maybesave(self):
  316.         if self.get_saved():
  317.             return "yes"
  318.         message = "Do you want to save %s before closing?" % (
  319.             self.filename or "this untitled document")
  320.         m = tkMessageBox.Message(
  321.             title="Save On Close",
  322.             message=message,
  323.             icon=tkMessageBox.QUESTION,
  324.             type=tkMessageBox.YESNOCANCEL,
  325.             master=self.text)
  326.         reply = m.show()
  327.         if reply == "yes":
  328.             self.save(None)
  329.             if not self.get_saved():
  330.                 reply = "cancel"
  331.         self.text.focus_set()
  332.         return reply
  333.  
  334.     def save(self, event):
  335.         if not self.filename:
  336.             self.save_as(event)
  337.         else:
  338.             if self.writefile(self.filename):
  339.                 self.set_saved(1)
  340.                 try:
  341.                     self.editwin.store_file_breaks()
  342.                 except AttributeError:  # may be a PyShell
  343.                     pass
  344.         self.text.focus_set()
  345.         return "break"
  346.  
  347.     def save_as(self, event):
  348.         filename = self.asksavefile()
  349.         if filename:
  350.             if self.writefile(filename):
  351.                 self.set_filename(filename)
  352.                 self.set_saved(1)
  353.                 try:
  354.                     self.editwin.store_file_breaks()
  355.                 except AttributeError:
  356.                     pass
  357.         self.text.focus_set()
  358.         self.updaterecentfileslist(filename)
  359.         return "break"
  360.  
  361.     def save_a_copy(self, event):
  362.         filename = self.asksavefile()
  363.         if filename:
  364.             self.writefile(filename)
  365.         self.text.focus_set()
  366.         self.updaterecentfileslist(filename)
  367.         return "break"
  368.  
  369.     def writefile(self, filename):
  370.         self.fixlastline()
  371.         chars = self.encode(self.text.get("1.0", "end-1c"))
  372.         if self.eol_convention != "\n":
  373.             chars = chars.replace("\n", self.eol_convention)
  374.         try:
  375.             f = open(filename, "wb")
  376.             f.write(chars)
  377.             f.close()
  378.             return True
  379.         except IOError, msg:
  380.             tkMessageBox.showerror("I/O Error", str(msg),
  381.                                    master=self.text)
  382.             return False
  383.  
  384.     def encode(self, chars):
  385.         if isinstance(chars, types.StringType):
  386.             # This is either plain ASCII, or Tk was returning mixed-encoding
  387.             # text to us. Don't try to guess further.
  388.             return chars
  389.         # See whether there is anything non-ASCII in it.
  390.         # If not, no need to figure out the encoding.
  391.         try:
  392.             return chars.encode('ascii')
  393.         except UnicodeError:
  394.             pass
  395.         # If there is an encoding declared, try this first.
  396.         try:
  397.             enc = coding_spec(chars)
  398.             failed = None
  399.         except LookupError, msg:
  400.             failed = msg
  401.             enc = None
  402.         if enc:
  403.             try:
  404.                 return chars.encode(enc)
  405.             except UnicodeError:
  406.                 failed = "Invalid encoding '%s'" % enc
  407.         if failed:
  408.             tkMessageBox.showerror(
  409.                 "I/O Error",
  410.                 "%s. Saving as UTF-8" % failed,
  411.                 master = self.text)
  412.         # If there was a UTF-8 signature, use that. This should not fail
  413.         if self.fileencoding == BOM_UTF8 or failed:
  414.             return BOM_UTF8 + chars.encode("utf-8")
  415.         # Try the original file encoding next, if any
  416.         if self.fileencoding:
  417.             try:
  418.                 return chars.encode(self.fileencoding)
  419.             except UnicodeError:
  420.                 tkMessageBox.showerror(
  421.                     "I/O Error",
  422.                     "Cannot save this as '%s' anymore. Saving as UTF-8" \
  423.                     % self.fileencoding,
  424.                     master = self.text)
  425.                 return BOM_UTF8 + chars.encode("utf-8")
  426.         # Nothing was declared, and we had not determined an encoding
  427.         # on loading. Recommend an encoding line.
  428.         config_encoding = idleConf.GetOption("main","EditorWindow",
  429.                                              "encoding")
  430.         if config_encoding == 'utf-8':
  431.             # User has requested that we save files as UTF-8
  432.             return BOM_UTF8 + chars.encode("utf-8")
  433.         ask_user = True
  434.         try:
  435.             chars = chars.encode(encoding)
  436.             enc = encoding
  437.             if config_encoding == 'locale':
  438.                 ask_user = False
  439.         except UnicodeError:
  440.             chars = BOM_UTF8 + chars.encode("utf-8")
  441.             enc = "utf-8"
  442.         if not ask_user:
  443.             return chars
  444.         dialog = EncodingMessage(self.editwin.top, enc)
  445.         dialog.go()
  446.         if dialog.num == 1:
  447.             # User asked us to edit the file
  448.             encline = "# -*- coding: %s -*-\n" % enc
  449.             firstline = self.text.get("1.0", "2.0")
  450.             if firstline.startswith("#!"):
  451.                 # Insert encoding after #! line
  452.                 self.text.insert("2.0", encline)
  453.             else:
  454.                 self.text.insert("1.0", encline)
  455.             return self.encode(self.text.get("1.0", "end-1c"))
  456.         return chars
  457.  
  458.     def fixlastline(self):
  459.         c = self.text.get("end-2c")
  460.         if c != '\n':
  461.             self.text.insert("end-1c", "\n")
  462.  
  463.     def print_window(self, event):
  464.         tempfilename = None
  465.         saved = self.get_saved()
  466.         if saved:
  467.             filename = self.filename
  468.         # shell undo is reset after every prompt, looks saved, probably isn't
  469.         if not saved or filename is None:
  470.             # XXX KBK 08Jun03 Wouldn't it be better to ask the user to save?
  471.             (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
  472.             filename = tempfilename
  473.             os.close(tfd)
  474.             if not self.writefile(tempfilename):
  475.                 os.unlink(tempfilename)
  476.                 return "break"
  477.         platform=os.name
  478.         printPlatform=1
  479.         if platform == 'posix': #posix platform
  480.             command = idleConf.GetOption('main','General',
  481.                                          'print-command-posix')
  482.             command = command + " 2>&1"
  483.         elif platform == 'nt': #win32 platform
  484.             command = idleConf.GetOption('main','General','print-command-win')
  485.         else: #no printing for this platform
  486.             printPlatform=0
  487.         if printPlatform:  #we can try to print for this platform
  488.             command = command % filename
  489.             pipe = os.popen(command, "r")
  490.             # things can get ugly on NT if there is no printer available.
  491.             output = pipe.read().strip()
  492.             status = pipe.close()
  493.             if status:
  494.                 output = "Printing failed (exit status 0x%x)\n" % \
  495.                          status + output
  496.             if output:
  497.                 output = "Printing command: %s\n" % repr(command) + output
  498.                 tkMessageBox.showerror("Print status", output, master=self.text)
  499.         else:  #no printing for this platform
  500.             message="Printing is not enabled for this platform: %s" % platform
  501.             tkMessageBox.showinfo("Print status", message, master=self.text)
  502.         if tempfilename:
  503.             os.unlink(tempfilename)
  504.         return "break"
  505.  
  506.     opendialog = None
  507.     savedialog = None
  508.  
  509.     filetypes = [
  510.         ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
  511.         ("All text files", "*", "TEXT"),
  512.         ("All files", "*"),
  513.         ]
  514.  
  515.     def askopenfile(self):
  516.         dir, base = self.defaultfilename("open")
  517.         if not self.opendialog:
  518.             self.opendialog = tkFileDialog.Open(master=self.text,
  519.                                                 filetypes=self.filetypes)
  520.         return self.opendialog.show(initialdir=dir, initialfile=base)
  521.  
  522.     def defaultfilename(self, mode="open"):
  523.         if self.filename:
  524.             return os.path.split(self.filename)
  525.         elif self.dirname:
  526.             return self.dirname, ""
  527.         else:
  528.             try:
  529.                 pwd = os.getcwd()
  530.             except os.error:
  531.                 pwd = ""
  532.             return pwd, ""
  533.  
  534.     def asksavefile(self):
  535.         dir, base = self.defaultfilename("save")
  536.         if not self.savedialog:
  537.             self.savedialog = tkFileDialog.SaveAs(master=self.text,
  538.                                                   filetypes=self.filetypes)
  539.         return self.savedialog.show(initialdir=dir, initialfile=base)
  540.  
  541.     def updaterecentfileslist(self,filename):
  542.         "Update recent file list on all editor windows"
  543.         self.editwin.UpdateRecentFilesList(filename)
  544.  
  545. def test():
  546.     root = Tk()
  547.     class MyEditWin:
  548.         def __init__(self, text):
  549.             self.text = text
  550.             self.flist = None
  551.             self.text.bind("<Control-o>", self.open)
  552.             self.text.bind("<Control-s>", self.save)
  553.             self.text.bind("<Alt-s>", self.save_as)
  554.             self.text.bind("<Alt-z>", self.save_a_copy)
  555.         def get_saved(self): return 0
  556.         def set_saved(self, flag): pass
  557.         def reset_undo(self): pass
  558.         def open(self, event):
  559.             self.text.event_generate("<<open-window-from-file>>")
  560.         def save(self, event):
  561.             self.text.event_generate("<<save-window>>")
  562.         def save_as(self, event):
  563.             self.text.event_generate("<<save-window-as-file>>")
  564.         def save_a_copy(self, event):
  565.             self.text.event_generate("<<save-copy-of-window-as-file>>")
  566.     text = Text(root)
  567.     text.pack()
  568.     text.focus_set()
  569.     editwin = MyEditWin(text)
  570.     io = IOBinding(editwin)
  571.     root.mainloop()
  572.  
  573. if __name__ == "__main__":
  574.     test()
  575.