blob: 594ecf64719156d07c9171f9d62ea6f75c915cae [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)
Tony Lowndse555fc72002-09-23 01:01:20 +000068 if encoding is None:
69 # situation occurs on Mac OS X
70 encoding = 'ascii'
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000071 codecs.lookup(encoding)
72 except (NameError, AttributeError, LookupError):
73 # Try getdefaultlocale well: it parses environment variables,
74 # which may give a clue. Unfortunately, getdefaultlocale has
75 # bugs that can cause ValueError.
76 try:
77 encoding = locale.getdefaultlocale()[1]
Tony Lowndse555fc72002-09-23 01:01:20 +000078 if encoding is None:
79 # situation occurs on Mac OS X
80 encoding = 'ascii'
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000081 codecs.lookup(encoding)
82 except (ValueError, LookupError):
83 pass
84
85encoding = encoding.lower()
86
87coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
88def coding_spec(str):
89
90 """Return the encoding declaration according to PEP 263.
91 Raise LookupError if the encoding is declared but unknown."""
92
93 # Only consider the first two lines
94 str = str.split("\n")[:2]
95 str = "\n".join(str)
96
97 match = coding_re.search(str)
98 if not match:
99 return None
100 name = match.group(1)
101 # Check whether the encoding is known
102 import codecs
103 try:
104 codecs.lookup(name)
105 except LookupError:
106 # The standard encoding error does not indicate the encoding
107 raise LookupError, "Unknown encoding "+name
108 return name
David Scherer7aced172000-08-15 01:13:23 +0000109
110class IOBinding:
111
112 def __init__(self, editwin):
113 self.editwin = editwin
114 self.text = editwin.text
115 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
116 self.__id_save = self.text.bind("<<save-window>>", self.save)
117 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
118 self.save_as)
119 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
120 self.save_a_copy)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000121 self.fileencoding = None
Steven M. Gava7981ce52002-06-11 04:45:34 +0000122 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
123
David Scherer7aced172000-08-15 01:13:23 +0000124 def close(self):
125 # Undo command bindings
126 self.text.unbind("<<open-window-from-file>>", self.__id_open)
127 self.text.unbind("<<save-window>>", self.__id_save)
128 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
129 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
Steven M. Gava7981ce52002-06-11 04:45:34 +0000130 self.text.unbind("<<print-window>>", self.__id_print)
David Scherer7aced172000-08-15 01:13:23 +0000131 # Break cycles
132 self.editwin = None
133 self.text = None
134 self.filename_change_hook = None
135
136 def get_saved(self):
137 return self.editwin.get_saved()
138
139 def set_saved(self, flag):
140 self.editwin.set_saved(flag)
141
142 def reset_undo(self):
143 self.editwin.reset_undo()
144
145 filename_change_hook = None
146
147 def set_filename_change_hook(self, hook):
148 self.filename_change_hook = hook
149
150 filename = None
151
152 def set_filename(self, filename):
153 self.filename = filename
154 self.set_saved(1)
155 if self.filename_change_hook:
156 self.filename_change_hook()
157
Steven M. Gava1d46e402002-03-27 08:40:46 +0000158 def open(self, event=None, editFile=None):
David Scherer7aced172000-08-15 01:13:23 +0000159 if self.editwin.flist:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000160 if not editFile:
161 filename = self.askopenfile()
162 else:
163 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000164 if filename:
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000165 # If the current window has no filename and hasn't been
166 # modified, we replace its contents (no loss). Otherwise
167 # we open a new window. But we won't replace the
168 # shell window (which has an interp(reter) attribute), which
169 # gets set to "not modified" at every new prompt.
170 try:
171 interp = self.editwin.interp
172 except:
173 interp = None
174 if not self.filename and self.get_saved() and not interp:
David Scherer7aced172000-08-15 01:13:23 +0000175 self.editwin.flist.open(filename, self.loadfile)
176 else:
177 self.editwin.flist.open(filename)
178 else:
179 self.text.focus_set()
David Scherer7aced172000-08-15 01:13:23 +0000180 return "break"
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000181 #
David Scherer7aced172000-08-15 01:13:23 +0000182 # Code for use outside IDLE:
183 if self.get_saved():
184 reply = self.maybesave()
185 if reply == "cancel":
186 self.text.focus_set()
187 return "break"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000188 if not editFile:
189 filename = self.askopenfile()
190 else:
191 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000192 if filename:
193 self.loadfile(filename)
194 else:
195 self.text.focus_set()
196 return "break"
197
198 def loadfile(self, filename):
199 try:
200 # open the file in binary mode so that we can handle
201 # end-of-line convention ourselves.
202 f = open(filename,'rb')
203 chars = f.read()
204 f.close()
205 except IOError, msg:
206 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000207 return False
David Scherer7aced172000-08-15 01:13:23 +0000208
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000209 chars = self.decode(chars)
David Scherer7aced172000-08-15 01:13:23 +0000210 # We now convert all end-of-lines to '\n's
211 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
212 chars = re.compile( eol ).sub( r"\n", chars )
213
214 self.text.delete("1.0", "end")
215 self.set_filename(None)
216 self.text.insert("1.0", chars)
217 self.reset_undo()
218 self.set_filename(filename)
219 self.text.mark_set("insert", "1.0")
220 self.text.see("insert")
Chui Tey993e81a2002-11-04 03:11:10 +0000221
222 self.updaterecentfileslist(filename)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000223 return True
224
225 def decode(self, chars):
226 # Try to create a Unicode string. If that fails, let Tcl try
227 # its best
228
229 # Check presence of a UTF-8 signature first
230 if chars.startswith(BOM_UTF8):
231 try:
232 chars = chars[3:].decode("utf-8")
233 except UnicodeError:
234 # has UTF-8 signature, but fails to decode...
235 return chars
236 else:
237 # Indicates that this file originally had a BOM
238 self.fileencoding = BOM_UTF8
239 return chars
240
241 # Next look for coding specification
242 try:
243 enc = coding_spec(chars)
244 except LookupError, name:
245 tkMessageBox.showerror(
246 title="Error loading the file",
247 message="The encoding '%s' is not known to this Python "\
248 "installation. The file may not display correctly" % name,
249 master = self.text)
250 enc = None
251
252 if enc:
253 try:
254 return unicode(chars, enc)
255 except UnicodeError:
256 pass
257
258 # If it is ASCII, we need not to record anything
259 try:
260 return unicode(chars, 'ascii')
261 except UnicodeError:
262 pass
263
264 # Finally, try the locale's encoding. This is deprecated;
265 # the user should declare a non-ASCII encoding
266 try:
267 chars = unicode(chars, encoding)
268 self.fileencoding = encoding
269 except UnicodeError:
270 pass
271 return chars
David Scherer7aced172000-08-15 01:13:23 +0000272
273 def maybesave(self):
274 if self.get_saved():
275 return "yes"
276 message = "Do you want to save %s before closing?" % (
277 self.filename or "this untitled document")
278 m = tkMessageBox.Message(
279 title="Save On Close",
280 message=message,
281 icon=tkMessageBox.QUESTION,
282 type=tkMessageBox.YESNOCANCEL,
283 master=self.text)
284 reply = m.show()
285 if reply == "yes":
286 self.save(None)
287 if not self.get_saved():
288 reply = "cancel"
289 self.text.focus_set()
290 return reply
291
292 def save(self, event):
293 if not self.filename:
294 self.save_as(event)
295 else:
296 if self.writefile(self.filename):
297 self.set_saved(1)
298 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000299
David Scherer7aced172000-08-15 01:13:23 +0000300 return "break"
301
302 def save_as(self, event):
303 filename = self.asksavefile()
304 if filename:
305 if self.writefile(filename):
306 self.set_filename(filename)
307 self.set_saved(1)
308 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000309
310 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000311 return "break"
312
313 def save_a_copy(self, event):
314 filename = self.asksavefile()
315 if filename:
316 self.writefile(filename)
317 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000318
319 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000320 return "break"
321
322 def writefile(self, filename):
323 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000324 chars = self.encode(self.text.get("1.0", "end-1c"))
David Scherer7aced172000-08-15 01:13:23 +0000325 try:
326 f = open(filename, "w")
David Scherer7aced172000-08-15 01:13:23 +0000327 f.write(chars)
328 f.close()
329 ## print "saved to", `filename`
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000330 return True
David Scherer7aced172000-08-15 01:13:23 +0000331 except IOError, msg:
332 tkMessageBox.showerror("I/O Error", str(msg),
333 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000334 return False
335
336 def encode(self, chars):
337 if isinstance(chars, types.StringType):
338 # This is either plain ASCII, or Tk was returning mixed-encoding
339 # text to us. Don't try to guess further.
340 return chars
341
342 # See whether there is anything non-ASCII in it.
343 # If not, no need to figure out the encoding.
344 try:
345 return chars.encode('ascii')
346 except UnicodeError:
347 pass
348
349 # If there is an encoding declared, try this first.
350 try:
351 enc = coding_spec(chars)
352 failed = None
353 except LookupError, msg:
354 failed = msg
355 enc = None
356 if enc:
357 try:
358 return chars.encode(enc)
359 except UnicodeError:
360 failed = "Invalid encoding '%s'" % enc
361
362 if failed:
363 tkMessageBox.showerror(
364 "I/O Error",
365 "%s. Saving as UTF-8" % failed,
366 master = self.text)
367
368 # If there was a UTF-8 signature, use that. This should not fail
369 if self.fileencoding == BOM_UTF8 or failed:
370 return BOM_UTF8 + chars.encode("utf-8")
371
372 # Try the original file encoding next, if any
373 if self.fileencoding:
374 try:
375 return chars.encode(self.fileencoding)
376 except UnicodeError:
377 tkMessageBox.showerror(
378 "I/O Error",
379 "Cannot save this as '%s' anymore. Saving as UTF-8" \
380 % self.fileencoding,
381 master = self.text)
382 return BOM_UTF8 + chars.encode("utf-8")
383
384 # Nothing was declared, and we had not determined an encoding
385 # on loading. Recommend an encoding line.
386 try:
387 chars = chars.encode(encoding)
388 enc = encoding
389 except UnicodeError:
390 chars = BOM_UTF8 + chars.encode("utf-8")
391 enc = "utf-8"
392 tkMessageBox.showerror(
393 "I/O Error",
394 "Non-ASCII found, yet no encoding declared. Add a line like\n"
395 "# -*- coding: %s -*- \nto your file" % enc,
396 master = self.text)
397 return chars
Steven M. Gava7981ce52002-06-11 04:45:34 +0000398
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000399 def fixlastline(self):
400 c = self.text.get("end-2c")
401 if c != '\n':
402 self.text.insert("end-1c", "\n")
403
Steven M. Gava7981ce52002-06-11 04:45:34 +0000404 def print_window(self, event):
405 tempfilename = None
406 if self.get_saved():
407 filename = self.filename
408 else:
409 filename = tempfilename = tempfile.mktemp()
410 if not self.writefile(filename):
411 os.unlink(tempfilename)
412 return "break"
413 platform=os.name
414 printPlatform=1
415 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000416 command = idleConf.GetOption('main','General',
417 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000418 command = command + " 2>&1"
419 elif platform == 'nt': #win32 platform
420 command = idleConf.GetOption('main','General','print-command-win')
421 else: #no printing for this platform
422 printPlatform=0
423 if printPlatform: #we can try to print for this platform
424 command = command % filename
425 pipe = os.popen(command, "r")
426 output = pipe.read().strip()
427 status = pipe.close()
428 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000429 output = "Printing failed (exit status 0x%x)\n" % \
430 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000431 if output:
432 output = "Printing command: %s\n" % repr(command) + output
433 tkMessageBox.showerror("Print status", output, master=self.text)
434 else: #no printing for this platform
435 message="Printing is not enabled for this platform: %s" % platform
436 tkMessageBox.showinfo("Print status", message, master=self.text)
437 return "break"
438
David Scherer7aced172000-08-15 01:13:23 +0000439 opendialog = None
440 savedialog = None
441
442 filetypes = [
443 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
444 ("All text files", "*", "TEXT"),
445 ("All files", "*"),
446 ]
447
448 def askopenfile(self):
449 dir, base = self.defaultfilename("open")
450 if not self.opendialog:
451 self.opendialog = tkFileDialog.Open(master=self.text,
452 filetypes=self.filetypes)
453 return self.opendialog.show(initialdir=dir, initialfile=base)
454
455 def defaultfilename(self, mode="open"):
456 if self.filename:
457 return os.path.split(self.filename)
458 else:
459 try:
460 pwd = os.getcwd()
461 except os.error:
462 pwd = ""
463 return pwd, ""
464
465 def asksavefile(self):
466 dir, base = self.defaultfilename("save")
467 if not self.savedialog:
468 self.savedialog = tkFileDialog.SaveAs(master=self.text,
469 filetypes=self.filetypes)
470 return self.savedialog.show(initialdir=dir, initialfile=base)
471
472
Chui Tey993e81a2002-11-04 03:11:10 +0000473 def updaterecentfileslist(self,filename):
474 #
475 # Updates recent file list on all editor windows
476 #
477 self.editwin.UpdateRecentFilesList(filename)
478
David Scherer7aced172000-08-15 01:13:23 +0000479def test():
David Scherer7aced172000-08-15 01:13:23 +0000480 root = Tk()
481 class MyEditWin:
482 def __init__(self, text):
483 self.text = text
484 self.flist = None
485 self.text.bind("<Control-o>", self.open)
486 self.text.bind("<Control-s>", self.save)
487 self.text.bind("<Alt-s>", self.save_as)
488 self.text.bind("<Alt-z>", self.save_a_copy)
489 def get_saved(self): return 0
490 def set_saved(self, flag): pass
491 def reset_undo(self): pass
492 def open(self, event):
493 self.text.event_generate("<<open-window-from-file>>")
494 def save(self, event):
495 self.text.event_generate("<<save-window>>")
496 def save_as(self, event):
497 self.text.event_generate("<<save-window-as-file>>")
498 def save_a_copy(self, event):
499 self.text.event_generate("<<save-copy-of-window-as-file>>")
500 text = Text(root)
501 text.pack()
502 text.focus_set()
503 editwin = MyEditWin(text)
504 io = IOBinding(editwin)
505 root.mainloop()
506
507if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000508 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000509 test()