blob: 8d654b9aec9454c012e0bf904bd007331f3dda17 [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
Tim Peters280488b2002-08-23 18:19:30 +0000212
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000213 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):
Guido van Rossum2ca78622002-06-10 18:52:02 +0000279 if self.get_saved():
280 filename = self.filename
281 else:
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000282 (tfd, tfn) = tempfile.mkstemp()
283 os.close(tfd)
284 filename = tfn
Guido van Rossum2ca78622002-06-10 18:52:02 +0000285 if not self.writefile(filename):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000286 os.unlink(tfn)
Guido van Rossum2ca78622002-06-10 18:52:02 +0000287 return "break"
288 edconf = idleconf.getsection('EditorWindow')
289 command = edconf.get('print-command')
290 command = command % filename
291 if os.name == 'posix':
292 command = command + " 2>&1"
293 pipe = os.popen(command, "r")
294 output = pipe.read().strip()
295 status = pipe.close()
296 if status:
297 output = "Printing failed (exit status 0x%x)\n" % status + output
298 if output:
299 output = "Printing command: %s\n" % repr(command) + output
300 tkMessageBox.showerror("Print status", output, master=self.text)
301 return "break"
302
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000303 def writefile(self, filename):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000304 self.fixlastline()
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000305 chars = self.encode(self.text.get("1.0", "end-1c"))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000306 try:
307 f = open(filename, "w")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000308 f.write(chars)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000309 f.close()
310 ## print "saved to", `filename`
Tim Petersbc0e9102002-04-04 22:55:58 +0000311 return True
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000312 except IOError, msg:
313 tkMessageBox.showerror("I/O Error", str(msg),
314 master=self.text)
Tim Petersbc0e9102002-04-04 22:55:58 +0000315 return False
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000316
Martin v. Löwiseade4a12002-08-05 14:55:21 +0000317 def encode(self, chars):
318 if isinstance(chars, types.StringType):
319 # This is either plain ASCII, or Tk was returning mixed-encoding
320 # text to us. Don't try to guess further.
321 return chars
322
323 # See whether there is anything non-ASCII in it.
324 # If not, no need to figure out the encoding.
325 try:
326 return chars.encode('ascii')
327 except UnicodeError:
328 pass
329
330 # If there is an encoding declared, try this first.
331 try:
332 enc = coding_spec(chars)
333 failed = None
334 except LookupError, msg:
335 failed = msg
336 enc = None
337 if enc:
338 try:
339 return chars.encode(enc)
340 except UnicodeError:
341 failed = "Invalid encoding '%s'" % enc
342
343 if failed:
344 tkMessageBox.showerror(
345 "I/O Error",
346 "%s. Saving as UTF-8" % failed,
347 master = self.text)
348
349 # If there was a UTF-8 signature, use that. This should not fail
350 if self.fileencoding == BOM_UTF8 or failed:
351 return BOM_UTF8 + chars.encode("utf-8")
352
353 # Try the original file encoding next, if any
354 if self.fileencoding:
355 try:
356 return chars.encode(self.fileencoding)
357 except UnicodeError:
358 tkMessageBox.showerror(
359 "I/O Error",
360 "Cannot save this as '%s' anymore. Saving as UTF-8" % self.fileencoding,
361 master = self.text)
362 return BOM_UTF8 + chars.encode("utf-8")
363
364 # Nothing was declared, and we had not determined an encoding
365 # on loading. Recommend an encoding line.
366 try:
367 chars = chars.encode(encoding)
368 enc = encoding
369 except UnicodeError:
370 chars = BOM_UTF8 + chars.encode("utf-8")
371 enc = "utf-8"
372 tkMessageBox.showerror(
373 "I/O Error",
374 "Non-ASCII found, yet no encoding declared. Add a line like\n"
375 "# -*- coding: %s -*- \nto your file" % enc,
376 master = self.text)
377 return chars
378
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000379 def fixlastline(self):
380 c = self.text.get("end-2c")
381 if c != '\n':
382 self.text.insert("end-1c", "\n")
383
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000384 opendialog = None
385 savedialog = None
386
387 filetypes = [
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000388 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000389 ("All text files", "*", "TEXT"),
390 ("All files", "*"),
391 ]
392
393 def askopenfile(self):
394 dir, base = self.defaultfilename("open")
395 if not self.opendialog:
396 self.opendialog = tkFileDialog.Open(master=self.text,
397 filetypes=self.filetypes)
398 return self.opendialog.show(initialdir=dir, initialfile=base)
399
400 def defaultfilename(self, mode="open"):
401 if self.filename:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000402 return os.path.split(self.filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000403 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000404 try:
405 pwd = os.getcwd()
406 except os.error:
407 pwd = ""
408 return pwd, ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000409
410 def asksavefile(self):
411 dir, base = self.defaultfilename("save")
412 if not self.savedialog:
413 self.savedialog = tkFileDialog.SaveAs(master=self.text,
414 filetypes=self.filetypes)
415 return self.savedialog.show(initialdir=dir, initialfile=base)
416
417
418def test():
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000419 root = Tk()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000420 class MyEditWin:
421 def __init__(self, text):
422 self.text = text
423 self.flist = None
424 self.text.bind("<Control-o>", self.open)
425 self.text.bind("<Control-s>", self.save)
426 self.text.bind("<Alt-s>", self.save_as)
427 self.text.bind("<Alt-z>", self.save_a_copy)
428 def get_saved(self): return 0
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000429 def set_saved(self, flag): pass
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000430 def reset_undo(self): pass
431 def open(self, event):
432 self.text.event_generate("<<open-window-from-file>>")
433 def save(self, event):
434 self.text.event_generate("<<save-window>>")
435 def save_as(self, event):
436 self.text.event_generate("<<save-window-as-file>>")
437 def save_a_copy(self, event):
438 self.text.event_generate("<<save-copy-of-window-as-file>>")
439 text = Text(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000440 text.pack()
441 text.focus_set()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000442 editwin = MyEditWin(text)
443 io = IOBinding(editwin)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000444 root.mainloop()
445
446if __name__ == "__main__":
Jeremy Hylton1eab0022001-02-02 20:07:46 +0000447 from Tkinter import *
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000448 test()