blob: c8b0d412ecb4aec93d707cb94bf102e423381ab8 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
3import string
4import re
5import imp
6from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009
10import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000011import idlever
12import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000013import SearchDialog
14import GrepDialog
15import ReplaceDialog
Steven M. Gavadc72f482002-01-03 11:51:07 +000016#from IdleConf import idleconf
17from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000018import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000019
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
23# File menu
24
25#$ event <<open-module>>
26#$ win <Alt-m>
27#$ unix <Control-x><Control-m>
28
29#$ event <<open-class-browser>>
30#$ win <Alt-c>
31#$ unix <Control-x><Control-b>
32
33#$ event <<open-path-browser>>
34
35#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000036
David Scherer7aced172000-08-15 01:13:23 +000037#$ unix <Control-x><Control-0>
38#$ unix <Control-x><Key-0>
39#$ win <Alt-F4>
40
41# Edit menu
42
43#$ event <<Copy>>
44#$ win <Control-c>
45#$ unix <Alt-w>
46
47#$ event <<Cut>>
48#$ win <Control-x>
49#$ unix <Control-w>
50
51#$ event <<Paste>>
52#$ win <Control-v>
53#$ unix <Control-y>
54
55#$ event <<select-all>>
56#$ win <Alt-a>
57#$ unix <Alt-a>
58
59# Help menu
60
61#$ event <<help>>
62#$ win <F1>
63#$ unix <F1>
64
65#$ event <<about-idle>>
66
67# Events without menu entries
68
69#$ event <<remove-selection>>
70#$ win <Escape>
71
72#$ event <<center-insert>>
73#$ win <Control-l>
74#$ unix <Control-l>
75
76#$ event <<do-nothing>>
77#$ unix <Control-x>
78
David Scherer7aced172000-08-15 01:13:23 +000079class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000080 from Percolator import Percolator
81 from ColorDelegator import ColorDelegator
82 from UndoDelegator import UndoDelegator
83 from IOBinding import IOBinding
84 import Bindings
85 from Tkinter import Toplevel
86 from MultiStatusBar import MultiStatusBar
87
David Scherer7aced172000-08-15 01:13:23 +000088 vars = {}
89
90 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000091 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000092 self.flist = flist
93 root = root or flist.root
94 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000095 self.menubar = Menu(root)
96 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000097 if flist:
98 self.vars = flist.vars
99 #self.top.instanceDict makes flist.inversedict avalable to
100 #configDialog.py so it can access all EditorWindow instaces
101 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +0000102 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
103 'recent-files.lst')
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000104 self.break_set = False
David Scherer7aced172000-08-15 01:13:23 +0000105 self.vbar = vbar = Scrollbar(top, name='vbar')
106 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000107 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
108 foreground=idleConf.GetHighlight(currentTheme,
109 'normal',fgBg='fg'),
110 background=idleConf.GetHighlight(currentTheme,
111 'normal',fgBg='bg'),
112 highlightcolor=idleConf.GetHighlight(currentTheme,
113 'hilite',fgBg='fg'),
114 highlightbackground=idleConf.GetHighlight(currentTheme,
115 'hilite',fgBg='bg'),
116 insertbackground=idleConf.GetHighlight(currentTheme,
117 'cursor',fgBg='fg'),
118 width=idleConf.GetOption('main','EditorWindow','width'),
119 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000120
121 self.createmenubar()
122 self.apply_bindings()
123
124 self.top.protocol("WM_DELETE_WINDOW", self.close)
125 self.top.bind("<<close-window>>", self.close_event)
126 text.bind("<<center-insert>>", self.center_insert_event)
127 text.bind("<<help>>", self.help_dialog)
128 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000129 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000130 text.bind("<<python-docs>>", self.python_docs)
131 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000132 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000133 text.bind("<<open-module>>", self.open_module)
134 text.bind("<<do-nothing>>", lambda event: "break")
135 text.bind("<<select-all>>", self.select_all)
136 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000137 text.bind("<<find>>", self.find_event)
138 text.bind("<<find-again>>", self.find_again_event)
139 text.bind("<<find-in-files>>", self.find_in_files_event)
140 text.bind("<<find-selection>>", self.find_selection_event)
141 text.bind("<<replace>>", self.replace_event)
142 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000143 text.bind("<3>", self.right_menu_event)
144 if flist:
145 flist.inversedict[self] = key
146 if key:
147 flist.dict[key] = self
148 text.bind("<<open-new-window>>", self.flist.new_callback)
149 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
150 text.bind("<<open-class-browser>>", self.open_class_browser)
151 text.bind("<<open-path-browser>>", self.open_path_browser)
152
Steven M. Gava898a3652001-10-07 11:10:44 +0000153 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000154 vbar['command'] = text.yview
155 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000156 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000157 fontWeight='normal'
158 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
159 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000160 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000161 idleConf.GetOption('main','EditorWindow','font-size'),
162 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000163 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
164 text.pack(side=TOP, fill=BOTH, expand=1)
165 text.focus_set()
166
167 self.per = per = self.Percolator(text)
168 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000169 self.color = color = self.ColorDelegator()
170 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000171 ##print "Initial colorizer"
172 else:
173 ##print "No initial colorizer"
174 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000175
176 self.undo = undo = self.UndoDelegator()
177 per.insertfilter(undo)
178 text.undo_block_start = undo.undo_block_start
179 text.undo_block_stop = undo.undo_block_stop
180 undo.set_saved_change_hook(self.saved_change_hook)
181
182 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000183 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000184 io.set_filename_change_hook(self.filename_change_hook)
185
Steven M. Gava1d46e402002-03-27 08:40:46 +0000186 #create the Recent Files submenu
187 self.menuRecentFiles=Menu(self.menubar)
188 self.menudict['file'].insert_cascade(3,label='Recent Files',
189 underline=0,menu=self.menuRecentFiles)
190 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000191
David Scherer7aced172000-08-15 01:13:23 +0000192 if filename:
193 if os.path.exists(filename):
194 io.loadfile(filename)
195 else:
196 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000197 self.saved_change_hook()
198
199 self.load_extensions()
200
201 menu = self.menudict.get('windows')
202 if menu:
203 end = menu.index("end")
204 if end is None:
205 end = -1
206 if end >= 0:
207 menu.add_separator()
208 end = end + 1
209 self.wmenu_end = end
210 WindowList.register_callback(self.postwindowsmenu)
211
212 # Some abstractions so IDLE extensions are cross-IDE
213 self.askyesno = tkMessageBox.askyesno
214 self.askinteger = tkSimpleDialog.askinteger
215 self.showerror = tkMessageBox.showerror
216
217 if self.extensions.has_key('AutoIndent'):
218 self.extensions['AutoIndent'].set_indentation_params(
219 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000220
David Scherer7aced172000-08-15 01:13:23 +0000221 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000222 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000223 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
224 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
225 self.status_bar.pack(side=BOTTOM, fill=X)
226 self.text.bind('<KeyRelease>', self.set_line_and_column)
227 self.text.bind('<ButtonRelease>', self.set_line_and_column)
228 self.text.after_idle(self.set_line_and_column)
229
230 def set_line_and_column(self, event=None):
231 line, column = string.split(self.text.index(INSERT), '.')
232 self.status_bar.set_label('column', 'Col: %s' % column)
233 self.status_bar.set_label('line', 'Ln: %s' % line)
234
235 def wakeup(self):
236 if self.top.wm_state() == "iconic":
237 self.top.wm_deiconify()
238 else:
239 self.top.tkraise()
240 self.text.focus_set()
241
242 menu_specs = [
243 ("file", "_File"),
244 ("edit", "_Edit"),
245 ("format", "F_ormat"),
246 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000247 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000248 ("windows", "_Windows"),
249 ("help", "_Help"),
250 ]
251
252 def createmenubar(self):
253 mbar = self.menubar
254 self.menudict = menudict = {}
255 for name, label in self.menu_specs:
256 underline, label = prepstr(label)
257 menudict[name] = menu = Menu(mbar, name=name)
258 mbar.add_cascade(label=label, menu=menu, underline=underline)
259 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000260 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000261 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000262
263 def postwindowsmenu(self):
264 # Only called when Windows menu exists
265 # XXX Actually, this Just-In-Time updating interferes badly
266 # XXX with the tear-off feature. It would be better to update
267 # XXX all Windows menus whenever the list of windows changes.
268 menu = self.menudict['windows']
269 end = menu.index("end")
270 if end is None:
271 end = -1
272 if end > self.wmenu_end:
273 menu.delete(self.wmenu_end+1, end)
274 WindowList.add_windows_to_menu(menu)
275
276 rmenu = None
277
278 def right_menu_event(self, event):
279 self.text.tag_remove("sel", "1.0", "end")
280 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
281 if not self.rmenu:
282 self.make_rmenu()
283 rmenu = self.rmenu
284 self.event = event
285 iswin = sys.platform[:3] == 'win'
286 if iswin:
287 self.text.config(cursor="arrow")
288 rmenu.tk_popup(event.x_root, event.y_root)
289 if iswin:
290 self.text.config(cursor="ibeam")
291
292 rmenu_specs = [
293 # ("Label", "<<virtual-event>>"), ...
294 ("Close", "<<close-window>>"), # Example
295 ]
296
297 def make_rmenu(self):
298 rmenu = Menu(self.text, tearoff=0)
299 for label, eventname in self.rmenu_specs:
300 def command(text=self.text, eventname=eventname):
301 text.event_generate(eventname)
302 rmenu.add_command(label=label, command=command)
303 self.rmenu = rmenu
304
305 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000306 aboutDialog.AboutDialog(self.top,'About IDLEfork')
307
Steven M. Gava3b55a892001-11-21 05:56:26 +0000308 def config_dialog(self, event=None):
309 configDialog.ConfigDialog(self.top,'Settings')
310
David Scherer7aced172000-08-15 01:13:23 +0000311 def good_advice(self, event=None):
312 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
313
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000314 def view_readme(self, event=None):
315 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
316 textView.TextViewer(self.top,'IDLEfork - README',fn)
317
David Scherer7aced172000-08-15 01:13:23 +0000318 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000319 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
320 textView.TextViewer(self.top,'Help',fn)
321
David Scherer7aced172000-08-15 01:13:23 +0000322 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000323 if sys.platform[:3] == "win":
324 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000325 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000326 fn = os.path.normpath(fn)
327 if os.path.isfile(fn):
328 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000329 else:
330 fn = os.path.dirname(__file__)
331 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
332 fn = os.path.normpath(fn)
333 if os.path.isfile(fn):
334 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000335 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000336 def python_docs(self, event=None):
337 os.startfile(self.help_url)
338 else:
339 def python_docs(self, event=None):
340 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000341
342 def display_docs(self, url):
343 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000344
345 def select_all(self, event=None):
346 self.text.tag_add("sel", "1.0", "end-1c")
347 self.text.mark_set("insert", "1.0")
348 self.text.see("insert")
349 return "break"
350
351 def remove_selection(self, event=None):
352 self.text.tag_remove("sel", "1.0", "end")
353 self.text.see("insert")
354
Steven M. Gavac5976402002-01-04 03:06:08 +0000355 def find_event(self, event):
356 SearchDialog.find(self.text)
357 return "break"
358
359 def find_again_event(self, event):
360 SearchDialog.find_again(self.text)
361 return "break"
362
363 def find_selection_event(self, event):
364 SearchDialog.find_selection(self.text)
365 return "break"
366
367 def find_in_files_event(self, event):
368 GrepDialog.grep(self.text, self.io, self.flist)
369 return "break"
370
371 def replace_event(self, event):
372 ReplaceDialog.replace(self.text)
373 return "break"
374
375 def goto_line_event(self, event):
376 text = self.text
377 lineno = tkSimpleDialog.askinteger("Goto",
378 "Go to line number:",parent=text)
379 if lineno is None:
380 return "break"
381 if lineno <= 0:
382 text.bell()
383 return "break"
384 text.mark_set("insert", "%d.0" % lineno)
385 text.see("insert")
386
David Scherer7aced172000-08-15 01:13:23 +0000387 def open_module(self, event=None):
388 # XXX Shouldn't this be in IOBinding or in FileList?
389 try:
390 name = self.text.get("sel.first", "sel.last")
391 except TclError:
392 name = ""
393 else:
394 name = string.strip(name)
395 if not name:
396 name = tkSimpleDialog.askstring("Module",
397 "Enter the name of a Python module\n"
398 "to search on sys.path and open:",
399 parent=self.text)
400 if name:
401 name = string.strip(name)
402 if not name:
403 return
404 # XXX Ought to support package syntax
405 # XXX Ought to insert current file's directory in front of path
406 try:
407 (f, file, (suffix, mode, type)) = imp.find_module(name)
408 except (NameError, ImportError), msg:
409 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
410 return
411 if type != imp.PY_SOURCE:
412 tkMessageBox.showerror("Unsupported type",
413 "%s is not a source module" % name, parent=self.text)
414 return
415 if f:
416 f.close()
417 if self.flist:
418 self.flist.open(file)
419 else:
420 self.io.loadfile(file)
421
422 def open_class_browser(self, event=None):
423 filename = self.io.filename
424 if not filename:
425 tkMessageBox.showerror(
426 "No filename",
427 "This buffer has no associated filename",
428 master=self.text)
429 self.text.focus_set()
430 return None
431 head, tail = os.path.split(filename)
432 base, ext = os.path.splitext(tail)
433 import ClassBrowser
434 ClassBrowser.ClassBrowser(self.flist, base, [head])
435
436 def open_path_browser(self, event=None):
437 import PathBrowser
438 PathBrowser.PathBrowser(self.flist)
439
440 def gotoline(self, lineno):
441 if lineno is not None and lineno > 0:
442 self.text.mark_set("insert", "%d.0" % lineno)
443 self.text.tag_remove("sel", "1.0", "end")
444 self.text.tag_add("sel", "insert", "insert +1l")
445 self.center()
446
447 def ispythonsource(self, filename):
448 if not filename:
449 return 1
450 base, ext = os.path.splitext(os.path.basename(filename))
451 if os.path.normcase(ext) in (".py", ".pyw"):
452 return 1
453 try:
454 f = open(filename)
455 line = f.readline()
456 f.close()
457 except IOError:
458 return 0
459 return line[:2] == '#!' and string.find(line, 'python') >= 0
460
461 def close_hook(self):
462 if self.flist:
463 self.flist.close_edit(self)
464
465 def set_close_hook(self, close_hook):
466 self.close_hook = close_hook
467
468 def filename_change_hook(self):
469 if self.flist:
470 self.flist.filename_changed_edit(self)
471 self.saved_change_hook()
472 if self.ispythonsource(self.io.filename):
473 self.addcolorizer()
474 else:
475 self.rmcolorizer()
476
477 def addcolorizer(self):
478 if self.color:
479 return
480 ##print "Add colorizer"
481 self.per.removefilter(self.undo)
482 self.color = self.ColorDelegator()
483 self.per.insertfilter(self.color)
484 self.per.insertfilter(self.undo)
485
486 def rmcolorizer(self):
487 if not self.color:
488 return
489 ##print "Remove colorizer"
490 self.per.removefilter(self.undo)
491 self.per.removefilter(self.color)
492 self.color = None
493 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000494
495 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000496 "Update the colour theme if it is changed"
497 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000498 if self.color:
499 self.color = self.ColorDelegator()
500 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000501
Steven M. Gavab1585412002-03-12 00:21:56 +0000502 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000503 "Update the text widgets' font if it is changed"
504 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000505 fontWeight='normal'
506 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
507 fontWeight='bold'
508 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
509 idleConf.GetOption('main','EditorWindow','font-size'),
510 fontWeight))
511
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000512 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000513 "Update the keybindings if they are changed"
514 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000515 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
516 keydefs = self.Bindings.default_keydefs
517 for event, keylist in keydefs.items():
518 self.text.event_delete(event)
519 self.apply_bindings()
520 #update menu accelerators
521 menuEventDict={}
522 for menu in self.Bindings.menudefs:
523 menuEventDict[menu[0]]={}
524 for item in menu[1]:
525 if item:
526 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
527 for menubarItem in self.menudict.keys():
528 menu=self.menudict[menubarItem]
529 end=menu.index(END)+1
530 for index in range(0,end):
531 if menu.type(index)=='command':
532 accel=menu.entrycget(index,'accelerator')
533 if accel:
534 itemName=menu.entrycget(index,'label')
535 event=''
536 if menuEventDict.has_key(menubarItem):
537 if menuEventDict[menubarItem].has_key(itemName):
538 event=menuEventDict[menubarItem][itemName]
539 if event:
540 #print 'accel was:',accel
541 accel=get_accelerator(keydefs, event)
542 menu.entryconfig(index,accelerator=accel)
543 #print 'accel now:',accel,'\n'
544
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000545 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000546 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000547 menuList=idleConf.GetAllExtraHelpSourcesList()
548 helpMenu=self.menudict['help']
549 cascadeIndex=helpMenu.index(END)-1
550 if menuList:
551 if not hasattr(self,'menuExtraHelp'):
552 self.menuExtraHelp=Menu(self.menubar)
553 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
554 underline=1,menu=self.menuExtraHelp)
555 self.menuExtraHelp.delete(1,END)
556 for menuItem in menuList:
557 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000558 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000559 else: #no extra help items
560 if hasattr(self,'menuExtraHelp'):
561 helpMenu.delete(cascadeIndex-1)
562 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000563
564 def __DisplayExtraHelpCallback(self,helpFile):
565 def DisplayExtraHelp(helpFile=helpFile):
566 self.display_docs(helpFile)
567 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000568
Steven M. Gava1d46e402002-03-27 08:40:46 +0000569 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000570 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000571 rfList=[]
572 if os.path.exists(self.recentFilesPath):
573 RFfile=open(self.recentFilesPath,'r')
574 try:
575 rfList=RFfile.readlines()
576 finally:
577 RFfile.close()
578 if newFile:
579 newFile=os.path.abspath(newFile)+'\n'
580 if newFile in rfList:
581 rfList.remove(newFile)
582 rfList.insert(0,newFile)
583 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000584 #print self.flist.inversedict
585 #print self.top.instanceDict
586 #print self
Steven M. Gava1d46e402002-03-27 08:40:46 +0000587 if rfList:
588 for instance in self.top.instanceDict.keys():
589 instance.menuRecentFiles.delete(1,END)
590 for file in rfList:
591 fileName=file[0:-1]
592 instance.menuRecentFiles.add_command(label=fileName,
593 command=instance.__RecentFileCallback(fileName))
594
595 def __CleanRecentFiles(self,rfList):
596 origRfList=rfList[:]
597 count=0
598 nonFiles=[]
599 for path in rfList:
600 if not os.path.exists(path[0:-1]):
601 nonFiles.append(count)
602 count=count+1
603 if nonFiles:
604 nonFiles.reverse()
605 for index in nonFiles:
606 del(rfList[index])
607 if len(rfList)>19:
608 rfList=rfList[0:19]
609 #if rfList != origRfList:
610 RFfile=open(self.recentFilesPath,'w')
611 try:
612 RFfile.writelines(rfList)
613 finally:
614 RFfile.close()
615 return rfList
616
617 def __RecentFileCallback(self,fileName):
618 def OpenRecentFile(fileName=fileName):
619 self.io.open(editFile=fileName)
620 return OpenRecentFile
621
David Scherer7aced172000-08-15 01:13:23 +0000622 def saved_change_hook(self):
623 short = self.short_title()
624 long = self.long_title()
625 if short and long:
626 title = short + " - " + long
627 elif short:
628 title = short
629 elif long:
630 title = long
631 else:
632 title = "Untitled"
633 icon = short or long or title
634 if not self.get_saved():
635 title = "*%s*" % title
636 icon = "*%s" % icon
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000637 if self.break_set:
638 shell = self.flist.pyshell
639 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000640 self.top.wm_title(title)
641 self.top.wm_iconname(icon)
642
643 def get_saved(self):
644 return self.undo.get_saved()
645
646 def set_saved(self, flag):
647 self.undo.set_saved(flag)
648
649 def reset_undo(self):
650 self.undo.reset_undo()
651
652 def short_title(self):
653 filename = self.io.filename
654 if filename:
655 filename = os.path.basename(filename)
656 return filename
657
658 def long_title(self):
659 return self.io.filename or ""
660
661 def center_insert_event(self, event):
662 self.center()
663
664 def center(self, mark="insert"):
665 text = self.text
666 top, bot = self.getwindowlines()
667 lineno = self.getlineno(mark)
668 height = bot - top
669 newtop = max(1, lineno - height/2)
670 text.yview(float(newtop))
671
672 def getwindowlines(self):
673 text = self.text
674 top = self.getlineno("@0,0")
675 bot = self.getlineno("@0,65535")
676 if top == bot and text.winfo_height() == 1:
677 # Geometry manager hasn't run yet
678 height = int(text['height'])
679 bot = top + height - 1
680 return top, bot
681
682 def getlineno(self, mark="insert"):
683 text = self.text
684 return int(float(text.index(mark)))
685
686 def close_event(self, event):
687 self.close()
688
689 def maybesave(self):
690 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000691 if not self.get_saved():
692 if self.top.state()!='normal':
693 self.top.deiconify()
694 self.top.lower()
695 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000696 return self.io.maybesave()
697
698 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000699 reply = self.maybesave()
700 if reply != "cancel":
701 self._close()
702 return reply
703
704 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000705 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000706 if self.io.filename:
707 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000708 if self.break_set:
709 shell = self.flist.pyshell
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000710 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000711 WindowList.unregister_callback(self.postwindowsmenu)
712 if self.close_hook:
713 self.close_hook()
714 self.flist = None
715 colorizing = 0
716 self.unload_extensions()
717 self.io.close(); self.io = None
718 self.undo = None # XXX
719 if self.color:
720 colorizing = self.color.colorizing
721 doh = colorizing and self.top
722 self.color.close(doh) # Cancel colorization
723 self.text = None
724 self.vars = None
725 self.per.close(); self.per = None
726 if not colorizing:
727 self.top.destroy()
728
729 def load_extensions(self):
730 self.extensions = {}
731 self.load_standard_extensions()
732
733 def unload_extensions(self):
734 for ins in self.extensions.values():
735 if hasattr(ins, "close"):
736 ins.close()
737 self.extensions = {}
738
739 def load_standard_extensions(self):
740 for name in self.get_standard_extension_names():
741 try:
742 self.load_extension(name)
743 except:
744 print "Failed to load extension", `name`
745 import traceback
746 traceback.print_exc()
747
748 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000749 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000750
751 def load_extension(self, name):
752 mod = __import__(name, globals(), locals(), [])
753 cls = getattr(mod, name)
754 ins = cls(self)
755 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000756 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000757 if keydefs:
758 self.apply_bindings(keydefs)
759 for vevent in keydefs.keys():
760 methodname = string.replace(vevent, "-", "_")
761 while methodname[:1] == '<':
762 methodname = methodname[1:]
763 while methodname[-1:] == '>':
764 methodname = methodname[:-1]
765 methodname = methodname + "_event"
766 if hasattr(ins, methodname):
767 self.text.bind(vevent, getattr(ins, methodname))
768 if hasattr(ins, "menudefs"):
769 self.fill_menus(ins.menudefs, keydefs)
770 return ins
771
772 def apply_bindings(self, keydefs=None):
773 if keydefs is None:
774 keydefs = self.Bindings.default_keydefs
775 text = self.text
776 text.keydefs = keydefs
777 for event, keylist in keydefs.items():
778 if keylist:
779 apply(text.event_add, (event,) + tuple(keylist))
780
781 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000782 """Add appropriate entries to the menus and submenus
783
784 Menus that are absent or None in self.menudict are ignored.
785 """
David Scherer7aced172000-08-15 01:13:23 +0000786 if defs is None:
787 defs = self.Bindings.menudefs
788 if keydefs is None:
789 keydefs = self.Bindings.default_keydefs
790 menudict = self.menudict
791 text = self.text
792 for mname, itemlist in defs:
793 menu = menudict.get(mname)
794 if not menu:
795 continue
796 for item in itemlist:
797 if not item:
798 menu.add_separator()
799 else:
800 label, event = item
801 checkbutton = (label[:1] == '!')
802 if checkbutton:
803 label = label[1:]
804 underline, label = prepstr(label)
805 accelerator = get_accelerator(keydefs, event)
806 def command(text=text, event=event):
807 text.event_generate(event)
808 if checkbutton:
809 var = self.getrawvar(event, BooleanVar)
810 menu.add_checkbutton(label=label, underline=underline,
811 command=command, accelerator=accelerator,
812 variable=var)
813 else:
814 menu.add_command(label=label, underline=underline,
815 command=command, accelerator=accelerator)
816
817 def getvar(self, name):
818 var = self.getrawvar(name)
819 if var:
820 return var.get()
821
822 def setvar(self, name, value, vartype=None):
823 var = self.getrawvar(name, vartype)
824 if var:
825 var.set(value)
826
827 def getrawvar(self, name, vartype=None):
828 var = self.vars.get(name)
829 if not var and vartype:
830 self.vars[name] = var = vartype(self.text)
831 return var
832
833 # Tk implementations of "virtual text methods" -- each platform
834 # reusing IDLE's support code needs to define these for its GUI's
835 # flavor of widget.
836
837 # Is character at text_index in a Python string? Return 0 for
838 # "guaranteed no", true for anything else. This info is expensive
839 # to compute ab initio, but is probably already known by the
840 # platform's colorizer.
841
842 def is_char_in_string(self, text_index):
843 if self.color:
844 # Return true iff colorizer hasn't (re)gotten this far
845 # yet, or the character is tagged as being in a string
846 return self.text.tag_prevrange("TODO", text_index) or \
847 "STRING" in self.text.tag_names(text_index)
848 else:
849 # The colorizer is missing: assume the worst
850 return 1
851
852 # If a selection is defined in the text widget, return (start,
853 # end) as Tkinter text indices, otherwise return (None, None)
854 def get_selection_indices(self):
855 try:
856 first = self.text.index("sel.first")
857 last = self.text.index("sel.last")
858 return first, last
859 except TclError:
860 return None, None
861
862 # Return the text widget's current view of what a tab stop means
863 # (equivalent width in spaces).
864
865 def get_tabwidth(self):
866 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
867 return int(current)
868
869 # Set the text widget's current view of what a tab stop means.
870
871 def set_tabwidth(self, newtabwidth):
872 text = self.text
873 if self.get_tabwidth() != newtabwidth:
874 pixels = text.tk.call("font", "measure", text["font"],
875 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000876 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000877 text.configure(tabs=pixels)
878
879def prepstr(s):
880 # Helper to extract the underscore from a string, e.g.
881 # prepstr("Co_py") returns (2, "Copy").
882 i = string.find(s, '_')
883 if i >= 0:
884 s = s[:i] + s[i+1:]
885 return i, s
886
887
888keynames = {
889 'bracketleft': '[',
890 'bracketright': ']',
891 'slash': '/',
892}
893
894def get_accelerator(keydefs, event):
895 keylist = keydefs.get(event)
896 if not keylist:
897 return ""
898 s = keylist[0]
899 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
900 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
901 s = re.sub("Key-", "", s)
902 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
903 s = re.sub("Control-", "Ctrl-", s)
904 s = re.sub("-", "+", s)
905 s = re.sub("><", " ", s)
906 s = re.sub("<", "", s)
907 s = re.sub(">", "", s)
908 return s
909
910
911def fixwordbreaks(root):
912 # Make sure that Tk's double-click and next/previous word
913 # operations use our definition of a word (i.e. an identifier)
914 tk = root.tk
915 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
916 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
917 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
918
919
920def test():
921 root = Tk()
922 fixwordbreaks(root)
923 root.withdraw()
924 if sys.argv[1:]:
925 filename = sys.argv[1]
926 else:
927 filename = None
928 edit = EditorWindow(root=root, filename=filename)
929 edit.set_close_hook(root.quit)
930 root.mainloop()
931 root.destroy()
932
933if __name__ == '__main__':
934 test()