blob: 58d1913c7e81b4d69c2c60e15024b058bf5ef6f5 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001# 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
8import os
Kurt B. Kaiser01166da2002-09-16 22:03:37 +00009import types
10import sys
11import codecs
Steven M. Gava7981ce52002-06-11 04:45:34 +000012import tempfile
David Scherer7aced172000-08-15 01:13:23 +000013import tkFileDialog
14import tkMessageBox
15import re
Steven M. Gava7981ce52002-06-11 04:45:34 +000016from configHandler import idleConf
David Scherer7aced172000-08-15 01:13:23 +000017
18#$ event <<open-window-from-file>>
19#$ win <Control-o>
20#$ unix <Control-x><Control-f>
21
22#$ event <<save-window>>
23#$ win <Control-s>
24#$ unix <Control-x><Control-s>
25
26#$ event <<save-window-as-file>>
27#$ win <Alt-s>
28#$ unix <Control-x><Control-w>
29
Steven M. Gava7981ce52002-06-11 04:45:34 +000030#$ event <<print-window>>
31#$ win <Control-p>
32#$ unix <Control-x><Control-p>
33
David Scherer7aced172000-08-15 01:13:23 +000034#$ event <<save-copy-of-window-as-file>>
35#$ win <Alt-Shift-s>
36#$ unix <Control-x><w>
37
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000038try:
39 from codecs import BOM_UTF8
40except ImportError:
41 # only available since Python 2.3
42 BOM_UTF8 = '\xef\xbb\xbf'
43
44# Try setting the locale, so that we can find out
45# what encoding to use
46try:
47 import locale
48 locale.setlocale(locale.LC_CTYPE, "")
49except ImportError:
50 pass
51
52encoding = "ascii"
53if sys.platform == 'win32':
54 # On Windows, we could use "mbcs". However, to give the user
55 # a portable encoding name, we need to find the code page
56 try:
57 encoding = locale.getdefaultlocale()[1]
58 codecs.lookup(encoding)
59 except LookupError:
60 pass
61else:
62 try:
63 # Different things can fail here: the locale module may not be
64 # loaded, it may not offer nl_langinfo, or CODESET, or the
65 # resulting codeset may be unknown to Python. We ignore all
66 # these problems, falling back to ASCII
67 encoding = locale.nl_langinfo(locale.CODESET)
68 codecs.lookup(encoding)
69 except (NameError, AttributeError, LookupError):
70 # Try getdefaultlocale well: it parses environment variables,
71 # which may give a clue. Unfortunately, getdefaultlocale has
72 # bugs that can cause ValueError.
73 try:
74 encoding = locale.getdefaultlocale()[1]
75 codecs.lookup(encoding)
76 except (ValueError, LookupError):
77 pass
78
79encoding = encoding.lower()
80
81coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
82def coding_spec(str):
83
84 """Return the encoding declaration according to PEP 263.
85 Raise LookupError if the encoding is declared but unknown."""
86
87 # Only consider the first two lines
88 str = str.split("\n")[:2]
89 str = "\n".join(str)
90
91 match = coding_re.search(str)
92 if not match:
93 return None
94 name = match.group(1)
95 # Check whether the encoding is known
96 import codecs
97 try:
98 codecs.lookup(name)
99 except LookupError:
100 # The standard encoding error does not indicate the encoding
101 raise LookupError, "Unknown encoding "+name
102 return name
David Scherer7aced172000-08-15 01:13:23 +0000103
104class IOBinding:
105
106 def __init__(self, editwin):
107 self.editwin = editwin
108 self.text = editwin.text
109 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
110 self.__id_save = self.text.bind("<<save-window>>", self.save)
111 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
112 self.save_as)
113 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
114 self.save_a_copy)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000115 self.fileencoding = None
Steven M. Gava7981ce52002-06-11 04:45:34 +0000116 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
117
David Scherer7aced172000-08-15 01:13:23 +0000118 def close(self):
119 # Undo command bindings
120 self.text.unbind("<<open-window-from-file>>", self.__id_open)
121 self.text.unbind("<<save-window>>", self.__id_save)
122 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
123 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
Steven M. Gava7981ce52002-06-11 04:45:34 +0000124 self.text.unbind("<<print-window>>", self.__id_print)
David Scherer7aced172000-08-15 01:13:23 +0000125 # Break cycles
126 self.editwin = None
127 self.text = None
128 self.filename_change_hook = None
129
130 def get_saved(self):
131 return self.editwin.get_saved()
132
133 def set_saved(self, flag):
134 self.editwin.set_saved(flag)
135
136 def reset_undo(self):
137 self.editwin.reset_undo()
138
139 filename_change_hook = None
140
141 def set_filename_change_hook(self, hook):
142 self.filename_change_hook = hook
143
144 filename = None
145
146 def set_filename(self, filename):
147 self.filename = filename
148 self.set_saved(1)
149 if self.filename_change_hook:
150 self.filename_change_hook()
151
Steven M. Gava1d46e402002-03-27 08:40:46 +0000152 def open(self, event=None, editFile=None):
David Scherer7aced172000-08-15 01:13:23 +0000153 if self.editwin.flist:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000154 if not editFile:
155 filename = self.askopenfile()
156 else:
157 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000158 if filename:
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000159 # If the current window has no filename and hasn't been
160 # modified, we replace its contents (no loss). Otherwise
161 # we open a new window. But we won't replace the
162 # shell window (which has an interp(reter) attribute), which
163 # gets set to "not modified" at every new prompt.
164 try:
165 interp = self.editwin.interp
166 except:
167 interp = None
168 if not self.filename and self.get_saved() and not interp:
David Scherer7aced172000-08-15 01:13:23 +0000169 self.editwin.flist.open(filename, self.loadfile)
170 else:
171 self.editwin.flist.open(filename)
172 else:
173 self.text.focus_set()
David Scherer7aced172000-08-15 01:13:23 +0000174 return "break"
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000175 #
David Scherer7aced172000-08-15 01:13:23 +0000176 # Code for use outside IDLE:
177 if self.get_saved():
178 reply = self.maybesave()
179 if reply == "cancel":
180 self.text.focus_set()
181 return "break"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000182 if not editFile:
183 filename = self.askopenfile()
184 else:
185 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000186 if filename:
187 self.loadfile(filename)
188 else:
189 self.text.focus_set()
190 return "break"
191
192 def loadfile(self, filename):
193 try:
194 # open the file in binary mode so that we can handle
195 # end-of-line convention ourselves.
196 f = open(filename,'rb')
197 chars = f.read()
198 f.close()
199 except IOError, msg:
200 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000201 return False
David Scherer7aced172000-08-15 01:13:23 +0000202
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000203 chars = self.decode(chars)
David Scherer7aced172000-08-15 01:13:23 +0000204 # We now convert all end-of-lines to '\n's
205 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
206 chars = re.compile( eol ).sub( r"\n", chars )
207
208 self.text.delete("1.0", "end")
209 self.set_filename(None)
210 self.text.insert("1.0", chars)
211 self.reset_undo()
212 self.set_filename(filename)
213 self.text.mark_set("insert", "1.0")
214 self.text.see("insert")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000215 return True
216
217 def decode(self, chars):
218 # Try to create a Unicode string. If that fails, let Tcl try
219 # its best
220
221 # Check presence of a UTF-8 signature first
222 if chars.startswith(BOM_UTF8):
223 try:
224 chars = chars[3:].decode("utf-8")
225 except UnicodeError:
226 # has UTF-8 signature, but fails to decode...
227 return chars
228 else:
229 # Indicates that this file originally had a BOM
230 self.fileencoding = BOM_UTF8
231 return chars
232
233 # Next look for coding specification
234 try:
235 enc = coding_spec(chars)
236 except LookupError, name:
237 tkMessageBox.showerror(
238 title="Error loading the file",
239 message="The encoding '%s' is not known to this Python "\
240 "installation. The file may not display correctly" % name,
241 master = self.text)
242 enc = None
243
244 if enc:
245 try:
246 return unicode(chars, enc)
247 except UnicodeError:
248 pass
249
250 # If it is ASCII, we need not to record anything
251 try:
252 return unicode(chars, 'ascii')
253 except UnicodeError:
254 pass
255
256 # Finally, try the locale's encoding. This is deprecated;
257 # the user should declare a non-ASCII encoding
258 try:
259 chars = unicode(chars, encoding)
260 self.fileencoding = encoding
261 except UnicodeError:
262 pass
263 return chars
David Scherer7aced172000-08-15 01:13:23 +0000264
265 def maybesave(self):
266 if self.get_saved():
267 return "yes"
268 message = "Do you want to save %s before closing?" % (
269 self.filename or "this untitled document")
270 m = tkMessageBox.Message(
271 title="Save On Close",
272 message=message,
273 icon=tkMessageBox.QUESTION,
274 type=tkMessageBox.YESNOCANCEL,
275 master=self.text)
276 reply = m.show()
277 if reply == "yes":
278 self.save(None)
279 if not self.get_saved():
280 reply = "cancel"
281 self.text.focus_set()
282 return reply
283
284 def save(self, event):
285 if not self.filename:
286 self.save_as(event)
287 else:
288 if self.writefile(self.filename):
289 self.set_saved(1)
290 self.text.focus_set()
291 return "break"
292
293 def save_as(self, event):
294 filename = self.asksavefile()
295 if filename:
296 if self.writefile(filename):
297 self.set_filename(filename)
298 self.set_saved(1)
299 self.text.focus_set()
300 return "break"
301
302 def save_a_copy(self, event):
303 filename = self.asksavefile()
304 if filename:
305 self.writefile(filename)
306 self.text.focus_set()
307 return "break"
308
309 def writefile(self, filename):
310 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000311 chars = self.encode(self.text.get("1.0", "end-1c"))
David Scherer7aced172000-08-15 01:13:23 +0000312 try:
313 f = open(filename, "w")
David Scherer7aced172000-08-15 01:13:23 +0000314 f.write(chars)
315 f.close()
316 ## print "saved to", `filename`
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000317 return True
David Scherer7aced172000-08-15 01:13:23 +0000318 except IOError, msg:
319 tkMessageBox.showerror("I/O Error", str(msg),
320 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000321 return False
322
323 def encode(self, chars):
324 if isinstance(chars, types.StringType):
325 # This is either plain ASCII, or Tk was returning mixed-encoding
326 # text to us. Don't try to guess further.
327 return chars
328
329 # See whether there is anything non-ASCII in it.
330 # If not, no need to figure out the encoding.
331 try:
332 return chars.encode('ascii')
333 except UnicodeError:
334 pass
335
336 # If there is an encoding declared, try this first.
337 try:
338 enc = coding_spec(chars)
339 failed = None
340 except LookupError, msg:
341 failed = msg
342 enc = None
343 if enc:
344 try:
345 return chars.encode(enc)
346 except UnicodeError:
347 failed = "Invalid encoding '%s'" % enc
348
349 if failed:
350 tkMessageBox.showerror(
351 "I/O Error",
352 "%s. Saving as UTF-8" % failed,
353 master = self.text)
354
355 # If there was a UTF-8 signature, use that. This should not fail
356 if self.fileencoding == BOM_UTF8 or failed:
357 return BOM_UTF8 + chars.encode("utf-8")
358
359 # Try the original file encoding next, if any
360 if self.fileencoding:
361 try:
362 return chars.encode(self.fileencoding)
363 except UnicodeError:
364 tkMessageBox.showerror(
365 "I/O Error",
366 "Cannot save this as '%s' anymore. Saving as UTF-8" \
367 % self.fileencoding,
368 master = self.text)
369 return BOM_UTF8 + chars.encode("utf-8")
370
371 # Nothing was declared, and we had not determined an encoding
372 # on loading. Recommend an encoding line.
373 try:
374 chars = chars.encode(encoding)
375 enc = encoding
376 except UnicodeError:
377 chars = BOM_UTF8 + chars.encode("utf-8")
378 enc = "utf-8"
379 tkMessageBox.showerror(
380 "I/O Error",
381 "Non-ASCII found, yet no encoding declared. Add a line like\n"
382 "# -*- coding: %s -*- \nto your file" % enc,
383 master = self.text)
384 return chars
Steven M. Gava7981ce52002-06-11 04:45:34 +0000385
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000386 def fixlastline(self):
387 c = self.text.get("end-2c")
388 if c != '\n':
389 self.text.insert("end-1c", "\n")
390
Steven M. Gava7981ce52002-06-11 04:45:34 +0000391 def print_window(self, event):
392 tempfilename = None
393 if self.get_saved():
394 filename = self.filename
395 else:
396 filename = tempfilename = tempfile.mktemp()
397 if not self.writefile(filename):
398 os.unlink(tempfilename)
399 return "break"
400 platform=os.name
401 printPlatform=1
402 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000403 command = idleConf.GetOption('main','General',
404 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000405 command = command + " 2>&1"
406 elif platform == 'nt': #win32 platform
407 command = idleConf.GetOption('main','General','print-command-win')
408 else: #no printing for this platform
409 printPlatform=0
410 if printPlatform: #we can try to print for this platform
411 command = command % filename
412 pipe = os.popen(command, "r")
413 output = pipe.read().strip()
414 status = pipe.close()
415 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000416 output = "Printing failed (exit status 0x%x)\n" % \
417 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000418 if output:
419 output = "Printing command: %s\n" % repr(command) + output
420 tkMessageBox.showerror("Print status", output, master=self.text)
421 else: #no printing for this platform
422 message="Printing is not enabled for this platform: %s" % platform
423 tkMessageBox.showinfo("Print status", message, master=self.text)
424 return "break"
425
David Scherer7aced172000-08-15 01:13:23 +0000426 opendialog = None
427 savedialog = None
428
429 filetypes = [
430 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
431 ("All text files", "*", "TEXT"),
432 ("All files", "*"),
433 ]
434
435 def askopenfile(self):
436 dir, base = self.defaultfilename("open")
437 if not self.opendialog:
438 self.opendialog = tkFileDialog.Open(master=self.text,
439 filetypes=self.filetypes)
440 return self.opendialog.show(initialdir=dir, initialfile=base)
441
442 def defaultfilename(self, mode="open"):
443 if self.filename:
444 return os.path.split(self.filename)
445 else:
446 try:
447 pwd = os.getcwd()
448 except os.error:
449 pwd = ""
450 return pwd, ""
451
452 def asksavefile(self):
453 dir, base = self.defaultfilename("save")
454 if not self.savedialog:
455 self.savedialog = tkFileDialog.SaveAs(master=self.text,
456 filetypes=self.filetypes)
457 return self.savedialog.show(initialdir=dir, initialfile=base)
458
459
460def test():
David Scherer7aced172000-08-15 01:13:23 +0000461 root = Tk()
462 class MyEditWin:
463 def __init__(self, text):
464 self.text = text
465 self.flist = None
466 self.text.bind("<Control-o>", self.open)
467 self.text.bind("<Control-s>", self.save)
468 self.text.bind("<Alt-s>", self.save_as)
469 self.text.bind("<Alt-z>", self.save_a_copy)
470 def get_saved(self): return 0
471 def set_saved(self, flag): pass
472 def reset_undo(self): pass
473 def open(self, event):
474 self.text.event_generate("<<open-window-from-file>>")
475 def save(self, event):
476 self.text.event_generate("<<save-window>>")
477 def save_as(self, event):
478 self.text.event_generate("<<save-window-as-file>>")
479 def save_a_copy(self, event):
480 self.text.event_generate("<<save-copy-of-window-as-file>>")
481 text = Text(root)
482 text.pack()
483 text.focus_set()
484 editwin = MyEditWin(text)
485 io = IOBinding(editwin)
486 root.mainloop()
487
488if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000489 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000490 test()