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