blob: 14081ab887d7077d0caac86b77daffb02042bd5c [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
19from IdleConf import idleconf
Steven M. Gava7d9ed722001-07-31 07:01:47 +000020import aboutDialog
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
25# File menu
26
27#$ event <<open-module>>
28#$ win <Alt-m>
29#$ unix <Control-x><Control-m>
30
31#$ event <<open-class-browser>>
32#$ win <Alt-c>
33#$ unix <Control-x><Control-b>
34
35#$ event <<open-path-browser>>
36
37#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000038
David Scherer7aced172000-08-15 01:13:23 +000039#$ unix <Control-x><Control-0>
40#$ unix <Control-x><Key-0>
41#$ win <Alt-F4>
42
43# Edit menu
44
45#$ event <<Copy>>
46#$ win <Control-c>
47#$ unix <Alt-w>
48
49#$ event <<Cut>>
50#$ win <Control-x>
51#$ unix <Control-w>
52
53#$ event <<Paste>>
54#$ win <Control-v>
55#$ unix <Control-y>
56
57#$ event <<select-all>>
58#$ win <Alt-a>
59#$ unix <Alt-a>
60
61# Help menu
62
63#$ event <<help>>
64#$ win <F1>
65#$ unix <F1>
66
67#$ event <<about-idle>>
68
69# Events without menu entries
70
71#$ event <<remove-selection>>
72#$ win <Escape>
73
74#$ event <<center-insert>>
75#$ win <Control-l>
76#$ unix <Control-l>
77
78#$ event <<do-nothing>>
79#$ unix <Control-x>
80
81
Steven M. Gava4d712a42001-07-19 04:49:13 +000082about_title = "About IDLEfork"
David Scherer7aced172000-08-15 01:13:23 +000083about_text = """\
Steven M. Gava4d712a42001-07-19 04:49:13 +000084IDLEfork %s
David Scherer7aced172000-08-15 01:13:23 +000085
Steven M. Gava4d712a42001-07-19 04:49:13 +000086IDLE is an Integrated DeveLopment Environment for Python \
87by Guido van Rossum.
David Scherer7aced172000-08-15 01:13:23 +000088
Steven M. Gavaba247a62001-07-21 09:59:58 +000089IDLEfork is an official experimental development version of IDLE. \
Steven M. Gava4d712a42001-07-19 04:49:13 +000090Succesful new features in IDLEfork will be mereged back in to stable IDLE.
David Scherer7aced172000-08-15 01:13:23 +000091
Steven M. Gava7d9ed722001-07-31 07:01:47 +000092This version of IDLEfork is based on the work in stable IDLE version 0.8, \
Steven M. Gavaba247a62001-07-21 09:59:58 +000093IDLE fork 0.7.1 released by David Scherer, and the VPython idle fork.
Steven M. Gava4d712a42001-07-19 04:49:13 +000094
Steven M. Gavaba247a62001-07-21 09:59:58 +000095See README.txt and NEWS.txt for more details on this verion of IDLEfork.
Steven M. Gava4d712a42001-07-19 04:49:13 +000096
97WARNING: IDLEfork is at this stage alpha quality software, expect things \
98to be broken.
99
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000100""" % (idlever.IDLE_VERSION)
David Scherer7aced172000-08-15 01:13:23 +0000101
102class EditorWindow:
103
104 from Percolator import Percolator
105 from ColorDelegator import ColorDelegator
106 from UndoDelegator import UndoDelegator
107 from IOBinding import IOBinding
108 import Bindings
109 from Tkinter import Toplevel
110 from MultiStatusBar import MultiStatusBar
111
112 about_title = about_title
113 about_text = about_text
114
115 vars = {}
116
117 def __init__(self, flist=None, filename=None, key=None, root=None):
118 edconf = idleconf.getsection('EditorWindow')
119 coconf = idleconf.getsection('Colors')
120 self.flist = flist
121 root = root or flist.root
122 self.root = root
123 if flist:
124 self.vars = flist.vars
125 self.menubar = Menu(root)
126 self.top = top = self.Toplevel(root, menu=self.menubar)
127 self.vbar = vbar = Scrollbar(top, name='vbar')
128 self.text_frame = text_frame = Frame(top)
129 self.text = text = Text(text_frame, name='text', padx=5,
130 foreground=coconf.getdef('normal-foreground'),
131 background=coconf.getdef('normal-background'),
132 highlightcolor=coconf.getdef('hilite-foreground'),
133 highlightbackground=coconf.getdef('hilite-background'),
134 insertbackground=coconf.getdef('cursor-background'),
135 width=edconf.getint('width'),
136 height=edconf.getint('height'),
137 wrap="none")
138
139 self.createmenubar()
140 self.apply_bindings()
141
142 self.top.protocol("WM_DELETE_WINDOW", self.close)
143 self.top.bind("<<close-window>>", self.close_event)
144 text.bind("<<center-insert>>", self.center_insert_event)
145 text.bind("<<help>>", self.help_dialog)
146 text.bind("<<good-advice>>", self.good_advice)
147 text.bind("<<python-docs>>", self.python_docs)
148 text.bind("<<about-idle>>", self.about_dialog)
149 text.bind("<<open-module>>", self.open_module)
150 text.bind("<<do-nothing>>", lambda event: "break")
151 text.bind("<<select-all>>", self.select_all)
152 text.bind("<<remove-selection>>", self.remove_selection)
153 text.bind("<3>", self.right_menu_event)
154 if flist:
155 flist.inversedict[self] = key
156 if key:
157 flist.dict[key] = self
158 text.bind("<<open-new-window>>", self.flist.new_callback)
159 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
160 text.bind("<<open-class-browser>>", self.open_class_browser)
161 text.bind("<<open-path-browser>>", self.open_path_browser)
162
163 vbar['command'] = text.yview
164 vbar.pack(side=RIGHT, fill=Y)
165
166 text['yscrollcommand'] = vbar.set
167 text['font'] = edconf.get('font-name'), edconf.get('font-size')
168 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))
216 self.set_status_bar()
217
218 def set_status_bar(self):
219 self.status_bar = self.MultiStatusBar(self.text_frame)
220 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"),
244 ("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()
256
257 def postwindowsmenu(self):
258 # Only called when Windows menu exists
259 # XXX Actually, this Just-In-Time updating interferes badly
260 # XXX with the tear-off feature. It would be better to update
261 # XXX all Windows menus whenever the list of windows changes.
262 menu = self.menudict['windows']
263 end = menu.index("end")
264 if end is None:
265 end = -1
266 if end > self.wmenu_end:
267 menu.delete(self.wmenu_end+1, end)
268 WindowList.add_windows_to_menu(menu)
269
270 rmenu = None
271
272 def right_menu_event(self, event):
273 self.text.tag_remove("sel", "1.0", "end")
274 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
275 if not self.rmenu:
276 self.make_rmenu()
277 rmenu = self.rmenu
278 self.event = event
279 iswin = sys.platform[:3] == 'win'
280 if iswin:
281 self.text.config(cursor="arrow")
282 rmenu.tk_popup(event.x_root, event.y_root)
283 if iswin:
284 self.text.config(cursor="ibeam")
285
286 rmenu_specs = [
287 # ("Label", "<<virtual-event>>"), ...
288 ("Close", "<<close-window>>"), # Example
289 ]
290
291 def make_rmenu(self):
292 rmenu = Menu(self.text, tearoff=0)
293 for label, eventname in self.rmenu_specs:
294 def command(text=self.text, eventname=eventname):
295 text.event_generate(eventname)
296 rmenu.add_command(label=label, command=command)
297 self.rmenu = rmenu
298
299 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000300 #tkMessageBox.showinfo(self.about_title, self.about_text,
301 # master=self.text)
302 aboutDialog.AboutDialog(self.top,'About IDLEfork')
303
David Scherer7aced172000-08-15 01:13:23 +0000304 helpfile = "help.txt"
305
306 def good_advice(self, event=None):
307 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
308
309 def help_dialog(self, event=None):
310 try:
311 helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
312 except NameError:
313 helpfile = self.helpfile
314 if self.flist:
315 self.flist.open(helpfile)
316 else:
317 self.io.loadfile(helpfile)
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__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000322 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000323 fn = os.path.normpath(fn)
324 if os.path.isfile(fn):
325 help_url = fn
326 del fn
David Scherer7aced172000-08-15 01:13:23 +0000327
328 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000329 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000330
331 def select_all(self, event=None):
332 self.text.tag_add("sel", "1.0", "end-1c")
333 self.text.mark_set("insert", "1.0")
334 self.text.see("insert")
335 return "break"
336
337 def remove_selection(self, event=None):
338 self.text.tag_remove("sel", "1.0", "end")
339 self.text.see("insert")
340
341 def open_module(self, event=None):
342 # XXX Shouldn't this be in IOBinding or in FileList?
343 try:
344 name = self.text.get("sel.first", "sel.last")
345 except TclError:
346 name = ""
347 else:
348 name = string.strip(name)
349 if not name:
350 name = tkSimpleDialog.askstring("Module",
351 "Enter the name of a Python module\n"
352 "to search on sys.path and open:",
353 parent=self.text)
354 if name:
355 name = string.strip(name)
356 if not name:
357 return
358 # XXX Ought to support package syntax
359 # XXX Ought to insert current file's directory in front of path
360 try:
361 (f, file, (suffix, mode, type)) = imp.find_module(name)
362 except (NameError, ImportError), msg:
363 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
364 return
365 if type != imp.PY_SOURCE:
366 tkMessageBox.showerror("Unsupported type",
367 "%s is not a source module" % name, parent=self.text)
368 return
369 if f:
370 f.close()
371 if self.flist:
372 self.flist.open(file)
373 else:
374 self.io.loadfile(file)
375
376 def open_class_browser(self, event=None):
377 filename = self.io.filename
378 if not filename:
379 tkMessageBox.showerror(
380 "No filename",
381 "This buffer has no associated filename",
382 master=self.text)
383 self.text.focus_set()
384 return None
385 head, tail = os.path.split(filename)
386 base, ext = os.path.splitext(tail)
387 import ClassBrowser
388 ClassBrowser.ClassBrowser(self.flist, base, [head])
389
390 def open_path_browser(self, event=None):
391 import PathBrowser
392 PathBrowser.PathBrowser(self.flist)
393
394 def gotoline(self, lineno):
395 if lineno is not None and lineno > 0:
396 self.text.mark_set("insert", "%d.0" % lineno)
397 self.text.tag_remove("sel", "1.0", "end")
398 self.text.tag_add("sel", "insert", "insert +1l")
399 self.center()
400
401 def ispythonsource(self, filename):
402 if not filename:
403 return 1
404 base, ext = os.path.splitext(os.path.basename(filename))
405 if os.path.normcase(ext) in (".py", ".pyw"):
406 return 1
407 try:
408 f = open(filename)
409 line = f.readline()
410 f.close()
411 except IOError:
412 return 0
413 return line[:2] == '#!' and string.find(line, 'python') >= 0
414
415 def close_hook(self):
416 if self.flist:
417 self.flist.close_edit(self)
418
419 def set_close_hook(self, close_hook):
420 self.close_hook = close_hook
421
422 def filename_change_hook(self):
423 if self.flist:
424 self.flist.filename_changed_edit(self)
425 self.saved_change_hook()
426 if self.ispythonsource(self.io.filename):
427 self.addcolorizer()
428 else:
429 self.rmcolorizer()
430
431 def addcolorizer(self):
432 if self.color:
433 return
434 ##print "Add colorizer"
435 self.per.removefilter(self.undo)
436 self.color = self.ColorDelegator()
437 self.per.insertfilter(self.color)
438 self.per.insertfilter(self.undo)
439
440 def rmcolorizer(self):
441 if not self.color:
442 return
443 ##print "Remove colorizer"
444 self.per.removefilter(self.undo)
445 self.per.removefilter(self.color)
446 self.color = None
447 self.per.insertfilter(self.undo)
448
449 def saved_change_hook(self):
450 short = self.short_title()
451 long = self.long_title()
452 if short and long:
453 title = short + " - " + long
454 elif short:
455 title = short
456 elif long:
457 title = long
458 else:
459 title = "Untitled"
460 icon = short or long or title
461 if not self.get_saved():
462 title = "*%s*" % title
463 icon = "*%s" % icon
464 self.top.wm_title(title)
465 self.top.wm_iconname(icon)
466
467 def get_saved(self):
468 return self.undo.get_saved()
469
470 def set_saved(self, flag):
471 self.undo.set_saved(flag)
472
473 def reset_undo(self):
474 self.undo.reset_undo()
475
476 def short_title(self):
477 filename = self.io.filename
478 if filename:
479 filename = os.path.basename(filename)
480 return filename
481
482 def long_title(self):
483 return self.io.filename or ""
484
485 def center_insert_event(self, event):
486 self.center()
487
488 def center(self, mark="insert"):
489 text = self.text
490 top, bot = self.getwindowlines()
491 lineno = self.getlineno(mark)
492 height = bot - top
493 newtop = max(1, lineno - height/2)
494 text.yview(float(newtop))
495
496 def getwindowlines(self):
497 text = self.text
498 top = self.getlineno("@0,0")
499 bot = self.getlineno("@0,65535")
500 if top == bot and text.winfo_height() == 1:
501 # Geometry manager hasn't run yet
502 height = int(text['height'])
503 bot = top + height - 1
504 return top, bot
505
506 def getlineno(self, mark="insert"):
507 text = self.text
508 return int(float(text.index(mark)))
509
510 def close_event(self, event):
511 self.close()
512
513 def maybesave(self):
514 if self.io:
515 return self.io.maybesave()
516
517 def close(self):
518 self.top.wm_deiconify()
519 self.top.tkraise()
520 reply = self.maybesave()
521 if reply != "cancel":
522 self._close()
523 return reply
524
525 def _close(self):
526 WindowList.unregister_callback(self.postwindowsmenu)
527 if self.close_hook:
528 self.close_hook()
529 self.flist = None
530 colorizing = 0
531 self.unload_extensions()
532 self.io.close(); self.io = None
533 self.undo = None # XXX
534 if self.color:
535 colorizing = self.color.colorizing
536 doh = colorizing and self.top
537 self.color.close(doh) # Cancel colorization
538 self.text = None
539 self.vars = None
540 self.per.close(); self.per = None
541 if not colorizing:
542 self.top.destroy()
543
544 def load_extensions(self):
545 self.extensions = {}
546 self.load_standard_extensions()
547
548 def unload_extensions(self):
549 for ins in self.extensions.values():
550 if hasattr(ins, "close"):
551 ins.close()
552 self.extensions = {}
553
554 def load_standard_extensions(self):
555 for name in self.get_standard_extension_names():
556 try:
557 self.load_extension(name)
558 except:
559 print "Failed to load extension", `name`
560 import traceback
561 traceback.print_exc()
562
563 def get_standard_extension_names(self):
564 return idleconf.getextensions()
565
566 def load_extension(self, name):
567 mod = __import__(name, globals(), locals(), [])
568 cls = getattr(mod, name)
569 ins = cls(self)
570 self.extensions[name] = ins
571 kdnames = ["keydefs"]
572 if sys.platform == 'win32':
573 kdnames.append("windows_keydefs")
574 elif sys.platform == 'mac':
575 kdnames.append("mac_keydefs")
576 else:
577 kdnames.append("unix_keydefs")
578 keydefs = {}
579 for kdname in kdnames:
580 if hasattr(ins, kdname):
581 keydefs.update(getattr(ins, kdname))
582 if keydefs:
583 self.apply_bindings(keydefs)
584 for vevent in keydefs.keys():
585 methodname = string.replace(vevent, "-", "_")
586 while methodname[:1] == '<':
587 methodname = methodname[1:]
588 while methodname[-1:] == '>':
589 methodname = methodname[:-1]
590 methodname = methodname + "_event"
591 if hasattr(ins, methodname):
592 self.text.bind(vevent, getattr(ins, methodname))
593 if hasattr(ins, "menudefs"):
594 self.fill_menus(ins.menudefs, keydefs)
595 return ins
596
597 def apply_bindings(self, keydefs=None):
598 if keydefs is None:
599 keydefs = self.Bindings.default_keydefs
600 text = self.text
601 text.keydefs = keydefs
602 for event, keylist in keydefs.items():
603 if keylist:
604 apply(text.event_add, (event,) + tuple(keylist))
605
606 def fill_menus(self, defs=None, keydefs=None):
607 # Fill the menus. Menus that are absent or None in
608 # self.menudict are ignored.
609 if defs is None:
610 defs = self.Bindings.menudefs
611 if keydefs is None:
612 keydefs = self.Bindings.default_keydefs
613 menudict = self.menudict
614 text = self.text
615 for mname, itemlist in defs:
616 menu = menudict.get(mname)
617 if not menu:
618 continue
619 for item in itemlist:
620 if not item:
621 menu.add_separator()
622 else:
623 label, event = item
624 checkbutton = (label[:1] == '!')
625 if checkbutton:
626 label = label[1:]
627 underline, label = prepstr(label)
628 accelerator = get_accelerator(keydefs, event)
629 def command(text=text, event=event):
630 text.event_generate(event)
631 if checkbutton:
632 var = self.getrawvar(event, BooleanVar)
633 menu.add_checkbutton(label=label, underline=underline,
634 command=command, accelerator=accelerator,
635 variable=var)
636 else:
637 menu.add_command(label=label, underline=underline,
638 command=command, accelerator=accelerator)
639
640 def getvar(self, name):
641 var = self.getrawvar(name)
642 if var:
643 return var.get()
644
645 def setvar(self, name, value, vartype=None):
646 var = self.getrawvar(name, vartype)
647 if var:
648 var.set(value)
649
650 def getrawvar(self, name, vartype=None):
651 var = self.vars.get(name)
652 if not var and vartype:
653 self.vars[name] = var = vartype(self.text)
654 return var
655
656 # Tk implementations of "virtual text methods" -- each platform
657 # reusing IDLE's support code needs to define these for its GUI's
658 # flavor of widget.
659
660 # Is character at text_index in a Python string? Return 0 for
661 # "guaranteed no", true for anything else. This info is expensive
662 # to compute ab initio, but is probably already known by the
663 # platform's colorizer.
664
665 def is_char_in_string(self, text_index):
666 if self.color:
667 # Return true iff colorizer hasn't (re)gotten this far
668 # yet, or the character is tagged as being in a string
669 return self.text.tag_prevrange("TODO", text_index) or \
670 "STRING" in self.text.tag_names(text_index)
671 else:
672 # The colorizer is missing: assume the worst
673 return 1
674
675 # If a selection is defined in the text widget, return (start,
676 # end) as Tkinter text indices, otherwise return (None, None)
677 def get_selection_indices(self):
678 try:
679 first = self.text.index("sel.first")
680 last = self.text.index("sel.last")
681 return first, last
682 except TclError:
683 return None, None
684
685 # Return the text widget's current view of what a tab stop means
686 # (equivalent width in spaces).
687
688 def get_tabwidth(self):
689 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
690 return int(current)
691
692 # Set the text widget's current view of what a tab stop means.
693
694 def set_tabwidth(self, newtabwidth):
695 text = self.text
696 if self.get_tabwidth() != newtabwidth:
697 pixels = text.tk.call("font", "measure", text["font"],
698 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000699 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000700 text.configure(tabs=pixels)
701
702def prepstr(s):
703 # Helper to extract the underscore from a string, e.g.
704 # prepstr("Co_py") returns (2, "Copy").
705 i = string.find(s, '_')
706 if i >= 0:
707 s = s[:i] + s[i+1:]
708 return i, s
709
710
711keynames = {
712 'bracketleft': '[',
713 'bracketright': ']',
714 'slash': '/',
715}
716
717def get_accelerator(keydefs, event):
718 keylist = keydefs.get(event)
719 if not keylist:
720 return ""
721 s = keylist[0]
722 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
723 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
724 s = re.sub("Key-", "", s)
725 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
726 s = re.sub("Control-", "Ctrl-", s)
727 s = re.sub("-", "+", s)
728 s = re.sub("><", " ", s)
729 s = re.sub("<", "", s)
730 s = re.sub(">", "", s)
731 return s
732
733
734def fixwordbreaks(root):
735 # Make sure that Tk's double-click and next/previous word
736 # operations use our definition of a word (i.e. an identifier)
737 tk = root.tk
738 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
739 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
740 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
741
742
743def test():
744 root = Tk()
745 fixwordbreaks(root)
746 root.withdraw()
747 if sys.argv[1:]:
748 filename = sys.argv[1]
749 else:
750 filename = None
751 edit = EditorWindow(root=root, filename=filename)
752 edit.set_close_hook(root.quit)
753 root.mainloop()
754 root.destroy()
755
756if __name__ == '__main__':
757 test()