blob: 10129e9b71fb60b1cd506d23483e1d6342cc62c0 [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")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000221 return True
222
223 def decode(self, chars):
224 # Try to create a Unicode string. If that fails, let Tcl try
225 # its best
226
227 # Check presence of a UTF-8 signature first
228 if chars.startswith(BOM_UTF8):
229 try:
230 chars = chars[3:].decode("utf-8")
231 except UnicodeError:
232 # has UTF-8 signature, but fails to decode...
233 return chars
234 else:
235 # Indicates that this file originally had a BOM
236 self.fileencoding = BOM_UTF8
237 return chars
238
239 # Next look for coding specification
240 try:
241 enc = coding_spec(chars)
242 except LookupError, name:
243 tkMessageBox.showerror(
244 title="Error loading the file",
245 message="The encoding '%s' is not known to this Python "\
246 "installation. The file may not display correctly" % name,
247 master = self.text)
248 enc = None
249
250 if enc:
251 try:
252 return unicode(chars, enc)
253 except UnicodeError:
254 pass
255
256 # If it is ASCII, we need not to record anything
257 try:
258 return unicode(chars, 'ascii')
259 except UnicodeError:
260 pass
261
262 # Finally, try the locale's encoding. This is deprecated;
263 # the user should declare a non-ASCII encoding
264 try:
265 chars = unicode(chars, encoding)
266 self.fileencoding = encoding
267 except UnicodeError:
268 pass
269 return chars
David Scherer7aced172000-08-15 01:13:23 +0000270
271 def maybesave(self):
272 if self.get_saved():
273 return "yes"
274 message = "Do you want to save %s before closing?" % (
275 self.filename or "this untitled document")
276 m = tkMessageBox.Message(
277 title="Save On Close",
278 message=message,
279 icon=tkMessageBox.QUESTION,
280 type=tkMessageBox.YESNOCANCEL,
281 master=self.text)
282 reply = m.show()
283 if reply == "yes":
284 self.save(None)
285 if not self.get_saved():
286 reply = "cancel"
287 self.text.focus_set()
288 return reply
289
290 def save(self, event):
291 if not self.filename:
292 self.save_as(event)
293 else:
294 if self.writefile(self.filename):
295 self.set_saved(1)
296 self.text.focus_set()
297 return "break"
298
299 def save_as(self, event):
300 filename = self.asksavefile()
301 if filename:
302 if self.writefile(filename):
303 self.set_filename(filename)
304 self.set_saved(1)
305 self.text.focus_set()
306 return "break"
307
308 def save_a_copy(self, event):
309 filename = self.asksavefile()
310 if filename:
311 self.writefile(filename)
312 self.text.focus_set()
313 return "break"
314
315 def writefile(self, filename):
316 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000317 chars = self.encode(self.text.get("1.0", "end-1c"))
David Scherer7aced172000-08-15 01:13:23 +0000318 try:
319 f = open(filename, "w")
David Scherer7aced172000-08-15 01:13:23 +0000320 f.write(chars)
321 f.close()
322 ## print "saved to", `filename`
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000323 return True
David Scherer7aced172000-08-15 01:13:23 +0000324 except IOError, msg:
325 tkMessageBox.showerror("I/O Error", str(msg),
326 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000327 return False
328
329 def encode(self, chars):
330 if isinstance(chars, types.StringType):
331 # This is either plain ASCII, or Tk was returning mixed-encoding
332 # text to us. Don't try to guess further.
333 return chars
334
335 # See whether there is anything non-ASCII in it.
336 # If not, no need to figure out the encoding.
337 try:
338 return chars.encode('ascii')
339 except UnicodeError:
340 pass
341
342 # If there is an encoding declared, try this first.
343 try:
344 enc = coding_spec(chars)
345 failed = None
346 except LookupError, msg:
347 failed = msg
348 enc = None
349 if enc:
350 try:
351 return chars.encode(enc)
352 except UnicodeError:
353 failed = "Invalid encoding '%s'" % enc
354
355 if failed:
356 tkMessageBox.showerror(
357 "I/O Error",
358 "%s. Saving as UTF-8" % failed,
359 master = self.text)
360
361 # If there was a UTF-8 signature, use that. This should not fail
362 if self.fileencoding == BOM_UTF8 or failed:
363 return BOM_UTF8 + chars.encode("utf-8")
364
365 # Try the original file encoding next, if any
366 if self.fileencoding:
367 try:
368 return chars.encode(self.fileencoding)
369 except UnicodeError:
370 tkMessageBox.showerror(
371 "I/O Error",
372 "Cannot save this as '%s' anymore. Saving as UTF-8" \
373 % self.fileencoding,
374 master = self.text)
375 return BOM_UTF8 + chars.encode("utf-8")
376
377 # Nothing was declared, and we had not determined an encoding
378 # on loading. Recommend an encoding line.
379 try:
380 chars = chars.encode(encoding)
381 enc = encoding
382 except UnicodeError:
383 chars = BOM_UTF8 + chars.encode("utf-8")
384 enc = "utf-8"
385 tkMessageBox.showerror(
386 "I/O Error",
387 "Non-ASCII found, yet no encoding declared. Add a line like\n"
388 "# -*- coding: %s -*- \nto your file" % enc,
389 master = self.text)
390 return chars
Steven M. Gava7981ce52002-06-11 04:45:34 +0000391
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000392 def fixlastline(self):
393 c = self.text.get("end-2c")
394 if c != '\n':
395 self.text.insert("end-1c", "\n")
396
Steven M. Gava7981ce52002-06-11 04:45:34 +0000397 def print_window(self, event):
398 tempfilename = None
399 if self.get_saved():
400 filename = self.filename
401 else:
402 filename = tempfilename = tempfile.mktemp()
403 if not self.writefile(filename):
404 os.unlink(tempfilename)
405 return "break"
406 platform=os.name
407 printPlatform=1
408 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000409 command = idleConf.GetOption('main','General',
410 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000411 command = command + " 2>&1"
412 elif platform == 'nt': #win32 platform
413 command = idleConf.GetOption('main','General','print-command-win')
414 else: #no printing for this platform
415 printPlatform=0
416 if printPlatform: #we can try to print for this platform
417 command = command % filename
418 pipe = os.popen(command, "r")
419 output = pipe.read().strip()
420 status = pipe.close()
421 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000422 output = "Printing failed (exit status 0x%x)\n" % \
423 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000424 if output:
425 output = "Printing command: %s\n" % repr(command) + output
426 tkMessageBox.showerror("Print status", output, master=self.text)
427 else: #no printing for this platform
428 message="Printing is not enabled for this platform: %s" % platform
429 tkMessageBox.showinfo("Print status", message, master=self.text)
430 return "break"
431
David Scherer7aced172000-08-15 01:13:23 +0000432 opendialog = None
433 savedialog = None
434
435 filetypes = [
436 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
437 ("All text files", "*", "TEXT"),
438 ("All files", "*"),
439 ]
440
441 def askopenfile(self):
442 dir, base = self.defaultfilename("open")
443 if not self.opendialog:
444 self.opendialog = tkFileDialog.Open(master=self.text,
445 filetypes=self.filetypes)
446 return self.opendialog.show(initialdir=dir, initialfile=base)
447
448 def defaultfilename(self, mode="open"):
449 if self.filename:
450 return os.path.split(self.filename)
451 else:
452 try:
453 pwd = os.getcwd()
454 except os.error:
455 pwd = ""
456 return pwd, ""
457
458 def asksavefile(self):
459 dir, base = self.defaultfilename("save")
460 if not self.savedialog:
461 self.savedialog = tkFileDialog.SaveAs(master=self.text,
462 filetypes=self.filetypes)
463 return self.savedialog.show(initialdir=dir, initialfile=base)
464
465
466def test():
David Scherer7aced172000-08-15 01:13:23 +0000467 root = Tk()
468 class MyEditWin:
469 def __init__(self, text):
470 self.text = text
471 self.flist = None
472 self.text.bind("<Control-o>", self.open)
473 self.text.bind("<Control-s>", self.save)
474 self.text.bind("<Alt-s>", self.save_as)
475 self.text.bind("<Alt-z>", self.save_a_copy)
476 def get_saved(self): return 0
477 def set_saved(self, flag): pass
478 def reset_undo(self): pass
479 def open(self, event):
480 self.text.event_generate("<<open-window-from-file>>")
481 def save(self, event):
482 self.text.event_generate("<<save-window>>")
483 def save_as(self, event):
484 self.text.event_generate("<<save-window-as-file>>")
485 def save_a_copy(self, event):
486 self.text.event_generate("<<save-copy-of-window-as-file>>")
487 text = Text(root)
488 text.pack()
489 text.focus_set()
490 editwin = MyEditWin(text)
491 io = IOBinding(editwin)
492 root.mainloop()
493
494if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000495 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000496 test()