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