blob: 26400f378e5fa727b3bac2ca6e54c77a703fe087 [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__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000328 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000329 fn = os.path.normpath(fn)
330 if os.path.isfile(fn):
331 help_url = fn
332 del fn
David Scherer7aced172000-08-15 01:13:23 +0000333
334 def python_docs(self, event=None):
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000335 self.display_docs(self.help_url)
336
337 def display_docs(self, url):
338 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000339
340 def select_all(self, event=None):
341 self.text.tag_add("sel", "1.0", "end-1c")
342 self.text.mark_set("insert", "1.0")
343 self.text.see("insert")
344 return "break"
345
346 def remove_selection(self, event=None):
347 self.text.tag_remove("sel", "1.0", "end")
348 self.text.see("insert")
349
Steven M. Gavac5976402002-01-04 03:06:08 +0000350 def find_event(self, event):
351 SearchDialog.find(self.text)
352 return "break"
353
354 def find_again_event(self, event):
355 SearchDialog.find_again(self.text)
356 return "break"
357
358 def find_selection_event(self, event):
359 SearchDialog.find_selection(self.text)
360 return "break"
361
362 def find_in_files_event(self, event):
363 GrepDialog.grep(self.text, self.io, self.flist)
364 return "break"
365
366 def replace_event(self, event):
367 ReplaceDialog.replace(self.text)
368 return "break"
369
370 def goto_line_event(self, event):
371 text = self.text
372 lineno = tkSimpleDialog.askinteger("Goto",
373 "Go to line number:",parent=text)
374 if lineno is None:
375 return "break"
376 if lineno <= 0:
377 text.bell()
378 return "break"
379 text.mark_set("insert", "%d.0" % lineno)
380 text.see("insert")
381
David Scherer7aced172000-08-15 01:13:23 +0000382 def open_module(self, event=None):
383 # XXX Shouldn't this be in IOBinding or in FileList?
384 try:
385 name = self.text.get("sel.first", "sel.last")
386 except TclError:
387 name = ""
388 else:
389 name = string.strip(name)
390 if not name:
391 name = tkSimpleDialog.askstring("Module",
392 "Enter the name of a Python module\n"
393 "to search on sys.path and open:",
394 parent=self.text)
395 if name:
396 name = string.strip(name)
397 if not name:
398 return
399 # XXX Ought to support package syntax
400 # XXX Ought to insert current file's directory in front of path
401 try:
402 (f, file, (suffix, mode, type)) = imp.find_module(name)
403 except (NameError, ImportError), msg:
404 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
405 return
406 if type != imp.PY_SOURCE:
407 tkMessageBox.showerror("Unsupported type",
408 "%s is not a source module" % name, parent=self.text)
409 return
410 if f:
411 f.close()
412 if self.flist:
413 self.flist.open(file)
414 else:
415 self.io.loadfile(file)
416
417 def open_class_browser(self, event=None):
418 filename = self.io.filename
419 if not filename:
420 tkMessageBox.showerror(
421 "No filename",
422 "This buffer has no associated filename",
423 master=self.text)
424 self.text.focus_set()
425 return None
426 head, tail = os.path.split(filename)
427 base, ext = os.path.splitext(tail)
428 import ClassBrowser
429 ClassBrowser.ClassBrowser(self.flist, base, [head])
430
431 def open_path_browser(self, event=None):
432 import PathBrowser
433 PathBrowser.PathBrowser(self.flist)
434
435 def gotoline(self, lineno):
436 if lineno is not None and lineno > 0:
437 self.text.mark_set("insert", "%d.0" % lineno)
438 self.text.tag_remove("sel", "1.0", "end")
439 self.text.tag_add("sel", "insert", "insert +1l")
440 self.center()
441
442 def ispythonsource(self, filename):
443 if not filename:
444 return 1
445 base, ext = os.path.splitext(os.path.basename(filename))
446 if os.path.normcase(ext) in (".py", ".pyw"):
447 return 1
448 try:
449 f = open(filename)
450 line = f.readline()
451 f.close()
452 except IOError:
453 return 0
454 return line[:2] == '#!' and string.find(line, 'python') >= 0
455
456 def close_hook(self):
457 if self.flist:
458 self.flist.close_edit(self)
459
460 def set_close_hook(self, close_hook):
461 self.close_hook = close_hook
462
463 def filename_change_hook(self):
464 if self.flist:
465 self.flist.filename_changed_edit(self)
466 self.saved_change_hook()
467 if self.ispythonsource(self.io.filename):
468 self.addcolorizer()
469 else:
470 self.rmcolorizer()
471
472 def addcolorizer(self):
473 if self.color:
474 return
475 ##print "Add colorizer"
476 self.per.removefilter(self.undo)
477 self.color = self.ColorDelegator()
478 self.per.insertfilter(self.color)
479 self.per.insertfilter(self.undo)
480
481 def rmcolorizer(self):
482 if not self.color:
483 return
484 ##print "Remove colorizer"
485 self.per.removefilter(self.undo)
486 self.per.removefilter(self.color)
487 self.color = None
488 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000489
490 def ResetColorizer(self):
491 #this function is called from configDialog.py
492 #to update the colour theme if it is changed
493 if self.color:
494 self.color = self.ColorDelegator()
495 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000496
Steven M. Gavab1585412002-03-12 00:21:56 +0000497 def ResetFont(self):
498 #this function is called from configDialog.py
499 #to update the text widgets' font if it is changed
500 fontWeight='normal'
501 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
502 fontWeight='bold'
503 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
504 idleConf.GetOption('main','EditorWindow','font-size'),
505 fontWeight))
506
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000507 def ResetKeybindings(self):
508 #this function is called from configDialog.py
509 #to update the keybindings if they are changed
510 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
511 keydefs = self.Bindings.default_keydefs
512 for event, keylist in keydefs.items():
513 self.text.event_delete(event)
514 self.apply_bindings()
515 #update menu accelerators
516 menuEventDict={}
517 for menu in self.Bindings.menudefs:
518 menuEventDict[menu[0]]={}
519 for item in menu[1]:
520 if item:
521 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
522 for menubarItem in self.menudict.keys():
523 menu=self.menudict[menubarItem]
524 end=menu.index(END)+1
525 for index in range(0,end):
526 if menu.type(index)=='command':
527 accel=menu.entrycget(index,'accelerator')
528 if accel:
529 itemName=menu.entrycget(index,'label')
530 event=''
531 if menuEventDict.has_key(menubarItem):
532 if menuEventDict[menubarItem].has_key(itemName):
533 event=menuEventDict[menubarItem][itemName]
534 if event:
535 #print 'accel was:',accel
536 accel=get_accelerator(keydefs, event)
537 menu.entryconfig(index,accelerator=accel)
538 #print 'accel now:',accel,'\n'
539
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000540 def ResetExtraHelpMenu(self):
541 #load or update the Extra Help menu if required
542 menuList=idleConf.GetAllExtraHelpSourcesList()
543 helpMenu=self.menudict['help']
544 cascadeIndex=helpMenu.index(END)-1
545 if menuList:
546 if not hasattr(self,'menuExtraHelp'):
547 self.menuExtraHelp=Menu(self.menubar)
548 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
549 underline=1,menu=self.menuExtraHelp)
550 self.menuExtraHelp.delete(1,END)
551 for menuItem in menuList:
552 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000553 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000554 else: #no extra help items
555 if hasattr(self,'menuExtraHelp'):
556 helpMenu.delete(cascadeIndex-1)
557 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000558
559 def __DisplayExtraHelpCallback(self,helpFile):
560 def DisplayExtraHelp(helpFile=helpFile):
561 self.display_docs(helpFile)
562 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000563
Steven M. Gava1d46e402002-03-27 08:40:46 +0000564 def UpdateRecentFilesList(self,newFile=None):
565 #load or update the recent files list, and menu if required
566 rfList=[]
567 if os.path.exists(self.recentFilesPath):
568 RFfile=open(self.recentFilesPath,'r')
569 try:
570 rfList=RFfile.readlines()
571 finally:
572 RFfile.close()
573 if newFile:
574 newFile=os.path.abspath(newFile)+'\n'
575 if newFile in rfList:
576 rfList.remove(newFile)
577 rfList.insert(0,newFile)
578 rfList=self.__CleanRecentFiles(rfList)
579 print self.top.instanceDict
580 print self
581 if rfList:
582 for instance in self.top.instanceDict.keys():
583 instance.menuRecentFiles.delete(1,END)
584 for file in rfList:
585 fileName=file[0:-1]
586 instance.menuRecentFiles.add_command(label=fileName,
587 command=instance.__RecentFileCallback(fileName))
588
589 def __CleanRecentFiles(self,rfList):
590 origRfList=rfList[:]
591 count=0
592 nonFiles=[]
593 for path in rfList:
594 if not os.path.exists(path[0:-1]):
595 nonFiles.append(count)
596 count=count+1
597 if nonFiles:
598 nonFiles.reverse()
599 for index in nonFiles:
600 del(rfList[index])
601 if len(rfList)>19:
602 rfList=rfList[0:19]
603 #if rfList != origRfList:
604 RFfile=open(self.recentFilesPath,'w')
605 try:
606 RFfile.writelines(rfList)
607 finally:
608 RFfile.close()
609 return rfList
610
611 def __RecentFileCallback(self,fileName):
612 def OpenRecentFile(fileName=fileName):
613 self.io.open(editFile=fileName)
614 return OpenRecentFile
615
David Scherer7aced172000-08-15 01:13:23 +0000616 def saved_change_hook(self):
617 short = self.short_title()
618 long = self.long_title()
619 if short and long:
620 title = short + " - " + long
621 elif short:
622 title = short
623 elif long:
624 title = long
625 else:
626 title = "Untitled"
627 icon = short or long or title
628 if not self.get_saved():
629 title = "*%s*" % title
630 icon = "*%s" % icon
631 self.top.wm_title(title)
632 self.top.wm_iconname(icon)
633
634 def get_saved(self):
635 return self.undo.get_saved()
636
637 def set_saved(self, flag):
638 self.undo.set_saved(flag)
639
640 def reset_undo(self):
641 self.undo.reset_undo()
642
643 def short_title(self):
644 filename = self.io.filename
645 if filename:
646 filename = os.path.basename(filename)
647 return filename
648
649 def long_title(self):
650 return self.io.filename or ""
651
652 def center_insert_event(self, event):
653 self.center()
654
655 def center(self, mark="insert"):
656 text = self.text
657 top, bot = self.getwindowlines()
658 lineno = self.getlineno(mark)
659 height = bot - top
660 newtop = max(1, lineno - height/2)
661 text.yview(float(newtop))
662
663 def getwindowlines(self):
664 text = self.text
665 top = self.getlineno("@0,0")
666 bot = self.getlineno("@0,65535")
667 if top == bot and text.winfo_height() == 1:
668 # Geometry manager hasn't run yet
669 height = int(text['height'])
670 bot = top + height - 1
671 return top, bot
672
673 def getlineno(self, mark="insert"):
674 text = self.text
675 return int(float(text.index(mark)))
676
677 def close_event(self, event):
678 self.close()
679
680 def maybesave(self):
681 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000682 if not self.get_saved():
683 if self.top.state()!='normal':
684 self.top.deiconify()
685 self.top.lower()
686 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000687 return self.io.maybesave()
688
689 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000690 reply = self.maybesave()
691 if reply != "cancel":
692 self._close()
693 return reply
694
695 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000696 print self.io.filename
697 if self.io.filename:
698 self.UpdateRecentFilesList(newFile=self.io.filename)
699
David Scherer7aced172000-08-15 01:13:23 +0000700 WindowList.unregister_callback(self.postwindowsmenu)
701 if self.close_hook:
702 self.close_hook()
703 self.flist = None
704 colorizing = 0
705 self.unload_extensions()
706 self.io.close(); self.io = None
707 self.undo = None # XXX
708 if self.color:
709 colorizing = self.color.colorizing
710 doh = colorizing and self.top
711 self.color.close(doh) # Cancel colorization
712 self.text = None
713 self.vars = None
714 self.per.close(); self.per = None
715 if not colorizing:
716 self.top.destroy()
717
718 def load_extensions(self):
719 self.extensions = {}
720 self.load_standard_extensions()
721
722 def unload_extensions(self):
723 for ins in self.extensions.values():
724 if hasattr(ins, "close"):
725 ins.close()
726 self.extensions = {}
727
728 def load_standard_extensions(self):
729 for name in self.get_standard_extension_names():
730 try:
731 self.load_extension(name)
732 except:
733 print "Failed to load extension", `name`
734 import traceback
735 traceback.print_exc()
736
737 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000738 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000739
740 def load_extension(self, name):
741 mod = __import__(name, globals(), locals(), [])
742 cls = getattr(mod, name)
743 ins = cls(self)
744 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000745 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000746 if keydefs:
747 self.apply_bindings(keydefs)
748 for vevent in keydefs.keys():
749 methodname = string.replace(vevent, "-", "_")
750 while methodname[:1] == '<':
751 methodname = methodname[1:]
752 while methodname[-1:] == '>':
753 methodname = methodname[:-1]
754 methodname = methodname + "_event"
755 if hasattr(ins, methodname):
756 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000757
David Scherer7aced172000-08-15 01:13:23 +0000758 if hasattr(ins, "menudefs"):
759 self.fill_menus(ins.menudefs, keydefs)
760 return ins
761
762 def apply_bindings(self, keydefs=None):
763 if keydefs is None:
764 keydefs = self.Bindings.default_keydefs
765 text = self.text
766 text.keydefs = keydefs
767 for event, keylist in keydefs.items():
768 if keylist:
769 apply(text.event_add, (event,) + tuple(keylist))
770
771 def fill_menus(self, defs=None, keydefs=None):
772 # Fill the menus. Menus that are absent or None in
773 # self.menudict are ignored.
774 if defs is None:
775 defs = self.Bindings.menudefs
776 if keydefs is None:
777 keydefs = self.Bindings.default_keydefs
778 menudict = self.menudict
779 text = self.text
780 for mname, itemlist in defs:
781 menu = menudict.get(mname)
782 if not menu:
783 continue
784 for item in itemlist:
785 if not item:
786 menu.add_separator()
787 else:
788 label, event = item
789 checkbutton = (label[:1] == '!')
790 if checkbutton:
791 label = label[1:]
792 underline, label = prepstr(label)
793 accelerator = get_accelerator(keydefs, event)
794 def command(text=text, event=event):
795 text.event_generate(event)
796 if checkbutton:
797 var = self.getrawvar(event, BooleanVar)
798 menu.add_checkbutton(label=label, underline=underline,
799 command=command, accelerator=accelerator,
800 variable=var)
801 else:
802 menu.add_command(label=label, underline=underline,
803 command=command, accelerator=accelerator)
804
805 def getvar(self, name):
806 var = self.getrawvar(name)
807 if var:
808 return var.get()
809
810 def setvar(self, name, value, vartype=None):
811 var = self.getrawvar(name, vartype)
812 if var:
813 var.set(value)
814
815 def getrawvar(self, name, vartype=None):
816 var = self.vars.get(name)
817 if not var and vartype:
818 self.vars[name] = var = vartype(self.text)
819 return var
820
821 # Tk implementations of "virtual text methods" -- each platform
822 # reusing IDLE's support code needs to define these for its GUI's
823 # flavor of widget.
824
825 # Is character at text_index in a Python string? Return 0 for
826 # "guaranteed no", true for anything else. This info is expensive
827 # to compute ab initio, but is probably already known by the
828 # platform's colorizer.
829
830 def is_char_in_string(self, text_index):
831 if self.color:
832 # Return true iff colorizer hasn't (re)gotten this far
833 # yet, or the character is tagged as being in a string
834 return self.text.tag_prevrange("TODO", text_index) or \
835 "STRING" in self.text.tag_names(text_index)
836 else:
837 # The colorizer is missing: assume the worst
838 return 1
839
840 # If a selection is defined in the text widget, return (start,
841 # end) as Tkinter text indices, otherwise return (None, None)
842 def get_selection_indices(self):
843 try:
844 first = self.text.index("sel.first")
845 last = self.text.index("sel.last")
846 return first, last
847 except TclError:
848 return None, None
849
850 # Return the text widget's current view of what a tab stop means
851 # (equivalent width in spaces).
852
853 def get_tabwidth(self):
854 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
855 return int(current)
856
857 # Set the text widget's current view of what a tab stop means.
858
859 def set_tabwidth(self, newtabwidth):
860 text = self.text
861 if self.get_tabwidth() != newtabwidth:
862 pixels = text.tk.call("font", "measure", text["font"],
863 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000864 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000865 text.configure(tabs=pixels)
866
867def prepstr(s):
868 # Helper to extract the underscore from a string, e.g.
869 # prepstr("Co_py") returns (2, "Copy").
870 i = string.find(s, '_')
871 if i >= 0:
872 s = s[:i] + s[i+1:]
873 return i, s
874
875
876keynames = {
877 'bracketleft': '[',
878 'bracketright': ']',
879 'slash': '/',
880}
881
882def get_accelerator(keydefs, event):
883 keylist = keydefs.get(event)
884 if not keylist:
885 return ""
886 s = keylist[0]
887 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
888 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
889 s = re.sub("Key-", "", s)
890 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
891 s = re.sub("Control-", "Ctrl-", s)
892 s = re.sub("-", "+", s)
893 s = re.sub("><", " ", s)
894 s = re.sub("<", "", s)
895 s = re.sub(">", "", s)
896 return s
897
898
899def fixwordbreaks(root):
900 # Make sure that Tk's double-click and next/previous word
901 # operations use our definition of a word (i.e. an identifier)
902 tk = root.tk
903 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
904 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
905 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
906
907
908def test():
909 root = Tk()
910 fixwordbreaks(root)
911 root.withdraw()
912 if sys.argv[1:]:
913 filename = sys.argv[1]
914 else:
915 filename = None
916 edit = EditorWindow(root=root, filename=filename)
917 edit.set_close_hook(root.quit)
918 root.mainloop()
919 root.destroy()
920
921if __name__ == '__main__':
922 test()