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