blob: 0ea4524ccda76e56e2198724cb002ce2571d875c [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import os
Martin v. Löwiseade4a12002-08-05 14:55:21 +00002import types
3import sys
4import codecs
5import re
Guido van Rossum2ca78622002-06-10 18:52:02 +00006import tempfile
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00007import tkFileDialog
8import tkMessageBox
Guido van Rossum2ca78622002-06-10 18:52:02 +00009from IdleConf import idleconf
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000010
Guido van Rossum504b0bf1999-01-02 21:28:54 +000011#$ event <<open-window-from-file>>
12#$ win <Control-o>
13#$ unix <Control-x><Control-f>
14
15#$ event <<save-window>>
16#$ win <Control-s>
17#$ unix <Control-x><Control-s>
18
19#$ event <<save-window-as-file>>
20#$ win <Alt-s>
21#$ unix <Control-x><Control-w>
22
23#$ event <<save-copy-of-window-as-file>>
24#$ win <Alt-Shift-s>
25#$ unix <Control-x><w>
26
Guido van Rossum2ca78622002-06-10 18:52:02 +000027#$ event <<print-window>>
28#$ win <Control-p>
29#$ unix <Control-x><Control-p>
30
Martin v. Löwiseade4a12002-08-05 14:55:21 +000031try:
32 from codecs import BOM_UTF8
33except ImportError:
34 # only available since Python 2.3
35 BOM_UTF8 = '\xef\xbb\xbf'
36
37# Try setting the locale, so that we can find out
38# what encoding to use
39try:
40 import locale
41 locale.setlocale(locale.LC_CTYPE, "")
42except ImportError:
43 pass
44
45encoding = "ascii"
46if sys.platform == 'win32':
47 # On Windows, we could use "mbcs". However, to give the user
48 # a portable encoding name, we need to find the code page
49 try:
50 encoding = locale.getdefaultlocale()[1]
51 codecs.lookup(encoding)
52 except LookupError:
53 pass
54else:
55 try:
56 # Different things can fail here: the locale module may not be
57 # loaded, it may not offer nl_langinfo, or CODESET, or the
58 # resulting codeset may be unknown to Python. We ignore all
59 # these problems, falling back to ASCII
60 encoding = locale.nl_langinfo(locale.CODESET)
61 codecs.lookup(encoding)
62 except (NameError, AttributeError, LookupError):
63 # Try getdefaultlocale well: it parses environment variables,
64 # which may give a clue. Unfortunately, getdefaultlocale has
65 # bugs that can cause ValueError.
66 try:
67 encoding = locale.getdefaultlocale()[1]
68 codecs.lookup(encoding)
69 except (ValueError, LookupError):
70 pass
71
72encoding = encoding.lower()
73
74coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
75def coding_spec(str):
76
77 """Return the encoding declaration according to PEP 263.
78 Raise LookupError if the encoding is declared but unknown."""
79
80 # Only consider the first two lines
81 str = str.split("\n")[:2]
82 str = "\n".join(str)
83
84 match = coding_re.search(str)
85 if not match:
86 return None
87 name = match.group(1)
88 # Check whether the encoding is known
89 import codecs
90 try:
91 codecs.lookup(name)
92 except LookupError:
93 # The standard encoding error does not indicate the encoding
94 raise LookupError, "Unknown encoding "+name
95 return name
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000096
97class IOBinding:
98
Guido van Rossum504b0bf1999-01-02 21:28:54 +000099 def __init__(self, editwin):
100 self.editwin = editwin
101 self.text = editwin.text
Guido van Rossume689f001999-06-25 16:02:22 +0000102 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
103 self.__id_save = self.text.bind("<<save-window>>", self.save)
104 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
105 self.save_as)
106 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
107 self.save_a_copy)
Guido van Rossum2ca78622002-06-10 18:52:02 +0000108 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000109 self.fileencoding = None
Guido van Rossume689f001999-06-25 16:02:22 +0000110
111 def close(self):
112 # Undo command bindings
113 self.text.unbind("<<open-window-from-file>>", self.__id_open)
114 self.text.unbind("<<save-window>>", self.__id_save)
115 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
116 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
Guido van Rossum2ca78622002-06-10 18:52:02 +0000117 self.text.unbind("<<print-window>>", self.__id_print)
Guido van Rossume689f001999-06-25 16:02:22 +0000118 # Break cycles
119 self.editwin = None
120 self.text = None
121 self.filename_change_hook = None
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000122
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000123 def get_saved(self):
124 return self.editwin.get_saved()
125
126 def set_saved(self, flag):
127 self.editwin.set_saved(flag)
128
129 def reset_undo(self):
130 self.editwin.reset_undo()
131
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000132 filename_change_hook = None
133
134 def set_filename_change_hook(self, hook):
135 self.filename_change_hook = hook
136
137 filename = None
138
139 def set_filename(self, filename):
140 self.filename = filename
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000141 self.set_saved(1)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000142 if self.filename_change_hook:
143 self.filename_change_hook()
144
145 def open(self, event):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000146 if self.editwin.flist:
147 filename = self.askopenfile()
148 if filename:
149 self.editwin.flist.open(filename)
150 else:
151 self.text.focus_set()
152 return "break"
153 # Code for use outside IDLE:
154 if self.get_saved():
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000155 reply = self.maybesave()
156 if reply == "cancel":
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000157 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000158 return "break"
159 filename = self.askopenfile()
160 if filename:
161 self.loadfile(filename)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000162 else:
163 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000164 return "break"
165
166 def loadfile(self, filename):
167 try:
168 f = open(filename)
169 chars = f.read()
170 f.close()
171 except IOError, msg:
172 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
Tim Petersbc0e9102002-04-04 22:55:58 +0000173 return False
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000174
175 chars = self.decode(chars)
176
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000177 self.text.delete("1.0", "end")
178 self.set_filename(None)
179 self.text.insert("1.0", chars)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000180 self.reset_undo()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000181 self.set_filename(filename)
182 self.text.mark_set("insert", "1.0")
183 self.text.see("insert")
Tim Petersbc0e9102002-04-04 22:55:58 +0000184 return True
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000185
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000186 def decode(self, chars):
187 # Try to create a Unicode string. If that fails, let Tcl try
188 # its best
189
190 # Check presence of a UTF-8 signature first
191 if chars.startswith(BOM_UTF8):
192 try:
193 chars = chars[3:].decode("utf-8")
194 except UnicodeError:
195 # has UTF-8 signature, but fails to decode...
196 return chars
197 else:
198 # Indicates that this file originally had a BOM
199 self.fileencoding = BOM_UTF8
200 return chars
201
202 # Next look for coding specification
203 try:
204 enc = coding_spec(chars)
205 except LookupError, name:
206 tkMessageBox.showerror(
207 title="Error loading the file",
208 message="The encoding '%s' is not known to this Python "\
209 "installation. The file may not display correctly" % name,
210 master = self.text)
211 enc = None
212
213 if enc:
214 try:
215 return unicode(chars, enc)
216 except UnicodeError:
217 pass
218
219 # If it is ASCII, we need not to record anything
220 try:
221 return unicode(chars, 'ascii')
222 except UnicodeError:
223 pass
224
225 # Finally, try the locale's encoding. This is deprecated;
226 # the user should declare a non-ASCII encoding
227 try:
228 chars = unicode(chars, encoding)
229 self.fileencoding = encoding
230 except UnicodeError:
231 pass
232 return chars
233
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000234 def maybesave(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000235 if self.get_saved():
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000236 return "yes"
237 message = "Do you want to save %s before closing?" % (
238 self.filename or "this untitled document")
239 m = tkMessageBox.Message(
240 title="Save On Close",
241 message=message,
242 icon=tkMessageBox.QUESTION,
243 type=tkMessageBox.YESNOCANCEL,
244 master=self.text)
245 reply = m.show()
246 if reply == "yes":
247 self.save(None)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000248 if not self.get_saved():
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000249 reply = "cancel"
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000250 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000251 return reply
252
253 def save(self, event):
254 if not self.filename:
255 self.save_as(event)
256 else:
257 if self.writefile(self.filename):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000258 self.set_saved(1)
259 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000260 return "break"
261
262 def save_as(self, event):
263 filename = self.asksavefile()
264 if filename:
265 if self.writefile(filename):
266 self.set_filename(filename)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000267 self.set_saved(1)
268 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000269 return "break"
270
271 def save_a_copy(self, event):
272 filename = self.asksavefile()
273 if filename:
274 self.writefile(filename)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000275 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000276 return "break"
277
Guido van Rossum2ca78622002-06-10 18:52:02 +0000278 def print_window(self, event):
279 tempfilename = None
280 if self.get_saved():
281 filename = self.filename
282 else:
283 filename = tempfilename = tempfile.mktemp()
284 if not self.writefile(filename):
285 os.unlink(tempfilename)
286 return "break"
287 edconf = idleconf.getsection('EditorWindow')
288 command = edconf.get('print-command')
289 command = command % filename
290 if os.name == 'posix':
291 command = command + " 2>&1"
292 pipe = os.popen(command, "r")
293 output = pipe.read().strip()
294 status = pipe.close()
295 if status:
296 output = "Printing failed (exit status 0x%x)\n" % status + output
297 if output:
298 output = "Printing command: %s\n" % repr(command) + output
299 tkMessageBox.showerror("Print status", output, master=self.text)
300 return "break"
301
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000302 def writefile(self, filename):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000303 self.fixlastline()
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000304 chars = self.encode(self.text.get("1.0", "end-1c"))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000305 try:
306 f = open(filename, "w")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000307 f.write(chars)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000308 f.close()
309 ## print "saved to", `filename`
Tim Petersbc0e9102002-04-04 22:55:58 +0000310 return True
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000311 except IOError, msg:
312 tkMessageBox.showerror("I/O Error", str(msg),
313 master=self.text)
Tim Petersbc0e9102002-04-04 22:55:58 +0000314 return False
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000315
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000316 def encode(self, chars):
317 if isinstance(chars, types.StringType):
318 # This is either plain ASCII, or Tk was returning mixed-encoding
319 # text to us. Don't try to guess further.
320 return chars
321
322 # See whether there is anything non-ASCII in it.
323 # If not, no need to figure out the encoding.
324 try:
325 return chars.encode('ascii')
326 except UnicodeError:
327 pass
328
329 # If there is an encoding declared, try this first.
330 try:
331 enc = coding_spec(chars)
332 failed = None
333 except LookupError, msg:
334 failed = msg
335 enc = None
336 if enc:
337 try:
338 return chars.encode(enc)
339 except UnicodeError:
340 failed = "Invalid encoding '%s'" % enc
341
342 if failed:
343 tkMessageBox.showerror(
344 "I/O Error",
345 "%s. Saving as UTF-8" % failed,
346 master = self.text)
347
348 # If there was a UTF-8 signature, use that. This should not fail
349 if self.fileencoding == BOM_UTF8 or failed:
350 return BOM_UTF8 + chars.encode("utf-8")
351
352 # Try the original file encoding next, if any
353 if self.fileencoding:
354 try:
355 return chars.encode(self.fileencoding)
356 except UnicodeError:
357 tkMessageBox.showerror(
358 "I/O Error",
359 "Cannot save this as '%s' anymore. Saving as UTF-8" % self.fileencoding,
360 master = self.text)
361 return BOM_UTF8 + chars.encode("utf-8")
362
363 # Nothing was declared, and we had not determined an encoding
364 # on loading. Recommend an encoding line.
365 try:
366 chars = chars.encode(encoding)
367 enc = encoding
368 except UnicodeError:
369 chars = BOM_UTF8 + chars.encode("utf-8")
370 enc = "utf-8"
371 tkMessageBox.showerror(
372 "I/O Error",
373 "Non-ASCII found, yet no encoding declared. Add a line like\n"
374 "# -*- coding: %s -*- \nto your file" % enc,
375 master = self.text)
376 return chars
377
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000378 def fixlastline(self):
379 c = self.text.get("end-2c")
380 if c != '\n':
381 self.text.insert("end-1c", "\n")
382
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000383 opendialog = None
384 savedialog = None
385
386 filetypes = [
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000387 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000388 ("All text files", "*", "TEXT"),
389 ("All files", "*"),
390 ]
391
392 def askopenfile(self):
393 dir, base = self.defaultfilename("open")
394 if not self.opendialog:
395 self.opendialog = tkFileDialog.Open(master=self.text,
396 filetypes=self.filetypes)
397 return self.opendialog.show(initialdir=dir, initialfile=base)
398
399 def defaultfilename(self, mode="open"):
400 if self.filename:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000401 return os.path.split(self.filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000402 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000403 try:
404 pwd = os.getcwd()
405 except os.error:
406 pwd = ""
407 return pwd, ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000408
409 def asksavefile(self):
410 dir, base = self.defaultfilename("save")
411 if not self.savedialog:
412 self.savedialog = tkFileDialog.SaveAs(master=self.text,
413 filetypes=self.filetypes)
414 return self.savedialog.show(initialdir=dir, initialfile=base)
415
416
417def test():
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000418 root = Tk()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000419 class MyEditWin:
420 def __init__(self, text):
421 self.text = text
422 self.flist = None
423 self.text.bind("<Control-o>", self.open)
424 self.text.bind("<Control-s>", self.save)
425 self.text.bind("<Alt-s>", self.save_as)
426 self.text.bind("<Alt-z>", self.save_a_copy)
427 def get_saved(self): return 0
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000428 def set_saved(self, flag): pass
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000429 def reset_undo(self): pass
430 def open(self, event):
431 self.text.event_generate("<<open-window-from-file>>")
432 def save(self, event):
433 self.text.event_generate("<<save-window>>")
434 def save_as(self, event):
435 self.text.event_generate("<<save-window-as-file>>")
436 def save_a_copy(self, event):
437 self.text.event_generate("<<save-copy-of-window-as-file>>")
438 text = Text(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000439 text.pack()
440 text.focus_set()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000441 editwin = MyEditWin(text)
442 io = IOBinding(editwin)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000443 root.mainloop()
444
445if __name__ == "__main__":
Jeremy Hylton1eab0022001-02-02 20:07:46 +0000446 from Tkinter import *
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000447 test()