blob: 453e6ca014db0c4fcedccd7412c5365db0438252 [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')
David Scherer7aced172000-08-15 01:13:23 +0000104 self.vbar = vbar = Scrollbar(top, name='vbar')
105 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000106 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
107 foreground=idleConf.GetHighlight(currentTheme,
108 'normal',fgBg='fg'),
109 background=idleConf.GetHighlight(currentTheme,
110 'normal',fgBg='bg'),
111 highlightcolor=idleConf.GetHighlight(currentTheme,
112 'hilite',fgBg='fg'),
113 highlightbackground=idleConf.GetHighlight(currentTheme,
114 'hilite',fgBg='bg'),
115 insertbackground=idleConf.GetHighlight(currentTheme,
116 'cursor',fgBg='fg'),
117 width=idleConf.GetOption('main','EditorWindow','width'),
118 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000119
120 self.createmenubar()
121 self.apply_bindings()
122
123 self.top.protocol("WM_DELETE_WINDOW", self.close)
124 self.top.bind("<<close-window>>", self.close_event)
125 text.bind("<<center-insert>>", self.center_insert_event)
126 text.bind("<<help>>", self.help_dialog)
127 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000128 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000129 text.bind("<<python-docs>>", self.python_docs)
130 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000131 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000132 text.bind("<<open-module>>", self.open_module)
133 text.bind("<<do-nothing>>", lambda event: "break")
134 text.bind("<<select-all>>", self.select_all)
135 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000136 text.bind("<<find>>", self.find_event)
137 text.bind("<<find-again>>", self.find_again_event)
138 text.bind("<<find-in-files>>", self.find_in_files_event)
139 text.bind("<<find-selection>>", self.find_selection_event)
140 text.bind("<<replace>>", self.replace_event)
141 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<3>", self.right_menu_event)
143 if flist:
144 flist.inversedict[self] = key
145 if key:
146 flist.dict[key] = self
147 text.bind("<<open-new-window>>", self.flist.new_callback)
148 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
149 text.bind("<<open-class-browser>>", self.open_class_browser)
150 text.bind("<<open-path-browser>>", self.open_path_browser)
151
Steven M. Gava898a3652001-10-07 11:10:44 +0000152 self.set_status_bar()
153
David Scherer7aced172000-08-15 01:13:23 +0000154 vbar['command'] = text.yview
155 vbar.pack(side=RIGHT, fill=Y)
156
157 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000158 fontWeight='normal'
159 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
160 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000161 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000162 idleConf.GetOption('main','EditorWindow','font-size'),
163 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000164 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
165 text.pack(side=TOP, fill=BOTH, expand=1)
166 text.focus_set()
167
168 self.per = per = self.Percolator(text)
169 if self.ispythonsource(filename):
170 self.color = color = self.ColorDelegator(); per.insertfilter(color)
171 ##print "Initial colorizer"
172 else:
173 ##print "No initial colorizer"
174 self.color = None
175 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
176 self.io = io = self.IOBinding(self)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000177 #create the Recent Files submenu
178 self.menuRecentFiles=Menu(self.menubar)
179 self.menudict['file'].insert_cascade(3,label='Recent Files',
180 underline=0,menu=self.menuRecentFiles)
181 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000182
183 text.undo_block_start = undo.undo_block_start
184 text.undo_block_stop = undo.undo_block_stop
185 undo.set_saved_change_hook(self.saved_change_hook)
186 io.set_filename_change_hook(self.filename_change_hook)
187
188 if filename:
189 if os.path.exists(filename):
190 io.loadfile(filename)
191 else:
192 io.set_filename(filename)
193
194 self.saved_change_hook()
195
196 self.load_extensions()
197
198 menu = self.menudict.get('windows')
199 if menu:
200 end = menu.index("end")
201 if end is None:
202 end = -1
203 if end >= 0:
204 menu.add_separator()
205 end = end + 1
206 self.wmenu_end = end
207 WindowList.register_callback(self.postwindowsmenu)
208
209 # Some abstractions so IDLE extensions are cross-IDE
210 self.askyesno = tkMessageBox.askyesno
211 self.askinteger = tkSimpleDialog.askinteger
212 self.showerror = tkMessageBox.showerror
213
214 if self.extensions.has_key('AutoIndent'):
215 self.extensions['AutoIndent'].set_indentation_params(
216 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000217
David Scherer7aced172000-08-15 01:13:23 +0000218 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000219 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000220 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
221 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
222 self.status_bar.pack(side=BOTTOM, fill=X)
223 self.text.bind('<KeyRelease>', self.set_line_and_column)
224 self.text.bind('<ButtonRelease>', self.set_line_and_column)
225 self.text.after_idle(self.set_line_and_column)
226
227 def set_line_and_column(self, event=None):
228 line, column = string.split(self.text.index(INSERT), '.')
229 self.status_bar.set_label('column', 'Col: %s' % column)
230 self.status_bar.set_label('line', 'Ln: %s' % line)
231
232 def wakeup(self):
233 if self.top.wm_state() == "iconic":
234 self.top.wm_deiconify()
235 else:
236 self.top.tkraise()
237 self.text.focus_set()
238
239 menu_specs = [
240 ("file", "_File"),
241 ("edit", "_Edit"),
242 ("format", "F_ormat"),
243 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000244 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000245 ("windows", "_Windows"),
246 ("help", "_Help"),
247 ]
248
249 def createmenubar(self):
250 mbar = self.menubar
251 self.menudict = menudict = {}
252 for name, label in self.menu_specs:
253 underline, label = prepstr(label)
254 menudict[name] = menu = Menu(mbar, name=name)
255 mbar.add_cascade(label=label, menu=menu, underline=underline)
256 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000257 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000258 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000259
260 def postwindowsmenu(self):
261 # Only called when Windows menu exists
262 # XXX Actually, this Just-In-Time updating interferes badly
263 # XXX with the tear-off feature. It would be better to update
264 # XXX all Windows menus whenever the list of windows changes.
265 menu = self.menudict['windows']
266 end = menu.index("end")
267 if end is None:
268 end = -1
269 if end > self.wmenu_end:
270 menu.delete(self.wmenu_end+1, end)
271 WindowList.add_windows_to_menu(menu)
272
273 rmenu = None
274
275 def right_menu_event(self, event):
276 self.text.tag_remove("sel", "1.0", "end")
277 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278 if not self.rmenu:
279 self.make_rmenu()
280 rmenu = self.rmenu
281 self.event = event
282 iswin = sys.platform[:3] == 'win'
283 if iswin:
284 self.text.config(cursor="arrow")
285 rmenu.tk_popup(event.x_root, event.y_root)
286 if iswin:
287 self.text.config(cursor="ibeam")
288
289 rmenu_specs = [
290 # ("Label", "<<virtual-event>>"), ...
291 ("Close", "<<close-window>>"), # Example
292 ]
293
294 def make_rmenu(self):
295 rmenu = Menu(self.text, tearoff=0)
296 for label, eventname in self.rmenu_specs:
297 def command(text=self.text, eventname=eventname):
298 text.event_generate(eventname)
299 rmenu.add_command(label=label, command=command)
300 self.rmenu = rmenu
301
302 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000303 aboutDialog.AboutDialog(self.top,'About IDLEfork')
304
Steven M. Gava3b55a892001-11-21 05:56:26 +0000305 def config_dialog(self, event=None):
306 configDialog.ConfigDialog(self.top,'Settings')
307
David Scherer7aced172000-08-15 01:13:23 +0000308 def good_advice(self, event=None):
309 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
310
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000311 def view_readme(self, event=None):
312 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
313 textView.TextViewer(self.top,'IDLEfork - README',fn)
314
David Scherer7aced172000-08-15 01:13:23 +0000315 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000316 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
317 textView.TextViewer(self.top,'Help',fn)
318
David Scherer7aced172000-08-15 01:13:23 +0000319 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000320 if sys.platform[:3] == "win":
321 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000322 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000323 fn = os.path.normpath(fn)
324 if os.path.isfile(fn):
325 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000326 else:
327 fn = os.path.dirname(__file__)
328 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
329 fn = os.path.normpath(fn)
330 if os.path.isfile(fn):
331 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000332 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000333 def python_docs(self, event=None):
334 os.startfile(self.help_url)
335 else:
336 def python_docs(self, event=None):
337 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000338
339 def display_docs(self, url):
340 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000341
342 def select_all(self, event=None):
343 self.text.tag_add("sel", "1.0", "end-1c")
344 self.text.mark_set("insert", "1.0")
345 self.text.see("insert")
346 return "break"
347
348 def remove_selection(self, event=None):
349 self.text.tag_remove("sel", "1.0", "end")
350 self.text.see("insert")
351
Steven M. Gavac5976402002-01-04 03:06:08 +0000352 def find_event(self, event):
353 SearchDialog.find(self.text)
354 return "break"
355
356 def find_again_event(self, event):
357 SearchDialog.find_again(self.text)
358 return "break"
359
360 def find_selection_event(self, event):
361 SearchDialog.find_selection(self.text)
362 return "break"
363
364 def find_in_files_event(self, event):
365 GrepDialog.grep(self.text, self.io, self.flist)
366 return "break"
367
368 def replace_event(self, event):
369 ReplaceDialog.replace(self.text)
370 return "break"
371
372 def goto_line_event(self, event):
373 text = self.text
374 lineno = tkSimpleDialog.askinteger("Goto",
375 "Go to line number:",parent=text)
376 if lineno is None:
377 return "break"
378 if lineno <= 0:
379 text.bell()
380 return "break"
381 text.mark_set("insert", "%d.0" % lineno)
382 text.see("insert")
383
David Scherer7aced172000-08-15 01:13:23 +0000384 def open_module(self, event=None):
385 # XXX Shouldn't this be in IOBinding or in FileList?
386 try:
387 name = self.text.get("sel.first", "sel.last")
388 except TclError:
389 name = ""
390 else:
391 name = string.strip(name)
392 if not name:
393 name = tkSimpleDialog.askstring("Module",
394 "Enter the name of a Python module\n"
395 "to search on sys.path and open:",
396 parent=self.text)
397 if name:
398 name = string.strip(name)
399 if not name:
400 return
401 # XXX Ought to support package syntax
402 # XXX Ought to insert current file's directory in front of path
403 try:
404 (f, file, (suffix, mode, type)) = imp.find_module(name)
405 except (NameError, ImportError), msg:
406 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
407 return
408 if type != imp.PY_SOURCE:
409 tkMessageBox.showerror("Unsupported type",
410 "%s is not a source module" % name, parent=self.text)
411 return
412 if f:
413 f.close()
414 if self.flist:
415 self.flist.open(file)
416 else:
417 self.io.loadfile(file)
418
419 def open_class_browser(self, event=None):
420 filename = self.io.filename
421 if not filename:
422 tkMessageBox.showerror(
423 "No filename",
424 "This buffer has no associated filename",
425 master=self.text)
426 self.text.focus_set()
427 return None
428 head, tail = os.path.split(filename)
429 base, ext = os.path.splitext(tail)
430 import ClassBrowser
431 ClassBrowser.ClassBrowser(self.flist, base, [head])
432
433 def open_path_browser(self, event=None):
434 import PathBrowser
435 PathBrowser.PathBrowser(self.flist)
436
437 def gotoline(self, lineno):
438 if lineno is not None and lineno > 0:
439 self.text.mark_set("insert", "%d.0" % lineno)
440 self.text.tag_remove("sel", "1.0", "end")
441 self.text.tag_add("sel", "insert", "insert +1l")
442 self.center()
443
444 def ispythonsource(self, filename):
445 if not filename:
446 return 1
447 base, ext = os.path.splitext(os.path.basename(filename))
448 if os.path.normcase(ext) in (".py", ".pyw"):
449 return 1
450 try:
451 f = open(filename)
452 line = f.readline()
453 f.close()
454 except IOError:
455 return 0
456 return line[:2] == '#!' and string.find(line, 'python') >= 0
457
458 def close_hook(self):
459 if self.flist:
460 self.flist.close_edit(self)
461
462 def set_close_hook(self, close_hook):
463 self.close_hook = close_hook
464
465 def filename_change_hook(self):
466 if self.flist:
467 self.flist.filename_changed_edit(self)
468 self.saved_change_hook()
469 if self.ispythonsource(self.io.filename):
470 self.addcolorizer()
471 else:
472 self.rmcolorizer()
473
474 def addcolorizer(self):
475 if self.color:
476 return
477 ##print "Add colorizer"
478 self.per.removefilter(self.undo)
479 self.color = self.ColorDelegator()
480 self.per.insertfilter(self.color)
481 self.per.insertfilter(self.undo)
482
483 def rmcolorizer(self):
484 if not self.color:
485 return
486 ##print "Remove colorizer"
487 self.per.removefilter(self.undo)
488 self.per.removefilter(self.color)
489 self.color = None
490 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000491
492 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000493 "Update the colour theme if it is changed"
494 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000495 if self.color:
496 self.color = self.ColorDelegator()
497 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000498
Steven M. Gavab1585412002-03-12 00:21:56 +0000499 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000500 "Update the text widgets' font if it is changed"
501 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000502 fontWeight='normal'
503 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
504 fontWeight='bold'
505 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
506 idleConf.GetOption('main','EditorWindow','font-size'),
507 fontWeight))
508
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000509 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000510 "Update the keybindings if they are changed"
511 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000512 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
513 keydefs = self.Bindings.default_keydefs
514 for event, keylist in keydefs.items():
515 self.text.event_delete(event)
516 self.apply_bindings()
517 #update menu accelerators
518 menuEventDict={}
519 for menu in self.Bindings.menudefs:
520 menuEventDict[menu[0]]={}
521 for item in menu[1]:
522 if item:
523 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
524 for menubarItem in self.menudict.keys():
525 menu=self.menudict[menubarItem]
526 end=menu.index(END)+1
527 for index in range(0,end):
528 if menu.type(index)=='command':
529 accel=menu.entrycget(index,'accelerator')
530 if accel:
531 itemName=menu.entrycget(index,'label')
532 event=''
533 if menuEventDict.has_key(menubarItem):
534 if menuEventDict[menubarItem].has_key(itemName):
535 event=menuEventDict[menubarItem][itemName]
536 if event:
537 #print 'accel was:',accel
538 accel=get_accelerator(keydefs, event)
539 menu.entryconfig(index,accelerator=accel)
540 #print 'accel now:',accel,'\n'
541
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000542 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000543 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000544 menuList=idleConf.GetAllExtraHelpSourcesList()
545 helpMenu=self.menudict['help']
546 cascadeIndex=helpMenu.index(END)-1
547 if menuList:
548 if not hasattr(self,'menuExtraHelp'):
549 self.menuExtraHelp=Menu(self.menubar)
550 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
551 underline=1,menu=self.menuExtraHelp)
552 self.menuExtraHelp.delete(1,END)
553 for menuItem in menuList:
554 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000555 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000556 else: #no extra help items
557 if hasattr(self,'menuExtraHelp'):
558 helpMenu.delete(cascadeIndex-1)
559 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000560
561 def __DisplayExtraHelpCallback(self,helpFile):
562 def DisplayExtraHelp(helpFile=helpFile):
563 self.display_docs(helpFile)
564 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000565
Steven M. Gava1d46e402002-03-27 08:40:46 +0000566 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000567 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000568 rfList=[]
569 if os.path.exists(self.recentFilesPath):
570 RFfile=open(self.recentFilesPath,'r')
571 try:
572 rfList=RFfile.readlines()
573 finally:
574 RFfile.close()
575 if newFile:
576 newFile=os.path.abspath(newFile)+'\n'
577 if newFile in rfList:
578 rfList.remove(newFile)
579 rfList.insert(0,newFile)
580 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000581 #print self.flist.inversedict
582 #print self.top.instanceDict
583 #print self
Steven M. Gava1d46e402002-03-27 08:40:46 +0000584 if rfList:
585 for instance in self.top.instanceDict.keys():
586 instance.menuRecentFiles.delete(1,END)
587 for file in rfList:
588 fileName=file[0:-1]
589 instance.menuRecentFiles.add_command(label=fileName,
590 command=instance.__RecentFileCallback(fileName))
591
592 def __CleanRecentFiles(self,rfList):
593 origRfList=rfList[:]
594 count=0
595 nonFiles=[]
596 for path in rfList:
597 if not os.path.exists(path[0:-1]):
598 nonFiles.append(count)
599 count=count+1
600 if nonFiles:
601 nonFiles.reverse()
602 for index in nonFiles:
603 del(rfList[index])
604 if len(rfList)>19:
605 rfList=rfList[0:19]
606 #if rfList != origRfList:
607 RFfile=open(self.recentFilesPath,'w')
608 try:
609 RFfile.writelines(rfList)
610 finally:
611 RFfile.close()
612 return rfList
613
614 def __RecentFileCallback(self,fileName):
615 def OpenRecentFile(fileName=fileName):
616 self.io.open(editFile=fileName)
617 return OpenRecentFile
618
David Scherer7aced172000-08-15 01:13:23 +0000619 def saved_change_hook(self):
620 short = self.short_title()
621 long = self.long_title()
622 if short and long:
623 title = short + " - " + long
624 elif short:
625 title = short
626 elif long:
627 title = long
628 else:
629 title = "Untitled"
630 icon = short or long or title
631 if not self.get_saved():
632 title = "*%s*" % title
633 icon = "*%s" % icon
634 self.top.wm_title(title)
635 self.top.wm_iconname(icon)
636
637 def get_saved(self):
638 return self.undo.get_saved()
639
640 def set_saved(self, flag):
641 self.undo.set_saved(flag)
642
643 def reset_undo(self):
644 self.undo.reset_undo()
645
646 def short_title(self):
647 filename = self.io.filename
648 if filename:
649 filename = os.path.basename(filename)
650 return filename
651
652 def long_title(self):
653 return self.io.filename or ""
654
655 def center_insert_event(self, event):
656 self.center()
657
658 def center(self, mark="insert"):
659 text = self.text
660 top, bot = self.getwindowlines()
661 lineno = self.getlineno(mark)
662 height = bot - top
663 newtop = max(1, lineno - height/2)
664 text.yview(float(newtop))
665
666 def getwindowlines(self):
667 text = self.text
668 top = self.getlineno("@0,0")
669 bot = self.getlineno("@0,65535")
670 if top == bot and text.winfo_height() == 1:
671 # Geometry manager hasn't run yet
672 height = int(text['height'])
673 bot = top + height - 1
674 return top, bot
675
676 def getlineno(self, mark="insert"):
677 text = self.text
678 return int(float(text.index(mark)))
679
680 def close_event(self, event):
681 self.close()
682
683 def maybesave(self):
684 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000685 if not self.get_saved():
686 if self.top.state()!='normal':
687 self.top.deiconify()
688 self.top.lower()
689 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000690 return self.io.maybesave()
691
692 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000693 reply = self.maybesave()
694 if reply != "cancel":
695 self._close()
696 return reply
697
698 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000699 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000700 if self.io.filename:
701 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000702 shell = self.flist.pyshell
703 if shell and shell.interp.debugger:
704 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000705 WindowList.unregister_callback(self.postwindowsmenu)
706 if self.close_hook:
707 self.close_hook()
708 self.flist = None
709 colorizing = 0
710 self.unload_extensions()
711 self.io.close(); self.io = None
712 self.undo = None # XXX
713 if self.color:
714 colorizing = self.color.colorizing
715 doh = colorizing and self.top
716 self.color.close(doh) # Cancel colorization
717 self.text = None
718 self.vars = None
719 self.per.close(); self.per = None
720 if not colorizing:
721 self.top.destroy()
722
723 def load_extensions(self):
724 self.extensions = {}
725 self.load_standard_extensions()
726
727 def unload_extensions(self):
728 for ins in self.extensions.values():
729 if hasattr(ins, "close"):
730 ins.close()
731 self.extensions = {}
732
733 def load_standard_extensions(self):
734 for name in self.get_standard_extension_names():
735 try:
736 self.load_extension(name)
737 except:
738 print "Failed to load extension", `name`
739 import traceback
740 traceback.print_exc()
741
742 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000743 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000744
745 def load_extension(self, name):
746 mod = __import__(name, globals(), locals(), [])
747 cls = getattr(mod, name)
748 ins = cls(self)
749 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000750 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000751 if keydefs:
752 self.apply_bindings(keydefs)
753 for vevent in keydefs.keys():
754 methodname = string.replace(vevent, "-", "_")
755 while methodname[:1] == '<':
756 methodname = methodname[1:]
757 while methodname[-1:] == '>':
758 methodname = methodname[:-1]
759 methodname = methodname + "_event"
760 if hasattr(ins, methodname):
761 self.text.bind(vevent, getattr(ins, methodname))
762 if hasattr(ins, "menudefs"):
763 self.fill_menus(ins.menudefs, keydefs)
764 return ins
765
766 def apply_bindings(self, keydefs=None):
767 if keydefs is None:
768 keydefs = self.Bindings.default_keydefs
769 text = self.text
770 text.keydefs = keydefs
771 for event, keylist in keydefs.items():
772 if keylist:
773 apply(text.event_add, (event,) + tuple(keylist))
774
775 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000776 """Add appropriate entries to the menus and submenus
777
778 Menus that are absent or None in self.menudict are ignored.
779 """
David Scherer7aced172000-08-15 01:13:23 +0000780 if defs is None:
781 defs = self.Bindings.menudefs
782 if keydefs is None:
783 keydefs = self.Bindings.default_keydefs
784 menudict = self.menudict
785 text = self.text
786 for mname, itemlist in defs:
787 menu = menudict.get(mname)
788 if not menu:
789 continue
790 for item in itemlist:
791 if not item:
792 menu.add_separator()
793 else:
794 label, event = item
795 checkbutton = (label[:1] == '!')
796 if checkbutton:
797 label = label[1:]
798 underline, label = prepstr(label)
799 accelerator = get_accelerator(keydefs, event)
800 def command(text=text, event=event):
801 text.event_generate(event)
802 if checkbutton:
803 var = self.getrawvar(event, BooleanVar)
804 menu.add_checkbutton(label=label, underline=underline,
805 command=command, accelerator=accelerator,
806 variable=var)
807 else:
808 menu.add_command(label=label, underline=underline,
809 command=command, accelerator=accelerator)
810
811 def getvar(self, name):
812 var = self.getrawvar(name)
813 if var:
814 return var.get()
815
816 def setvar(self, name, value, vartype=None):
817 var = self.getrawvar(name, vartype)
818 if var:
819 var.set(value)
820
821 def getrawvar(self, name, vartype=None):
822 var = self.vars.get(name)
823 if not var and vartype:
824 self.vars[name] = var = vartype(self.text)
825 return var
826
827 # Tk implementations of "virtual text methods" -- each platform
828 # reusing IDLE's support code needs to define these for its GUI's
829 # flavor of widget.
830
831 # Is character at text_index in a Python string? Return 0 for
832 # "guaranteed no", true for anything else. This info is expensive
833 # to compute ab initio, but is probably already known by the
834 # platform's colorizer.
835
836 def is_char_in_string(self, text_index):
837 if self.color:
838 # Return true iff colorizer hasn't (re)gotten this far
839 # yet, or the character is tagged as being in a string
840 return self.text.tag_prevrange("TODO", text_index) or \
841 "STRING" in self.text.tag_names(text_index)
842 else:
843 # The colorizer is missing: assume the worst
844 return 1
845
846 # If a selection is defined in the text widget, return (start,
847 # end) as Tkinter text indices, otherwise return (None, None)
848 def get_selection_indices(self):
849 try:
850 first = self.text.index("sel.first")
851 last = self.text.index("sel.last")
852 return first, last
853 except TclError:
854 return None, None
855
856 # Return the text widget's current view of what a tab stop means
857 # (equivalent width in spaces).
858
859 def get_tabwidth(self):
860 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
861 return int(current)
862
863 # Set the text widget's current view of what a tab stop means.
864
865 def set_tabwidth(self, newtabwidth):
866 text = self.text
867 if self.get_tabwidth() != newtabwidth:
868 pixels = text.tk.call("font", "measure", text["font"],
869 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000870 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000871 text.configure(tabs=pixels)
872
873def prepstr(s):
874 # Helper to extract the underscore from a string, e.g.
875 # prepstr("Co_py") returns (2, "Copy").
876 i = string.find(s, '_')
877 if i >= 0:
878 s = s[:i] + s[i+1:]
879 return i, s
880
881
882keynames = {
883 'bracketleft': '[',
884 'bracketright': ']',
885 'slash': '/',
886}
887
888def get_accelerator(keydefs, event):
889 keylist = keydefs.get(event)
890 if not keylist:
891 return ""
892 s = keylist[0]
893 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
894 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
895 s = re.sub("Key-", "", s)
896 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
897 s = re.sub("Control-", "Ctrl-", s)
898 s = re.sub("-", "+", s)
899 s = re.sub("><", " ", s)
900 s = re.sub("<", "", s)
901 s = re.sub(">", "", s)
902 return s
903
904
905def fixwordbreaks(root):
906 # Make sure that Tk's double-click and next/previous word
907 # operations use our definition of a word (i.e. an identifier)
908 tk = root.tk
909 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
910 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
911 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
912
913
914def test():
915 root = Tk()
916 fixwordbreaks(root)
917 root.withdraw()
918 if sys.argv[1:]:
919 filename = sys.argv[1]
920 else:
921 filename = None
922 edit = EditorWindow(root=root, filename=filename)
923 edit.set_close_hook(root.quit)
924 root.mainloop()
925 root.destroy()
926
927if __name__ == '__main__':
928 test()