blob: 6a69e4ad638ba1db291fc8226c28fec2c7c5e4b1 [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. Gavadc72f482002-01-03 11:51:07 +000019#from IdleConf import idleconf
20from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000021import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000022
23# The default tab setting for a Text widget, in average-width characters.
24TK_TABWIDTH_DEFAULT = 8
25
26# File menu
27
28#$ event <<open-module>>
29#$ win <Alt-m>
30#$ unix <Control-x><Control-m>
31
32#$ event <<open-class-browser>>
33#$ win <Alt-c>
34#$ unix <Control-x><Control-b>
35
36#$ event <<open-path-browser>>
37
38#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000039
David Scherer7aced172000-08-15 01:13:23 +000040#$ unix <Control-x><Control-0>
41#$ unix <Control-x><Key-0>
42#$ win <Alt-F4>
43
44# Edit menu
45
46#$ event <<Copy>>
47#$ win <Control-c>
48#$ unix <Alt-w>
49
50#$ event <<Cut>>
51#$ win <Control-x>
52#$ unix <Control-w>
53
54#$ event <<Paste>>
55#$ win <Control-v>
56#$ unix <Control-y>
57
58#$ event <<select-all>>
59#$ win <Alt-a>
60#$ unix <Alt-a>
61
62# Help menu
63
64#$ event <<help>>
65#$ win <F1>
66#$ unix <F1>
67
68#$ event <<about-idle>>
69
70# Events without menu entries
71
72#$ event <<remove-selection>>
73#$ win <Escape>
74
75#$ event <<center-insert>>
76#$ win <Control-l>
77#$ unix <Control-l>
78
79#$ event <<do-nothing>>
80#$ unix <Control-x>
81
David Scherer7aced172000-08-15 01:13:23 +000082class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000083 from Percolator import Percolator
84 from ColorDelegator import ColorDelegator
85 from UndoDelegator import UndoDelegator
86 from IOBinding import IOBinding
87 import Bindings
88 from Tkinter import Toplevel
89 from MultiStatusBar import MultiStatusBar
90
David Scherer7aced172000-08-15 01:13:23 +000091 vars = {}
92
93 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000094 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000095 self.flist = flist
96 root = root or flist.root
97 self.root = root
98 if flist:
99 self.vars = flist.vars
100 self.menubar = Menu(root)
101 self.top = top = self.Toplevel(root, menu=self.menubar)
102 self.vbar = vbar = Scrollbar(top, name='vbar')
103 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000104 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
105 foreground=idleConf.GetHighlight(currentTheme,
106 'normal',fgBg='fg'),
107 background=idleConf.GetHighlight(currentTheme,
108 'normal',fgBg='bg'),
109 highlightcolor=idleConf.GetHighlight(currentTheme,
110 'hilite',fgBg='fg'),
111 highlightbackground=idleConf.GetHighlight(currentTheme,
112 'hilite',fgBg='bg'),
113 insertbackground=idleConf.GetHighlight(currentTheme,
114 'cursor',fgBg='fg'),
115 width=idleConf.GetOption('main','EditorWindow','width'),
116 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000117
118 self.createmenubar()
119 self.apply_bindings()
120
121 self.top.protocol("WM_DELETE_WINDOW", self.close)
122 self.top.bind("<<close-window>>", self.close_event)
123 text.bind("<<center-insert>>", self.center_insert_event)
124 text.bind("<<help>>", self.help_dialog)
125 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000126 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000127 text.bind("<<python-docs>>", self.python_docs)
128 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000129 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000130 text.bind("<<open-module>>", self.open_module)
131 text.bind("<<do-nothing>>", lambda event: "break")
132 text.bind("<<select-all>>", self.select_all)
133 text.bind("<<remove-selection>>", self.remove_selection)
134 text.bind("<3>", self.right_menu_event)
135 if flist:
136 flist.inversedict[self] = key
137 if key:
138 flist.dict[key] = self
139 text.bind("<<open-new-window>>", self.flist.new_callback)
140 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
141 text.bind("<<open-class-browser>>", self.open_class_browser)
142 text.bind("<<open-path-browser>>", self.open_path_browser)
143
Steven M. Gava898a3652001-10-07 11:10:44 +0000144 self.set_status_bar()
145
David Scherer7aced172000-08-15 01:13:23 +0000146 vbar['command'] = text.yview
147 vbar.pack(side=RIGHT, fill=Y)
148
149 text['yscrollcommand'] = vbar.set
Steven M. Gavadc72f482002-01-03 11:51:07 +0000150 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
151 idleConf.GetOption('main','EditorWindow','font-size')))
David Scherer7aced172000-08-15 01:13:23 +0000152 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
153 text.pack(side=TOP, fill=BOTH, expand=1)
154 text.focus_set()
155
156 self.per = per = self.Percolator(text)
157 if self.ispythonsource(filename):
158 self.color = color = self.ColorDelegator(); per.insertfilter(color)
159 ##print "Initial colorizer"
160 else:
161 ##print "No initial colorizer"
162 self.color = None
163 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
164 self.io = io = self.IOBinding(self)
165
166 text.undo_block_start = undo.undo_block_start
167 text.undo_block_stop = undo.undo_block_stop
168 undo.set_saved_change_hook(self.saved_change_hook)
169 io.set_filename_change_hook(self.filename_change_hook)
170
171 if filename:
172 if os.path.exists(filename):
173 io.loadfile(filename)
174 else:
175 io.set_filename(filename)
176
177 self.saved_change_hook()
178
179 self.load_extensions()
180
181 menu = self.menudict.get('windows')
182 if menu:
183 end = menu.index("end")
184 if end is None:
185 end = -1
186 if end >= 0:
187 menu.add_separator()
188 end = end + 1
189 self.wmenu_end = end
190 WindowList.register_callback(self.postwindowsmenu)
191
192 # Some abstractions so IDLE extensions are cross-IDE
193 self.askyesno = tkMessageBox.askyesno
194 self.askinteger = tkSimpleDialog.askinteger
195 self.showerror = tkMessageBox.showerror
196
197 if self.extensions.has_key('AutoIndent'):
198 self.extensions['AutoIndent'].set_indentation_params(
199 self.ispythonsource(filename))
Steven M. Gava898a3652001-10-07 11:10:44 +0000200
David Scherer7aced172000-08-15 01:13:23 +0000201
202 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000203 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000204 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
205 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
206 self.status_bar.pack(side=BOTTOM, fill=X)
207 self.text.bind('<KeyRelease>', self.set_line_and_column)
208 self.text.bind('<ButtonRelease>', self.set_line_and_column)
209 self.text.after_idle(self.set_line_and_column)
210
211 def set_line_and_column(self, event=None):
212 line, column = string.split(self.text.index(INSERT), '.')
213 self.status_bar.set_label('column', 'Col: %s' % column)
214 self.status_bar.set_label('line', 'Ln: %s' % line)
215
216 def wakeup(self):
217 if self.top.wm_state() == "iconic":
218 self.top.wm_deiconify()
219 else:
220 self.top.tkraise()
221 self.text.focus_set()
222
223 menu_specs = [
224 ("file", "_File"),
225 ("edit", "_Edit"),
226 ("format", "F_ormat"),
227 ("run", "_Run"),
Steven M. Gava3b55a892001-11-21 05:56:26 +0000228 #("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000229 ("windows", "_Windows"),
230 ("help", "_Help"),
231 ]
232
233 def createmenubar(self):
234 mbar = self.menubar
235 self.menudict = menudict = {}
236 for name, label in self.menu_specs:
237 underline, label = prepstr(label)
238 menudict[name] = menu = Menu(mbar, name=name)
239 mbar.add_cascade(label=label, menu=menu, underline=underline)
240 self.fill_menus()
241
242 def postwindowsmenu(self):
243 # Only called when Windows menu exists
244 # XXX Actually, this Just-In-Time updating interferes badly
245 # XXX with the tear-off feature. It would be better to update
246 # XXX all Windows menus whenever the list of windows changes.
247 menu = self.menudict['windows']
248 end = menu.index("end")
249 if end is None:
250 end = -1
251 if end > self.wmenu_end:
252 menu.delete(self.wmenu_end+1, end)
253 WindowList.add_windows_to_menu(menu)
254
255 rmenu = None
256
257 def right_menu_event(self, event):
258 self.text.tag_remove("sel", "1.0", "end")
259 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
260 if not self.rmenu:
261 self.make_rmenu()
262 rmenu = self.rmenu
263 self.event = event
264 iswin = sys.platform[:3] == 'win'
265 if iswin:
266 self.text.config(cursor="arrow")
267 rmenu.tk_popup(event.x_root, event.y_root)
268 if iswin:
269 self.text.config(cursor="ibeam")
270
271 rmenu_specs = [
272 # ("Label", "<<virtual-event>>"), ...
273 ("Close", "<<close-window>>"), # Example
274 ]
275
276 def make_rmenu(self):
277 rmenu = Menu(self.text, tearoff=0)
278 for label, eventname in self.rmenu_specs:
279 def command(text=self.text, eventname=eventname):
280 text.event_generate(eventname)
281 rmenu.add_command(label=label, command=command)
282 self.rmenu = rmenu
283
284 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000285 aboutDialog.AboutDialog(self.top,'About IDLEfork')
286
Steven M. Gava3b55a892001-11-21 05:56:26 +0000287 def config_dialog(self, event=None):
288 configDialog.ConfigDialog(self.top,'Settings')
289
David Scherer7aced172000-08-15 01:13:23 +0000290 def good_advice(self, event=None):
291 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
292
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000293 def view_readme(self, event=None):
294 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
295 textView.TextViewer(self.top,'IDLEfork - README',fn)
296
David Scherer7aced172000-08-15 01:13:23 +0000297 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000298 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
299 textView.TextViewer(self.top,'Help',fn)
300
David Scherer7aced172000-08-15 01:13:23 +0000301 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000302 if sys.platform[:3] == "win":
303 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000304 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000305 fn = os.path.normpath(fn)
306 if os.path.isfile(fn):
307 help_url = fn
308 del fn
David Scherer7aced172000-08-15 01:13:23 +0000309
310 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000311 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000312
313 def select_all(self, event=None):
314 self.text.tag_add("sel", "1.0", "end-1c")
315 self.text.mark_set("insert", "1.0")
316 self.text.see("insert")
317 return "break"
318
319 def remove_selection(self, event=None):
320 self.text.tag_remove("sel", "1.0", "end")
321 self.text.see("insert")
322
323 def open_module(self, event=None):
324 # XXX Shouldn't this be in IOBinding or in FileList?
325 try:
326 name = self.text.get("sel.first", "sel.last")
327 except TclError:
328 name = ""
329 else:
330 name = string.strip(name)
331 if not name:
332 name = tkSimpleDialog.askstring("Module",
333 "Enter the name of a Python module\n"
334 "to search on sys.path and open:",
335 parent=self.text)
336 if name:
337 name = string.strip(name)
338 if not name:
339 return
340 # XXX Ought to support package syntax
341 # XXX Ought to insert current file's directory in front of path
342 try:
343 (f, file, (suffix, mode, type)) = imp.find_module(name)
344 except (NameError, ImportError), msg:
345 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
346 return
347 if type != imp.PY_SOURCE:
348 tkMessageBox.showerror("Unsupported type",
349 "%s is not a source module" % name, parent=self.text)
350 return
351 if f:
352 f.close()
353 if self.flist:
354 self.flist.open(file)
355 else:
356 self.io.loadfile(file)
357
358 def open_class_browser(self, event=None):
359 filename = self.io.filename
360 if not filename:
361 tkMessageBox.showerror(
362 "No filename",
363 "This buffer has no associated filename",
364 master=self.text)
365 self.text.focus_set()
366 return None
367 head, tail = os.path.split(filename)
368 base, ext = os.path.splitext(tail)
369 import ClassBrowser
370 ClassBrowser.ClassBrowser(self.flist, base, [head])
371
372 def open_path_browser(self, event=None):
373 import PathBrowser
374 PathBrowser.PathBrowser(self.flist)
375
376 def gotoline(self, lineno):
377 if lineno is not None and lineno > 0:
378 self.text.mark_set("insert", "%d.0" % lineno)
379 self.text.tag_remove("sel", "1.0", "end")
380 self.text.tag_add("sel", "insert", "insert +1l")
381 self.center()
382
383 def ispythonsource(self, filename):
384 if not filename:
385 return 1
386 base, ext = os.path.splitext(os.path.basename(filename))
387 if os.path.normcase(ext) in (".py", ".pyw"):
388 return 1
389 try:
390 f = open(filename)
391 line = f.readline()
392 f.close()
393 except IOError:
394 return 0
395 return line[:2] == '#!' and string.find(line, 'python') >= 0
396
397 def close_hook(self):
398 if self.flist:
399 self.flist.close_edit(self)
400
401 def set_close_hook(self, close_hook):
402 self.close_hook = close_hook
403
404 def filename_change_hook(self):
405 if self.flist:
406 self.flist.filename_changed_edit(self)
407 self.saved_change_hook()
408 if self.ispythonsource(self.io.filename):
409 self.addcolorizer()
410 else:
411 self.rmcolorizer()
412
413 def addcolorizer(self):
414 if self.color:
415 return
416 ##print "Add colorizer"
417 self.per.removefilter(self.undo)
418 self.color = self.ColorDelegator()
419 self.per.insertfilter(self.color)
420 self.per.insertfilter(self.undo)
421
422 def rmcolorizer(self):
423 if not self.color:
424 return
425 ##print "Remove colorizer"
426 self.per.removefilter(self.undo)
427 self.per.removefilter(self.color)
428 self.color = None
429 self.per.insertfilter(self.undo)
430
431 def saved_change_hook(self):
432 short = self.short_title()
433 long = self.long_title()
434 if short and long:
435 title = short + " - " + long
436 elif short:
437 title = short
438 elif long:
439 title = long
440 else:
441 title = "Untitled"
442 icon = short or long or title
443 if not self.get_saved():
444 title = "*%s*" % title
445 icon = "*%s" % icon
446 self.top.wm_title(title)
447 self.top.wm_iconname(icon)
448
449 def get_saved(self):
450 return self.undo.get_saved()
451
452 def set_saved(self, flag):
453 self.undo.set_saved(flag)
454
455 def reset_undo(self):
456 self.undo.reset_undo()
457
458 def short_title(self):
459 filename = self.io.filename
460 if filename:
461 filename = os.path.basename(filename)
462 return filename
463
464 def long_title(self):
465 return self.io.filename or ""
466
467 def center_insert_event(self, event):
468 self.center()
469
470 def center(self, mark="insert"):
471 text = self.text
472 top, bot = self.getwindowlines()
473 lineno = self.getlineno(mark)
474 height = bot - top
475 newtop = max(1, lineno - height/2)
476 text.yview(float(newtop))
477
478 def getwindowlines(self):
479 text = self.text
480 top = self.getlineno("@0,0")
481 bot = self.getlineno("@0,65535")
482 if top == bot and text.winfo_height() == 1:
483 # Geometry manager hasn't run yet
484 height = int(text['height'])
485 bot = top + height - 1
486 return top, bot
487
488 def getlineno(self, mark="insert"):
489 text = self.text
490 return int(float(text.index(mark)))
491
492 def close_event(self, event):
493 self.close()
494
495 def maybesave(self):
496 if self.io:
497 return self.io.maybesave()
498
499 def close(self):
500 self.top.wm_deiconify()
501 self.top.tkraise()
502 reply = self.maybesave()
503 if reply != "cancel":
504 self._close()
505 return reply
506
507 def _close(self):
508 WindowList.unregister_callback(self.postwindowsmenu)
509 if self.close_hook:
510 self.close_hook()
511 self.flist = None
512 colorizing = 0
513 self.unload_extensions()
514 self.io.close(); self.io = None
515 self.undo = None # XXX
516 if self.color:
517 colorizing = self.color.colorizing
518 doh = colorizing and self.top
519 self.color.close(doh) # Cancel colorization
520 self.text = None
521 self.vars = None
522 self.per.close(); self.per = None
523 if not colorizing:
524 self.top.destroy()
525
526 def load_extensions(self):
527 self.extensions = {}
528 self.load_standard_extensions()
529
530 def unload_extensions(self):
531 for ins in self.extensions.values():
532 if hasattr(ins, "close"):
533 ins.close()
534 self.extensions = {}
535
536 def load_standard_extensions(self):
537 for name in self.get_standard_extension_names():
538 try:
539 self.load_extension(name)
540 except:
541 print "Failed to load extension", `name`
542 import traceback
543 traceback.print_exc()
544
545 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000546 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000547
548 def load_extension(self, name):
549 mod = __import__(name, globals(), locals(), [])
550 cls = getattr(mod, name)
551 ins = cls(self)
552 self.extensions[name] = ins
553 kdnames = ["keydefs"]
554 if sys.platform == 'win32':
555 kdnames.append("windows_keydefs")
556 elif sys.platform == 'mac':
557 kdnames.append("mac_keydefs")
558 else:
559 kdnames.append("unix_keydefs")
560 keydefs = {}
561 for kdname in kdnames:
562 if hasattr(ins, kdname):
563 keydefs.update(getattr(ins, kdname))
564 if keydefs:
565 self.apply_bindings(keydefs)
566 for vevent in keydefs.keys():
567 methodname = string.replace(vevent, "-", "_")
568 while methodname[:1] == '<':
569 methodname = methodname[1:]
570 while methodname[-1:] == '>':
571 methodname = methodname[:-1]
572 methodname = methodname + "_event"
573 if hasattr(ins, methodname):
574 self.text.bind(vevent, getattr(ins, methodname))
575 if hasattr(ins, "menudefs"):
576 self.fill_menus(ins.menudefs, keydefs)
577 return ins
578
579 def apply_bindings(self, keydefs=None):
580 if keydefs is None:
581 keydefs = self.Bindings.default_keydefs
582 text = self.text
583 text.keydefs = keydefs
584 for event, keylist in keydefs.items():
585 if keylist:
586 apply(text.event_add, (event,) + tuple(keylist))
587
588 def fill_menus(self, defs=None, keydefs=None):
589 # Fill the menus. Menus that are absent or None in
590 # self.menudict are ignored.
591 if defs is None:
592 defs = self.Bindings.menudefs
593 if keydefs is None:
594 keydefs = self.Bindings.default_keydefs
595 menudict = self.menudict
596 text = self.text
597 for mname, itemlist in defs:
598 menu = menudict.get(mname)
599 if not menu:
600 continue
601 for item in itemlist:
602 if not item:
603 menu.add_separator()
604 else:
605 label, event = item
606 checkbutton = (label[:1] == '!')
607 if checkbutton:
608 label = label[1:]
609 underline, label = prepstr(label)
610 accelerator = get_accelerator(keydefs, event)
611 def command(text=text, event=event):
612 text.event_generate(event)
613 if checkbutton:
614 var = self.getrawvar(event, BooleanVar)
615 menu.add_checkbutton(label=label, underline=underline,
616 command=command, accelerator=accelerator,
617 variable=var)
618 else:
619 menu.add_command(label=label, underline=underline,
620 command=command, accelerator=accelerator)
621
622 def getvar(self, name):
623 var = self.getrawvar(name)
624 if var:
625 return var.get()
626
627 def setvar(self, name, value, vartype=None):
628 var = self.getrawvar(name, vartype)
629 if var:
630 var.set(value)
631
632 def getrawvar(self, name, vartype=None):
633 var = self.vars.get(name)
634 if not var and vartype:
635 self.vars[name] = var = vartype(self.text)
636 return var
637
638 # Tk implementations of "virtual text methods" -- each platform
639 # reusing IDLE's support code needs to define these for its GUI's
640 # flavor of widget.
641
642 # Is character at text_index in a Python string? Return 0 for
643 # "guaranteed no", true for anything else. This info is expensive
644 # to compute ab initio, but is probably already known by the
645 # platform's colorizer.
646
647 def is_char_in_string(self, text_index):
648 if self.color:
649 # Return true iff colorizer hasn't (re)gotten this far
650 # yet, or the character is tagged as being in a string
651 return self.text.tag_prevrange("TODO", text_index) or \
652 "STRING" in self.text.tag_names(text_index)
653 else:
654 # The colorizer is missing: assume the worst
655 return 1
656
657 # If a selection is defined in the text widget, return (start,
658 # end) as Tkinter text indices, otherwise return (None, None)
659 def get_selection_indices(self):
660 try:
661 first = self.text.index("sel.first")
662 last = self.text.index("sel.last")
663 return first, last
664 except TclError:
665 return None, None
666
667 # Return the text widget's current view of what a tab stop means
668 # (equivalent width in spaces).
669
670 def get_tabwidth(self):
671 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
672 return int(current)
673
674 # Set the text widget's current view of what a tab stop means.
675
676 def set_tabwidth(self, newtabwidth):
677 text = self.text
678 if self.get_tabwidth() != newtabwidth:
679 pixels = text.tk.call("font", "measure", text["font"],
680 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000681 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000682 text.configure(tabs=pixels)
683
684def prepstr(s):
685 # Helper to extract the underscore from a string, e.g.
686 # prepstr("Co_py") returns (2, "Copy").
687 i = string.find(s, '_')
688 if i >= 0:
689 s = s[:i] + s[i+1:]
690 return i, s
691
692
693keynames = {
694 'bracketleft': '[',
695 'bracketright': ']',
696 'slash': '/',
697}
698
699def get_accelerator(keydefs, event):
700 keylist = keydefs.get(event)
701 if not keylist:
702 return ""
703 s = keylist[0]
704 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
705 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
706 s = re.sub("Key-", "", s)
707 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
708 s = re.sub("Control-", "Ctrl-", s)
709 s = re.sub("-", "+", s)
710 s = re.sub("><", " ", s)
711 s = re.sub("<", "", s)
712 s = re.sub(">", "", s)
713 return s
714
715
716def fixwordbreaks(root):
717 # Make sure that Tk's double-click and next/previous word
718 # operations use our definition of a word (i.e. an identifier)
719 tk = root.tk
720 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
721 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
722 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
723
724
725def test():
726 root = Tk()
727 fixwordbreaks(root)
728 root.withdraw()
729 if sys.argv[1:]:
730 filename = sys.argv[1]
731 else:
732 filename = None
733 edit = EditorWindow(root=root, filename=filename)
734 edit.set_close_hook(root.quit)
735 root.mainloop()
736 root.destroy()
737
738if __name__ == '__main__':
739 test()