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