blob: 99f40a7694372a44776936d74d1f121483e28e99 [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. 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))
David Scherer7aced172000-08-15 01:13:23 +0000216
217 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()
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 aboutDialog.AboutDialog(self.top,'About IDLEfork')
301
Steven M. Gava3b55a892001-11-21 05:56:26 +0000302 def config_dialog(self, event=None):
303 configDialog.ConfigDialog(self.top,'Settings')
304
David Scherer7aced172000-08-15 01:13:23 +0000305 def good_advice(self, event=None):
306 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
307
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000308 def view_readme(self, event=None):
309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
310 textView.TextViewer(self.top,'IDLEfork - README',fn)
311
David Scherer7aced172000-08-15 01:13:23 +0000312 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000313 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
314 textView.TextViewer(self.top,'Help',fn)
315
David Scherer7aced172000-08-15 01:13:23 +0000316 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000317 if sys.platform[:3] == "win":
318 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000319 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000320 fn = os.path.normpath(fn)
321 if os.path.isfile(fn):
322 help_url = fn
323 del fn
David Scherer7aced172000-08-15 01:13:23 +0000324
325 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000326 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000327
328 def select_all(self, event=None):
329 self.text.tag_add("sel", "1.0", "end-1c")
330 self.text.mark_set("insert", "1.0")
331 self.text.see("insert")
332 return "break"
333
334 def remove_selection(self, event=None):
335 self.text.tag_remove("sel", "1.0", "end")
336 self.text.see("insert")
337
Steven M. Gavac5976402002-01-04 03:06:08 +0000338 def find_event(self, event):
339 SearchDialog.find(self.text)
340 return "break"
341
342 def find_again_event(self, event):
343 SearchDialog.find_again(self.text)
344 return "break"
345
346 def find_selection_event(self, event):
347 SearchDialog.find_selection(self.text)
348 return "break"
349
350 def find_in_files_event(self, event):
351 GrepDialog.grep(self.text, self.io, self.flist)
352 return "break"
353
354 def replace_event(self, event):
355 ReplaceDialog.replace(self.text)
356 return "break"
357
358 def goto_line_event(self, event):
359 text = self.text
360 lineno = tkSimpleDialog.askinteger("Goto",
361 "Go to line number:",parent=text)
362 if lineno is None:
363 return "break"
364 if lineno <= 0:
365 text.bell()
366 return "break"
367 text.mark_set("insert", "%d.0" % lineno)
368 text.see("insert")
369
David Scherer7aced172000-08-15 01:13:23 +0000370 def open_module(self, event=None):
371 # XXX Shouldn't this be in IOBinding or in FileList?
372 try:
373 name = self.text.get("sel.first", "sel.last")
374 except TclError:
375 name = ""
376 else:
377 name = string.strip(name)
378 if not name:
379 name = tkSimpleDialog.askstring("Module",
380 "Enter the name of a Python module\n"
381 "to search on sys.path and open:",
382 parent=self.text)
383 if name:
384 name = string.strip(name)
385 if not name:
386 return
387 # XXX Ought to support package syntax
388 # XXX Ought to insert current file's directory in front of path
389 try:
390 (f, file, (suffix, mode, type)) = imp.find_module(name)
391 except (NameError, ImportError), msg:
392 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
393 return
394 if type != imp.PY_SOURCE:
395 tkMessageBox.showerror("Unsupported type",
396 "%s is not a source module" % name, parent=self.text)
397 return
398 if f:
399 f.close()
400 if self.flist:
401 self.flist.open(file)
402 else:
403 self.io.loadfile(file)
404
405 def open_class_browser(self, event=None):
406 filename = self.io.filename
407 if not filename:
408 tkMessageBox.showerror(
409 "No filename",
410 "This buffer has no associated filename",
411 master=self.text)
412 self.text.focus_set()
413 return None
414 head, tail = os.path.split(filename)
415 base, ext = os.path.splitext(tail)
416 import ClassBrowser
417 ClassBrowser.ClassBrowser(self.flist, base, [head])
418
419 def open_path_browser(self, event=None):
420 import PathBrowser
421 PathBrowser.PathBrowser(self.flist)
422
423 def gotoline(self, lineno):
424 if lineno is not None and lineno > 0:
425 self.text.mark_set("insert", "%d.0" % lineno)
426 self.text.tag_remove("sel", "1.0", "end")
427 self.text.tag_add("sel", "insert", "insert +1l")
428 self.center()
429
430 def ispythonsource(self, filename):
431 if not filename:
432 return 1
433 base, ext = os.path.splitext(os.path.basename(filename))
434 if os.path.normcase(ext) in (".py", ".pyw"):
435 return 1
436 try:
437 f = open(filename)
438 line = f.readline()
439 f.close()
440 except IOError:
441 return 0
442 return line[:2] == '#!' and string.find(line, 'python') >= 0
443
444 def close_hook(self):
445 if self.flist:
446 self.flist.close_edit(self)
447
448 def set_close_hook(self, close_hook):
449 self.close_hook = close_hook
450
451 def filename_change_hook(self):
452 if self.flist:
453 self.flist.filename_changed_edit(self)
454 self.saved_change_hook()
455 if self.ispythonsource(self.io.filename):
456 self.addcolorizer()
457 else:
458 self.rmcolorizer()
459
460 def addcolorizer(self):
461 if self.color:
462 return
463 ##print "Add colorizer"
464 self.per.removefilter(self.undo)
465 self.color = self.ColorDelegator()
466 self.per.insertfilter(self.color)
467 self.per.insertfilter(self.undo)
468
469 def rmcolorizer(self):
470 if not self.color:
471 return
472 ##print "Remove colorizer"
473 self.per.removefilter(self.undo)
474 self.per.removefilter(self.color)
475 self.color = None
476 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000477
478 def ResetColorizer(self):
479 #this function is called from configDialog.py
480 #to update the colour theme if it is changed
481 if self.color:
482 self.color = self.ColorDelegator()
483 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000484
Steven M. Gavab1585412002-03-12 00:21:56 +0000485 def ResetFont(self):
486 #this function is called from configDialog.py
487 #to update the text widgets' font if it is changed
488 fontWeight='normal'
489 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
490 fontWeight='bold'
491 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
492 idleConf.GetOption('main','EditorWindow','font-size'),
493 fontWeight))
494
David Scherer7aced172000-08-15 01:13:23 +0000495 def saved_change_hook(self):
496 short = self.short_title()
497 long = self.long_title()
498 if short and long:
499 title = short + " - " + long
500 elif short:
501 title = short
502 elif long:
503 title = long
504 else:
505 title = "Untitled"
506 icon = short or long or title
507 if not self.get_saved():
508 title = "*%s*" % title
509 icon = "*%s" % icon
510 self.top.wm_title(title)
511 self.top.wm_iconname(icon)
512
513 def get_saved(self):
514 return self.undo.get_saved()
515
516 def set_saved(self, flag):
517 self.undo.set_saved(flag)
518
519 def reset_undo(self):
520 self.undo.reset_undo()
521
522 def short_title(self):
523 filename = self.io.filename
524 if filename:
525 filename = os.path.basename(filename)
526 return filename
527
528 def long_title(self):
529 return self.io.filename or ""
530
531 def center_insert_event(self, event):
532 self.center()
533
534 def center(self, mark="insert"):
535 text = self.text
536 top, bot = self.getwindowlines()
537 lineno = self.getlineno(mark)
538 height = bot - top
539 newtop = max(1, lineno - height/2)
540 text.yview(float(newtop))
541
542 def getwindowlines(self):
543 text = self.text
544 top = self.getlineno("@0,0")
545 bot = self.getlineno("@0,65535")
546 if top == bot and text.winfo_height() == 1:
547 # Geometry manager hasn't run yet
548 height = int(text['height'])
549 bot = top + height - 1
550 return top, bot
551
552 def getlineno(self, mark="insert"):
553 text = self.text
554 return int(float(text.index(mark)))
555
556 def close_event(self, event):
557 self.close()
558
559 def maybesave(self):
560 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000561 if not self.get_saved():
562 if self.top.state()!='normal':
563 self.top.deiconify()
564 self.top.lower()
565 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000566 return self.io.maybesave()
567
568 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000569 reply = self.maybesave()
570 if reply != "cancel":
571 self._close()
572 return reply
573
574 def _close(self):
575 WindowList.unregister_callback(self.postwindowsmenu)
576 if self.close_hook:
577 self.close_hook()
578 self.flist = None
579 colorizing = 0
580 self.unload_extensions()
581 self.io.close(); self.io = None
582 self.undo = None # XXX
583 if self.color:
584 colorizing = self.color.colorizing
585 doh = colorizing and self.top
586 self.color.close(doh) # Cancel colorization
587 self.text = None
588 self.vars = None
589 self.per.close(); self.per = None
590 if not colorizing:
591 self.top.destroy()
592
593 def load_extensions(self):
594 self.extensions = {}
595 self.load_standard_extensions()
596
597 def unload_extensions(self):
598 for ins in self.extensions.values():
599 if hasattr(ins, "close"):
600 ins.close()
601 self.extensions = {}
602
603 def load_standard_extensions(self):
604 for name in self.get_standard_extension_names():
605 try:
606 self.load_extension(name)
607 except:
608 print "Failed to load extension", `name`
609 import traceback
610 traceback.print_exc()
611
612 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000613 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000614
615 def load_extension(self, name):
616 mod = __import__(name, globals(), locals(), [])
617 cls = getattr(mod, name)
618 ins = cls(self)
619 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000620 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000621 if keydefs:
622 self.apply_bindings(keydefs)
623 for vevent in keydefs.keys():
624 methodname = string.replace(vevent, "-", "_")
625 while methodname[:1] == '<':
626 methodname = methodname[1:]
627 while methodname[-1:] == '>':
628 methodname = methodname[:-1]
629 methodname = methodname + "_event"
630 if hasattr(ins, methodname):
631 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000632
David Scherer7aced172000-08-15 01:13:23 +0000633 if hasattr(ins, "menudefs"):
634 self.fill_menus(ins.menudefs, keydefs)
635 return ins
636
637 def apply_bindings(self, keydefs=None):
638 if keydefs is None:
639 keydefs = self.Bindings.default_keydefs
640 text = self.text
641 text.keydefs = keydefs
642 for event, keylist in keydefs.items():
643 if keylist:
644 apply(text.event_add, (event,) + tuple(keylist))
645
646 def fill_menus(self, defs=None, keydefs=None):
647 # Fill the menus. Menus that are absent or None in
648 # self.menudict are ignored.
649 if defs is None:
650 defs = self.Bindings.menudefs
651 if keydefs is None:
652 keydefs = self.Bindings.default_keydefs
653 menudict = self.menudict
654 text = self.text
655 for mname, itemlist in defs:
656 menu = menudict.get(mname)
657 if not menu:
658 continue
659 for item in itemlist:
660 if not item:
661 menu.add_separator()
662 else:
663 label, event = item
664 checkbutton = (label[:1] == '!')
665 if checkbutton:
666 label = label[1:]
667 underline, label = prepstr(label)
668 accelerator = get_accelerator(keydefs, event)
669 def command(text=text, event=event):
670 text.event_generate(event)
671 if checkbutton:
672 var = self.getrawvar(event, BooleanVar)
673 menu.add_checkbutton(label=label, underline=underline,
674 command=command, accelerator=accelerator,
675 variable=var)
676 else:
677 menu.add_command(label=label, underline=underline,
678 command=command, accelerator=accelerator)
679
680 def getvar(self, name):
681 var = self.getrawvar(name)
682 if var:
683 return var.get()
684
685 def setvar(self, name, value, vartype=None):
686 var = self.getrawvar(name, vartype)
687 if var:
688 var.set(value)
689
690 def getrawvar(self, name, vartype=None):
691 var = self.vars.get(name)
692 if not var and vartype:
693 self.vars[name] = var = vartype(self.text)
694 return var
695
696 # Tk implementations of "virtual text methods" -- each platform
697 # reusing IDLE's support code needs to define these for its GUI's
698 # flavor of widget.
699
700 # Is character at text_index in a Python string? Return 0 for
701 # "guaranteed no", true for anything else. This info is expensive
702 # to compute ab initio, but is probably already known by the
703 # platform's colorizer.
704
705 def is_char_in_string(self, text_index):
706 if self.color:
707 # Return true iff colorizer hasn't (re)gotten this far
708 # yet, or the character is tagged as being in a string
709 return self.text.tag_prevrange("TODO", text_index) or \
710 "STRING" in self.text.tag_names(text_index)
711 else:
712 # The colorizer is missing: assume the worst
713 return 1
714
715 # If a selection is defined in the text widget, return (start,
716 # end) as Tkinter text indices, otherwise return (None, None)
717 def get_selection_indices(self):
718 try:
719 first = self.text.index("sel.first")
720 last = self.text.index("sel.last")
721 return first, last
722 except TclError:
723 return None, None
724
725 # Return the text widget's current view of what a tab stop means
726 # (equivalent width in spaces).
727
728 def get_tabwidth(self):
729 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
730 return int(current)
731
732 # Set the text widget's current view of what a tab stop means.
733
734 def set_tabwidth(self, newtabwidth):
735 text = self.text
736 if self.get_tabwidth() != newtabwidth:
737 pixels = text.tk.call("font", "measure", text["font"],
738 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000739 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000740 text.configure(tabs=pixels)
741
742def prepstr(s):
743 # Helper to extract the underscore from a string, e.g.
744 # prepstr("Co_py") returns (2, "Copy").
745 i = string.find(s, '_')
746 if i >= 0:
747 s = s[:i] + s[i+1:]
748 return i, s
749
750
751keynames = {
752 'bracketleft': '[',
753 'bracketright': ']',
754 'slash': '/',
755}
756
757def get_accelerator(keydefs, event):
758 keylist = keydefs.get(event)
759 if not keylist:
760 return ""
761 s = keylist[0]
762 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
763 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
764 s = re.sub("Key-", "", s)
765 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
766 s = re.sub("Control-", "Ctrl-", s)
767 s = re.sub("-", "+", s)
768 s = re.sub("><", " ", s)
769 s = re.sub("<", "", s)
770 s = re.sub(">", "", s)
771 return s
772
773
774def fixwordbreaks(root):
775 # Make sure that Tk's double-click and next/previous word
776 # operations use our definition of a word (i.e. an identifier)
777 tk = root.tk
778 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
779 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
780 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
781
782
783def test():
784 root = Tk()
785 fixwordbreaks(root)
786 root.withdraw()
787 if sys.argv[1:]:
788 filename = sys.argv[1]
789 else:
790 filename = None
791 edit = EditorWindow(root=root, filename=filename)
792 edit.set_close_hook(root.quit)
793 root.mainloop()
794 root.destroy()
795
796if __name__ == '__main__':
797 test()