blob: c2ee115bd4f213d117d18140269d6e626ae5e951 [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
David Scherer7aced172000-08-15 01:13:23 +0000108 self.vbar = vbar = Scrollbar(top, name='vbar')
109 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000110 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
111 foreground=idleConf.GetHighlight(currentTheme,
112 'normal',fgBg='fg'),
113 background=idleConf.GetHighlight(currentTheme,
114 'normal',fgBg='bg'),
115 highlightcolor=idleConf.GetHighlight(currentTheme,
116 'hilite',fgBg='fg'),
117 highlightbackground=idleConf.GetHighlight(currentTheme,
118 'hilite',fgBg='bg'),
119 insertbackground=idleConf.GetHighlight(currentTheme,
120 'cursor',fgBg='fg'),
121 width=idleConf.GetOption('main','EditorWindow','width'),
122 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000123
124 self.createmenubar()
125 self.apply_bindings()
126
127 self.top.protocol("WM_DELETE_WINDOW", self.close)
128 self.top.bind("<<close-window>>", self.close_event)
129 text.bind("<<center-insert>>", self.center_insert_event)
130 text.bind("<<help>>", self.help_dialog)
131 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000132 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000133 text.bind("<<python-docs>>", self.python_docs)
134 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000135 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000136 text.bind("<<open-module>>", self.open_module)
137 text.bind("<<do-nothing>>", lambda event: "break")
138 text.bind("<<select-all>>", self.select_all)
139 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000140 text.bind("<<find>>", self.find_event)
141 text.bind("<<find-again>>", self.find_again_event)
142 text.bind("<<find-in-files>>", self.find_in_files_event)
143 text.bind("<<find-selection>>", self.find_selection_event)
144 text.bind("<<replace>>", self.replace_event)
145 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<3>", self.right_menu_event)
147 if flist:
148 flist.inversedict[self] = key
149 if key:
150 flist.dict[key] = self
151 text.bind("<<open-new-window>>", self.flist.new_callback)
152 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
153 text.bind("<<open-class-browser>>", self.open_class_browser)
154 text.bind("<<open-path-browser>>", self.open_path_browser)
155
Steven M. Gava898a3652001-10-07 11:10:44 +0000156 self.set_status_bar()
157
David Scherer7aced172000-08-15 01:13:23 +0000158 vbar['command'] = text.yview
159 vbar.pack(side=RIGHT, fill=Y)
160
161 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000162 fontWeight='normal'
163 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
164 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000165 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000166 idleConf.GetOption('main','EditorWindow','font-size'),
167 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000168 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
169 text.pack(side=TOP, fill=BOTH, expand=1)
170 text.focus_set()
171
172 self.per = per = self.Percolator(text)
173 if self.ispythonsource(filename):
174 self.color = color = self.ColorDelegator(); per.insertfilter(color)
175 ##print "Initial colorizer"
176 else:
177 ##print "No initial colorizer"
178 self.color = None
179 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
180 self.io = io = self.IOBinding(self)
181
182 text.undo_block_start = undo.undo_block_start
183 text.undo_block_stop = undo.undo_block_stop
184 undo.set_saved_change_hook(self.saved_change_hook)
185 io.set_filename_change_hook(self.filename_change_hook)
186
187 if filename:
188 if os.path.exists(filename):
189 io.loadfile(filename)
190 else:
191 io.set_filename(filename)
192
193 self.saved_change_hook()
194
195 self.load_extensions()
196
197 menu = self.menudict.get('windows')
198 if menu:
199 end = menu.index("end")
200 if end is None:
201 end = -1
202 if end >= 0:
203 menu.add_separator()
204 end = end + 1
205 self.wmenu_end = end
206 WindowList.register_callback(self.postwindowsmenu)
207
208 # Some abstractions so IDLE extensions are cross-IDE
209 self.askyesno = tkMessageBox.askyesno
210 self.askinteger = tkSimpleDialog.askinteger
211 self.showerror = tkMessageBox.showerror
212
213 if self.extensions.has_key('AutoIndent'):
214 self.extensions['AutoIndent'].set_indentation_params(
215 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000216
David Scherer7aced172000-08-15 01:13:23 +0000217 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000218 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000219 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
220 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
221 self.status_bar.pack(side=BOTTOM, fill=X)
222 self.text.bind('<KeyRelease>', self.set_line_and_column)
223 self.text.bind('<ButtonRelease>', self.set_line_and_column)
224 self.text.after_idle(self.set_line_and_column)
225
226 def set_line_and_column(self, event=None):
227 line, column = string.split(self.text.index(INSERT), '.')
228 self.status_bar.set_label('column', 'Col: %s' % column)
229 self.status_bar.set_label('line', 'Ln: %s' % line)
230
231 def wakeup(self):
232 if self.top.wm_state() == "iconic":
233 self.top.wm_deiconify()
234 else:
235 self.top.tkraise()
236 self.text.focus_set()
237
238 menu_specs = [
239 ("file", "_File"),
240 ("edit", "_Edit"),
241 ("format", "F_ormat"),
242 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000243 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000244 ("windows", "_Windows"),
245 ("help", "_Help"),
246 ]
247
248 def createmenubar(self):
249 mbar = self.menubar
250 self.menudict = menudict = {}
251 for name, label in self.menu_specs:
252 underline, label = prepstr(label)
253 menudict[name] = menu = Menu(mbar, name=name)
254 mbar.add_cascade(label=label, menu=menu, underline=underline)
255 self.fill_menus()
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000256 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000257
258 def postwindowsmenu(self):
259 # Only called when Windows menu exists
260 # XXX Actually, this Just-In-Time updating interferes badly
261 # XXX with the tear-off feature. It would be better to update
262 # XXX all Windows menus whenever the list of windows changes.
263 menu = self.menudict['windows']
264 end = menu.index("end")
265 if end is None:
266 end = -1
267 if end > self.wmenu_end:
268 menu.delete(self.wmenu_end+1, end)
269 WindowList.add_windows_to_menu(menu)
270
271 rmenu = None
272
273 def right_menu_event(self, event):
274 self.text.tag_remove("sel", "1.0", "end")
275 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
276 if not self.rmenu:
277 self.make_rmenu()
278 rmenu = self.rmenu
279 self.event = event
280 iswin = sys.platform[:3] == 'win'
281 if iswin:
282 self.text.config(cursor="arrow")
283 rmenu.tk_popup(event.x_root, event.y_root)
284 if iswin:
285 self.text.config(cursor="ibeam")
286
287 rmenu_specs = [
288 # ("Label", "<<virtual-event>>"), ...
289 ("Close", "<<close-window>>"), # Example
290 ]
291
292 def make_rmenu(self):
293 rmenu = Menu(self.text, tearoff=0)
294 for label, eventname in self.rmenu_specs:
295 def command(text=self.text, eventname=eventname):
296 text.event_generate(eventname)
297 rmenu.add_command(label=label, command=command)
298 self.rmenu = rmenu
299
300 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000301 aboutDialog.AboutDialog(self.top,'About IDLEfork')
302
Steven M. Gava3b55a892001-11-21 05:56:26 +0000303 def config_dialog(self, event=None):
304 configDialog.ConfigDialog(self.top,'Settings')
305
David Scherer7aced172000-08-15 01:13:23 +0000306 def good_advice(self, event=None):
307 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
308
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000309 def view_readme(self, event=None):
310 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
311 textView.TextViewer(self.top,'IDLEfork - README',fn)
312
David Scherer7aced172000-08-15 01:13:23 +0000313 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000314 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
315 textView.TextViewer(self.top,'Help',fn)
316
David Scherer7aced172000-08-15 01:13:23 +0000317 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000318 if sys.platform[:3] == "win":
319 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000320 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000321 fn = os.path.normpath(fn)
322 if os.path.isfile(fn):
323 help_url = fn
324 del fn
David Scherer7aced172000-08-15 01:13:23 +0000325
326 def python_docs(self, event=None):
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000327 self.display_docs(self.help_url)
328
329 def display_docs(self, url):
330 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000331
332 def select_all(self, event=None):
333 self.text.tag_add("sel", "1.0", "end-1c")
334 self.text.mark_set("insert", "1.0")
335 self.text.see("insert")
336 return "break"
337
338 def remove_selection(self, event=None):
339 self.text.tag_remove("sel", "1.0", "end")
340 self.text.see("insert")
341
Steven M. Gavac5976402002-01-04 03:06:08 +0000342 def find_event(self, event):
343 SearchDialog.find(self.text)
344 return "break"
345
346 def find_again_event(self, event):
347 SearchDialog.find_again(self.text)
348 return "break"
349
350 def find_selection_event(self, event):
351 SearchDialog.find_selection(self.text)
352 return "break"
353
354 def find_in_files_event(self, event):
355 GrepDialog.grep(self.text, self.io, self.flist)
356 return "break"
357
358 def replace_event(self, event):
359 ReplaceDialog.replace(self.text)
360 return "break"
361
362 def goto_line_event(self, event):
363 text = self.text
364 lineno = tkSimpleDialog.askinteger("Goto",
365 "Go to line number:",parent=text)
366 if lineno is None:
367 return "break"
368 if lineno <= 0:
369 text.bell()
370 return "break"
371 text.mark_set("insert", "%d.0" % lineno)
372 text.see("insert")
373
David Scherer7aced172000-08-15 01:13:23 +0000374 def open_module(self, event=None):
375 # XXX Shouldn't this be in IOBinding or in FileList?
376 try:
377 name = self.text.get("sel.first", "sel.last")
378 except TclError:
379 name = ""
380 else:
381 name = string.strip(name)
382 if not name:
383 name = tkSimpleDialog.askstring("Module",
384 "Enter the name of a Python module\n"
385 "to search on sys.path and open:",
386 parent=self.text)
387 if name:
388 name = string.strip(name)
389 if not name:
390 return
391 # XXX Ought to support package syntax
392 # XXX Ought to insert current file's directory in front of path
393 try:
394 (f, file, (suffix, mode, type)) = imp.find_module(name)
395 except (NameError, ImportError), msg:
396 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
397 return
398 if type != imp.PY_SOURCE:
399 tkMessageBox.showerror("Unsupported type",
400 "%s is not a source module" % name, parent=self.text)
401 return
402 if f:
403 f.close()
404 if self.flist:
405 self.flist.open(file)
406 else:
407 self.io.loadfile(file)
408
409 def open_class_browser(self, event=None):
410 filename = self.io.filename
411 if not filename:
412 tkMessageBox.showerror(
413 "No filename",
414 "This buffer has no associated filename",
415 master=self.text)
416 self.text.focus_set()
417 return None
418 head, tail = os.path.split(filename)
419 base, ext = os.path.splitext(tail)
420 import ClassBrowser
421 ClassBrowser.ClassBrowser(self.flist, base, [head])
422
423 def open_path_browser(self, event=None):
424 import PathBrowser
425 PathBrowser.PathBrowser(self.flist)
426
427 def gotoline(self, lineno):
428 if lineno is not None and lineno > 0:
429 self.text.mark_set("insert", "%d.0" % lineno)
430 self.text.tag_remove("sel", "1.0", "end")
431 self.text.tag_add("sel", "insert", "insert +1l")
432 self.center()
433
434 def ispythonsource(self, filename):
435 if not filename:
436 return 1
437 base, ext = os.path.splitext(os.path.basename(filename))
438 if os.path.normcase(ext) in (".py", ".pyw"):
439 return 1
440 try:
441 f = open(filename)
442 line = f.readline()
443 f.close()
444 except IOError:
445 return 0
446 return line[:2] == '#!' and string.find(line, 'python') >= 0
447
448 def close_hook(self):
449 if self.flist:
450 self.flist.close_edit(self)
451
452 def set_close_hook(self, close_hook):
453 self.close_hook = close_hook
454
455 def filename_change_hook(self):
456 if self.flist:
457 self.flist.filename_changed_edit(self)
458 self.saved_change_hook()
459 if self.ispythonsource(self.io.filename):
460 self.addcolorizer()
461 else:
462 self.rmcolorizer()
463
464 def addcolorizer(self):
465 if self.color:
466 return
467 ##print "Add colorizer"
468 self.per.removefilter(self.undo)
469 self.color = self.ColorDelegator()
470 self.per.insertfilter(self.color)
471 self.per.insertfilter(self.undo)
472
473 def rmcolorizer(self):
474 if not self.color:
475 return
476 ##print "Remove colorizer"
477 self.per.removefilter(self.undo)
478 self.per.removefilter(self.color)
479 self.color = None
480 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000481
482 def ResetColorizer(self):
483 #this function is called from configDialog.py
484 #to update the colour theme if it is changed
485 if self.color:
486 self.color = self.ColorDelegator()
487 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000488
Steven M. Gavab1585412002-03-12 00:21:56 +0000489 def ResetFont(self):
490 #this function is called from configDialog.py
491 #to update the text widgets' font if it is changed
492 fontWeight='normal'
493 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
494 fontWeight='bold'
495 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
496 idleConf.GetOption('main','EditorWindow','font-size'),
497 fontWeight))
498
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000499 def ResetKeybindings(self):
500 #this function is called from configDialog.py
501 #to update the keybindings if they are changed
502 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
503 keydefs = self.Bindings.default_keydefs
504 for event, keylist in keydefs.items():
505 self.text.event_delete(event)
506 self.apply_bindings()
507 #update menu accelerators
508 menuEventDict={}
509 for menu in self.Bindings.menudefs:
510 menuEventDict[menu[0]]={}
511 for item in menu[1]:
512 if item:
513 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
514 for menubarItem in self.menudict.keys():
515 menu=self.menudict[menubarItem]
516 end=menu.index(END)+1
517 for index in range(0,end):
518 if menu.type(index)=='command':
519 accel=menu.entrycget(index,'accelerator')
520 if accel:
521 itemName=menu.entrycget(index,'label')
522 event=''
523 if menuEventDict.has_key(menubarItem):
524 if menuEventDict[menubarItem].has_key(itemName):
525 event=menuEventDict[menubarItem][itemName]
526 if event:
527 #print 'accel was:',accel
528 accel=get_accelerator(keydefs, event)
529 menu.entryconfig(index,accelerator=accel)
530 #print 'accel now:',accel,'\n'
531
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000532 def ResetExtraHelpMenu(self):
533 #load or update the Extra Help menu if required
534 menuList=idleConf.GetAllExtraHelpSourcesList()
535 helpMenu=self.menudict['help']
536 cascadeIndex=helpMenu.index(END)-1
537 if menuList:
538 if not hasattr(self,'menuExtraHelp'):
539 self.menuExtraHelp=Menu(self.menubar)
540 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
541 underline=1,menu=self.menuExtraHelp)
542 self.menuExtraHelp.delete(1,END)
543 for menuItem in menuList:
544 self.menuExtraHelp.add_command(label=menuItem[0],
545 command=lambda:self.display_docs(menuItem[1]))
546 else: #no extra help items
547 if hasattr(self,'menuExtraHelp'):
548 helpMenu.delete(cascadeIndex-1)
549 del(self.menuExtraHelp)
550
David Scherer7aced172000-08-15 01:13:23 +0000551 def saved_change_hook(self):
552 short = self.short_title()
553 long = self.long_title()
554 if short and long:
555 title = short + " - " + long
556 elif short:
557 title = short
558 elif long:
559 title = long
560 else:
561 title = "Untitled"
562 icon = short or long or title
563 if not self.get_saved():
564 title = "*%s*" % title
565 icon = "*%s" % icon
566 self.top.wm_title(title)
567 self.top.wm_iconname(icon)
568
569 def get_saved(self):
570 return self.undo.get_saved()
571
572 def set_saved(self, flag):
573 self.undo.set_saved(flag)
574
575 def reset_undo(self):
576 self.undo.reset_undo()
577
578 def short_title(self):
579 filename = self.io.filename
580 if filename:
581 filename = os.path.basename(filename)
582 return filename
583
584 def long_title(self):
585 return self.io.filename or ""
586
587 def center_insert_event(self, event):
588 self.center()
589
590 def center(self, mark="insert"):
591 text = self.text
592 top, bot = self.getwindowlines()
593 lineno = self.getlineno(mark)
594 height = bot - top
595 newtop = max(1, lineno - height/2)
596 text.yview(float(newtop))
597
598 def getwindowlines(self):
599 text = self.text
600 top = self.getlineno("@0,0")
601 bot = self.getlineno("@0,65535")
602 if top == bot and text.winfo_height() == 1:
603 # Geometry manager hasn't run yet
604 height = int(text['height'])
605 bot = top + height - 1
606 return top, bot
607
608 def getlineno(self, mark="insert"):
609 text = self.text
610 return int(float(text.index(mark)))
611
612 def close_event(self, event):
613 self.close()
614
615 def maybesave(self):
616 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000617 if not self.get_saved():
618 if self.top.state()!='normal':
619 self.top.deiconify()
620 self.top.lower()
621 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000622 return self.io.maybesave()
623
624 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000625 reply = self.maybesave()
626 if reply != "cancel":
627 self._close()
628 return reply
629
630 def _close(self):
631 WindowList.unregister_callback(self.postwindowsmenu)
632 if self.close_hook:
633 self.close_hook()
634 self.flist = None
635 colorizing = 0
636 self.unload_extensions()
637 self.io.close(); self.io = None
638 self.undo = None # XXX
639 if self.color:
640 colorizing = self.color.colorizing
641 doh = colorizing and self.top
642 self.color.close(doh) # Cancel colorization
643 self.text = None
644 self.vars = None
645 self.per.close(); self.per = None
646 if not colorizing:
647 self.top.destroy()
648
649 def load_extensions(self):
650 self.extensions = {}
651 self.load_standard_extensions()
652
653 def unload_extensions(self):
654 for ins in self.extensions.values():
655 if hasattr(ins, "close"):
656 ins.close()
657 self.extensions = {}
658
659 def load_standard_extensions(self):
660 for name in self.get_standard_extension_names():
661 try:
662 self.load_extension(name)
663 except:
664 print "Failed to load extension", `name`
665 import traceback
666 traceback.print_exc()
667
668 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000669 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000670
671 def load_extension(self, name):
672 mod = __import__(name, globals(), locals(), [])
673 cls = getattr(mod, name)
674 ins = cls(self)
675 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000676 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000677 if keydefs:
678 self.apply_bindings(keydefs)
679 for vevent in keydefs.keys():
680 methodname = string.replace(vevent, "-", "_")
681 while methodname[:1] == '<':
682 methodname = methodname[1:]
683 while methodname[-1:] == '>':
684 methodname = methodname[:-1]
685 methodname = methodname + "_event"
686 if hasattr(ins, methodname):
687 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000688
David Scherer7aced172000-08-15 01:13:23 +0000689 if hasattr(ins, "menudefs"):
690 self.fill_menus(ins.menudefs, keydefs)
691 return ins
692
693 def apply_bindings(self, keydefs=None):
694 if keydefs is None:
695 keydefs = self.Bindings.default_keydefs
696 text = self.text
697 text.keydefs = keydefs
698 for event, keylist in keydefs.items():
699 if keylist:
700 apply(text.event_add, (event,) + tuple(keylist))
701
702 def fill_menus(self, defs=None, keydefs=None):
703 # Fill the menus. Menus that are absent or None in
704 # self.menudict are ignored.
705 if defs is None:
706 defs = self.Bindings.menudefs
707 if keydefs is None:
708 keydefs = self.Bindings.default_keydefs
709 menudict = self.menudict
710 text = self.text
711 for mname, itemlist in defs:
712 menu = menudict.get(mname)
713 if not menu:
714 continue
715 for item in itemlist:
716 if not item:
717 menu.add_separator()
718 else:
719 label, event = item
720 checkbutton = (label[:1] == '!')
721 if checkbutton:
722 label = label[1:]
723 underline, label = prepstr(label)
724 accelerator = get_accelerator(keydefs, event)
725 def command(text=text, event=event):
726 text.event_generate(event)
727 if checkbutton:
728 var = self.getrawvar(event, BooleanVar)
729 menu.add_checkbutton(label=label, underline=underline,
730 command=command, accelerator=accelerator,
731 variable=var)
732 else:
733 menu.add_command(label=label, underline=underline,
734 command=command, accelerator=accelerator)
735
736 def getvar(self, name):
737 var = self.getrawvar(name)
738 if var:
739 return var.get()
740
741 def setvar(self, name, value, vartype=None):
742 var = self.getrawvar(name, vartype)
743 if var:
744 var.set(value)
745
746 def getrawvar(self, name, vartype=None):
747 var = self.vars.get(name)
748 if not var and vartype:
749 self.vars[name] = var = vartype(self.text)
750 return var
751
752 # Tk implementations of "virtual text methods" -- each platform
753 # reusing IDLE's support code needs to define these for its GUI's
754 # flavor of widget.
755
756 # Is character at text_index in a Python string? Return 0 for
757 # "guaranteed no", true for anything else. This info is expensive
758 # to compute ab initio, but is probably already known by the
759 # platform's colorizer.
760
761 def is_char_in_string(self, text_index):
762 if self.color:
763 # Return true iff colorizer hasn't (re)gotten this far
764 # yet, or the character is tagged as being in a string
765 return self.text.tag_prevrange("TODO", text_index) or \
766 "STRING" in self.text.tag_names(text_index)
767 else:
768 # The colorizer is missing: assume the worst
769 return 1
770
771 # If a selection is defined in the text widget, return (start,
772 # end) as Tkinter text indices, otherwise return (None, None)
773 def get_selection_indices(self):
774 try:
775 first = self.text.index("sel.first")
776 last = self.text.index("sel.last")
777 return first, last
778 except TclError:
779 return None, None
780
781 # Return the text widget's current view of what a tab stop means
782 # (equivalent width in spaces).
783
784 def get_tabwidth(self):
785 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
786 return int(current)
787
788 # Set the text widget's current view of what a tab stop means.
789
790 def set_tabwidth(self, newtabwidth):
791 text = self.text
792 if self.get_tabwidth() != newtabwidth:
793 pixels = text.tk.call("font", "measure", text["font"],
794 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000795 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000796 text.configure(tabs=pixels)
797
798def prepstr(s):
799 # Helper to extract the underscore from a string, e.g.
800 # prepstr("Co_py") returns (2, "Copy").
801 i = string.find(s, '_')
802 if i >= 0:
803 s = s[:i] + s[i+1:]
804 return i, s
805
806
807keynames = {
808 'bracketleft': '[',
809 'bracketright': ']',
810 'slash': '/',
811}
812
813def get_accelerator(keydefs, event):
814 keylist = keydefs.get(event)
815 if not keylist:
816 return ""
817 s = keylist[0]
818 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
819 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
820 s = re.sub("Key-", "", s)
821 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
822 s = re.sub("Control-", "Ctrl-", s)
823 s = re.sub("-", "+", s)
824 s = re.sub("><", " ", s)
825 s = re.sub("<", "", s)
826 s = re.sub(">", "", s)
827 return s
828
829
830def fixwordbreaks(root):
831 # Make sure that Tk's double-click and next/previous word
832 # operations use our definition of a word (i.e. an identifier)
833 tk = root.tk
834 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
835 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
836 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
837
838
839def test():
840 root = Tk()
841 fixwordbreaks(root)
842 root.withdraw()
843 if sys.argv[1:]:
844 filename = sys.argv[1]
845 else:
846 filename = None
847 edit = EditorWindow(root=root, filename=filename)
848 edit.set_close_hook(root.quit)
849 root.mainloop()
850 root.destroy()
851
852if __name__ == '__main__':
853 test()