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