blob: 007a99b571635310dd1b4f4bdf5010b3635d578c [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import os
3import string
Guido van Rossum07ec8961999-01-28 22:02:47 +00004import re
Guido van Rossumb3418881998-10-13 03:45:15 +00005import imp
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00006from Tkinter import *
Guido van Rossumb3418881998-10-13 03:45:15 +00007import tkSimpleDialog
Guido van Rossum2aeeb551998-10-12 21:01:37 +00008import tkMessageBox
Guido van Rossum504b0bf1999-01-02 21:28:54 +00009import idlever
Guido van Rossumc4f752f1999-02-17 17:20:50 +000010import WindowList
Guido van Rossum504b0bf1999-01-02 21:28:54 +000011
Guido van Rossum13205601999-06-11 15:03:00 +000012# The default tab setting for a Text widget, in average-width characters.
13TK_TABWIDTH_DEFAULT = 8
14
Guido van Rossum504b0bf1999-01-02 21:28:54 +000015# File menu
16
17#$ event <<open-module>>
18#$ win <Alt-m>
19#$ unix <Control-x><Control-m>
20
21#$ event <<open-class-browser>>
22#$ win <Alt-c>
23#$ unix <Control-x><Control-b>
24
Guido van Rossumd6e87131999-03-10 05:18:02 +000025#$ event <<open-path-browser>>
26
Guido van Rossum504b0bf1999-01-02 21:28:54 +000027#$ event <<close-window>>
28#$ unix <Control-x><Control-0>
29#$ unix <Control-x><Key-0>
30#$ win <Alt-F4>
31
32# Edit menu
33
34#$ event <<Copy>>
35#$ win <Control-c>
36#$ unix <Alt-w>
37
38#$ event <<Cut>>
39#$ win <Control-x>
40#$ unix <Control-w>
41
42#$ event <<Paste>>
43#$ win <Control-v>
44#$ unix <Control-y>
45
46#$ event <<select-all>>
47#$ win <Alt-a>
48#$ unix <Alt-a>
49
50# Help menu
51
52#$ event <<help>>
53#$ win <F1>
54#$ unix <F1>
55
56#$ event <<about-idle>>
57
58# Events without menu entries
59
60#$ event <<remove-selection>>
61#$ win <Escape>
62
63#$ event <<center-insert>>
64#$ win <Control-l>
65#$ unix <Control-l>
66
67#$ event <<do-nothing>>
68#$ unix <Control-x>
69
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000070
Guido van Rossum2aeeb551998-10-12 21:01:37 +000071about_title = "About IDLE"
72about_text = """\
Guido van Rossum504b0bf1999-01-02 21:28:54 +000073IDLE %s
Guido van Rossum2aeeb551998-10-12 21:01:37 +000074
Guido van Rossum504b0bf1999-01-02 21:28:54 +000075An Integrated DeveLopment Environment for Python
Guido van Rossum2aeeb551998-10-12 21:01:37 +000076
77by Guido van Rossum
Guido van Rossum504b0bf1999-01-02 21:28:54 +000078""" % idlever.IDLE_VERSION
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000079
80class EditorWindow:
81
82 from Percolator import Percolator
83 from ColorDelegator import ColorDelegator
84 from UndoDelegator import UndoDelegator
85 from IOBinding import IOBinding
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000086 import Bindings
Guido van Rossum504b0bf1999-01-02 21:28:54 +000087 from Tkinter import Toplevel
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000088
Guido van Rossum504b0bf1999-01-02 21:28:54 +000089 about_title = about_title
90 about_text = about_text
91
Guido van Rossumb7ebb831999-01-28 22:24:30 +000092 vars = {}
93
Guido van Rossum504b0bf1999-01-02 21:28:54 +000094 def __init__(self, flist=None, filename=None, key=None, root=None):
Guido van Rossum7de69751999-04-20 15:45:30 +000095 cprefs = self.ColorDelegator.cprefs
Guido van Rossum504b0bf1999-01-02 21:28:54 +000096 self.flist = flist
97 root = root or flist.root
Guido van Rossum2aeeb551998-10-12 21:01:37 +000098 self.root = root
Guido van Rossumb7ebb831999-01-28 22:24:30 +000099 if flist:
100 self.vars = flist.vars
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000101 self.menubar = Menu(root)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000102 self.top = top = self.Toplevel(root, menu=self.menubar)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000103 self.vbar = vbar = Scrollbar(top, name='vbar')
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000104 self.text = text = Text(top, name='text', padx=5,
Guido van Rossum7de69751999-04-20 15:45:30 +0000105 foreground=cprefs.CNormal[0],
Guido van Rossumdef2c961999-05-21 04:38:27 +0000106 background=cprefs.CNormal[1],
Guido van Rossum7de69751999-04-20 15:45:30 +0000107 highlightcolor=cprefs.CHilite[0],
108 highlightbackground=cprefs.CHilite[1],
109 insertbackground=cprefs.CCursor[1],
110 wrap="none")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000111
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000112 self.createmenubar()
Guido van Rossum07ec8961999-01-28 22:02:47 +0000113 self.apply_bindings()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000114
115 self.top.protocol("WM_DELETE_WINDOW", self.close)
116 self.top.bind("<<close-window>>", self.close_event)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000117 text.bind("<<center-insert>>", self.center_insert_event)
118 text.bind("<<help>>", self.help_dialog)
119 text.bind("<<about-idle>>", self.about_dialog)
120 text.bind("<<open-module>>", self.open_module)
121 text.bind("<<do-nothing>>", lambda event: "break")
122 text.bind("<<select-all>>", self.select_all)
123 text.bind("<<remove-selection>>", self.remove_selection)
124 text.bind("<3>", self.right_menu_event)
125 if flist:
126 flist.inversedict[self] = key
127 if key:
128 flist.dict[key] = self
129 text.bind("<<open-new-window>>", self.flist.new_callback)
130 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
131 text.bind("<<open-class-browser>>", self.open_class_browser)
Guido van Rossumd6e87131999-03-10 05:18:02 +0000132 text.bind("<<open-path-browser>>", self.open_path_browser)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000133
134 vbar['command'] = text.yview
135 vbar.pack(side=RIGHT, fill=Y)
136
137 text['yscrollcommand'] = vbar.set
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000138 if sys.platform[:3] == 'win':
139 text['font'] = ("lucida console", 8)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000140# text['font'] = ("courier new", 10)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000141 text.pack(side=LEFT, fill=BOTH, expand=1)
142 text.focus_set()
143
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000144 self.per = per = self.Percolator(text)
145 if self.ispythonsource(filename):
146 self.color = color = self.ColorDelegator(); per.insertfilter(color)
147 ##print "Initial colorizer"
148 else:
149 ##print "No initial colorizer"
150 self.color = None
151 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000152 self.io = io = self.IOBinding(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000153
Guido van Rossum318a70d1999-05-03 15:49:52 +0000154 text.undo_block_start = undo.undo_block_start
155 text.undo_block_stop = undo.undo_block_stop
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000156 undo.set_saved_change_hook(self.saved_change_hook)
157 io.set_filename_change_hook(self.filename_change_hook)
158
159 if filename:
160 if os.path.exists(filename):
161 io.loadfile(filename)
162 else:
163 io.set_filename(filename)
164
165 self.saved_change_hook()
166
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000167 self.load_extensions()
168
169 menu = self.menudict.get('windows')
170 if menu:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000171 end = menu.index("end")
172 if end is None:
173 end = -1
174 if end >= 0:
175 menu.add_separator()
176 end = end + 1
177 self.wmenu_end = end
Guido van Rossumc4f752f1999-02-17 17:20:50 +0000178 WindowList.register_callback(self.postwindowsmenu)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000179
Guido van Rossumd395aee1999-06-02 11:04:29 +0000180 # Some abstractions so IDLE extensions are cross-IDE
181 self.askyesno = tkMessageBox.askyesno
182 self.askinteger = tkSimpleDialog.askinteger
183 self.showerror = tkMessageBox.showerror
184
Guido van Rossumdef2c961999-05-21 04:38:27 +0000185 if self.extensions.has_key('AutoIndent'):
186 self.extensions['AutoIndent'].set_indentation_params(
187 self.ispythonsource(filename))
188
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000189 def wakeup(self):
Guido van Rossum36911a11999-01-18 15:18:57 +0000190 if self.top.wm_state() == "iconic":
191 self.top.wm_deiconify()
192 else:
193 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000194 self.text.focus_set()
195
Guido van Rossume7b2e651998-10-12 23:56:08 +0000196 menu_specs = [
Guido van Rossumb5eed031998-11-27 03:19:07 +0000197 ("file", "_File"),
198 ("edit", "_Edit"),
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000199 ("windows", "_Windows"),
Guido van Rossumb5eed031998-11-27 03:19:07 +0000200 ("help", "_Help"),
Guido van Rossume7b2e651998-10-12 23:56:08 +0000201 ]
202
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000203 def createmenubar(self):
204 mbar = self.menubar
Guido van Rossum07ec8961999-01-28 22:02:47 +0000205 self.menudict = menudict = {}
Guido van Rossume7b2e651998-10-12 23:56:08 +0000206 for name, label in self.menu_specs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000207 underline, label = prepstr(label)
208 menudict[name] = menu = Menu(mbar, name=name)
Guido van Rossumb5eed031998-11-27 03:19:07 +0000209 mbar.add_cascade(label=label, menu=menu, underline=underline)
Guido van Rossum07ec8961999-01-28 22:02:47 +0000210 self.fill_menus()
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000211
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000212 def postwindowsmenu(self):
213 # Only called when Windows menu exists
Guido van Rossum85a36a51999-06-10 17:43:17 +0000214 # XXX Actually, this Just-In-Time updating interferes badly
215 # XXX with the tear-off feature. It would be better to update
216 # XXX all Windows menus whenever the list of windows changes.
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000217 menu = self.menudict['windows']
218 end = menu.index("end")
219 if end is None:
220 end = -1
221 if end > self.wmenu_end:
222 menu.delete(self.wmenu_end+1, end)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000223 WindowList.add_windows_to_menu(menu)
224
225 rmenu = None
226
227 def right_menu_event(self, event):
228 self.text.tag_remove("sel", "1.0", "end")
229 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
230 if not self.rmenu:
231 self.make_rmenu()
232 rmenu = self.rmenu
233 self.event = event
234 iswin = sys.platform[:3] == 'win'
235 if iswin:
236 self.text.config(cursor="arrow")
237 rmenu.tk_popup(event.x_root, event.y_root)
238 if iswin:
239 self.text.config(cursor="ibeam")
240
241 rmenu_specs = [
242 # ("Label", "<<virtual-event>>"), ...
243 ("Close", "<<close-window>>"), # Example
244 ]
245
246 def make_rmenu(self):
247 rmenu = Menu(self.text, tearoff=0)
248 for label, eventname in self.rmenu_specs:
249 def command(text=self.text, eventname=eventname):
250 text.event_generate(eventname)
251 rmenu.add_command(label=label, command=command)
252 self.rmenu = rmenu
253
Guido van Rossume7b2e651998-10-12 23:56:08 +0000254 def about_dialog(self, event=None):
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000255 tkMessageBox.showinfo(self.about_title, self.about_text,
256 master=self.text)
257
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000258 helpfile = "help.txt"
259
Guido van Rossume7b2e651998-10-12 23:56:08 +0000260 def help_dialog(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000261 helpfile = self.helpfile
262 if not os.path.exists(helpfile):
263 base = os.path.basename(self.helpfile)
264 for dir in sys.path:
265 fullname = os.path.join(dir, base)
266 if os.path.exists(fullname):
267 helpfile = fullname
268 break
269 if self.flist:
270 self.flist.open(helpfile)
271 else:
272 self.io.loadfile(helpfile)
273
274 def select_all(self, event=None):
275 self.text.tag_add("sel", "1.0", "end-1c")
276 self.text.mark_set("insert", "1.0")
277 self.text.see("insert")
278 return "break"
279
280 def remove_selection(self, event=None):
281 self.text.tag_remove("sel", "1.0", "end")
282 self.text.see("insert")
283
Guido van Rossumb3418881998-10-13 03:45:15 +0000284 def open_module(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000285 # XXX Shouldn't this be in IOBinding or in FileList?
Guido van Rossumb3418881998-10-13 03:45:15 +0000286 try:
287 name = self.text.get("sel.first", "sel.last")
288 except TclError:
289 name = ""
290 else:
291 name = string.strip(name)
292 if not name:
293 name = tkSimpleDialog.askstring("Module",
Guido van Rossume1dedc01998-10-16 16:09:57 +0000294 "Enter the name of a Python module\n"
295 "to search on sys.path and open:",
Guido van Rossumb3418881998-10-13 03:45:15 +0000296 parent=self.text)
297 if name:
298 name = string.strip(name)
299 if not name:
300 return
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000301 # XXX Ought to support package syntax
302 # XXX Ought to insert current file's directory in front of path
Guido van Rossumb3418881998-10-13 03:45:15 +0000303 try:
304 (f, file, (suffix, mode, type)) = imp.find_module(name)
Guido van Rossum74311b21999-06-01 18:27:14 +0000305 except (NameError, ImportError), msg:
Guido van Rossumb3418881998-10-13 03:45:15 +0000306 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
307 return
308 if type != imp.PY_SOURCE:
309 tkMessageBox.showerror("Unsupported type",
310 "%s is not a source module" % name, parent=self.text)
311 return
312 if f:
313 f.close()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000314 if self.flist:
315 self.flist.open(file)
316 else:
317 self.io.loadfile(file)
318
319 def open_class_browser(self, event=None):
320 filename = self.io.filename
321 if not filename:
322 tkMessageBox.showerror(
323 "No filename",
324 "This buffer has no associated filename",
325 master=self.text)
Guido van Rossum74311b21999-06-01 18:27:14 +0000326 self.text.focus_set()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000327 return None
328 head, tail = os.path.split(filename)
329 base, ext = os.path.splitext(tail)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000330 import ClassBrowser
331 ClassBrowser.ClassBrowser(self.flist, base, [head])
Guido van Rossumdef2c961999-05-21 04:38:27 +0000332
Guido van Rossumd6e87131999-03-10 05:18:02 +0000333 def open_path_browser(self, event=None):
334 import PathBrowser
335 PathBrowser.PathBrowser(self.flist)
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000336
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000337 def gotoline(self, lineno):
338 if lineno is not None and lineno > 0:
339 self.text.mark_set("insert", "%d.0" % lineno)
340 self.text.tag_remove("sel", "1.0", "end")
341 self.text.tag_add("sel", "insert", "insert +1l")
342 self.center()
343
344 def ispythonsource(self, filename):
345 if not filename:
346 return 1
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000347 base, ext = os.path.splitext(os.path.basename(filename))
348 if os.path.normcase(ext) in (".py", ".pyw"):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000349 return 1
350 try:
351 f = open(filename)
352 line = f.readline()
353 f.close()
354 except IOError:
355 return 0
356 return line[:2] == '#!' and string.find(line, 'python') >= 0
357
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000358 def close_hook(self):
359 if self.flist:
360 self.flist.close_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000361
362 def set_close_hook(self, close_hook):
363 self.close_hook = close_hook
364
365 def filename_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000366 if self.flist:
367 self.flist.filename_changed_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000368 self.saved_change_hook()
369 if self.ispythonsource(self.io.filename):
370 self.addcolorizer()
371 else:
372 self.rmcolorizer()
373
374 def addcolorizer(self):
375 if self.color:
376 return
377 ##print "Add colorizer"
378 self.per.removefilter(self.undo)
379 self.color = self.ColorDelegator()
380 self.per.insertfilter(self.color)
381 self.per.insertfilter(self.undo)
382
383 def rmcolorizer(self):
384 if not self.color:
385 return
386 ##print "Remove colorizer"
387 self.per.removefilter(self.undo)
388 self.per.removefilter(self.color)
389 self.color = None
390 self.per.insertfilter(self.undo)
391
392 def saved_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000393 short = self.short_title()
394 long = self.long_title()
395 if short and long:
396 title = short + " - " + long
397 elif short:
398 title = short
399 elif long:
400 title = long
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000401 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000402 title = "Untitled"
403 icon = short or long or title
404 if not self.get_saved():
405 title = "*%s*" % title
406 icon = "*%s" % icon
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000407 self.top.wm_title(title)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000408 self.top.wm_iconname(icon)
409
410 def get_saved(self):
411 return self.undo.get_saved()
412
413 def set_saved(self, flag):
414 self.undo.set_saved(flag)
415
416 def reset_undo(self):
417 self.undo.reset_undo()
418
419 def short_title(self):
420 filename = self.io.filename
421 if filename:
422 filename = os.path.basename(filename)
423 return filename
424
425 def long_title(self):
426 return self.io.filename or ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000427
428 def center_insert_event(self, event):
429 self.center()
430
431 def center(self, mark="insert"):
Guido van Rossum245ddc41999-01-11 14:51:32 +0000432 text = self.text
433 top, bot = self.getwindowlines()
434 lineno = self.getlineno(mark)
435 height = bot - top
436 newtop = max(1, lineno - height/2)
437 text.yview(float(newtop))
438
439 def getwindowlines(self):
440 text = self.text
441 top = self.getlineno("@0,0")
442 bot = self.getlineno("@0,65535")
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000443 if top == bot and text.winfo_height() == 1:
444 # Geometry manager hasn't run yet
Guido van Rossum245ddc41999-01-11 14:51:32 +0000445 height = int(text['height'])
446 bot = top + height - 1
447 return top, bot
448
449 def getlineno(self, mark="insert"):
450 text = self.text
451 return int(float(text.index(mark)))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000452
453 def close_event(self, event):
454 self.close()
455
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000456 def maybesave(self):
457 if self.io:
458 return self.io.maybesave()
459
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000460 def close(self):
461 self.top.wm_deiconify()
462 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000463 reply = self.maybesave()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000464 if reply != "cancel":
Guido van Rossum205afb41999-06-25 16:06:29 +0000465 self._close()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000466 return reply
467
Guido van Rossum205afb41999-06-25 16:06:29 +0000468 def _close(self):
469 WindowList.unregister_callback(self.postwindowsmenu)
470 if self.close_hook:
471 self.close_hook()
472 self.flist = None
473 colorizing = 0
474 self.unload_extensions()
475 self.io.close(); self.io = None
476 self.undo = None # XXX
477 if self.color:
478 colorizing = self.color.colorizing
479 doh = colorizing and self.top
480 self.color.close(doh) # Cancel colorization
481 self.text = None
482 self.vars = None
483 self.per.close(); self.per = None
484 if not colorizing:
485 self.top.destroy()
486
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000487 def load_extensions(self):
488 self.extensions = {}
489 self.load_standard_extensions()
490
Guido van Rossum205afb41999-06-25 16:06:29 +0000491 def unload_extensions(self):
492 for ins in self.extensions.values():
493 if hasattr(ins, "close"):
494 ins.close()
495 self.extensions = {}
496
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000497 def load_standard_extensions(self):
498 for name in self.get_standard_extension_names():
499 try:
500 self.load_extension(name)
501 except:
502 print "Failed to load extension", `name`
503 import traceback
504 traceback.print_exc()
505
506 def get_standard_extension_names(self):
507 import extend
508 return extend.standard
509
510 def load_extension(self, name):
Guido van Rossum9dd52091999-04-23 14:01:25 +0000511 mod = __import__(name, globals(), locals(), [])
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000512 cls = getattr(mod, name)
513 ins = cls(self)
514 self.extensions[name] = ins
515 kdnames = ["keydefs"]
516 if sys.platform == 'win32':
517 kdnames.append("windows_keydefs")
518 elif sys.platform == 'mac':
519 kdnames.append("mac_keydefs")
520 else:
521 kdnames.append("unix_keydefs")
522 keydefs = {}
523 for kdname in kdnames:
524 if hasattr(ins, kdname):
525 keydefs.update(getattr(ins, kdname))
526 if keydefs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000527 self.apply_bindings(keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000528 for vevent in keydefs.keys():
529 methodname = string.replace(vevent, "-", "_")
530 while methodname[:1] == '<':
531 methodname = methodname[1:]
532 while methodname[-1:] == '>':
533 methodname = methodname[:-1]
534 methodname = methodname + "_event"
535 if hasattr(ins, methodname):
536 self.text.bind(vevent, getattr(ins, methodname))
537 if hasattr(ins, "menudefs"):
Guido van Rossum07ec8961999-01-28 22:02:47 +0000538 self.fill_menus(ins.menudefs, keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000539 return ins
540
Guido van Rossum07ec8961999-01-28 22:02:47 +0000541 def apply_bindings(self, keydefs=None):
542 if keydefs is None:
543 keydefs = self.Bindings.default_keydefs
544 text = self.text
545 text.keydefs = keydefs
546 for event, keylist in keydefs.items():
547 if keylist:
548 apply(text.event_add, (event,) + tuple(keylist))
549
550 def fill_menus(self, defs=None, keydefs=None):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000551 # Fill the menus. Menus that are absent or None in
552 # self.menudict are ignored.
Guido van Rossum07ec8961999-01-28 22:02:47 +0000553 if defs is None:
554 defs = self.Bindings.menudefs
555 if keydefs is None:
556 keydefs = self.Bindings.default_keydefs
557 menudict = self.menudict
558 text = self.text
559 for mname, itemlist in defs:
560 menu = menudict.get(mname)
561 if not menu:
562 continue
563 for item in itemlist:
564 if not item:
565 menu.add_separator()
566 else:
567 label, event = item
568 checkbutton = (label[:1] == '!')
569 if checkbutton:
570 label = label[1:]
571 underline, label = prepstr(label)
572 accelerator = get_accelerator(keydefs, event)
573 def command(text=text, event=event):
574 text.event_generate(event)
575 if checkbutton:
576 var = self.getrawvar(event, BooleanVar)
577 menu.add_checkbutton(label=label, underline=underline,
578 command=command, accelerator=accelerator,
579 variable=var)
580 else:
581 menu.add_command(label=label, underline=underline,
582 command=command, accelerator=accelerator)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000583
Guido van Rossum07ec8961999-01-28 22:02:47 +0000584 def getvar(self, name):
585 var = self.getrawvar(name)
586 if var:
587 return var.get()
Guido van Rossumdef2c961999-05-21 04:38:27 +0000588
Guido van Rossum07ec8961999-01-28 22:02:47 +0000589 def setvar(self, name, value, vartype=None):
590 var = self.getrawvar(name, vartype)
591 if var:
592 var.set(value)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000593
Guido van Rossum07ec8961999-01-28 22:02:47 +0000594 def getrawvar(self, name, vartype=None):
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000595 var = self.vars.get(name)
596 if not var and vartype:
597 self.vars[name] = var = vartype(self.text)
598 return var
Guido van Rossum07ec8961999-01-28 22:02:47 +0000599
Guido van Rossumf4a15081999-06-03 14:32:16 +0000600 # Tk implementations of "virtual text methods" -- each platform
601 # reusing IDLE's support code needs to define these for its GUI's
602 # flavor of widget.
603
604 # Is character at text_index in a Python string? Return 0 for
Guido van Rossum85a36a51999-06-10 17:43:17 +0000605 # "guaranteed no", true for anything else. This info is expensive
606 # to compute ab initio, but is probably already known by the
607 # platform's colorizer.
Guido van Rossumf4a15081999-06-03 14:32:16 +0000608
609 def is_char_in_string(self, text_index):
610 if self.color:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000611 # Return true iff colorizer hasn't (re)gotten this far
612 # yet, or the character is tagged as being in a string
Guido van Rossumf4a15081999-06-03 14:32:16 +0000613 return self.text.tag_prevrange("TODO", text_index) or \
614 "STRING" in self.text.tag_names(text_index)
615 else:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000616 # The colorizer is missing: assume the worst
Guido van Rossumf4a15081999-06-03 14:32:16 +0000617 return 1
618
Guido van Rossum85a36a51999-06-10 17:43:17 +0000619 # If a selection is defined in the text widget, return (start,
620 # end) as Tkinter text indices, otherwise return (None, None)
Guido van Rossum13205601999-06-11 15:03:00 +0000621 def get_selection_indices(self):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000622 try:
623 first = self.text.index("sel.first")
624 last = self.text.index("sel.last")
625 return first, last
626 except TclError:
627 return None, None
628
Guido van Rossum13205601999-06-11 15:03:00 +0000629 # Return the text widget's current view of what a tab stop means
630 # (equivalent width in spaces).
631
632 def get_tabwidth(self):
633 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
634 return int(current)
635
636 # Set the text widget's current view of what a tab stop means.
637
638 def set_tabwidth(self, newtabwidth):
639 text = self.text
640 if self.get_tabwidth() != newtabwidth:
641 pixels = text.tk.call("font", "measure", text["font"],
642 "-displayof", text.master,
643 "n" * newtabwith)
644 text.configure(tabs=pixels)
645
Guido van Rossum07ec8961999-01-28 22:02:47 +0000646def prepstr(s):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000647 # Helper to extract the underscore from a string, e.g.
648 # prepstr("Co_py") returns (2, "Copy").
Guido van Rossum07ec8961999-01-28 22:02:47 +0000649 i = string.find(s, '_')
650 if i >= 0:
651 s = s[:i] + s[i+1:]
652 return i, s
653
654
655keynames = {
656 'bracketleft': '[',
657 'bracketright': ']',
658 'slash': '/',
659}
660
661def get_accelerator(keydefs, event):
662 keylist = keydefs.get(event)
663 if not keylist:
664 return ""
665 s = keylist[0]
666 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
667 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
668 s = re.sub("Key-", "", s)
669 s = re.sub("Control-", "Ctrl-", s)
670 s = re.sub("-", "+", s)
671 s = re.sub("><", " ", s)
672 s = re.sub("<", "", s)
673 s = re.sub(">", "", s)
674 return s
675
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000676
677def fixwordbreaks(root):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000678 # Make sure that Tk's double-click and next/previous word
679 # operations use our definition of a word (i.e. an identifier)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000680 tk = root.tk
681 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
682 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
683 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
684
685
686def test():
687 root = Tk()
688 fixwordbreaks(root)
689 root.withdraw()
690 if sys.argv[1:]:
691 filename = sys.argv[1]
692 else:
693 filename = None
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000694 edit = EditorWindow(root=root, filename=filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000695 edit.set_close_hook(root.quit)
696 root.mainloop()
697 root.destroy()
698
699if __name__ == '__main__':
700 test()