blob: 8a2109d359326bd26820d96be9c5df37559e9ae2 [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
101 if flist:
102 self.vars = flist.vars
103 self.menubar = Menu(root)
104 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000105 #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. Gavadc72f482002-01-03 11:51:07 +0000162 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
163 idleConf.GetOption('main','EditorWindow','font-size')))
David Scherer7aced172000-08-15 01:13:23 +0000164 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
165 text.pack(side=TOP, fill=BOTH, expand=1)
166 text.focus_set()
167
168 self.per = per = self.Percolator(text)
169 if self.ispythonsource(filename):
170 self.color = color = self.ColorDelegator(); per.insertfilter(color)
171 ##print "Initial colorizer"
172 else:
173 ##print "No initial colorizer"
174 self.color = None
175 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
176 self.io = io = self.IOBinding(self)
177
178 text.undo_block_start = undo.undo_block_start
179 text.undo_block_stop = undo.undo_block_stop
180 undo.set_saved_change_hook(self.saved_change_hook)
181 io.set_filename_change_hook(self.filename_change_hook)
182
183 if filename:
184 if os.path.exists(filename):
185 io.loadfile(filename)
186 else:
187 io.set_filename(filename)
188
189 self.saved_change_hook()
190
191 self.load_extensions()
192
193 menu = self.menudict.get('windows')
194 if menu:
195 end = menu.index("end")
196 if end is None:
197 end = -1
198 if end >= 0:
199 menu.add_separator()
200 end = end + 1
201 self.wmenu_end = end
202 WindowList.register_callback(self.postwindowsmenu)
203
204 # Some abstractions so IDLE extensions are cross-IDE
205 self.askyesno = tkMessageBox.askyesno
206 self.askinteger = tkSimpleDialog.askinteger
207 self.showerror = tkMessageBox.showerror
208
209 if self.extensions.has_key('AutoIndent'):
210 self.extensions['AutoIndent'].set_indentation_params(
211 self.ispythonsource(filename))
David Scherer7aced172000-08-15 01:13:23 +0000212
213 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000214 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000215 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
216 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
217 self.status_bar.pack(side=BOTTOM, fill=X)
218 self.text.bind('<KeyRelease>', self.set_line_and_column)
219 self.text.bind('<ButtonRelease>', self.set_line_and_column)
220 self.text.after_idle(self.set_line_and_column)
221
222 def set_line_and_column(self, event=None):
223 line, column = string.split(self.text.index(INSERT), '.')
224 self.status_bar.set_label('column', 'Col: %s' % column)
225 self.status_bar.set_label('line', 'Ln: %s' % line)
226
227 def wakeup(self):
228 if self.top.wm_state() == "iconic":
229 self.top.wm_deiconify()
230 else:
231 self.top.tkraise()
232 self.text.focus_set()
233
234 menu_specs = [
235 ("file", "_File"),
236 ("edit", "_Edit"),
237 ("format", "F_ormat"),
238 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000239 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000240 ("windows", "_Windows"),
241 ("help", "_Help"),
242 ]
243
244 def createmenubar(self):
245 mbar = self.menubar
246 self.menudict = menudict = {}
247 for name, label in self.menu_specs:
248 underline, label = prepstr(label)
249 menudict[name] = menu = Menu(mbar, name=name)
250 mbar.add_cascade(label=label, menu=menu, underline=underline)
251 self.fill_menus()
252
253 def postwindowsmenu(self):
254 # Only called when Windows menu exists
255 # XXX Actually, this Just-In-Time updating interferes badly
256 # XXX with the tear-off feature. It would be better to update
257 # XXX all Windows menus whenever the list of windows changes.
258 menu = self.menudict['windows']
259 end = menu.index("end")
260 if end is None:
261 end = -1
262 if end > self.wmenu_end:
263 menu.delete(self.wmenu_end+1, end)
264 WindowList.add_windows_to_menu(menu)
265
266 rmenu = None
267
268 def right_menu_event(self, event):
269 self.text.tag_remove("sel", "1.0", "end")
270 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
271 if not self.rmenu:
272 self.make_rmenu()
273 rmenu = self.rmenu
274 self.event = event
275 iswin = sys.platform[:3] == 'win'
276 if iswin:
277 self.text.config(cursor="arrow")
278 rmenu.tk_popup(event.x_root, event.y_root)
279 if iswin:
280 self.text.config(cursor="ibeam")
281
282 rmenu_specs = [
283 # ("Label", "<<virtual-event>>"), ...
284 ("Close", "<<close-window>>"), # Example
285 ]
286
287 def make_rmenu(self):
288 rmenu = Menu(self.text, tearoff=0)
289 for label, eventname in self.rmenu_specs:
290 def command(text=self.text, eventname=eventname):
291 text.event_generate(eventname)
292 rmenu.add_command(label=label, command=command)
293 self.rmenu = rmenu
294
295 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000296 aboutDialog.AboutDialog(self.top,'About IDLEfork')
297
Steven M. Gava3b55a892001-11-21 05:56:26 +0000298 def config_dialog(self, event=None):
299 configDialog.ConfigDialog(self.top,'Settings')
300
David Scherer7aced172000-08-15 01:13:23 +0000301 def good_advice(self, event=None):
302 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
303
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000304 def view_readme(self, event=None):
305 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
306 textView.TextViewer(self.top,'IDLEfork - README',fn)
307
David Scherer7aced172000-08-15 01:13:23 +0000308 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
310 textView.TextViewer(self.top,'Help',fn)
311
David Scherer7aced172000-08-15 01:13:23 +0000312 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000313 if sys.platform[:3] == "win":
314 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000315 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000316 fn = os.path.normpath(fn)
317 if os.path.isfile(fn):
318 help_url = fn
319 del fn
David Scherer7aced172000-08-15 01:13:23 +0000320
321 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000322 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000323
324 def select_all(self, event=None):
325 self.text.tag_add("sel", "1.0", "end-1c")
326 self.text.mark_set("insert", "1.0")
327 self.text.see("insert")
328 return "break"
329
330 def remove_selection(self, event=None):
331 self.text.tag_remove("sel", "1.0", "end")
332 self.text.see("insert")
333
Steven M. Gavac5976402002-01-04 03:06:08 +0000334 def find_event(self, event):
335 SearchDialog.find(self.text)
336 return "break"
337
338 def find_again_event(self, event):
339 SearchDialog.find_again(self.text)
340 return "break"
341
342 def find_selection_event(self, event):
343 SearchDialog.find_selection(self.text)
344 return "break"
345
346 def find_in_files_event(self, event):
347 GrepDialog.grep(self.text, self.io, self.flist)
348 return "break"
349
350 def replace_event(self, event):
351 ReplaceDialog.replace(self.text)
352 return "break"
353
354 def goto_line_event(self, event):
355 text = self.text
356 lineno = tkSimpleDialog.askinteger("Goto",
357 "Go to line number:",parent=text)
358 if lineno is None:
359 return "break"
360 if lineno <= 0:
361 text.bell()
362 return "break"
363 text.mark_set("insert", "%d.0" % lineno)
364 text.see("insert")
365
David Scherer7aced172000-08-15 01:13:23 +0000366 def open_module(self, event=None):
367 # XXX Shouldn't this be in IOBinding or in FileList?
368 try:
369 name = self.text.get("sel.first", "sel.last")
370 except TclError:
371 name = ""
372 else:
373 name = string.strip(name)
374 if not name:
375 name = tkSimpleDialog.askstring("Module",
376 "Enter the name of a Python module\n"
377 "to search on sys.path and open:",
378 parent=self.text)
379 if name:
380 name = string.strip(name)
381 if not name:
382 return
383 # XXX Ought to support package syntax
384 # XXX Ought to insert current file's directory in front of path
385 try:
386 (f, file, (suffix, mode, type)) = imp.find_module(name)
387 except (NameError, ImportError), msg:
388 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
389 return
390 if type != imp.PY_SOURCE:
391 tkMessageBox.showerror("Unsupported type",
392 "%s is not a source module" % name, parent=self.text)
393 return
394 if f:
395 f.close()
396 if self.flist:
397 self.flist.open(file)
398 else:
399 self.io.loadfile(file)
400
401 def open_class_browser(self, event=None):
402 filename = self.io.filename
403 if not filename:
404 tkMessageBox.showerror(
405 "No filename",
406 "This buffer has no associated filename",
407 master=self.text)
408 self.text.focus_set()
409 return None
410 head, tail = os.path.split(filename)
411 base, ext = os.path.splitext(tail)
412 import ClassBrowser
413 ClassBrowser.ClassBrowser(self.flist, base, [head])
414
415 def open_path_browser(self, event=None):
416 import PathBrowser
417 PathBrowser.PathBrowser(self.flist)
418
419 def gotoline(self, lineno):
420 if lineno is not None and lineno > 0:
421 self.text.mark_set("insert", "%d.0" % lineno)
422 self.text.tag_remove("sel", "1.0", "end")
423 self.text.tag_add("sel", "insert", "insert +1l")
424 self.center()
425
426 def ispythonsource(self, filename):
427 if not filename:
428 return 1
429 base, ext = os.path.splitext(os.path.basename(filename))
430 if os.path.normcase(ext) in (".py", ".pyw"):
431 return 1
432 try:
433 f = open(filename)
434 line = f.readline()
435 f.close()
436 except IOError:
437 return 0
438 return line[:2] == '#!' and string.find(line, 'python') >= 0
439
440 def close_hook(self):
441 if self.flist:
442 self.flist.close_edit(self)
443
444 def set_close_hook(self, close_hook):
445 self.close_hook = close_hook
446
447 def filename_change_hook(self):
448 if self.flist:
449 self.flist.filename_changed_edit(self)
450 self.saved_change_hook()
451 if self.ispythonsource(self.io.filename):
452 self.addcolorizer()
453 else:
454 self.rmcolorizer()
455
456 def addcolorizer(self):
457 if self.color:
458 return
459 ##print "Add colorizer"
460 self.per.removefilter(self.undo)
461 self.color = self.ColorDelegator()
462 self.per.insertfilter(self.color)
463 self.per.insertfilter(self.undo)
464
465 def rmcolorizer(self):
466 if not self.color:
467 return
468 ##print "Remove colorizer"
469 self.per.removefilter(self.undo)
470 self.per.removefilter(self.color)
471 self.color = None
472 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000473
474 def ResetColorizer(self):
475 #this function is called from configDialog.py
476 #to update the colour theme if it is changed
477 if self.color:
478 self.color = self.ColorDelegator()
479 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000480
481 def saved_change_hook(self):
482 short = self.short_title()
483 long = self.long_title()
484 if short and long:
485 title = short + " - " + long
486 elif short:
487 title = short
488 elif long:
489 title = long
490 else:
491 title = "Untitled"
492 icon = short or long or title
493 if not self.get_saved():
494 title = "*%s*" % title
495 icon = "*%s" % icon
496 self.top.wm_title(title)
497 self.top.wm_iconname(icon)
498
499 def get_saved(self):
500 return self.undo.get_saved()
501
502 def set_saved(self, flag):
503 self.undo.set_saved(flag)
504
505 def reset_undo(self):
506 self.undo.reset_undo()
507
508 def short_title(self):
509 filename = self.io.filename
510 if filename:
511 filename = os.path.basename(filename)
512 return filename
513
514 def long_title(self):
515 return self.io.filename or ""
516
517 def center_insert_event(self, event):
518 self.center()
519
520 def center(self, mark="insert"):
521 text = self.text
522 top, bot = self.getwindowlines()
523 lineno = self.getlineno(mark)
524 height = bot - top
525 newtop = max(1, lineno - height/2)
526 text.yview(float(newtop))
527
528 def getwindowlines(self):
529 text = self.text
530 top = self.getlineno("@0,0")
531 bot = self.getlineno("@0,65535")
532 if top == bot and text.winfo_height() == 1:
533 # Geometry manager hasn't run yet
534 height = int(text['height'])
535 bot = top + height - 1
536 return top, bot
537
538 def getlineno(self, mark="insert"):
539 text = self.text
540 return int(float(text.index(mark)))
541
542 def close_event(self, event):
543 self.close()
544
545 def maybesave(self):
546 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000547 if not self.get_saved():
548 if self.top.state()!='normal':
549 self.top.deiconify()
550 self.top.lower()
551 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000552 return self.io.maybesave()
553
554 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000555 reply = self.maybesave()
556 if reply != "cancel":
557 self._close()
558 return reply
559
560 def _close(self):
561 WindowList.unregister_callback(self.postwindowsmenu)
562 if self.close_hook:
563 self.close_hook()
564 self.flist = None
565 colorizing = 0
566 self.unload_extensions()
567 self.io.close(); self.io = None
568 self.undo = None # XXX
569 if self.color:
570 colorizing = self.color.colorizing
571 doh = colorizing and self.top
572 self.color.close(doh) # Cancel colorization
573 self.text = None
574 self.vars = None
575 self.per.close(); self.per = None
576 if not colorizing:
577 self.top.destroy()
578
579 def load_extensions(self):
580 self.extensions = {}
581 self.load_standard_extensions()
582
583 def unload_extensions(self):
584 for ins in self.extensions.values():
585 if hasattr(ins, "close"):
586 ins.close()
587 self.extensions = {}
588
589 def load_standard_extensions(self):
590 for name in self.get_standard_extension_names():
591 try:
592 self.load_extension(name)
593 except:
594 print "Failed to load extension", `name`
595 import traceback
596 traceback.print_exc()
597
598 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000599 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000600
601 def load_extension(self, name):
602 mod = __import__(name, globals(), locals(), [])
603 cls = getattr(mod, name)
604 ins = cls(self)
605 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000606 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000607 if keydefs:
608 self.apply_bindings(keydefs)
609 for vevent in keydefs.keys():
610 methodname = string.replace(vevent, "-", "_")
611 while methodname[:1] == '<':
612 methodname = methodname[1:]
613 while methodname[-1:] == '>':
614 methodname = methodname[:-1]
615 methodname = methodname + "_event"
616 if hasattr(ins, methodname):
617 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000618
David Scherer7aced172000-08-15 01:13:23 +0000619 if hasattr(ins, "menudefs"):
620 self.fill_menus(ins.menudefs, keydefs)
621 return ins
622
623 def apply_bindings(self, keydefs=None):
624 if keydefs is None:
625 keydefs = self.Bindings.default_keydefs
626 text = self.text
627 text.keydefs = keydefs
628 for event, keylist in keydefs.items():
629 if keylist:
630 apply(text.event_add, (event,) + tuple(keylist))
631
632 def fill_menus(self, defs=None, keydefs=None):
633 # Fill the menus. Menus that are absent or None in
634 # self.menudict are ignored.
635 if defs is None:
636 defs = self.Bindings.menudefs
637 if keydefs is None:
638 keydefs = self.Bindings.default_keydefs
639 menudict = self.menudict
640 text = self.text
641 for mname, itemlist in defs:
642 menu = menudict.get(mname)
643 if not menu:
644 continue
645 for item in itemlist:
646 if not item:
647 menu.add_separator()
648 else:
649 label, event = item
650 checkbutton = (label[:1] == '!')
651 if checkbutton:
652 label = label[1:]
653 underline, label = prepstr(label)
654 accelerator = get_accelerator(keydefs, event)
655 def command(text=text, event=event):
656 text.event_generate(event)
657 if checkbutton:
658 var = self.getrawvar(event, BooleanVar)
659 menu.add_checkbutton(label=label, underline=underline,
660 command=command, accelerator=accelerator,
661 variable=var)
662 else:
663 menu.add_command(label=label, underline=underline,
664 command=command, accelerator=accelerator)
665
666 def getvar(self, name):
667 var = self.getrawvar(name)
668 if var:
669 return var.get()
670
671 def setvar(self, name, value, vartype=None):
672 var = self.getrawvar(name, vartype)
673 if var:
674 var.set(value)
675
676 def getrawvar(self, name, vartype=None):
677 var = self.vars.get(name)
678 if not var and vartype:
679 self.vars[name] = var = vartype(self.text)
680 return var
681
682 # Tk implementations of "virtual text methods" -- each platform
683 # reusing IDLE's support code needs to define these for its GUI's
684 # flavor of widget.
685
686 # Is character at text_index in a Python string? Return 0 for
687 # "guaranteed no", true for anything else. This info is expensive
688 # to compute ab initio, but is probably already known by the
689 # platform's colorizer.
690
691 def is_char_in_string(self, text_index):
692 if self.color:
693 # Return true iff colorizer hasn't (re)gotten this far
694 # yet, or the character is tagged as being in a string
695 return self.text.tag_prevrange("TODO", text_index) or \
696 "STRING" in self.text.tag_names(text_index)
697 else:
698 # The colorizer is missing: assume the worst
699 return 1
700
701 # If a selection is defined in the text widget, return (start,
702 # end) as Tkinter text indices, otherwise return (None, None)
703 def get_selection_indices(self):
704 try:
705 first = self.text.index("sel.first")
706 last = self.text.index("sel.last")
707 return first, last
708 except TclError:
709 return None, None
710
711 # Return the text widget's current view of what a tab stop means
712 # (equivalent width in spaces).
713
714 def get_tabwidth(self):
715 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
716 return int(current)
717
718 # Set the text widget's current view of what a tab stop means.
719
720 def set_tabwidth(self, newtabwidth):
721 text = self.text
722 if self.get_tabwidth() != newtabwidth:
723 pixels = text.tk.call("font", "measure", text["font"],
724 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000725 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000726 text.configure(tabs=pixels)
727
728def prepstr(s):
729 # Helper to extract the underscore from a string, e.g.
730 # prepstr("Co_py") returns (2, "Copy").
731 i = string.find(s, '_')
732 if i >= 0:
733 s = s[:i] + s[i+1:]
734 return i, s
735
736
737keynames = {
738 'bracketleft': '[',
739 'bracketright': ']',
740 'slash': '/',
741}
742
743def get_accelerator(keydefs, event):
744 keylist = keydefs.get(event)
745 if not keylist:
746 return ""
747 s = keylist[0]
748 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
749 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
750 s = re.sub("Key-", "", s)
751 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
752 s = re.sub("Control-", "Ctrl-", s)
753 s = re.sub("-", "+", s)
754 s = re.sub("><", " ", s)
755 s = re.sub("<", "", s)
756 s = re.sub(">", "", s)
757 return s
758
759
760def fixwordbreaks(root):
761 # Make sure that Tk's double-click and next/previous word
762 # operations use our definition of a word (i.e. an identifier)
763 tk = root.tk
764 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
765 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
766 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
767
768
769def test():
770 root = Tk()
771 fixwordbreaks(root)
772 root.withdraw()
773 if sys.argv[1:]:
774 filename = sys.argv[1]
775 else:
776 filename = None
777 edit = EditorWindow(root=root, filename=filename)
778 edit.set_close_hook(root.quit)
779 root.mainloop()
780 root.destroy()
781
782if __name__ == '__main__':
783 test()