blob: be074b0d13ad295ee586dc1b1c56f945b2210762 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
3import string
4import re
5import imp
6from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009
10import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000011import idlever
12import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000013import SearchDialog
14import GrepDialog
15import ReplaceDialog
Steven M. Gavadc72f482002-01-03 11:51:07 +000016#from IdleConf import idleconf
17from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000018import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000019
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
23# File menu
24
25#$ event <<open-module>>
26#$ win <Alt-m>
27#$ unix <Control-x><Control-m>
28
29#$ event <<open-class-browser>>
30#$ win <Alt-c>
31#$ unix <Control-x><Control-b>
32
33#$ event <<open-path-browser>>
34
35#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000036
David Scherer7aced172000-08-15 01:13:23 +000037#$ unix <Control-x><Control-0>
38#$ unix <Control-x><Key-0>
39#$ win <Alt-F4>
40
41# Edit menu
42
43#$ event <<Copy>>
44#$ win <Control-c>
45#$ unix <Alt-w>
46
47#$ event <<Cut>>
48#$ win <Control-x>
49#$ unix <Control-w>
50
51#$ event <<Paste>>
52#$ win <Control-v>
53#$ unix <Control-y>
54
55#$ event <<select-all>>
56#$ win <Alt-a>
57#$ unix <Alt-a>
58
59# Help menu
60
61#$ event <<help>>
62#$ win <F1>
63#$ unix <F1>
64
65#$ event <<about-idle>>
66
67# Events without menu entries
68
69#$ event <<remove-selection>>
70#$ win <Escape>
71
72#$ event <<center-insert>>
73#$ win <Control-l>
74#$ unix <Control-l>
75
76#$ event <<do-nothing>>
77#$ unix <Control-x>
78
David Scherer7aced172000-08-15 01:13:23 +000079class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000080 from Percolator import Percolator
81 from ColorDelegator import ColorDelegator
82 from UndoDelegator import UndoDelegator
83 from IOBinding import IOBinding
84 import Bindings
85 from Tkinter import Toplevel
86 from MultiStatusBar import MultiStatusBar
87
David Scherer7aced172000-08-15 01:13:23 +000088 vars = {}
89
90 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000091 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000092 self.flist = flist
93 root = root or flist.root
94 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000095 self.menubar = Menu(root)
96 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000097 if flist:
98 self.vars = flist.vars
99 #self.top.instanceDict makes flist.inversedict avalable to
100 #configDialog.py so it can access all EditorWindow instaces
101 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +0000102 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
103 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000104 self.vbar = vbar = Scrollbar(top, name='vbar')
105 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000106 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
107 foreground=idleConf.GetHighlight(currentTheme,
108 'normal',fgBg='fg'),
109 background=idleConf.GetHighlight(currentTheme,
110 'normal',fgBg='bg'),
111 highlightcolor=idleConf.GetHighlight(currentTheme,
112 'hilite',fgBg='fg'),
113 highlightbackground=idleConf.GetHighlight(currentTheme,
114 'hilite',fgBg='bg'),
115 insertbackground=idleConf.GetHighlight(currentTheme,
116 'cursor',fgBg='fg'),
117 width=idleConf.GetOption('main','EditorWindow','width'),
118 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000119
120 self.createmenubar()
121 self.apply_bindings()
122
123 self.top.protocol("WM_DELETE_WINDOW", self.close)
124 self.top.bind("<<close-window>>", self.close_event)
125 text.bind("<<center-insert>>", self.center_insert_event)
126 text.bind("<<help>>", self.help_dialog)
127 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000128 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000129 text.bind("<<python-docs>>", self.python_docs)
130 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000131 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000132 text.bind("<<open-module>>", self.open_module)
133 text.bind("<<do-nothing>>", lambda event: "break")
134 text.bind("<<select-all>>", self.select_all)
135 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000136 text.bind("<<find>>", self.find_event)
137 text.bind("<<find-again>>", self.find_again_event)
138 text.bind("<<find-in-files>>", self.find_in_files_event)
139 text.bind("<<find-selection>>", self.find_selection_event)
140 text.bind("<<replace>>", self.replace_event)
141 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<3>", self.right_menu_event)
143 if flist:
144 flist.inversedict[self] = key
145 if key:
146 flist.dict[key] = self
147 text.bind("<<open-new-window>>", self.flist.new_callback)
148 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
149 text.bind("<<open-class-browser>>", self.open_class_browser)
150 text.bind("<<open-path-browser>>", self.open_path_browser)
151
Steven M. Gava898a3652001-10-07 11:10:44 +0000152 self.set_status_bar()
153
David Scherer7aced172000-08-15 01:13:23 +0000154 vbar['command'] = text.yview
155 vbar.pack(side=RIGHT, fill=Y)
156
157 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000158 fontWeight='normal'
159 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
160 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000161 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000162 idleConf.GetOption('main','EditorWindow','font-size'),
163 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000164 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
165 text.pack(side=TOP, fill=BOTH, expand=1)
166 text.focus_set()
167
168 self.per = per = self.Percolator(text)
169 if self.ispythonsource(filename):
170 self.color = color = self.ColorDelegator(); per.insertfilter(color)
171 ##print "Initial colorizer"
172 else:
173 ##print "No initial colorizer"
174 self.color = None
175 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
176 self.io = io = self.IOBinding(self)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000177 #create the Recent Files submenu
178 self.menuRecentFiles=Menu(self.menubar)
179 self.menudict['file'].insert_cascade(3,label='Recent Files',
180 underline=0,menu=self.menuRecentFiles)
181 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000182
183 text.undo_block_start = undo.undo_block_start
184 text.undo_block_stop = undo.undo_block_stop
185 undo.set_saved_change_hook(self.saved_change_hook)
186 io.set_filename_change_hook(self.filename_change_hook)
187
188 if filename:
189 if os.path.exists(filename):
190 io.loadfile(filename)
191 else:
192 io.set_filename(filename)
193
194 self.saved_change_hook()
195
196 self.load_extensions()
197
198 menu = self.menudict.get('windows')
199 if menu:
200 end = menu.index("end")
201 if end is None:
202 end = -1
203 if end >= 0:
204 menu.add_separator()
205 end = end + 1
206 self.wmenu_end = end
207 WindowList.register_callback(self.postwindowsmenu)
208
209 # Some abstractions so IDLE extensions are cross-IDE
210 self.askyesno = tkMessageBox.askyesno
211 self.askinteger = tkSimpleDialog.askinteger
212 self.showerror = tkMessageBox.showerror
213
214 if self.extensions.has_key('AutoIndent'):
215 self.extensions['AutoIndent'].set_indentation_params(
216 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000217
David Scherer7aced172000-08-15 01:13:23 +0000218 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000219 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000220 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
221 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
222 self.status_bar.pack(side=BOTTOM, fill=X)
223 self.text.bind('<KeyRelease>', self.set_line_and_column)
224 self.text.bind('<ButtonRelease>', self.set_line_and_column)
225 self.text.after_idle(self.set_line_and_column)
226
227 def set_line_and_column(self, event=None):
228 line, column = string.split(self.text.index(INSERT), '.')
229 self.status_bar.set_label('column', 'Col: %s' % column)
230 self.status_bar.set_label('line', 'Ln: %s' % line)
231
232 def wakeup(self):
233 if self.top.wm_state() == "iconic":
234 self.top.wm_deiconify()
235 else:
236 self.top.tkraise()
237 self.text.focus_set()
238
239 menu_specs = [
240 ("file", "_File"),
241 ("edit", "_Edit"),
242 ("format", "F_ormat"),
243 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000244 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000245 ("windows", "_Windows"),
246 ("help", "_Help"),
247 ]
248
249 def createmenubar(self):
250 mbar = self.menubar
251 self.menudict = menudict = {}
252 for name, label in self.menu_specs:
253 underline, label = prepstr(label)
254 menudict[name] = menu = Menu(mbar, name=name)
255 mbar.add_cascade(label=label, menu=menu, underline=underline)
256 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000257 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000258 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000259
260 def postwindowsmenu(self):
261 # Only called when Windows menu exists
262 # XXX Actually, this Just-In-Time updating interferes badly
263 # XXX with the tear-off feature. It would be better to update
264 # XXX all Windows menus whenever the list of windows changes.
265 menu = self.menudict['windows']
266 end = menu.index("end")
267 if end is None:
268 end = -1
269 if end > self.wmenu_end:
270 menu.delete(self.wmenu_end+1, end)
271 WindowList.add_windows_to_menu(menu)
272
273 rmenu = None
274
275 def right_menu_event(self, event):
276 self.text.tag_remove("sel", "1.0", "end")
277 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278 if not self.rmenu:
279 self.make_rmenu()
280 rmenu = self.rmenu
281 self.event = event
282 iswin = sys.platform[:3] == 'win'
283 if iswin:
284 self.text.config(cursor="arrow")
285 rmenu.tk_popup(event.x_root, event.y_root)
286 if iswin:
287 self.text.config(cursor="ibeam")
288
289 rmenu_specs = [
290 # ("Label", "<<virtual-event>>"), ...
291 ("Close", "<<close-window>>"), # Example
292 ]
293
294 def make_rmenu(self):
295 rmenu = Menu(self.text, tearoff=0)
296 for label, eventname in self.rmenu_specs:
297 def command(text=self.text, eventname=eventname):
298 text.event_generate(eventname)
299 rmenu.add_command(label=label, command=command)
300 self.rmenu = rmenu
301
302 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000303 aboutDialog.AboutDialog(self.top,'About IDLEfork')
304
Steven M. Gava3b55a892001-11-21 05:56:26 +0000305 def config_dialog(self, event=None):
306 configDialog.ConfigDialog(self.top,'Settings')
307
David Scherer7aced172000-08-15 01:13:23 +0000308 def good_advice(self, event=None):
309 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
310
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000311 def view_readme(self, event=None):
312 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
313 textView.TextViewer(self.top,'IDLEfork - README',fn)
314
David Scherer7aced172000-08-15 01:13:23 +0000315 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000316 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
317 textView.TextViewer(self.top,'Help',fn)
318
David Scherer7aced172000-08-15 01:13:23 +0000319 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000320 if sys.platform[:3] == "win":
321 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000322 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000323 fn = os.path.normpath(fn)
324 if os.path.isfile(fn):
325 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000326 else:
327 fn = os.path.dirname(__file__)
328 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
329 fn = os.path.normpath(fn)
330 if os.path.isfile(fn):
331 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000332 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000333 def python_docs(self, event=None):
334 os.startfile(self.help_url)
335 else:
336 def python_docs(self, event=None):
337 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000338
339 def display_docs(self, url):
340 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000341
342 def select_all(self, event=None):
343 self.text.tag_add("sel", "1.0", "end-1c")
344 self.text.mark_set("insert", "1.0")
345 self.text.see("insert")
346 return "break"
347
348 def remove_selection(self, event=None):
349 self.text.tag_remove("sel", "1.0", "end")
350 self.text.see("insert")
351
Steven M. Gavac5976402002-01-04 03:06:08 +0000352 def find_event(self, event):
353 SearchDialog.find(self.text)
354 return "break"
355
356 def find_again_event(self, event):
357 SearchDialog.find_again(self.text)
358 return "break"
359
360 def find_selection_event(self, event):
361 SearchDialog.find_selection(self.text)
362 return "break"
363
364 def find_in_files_event(self, event):
365 GrepDialog.grep(self.text, self.io, self.flist)
366 return "break"
367
368 def replace_event(self, event):
369 ReplaceDialog.replace(self.text)
370 return "break"
371
372 def goto_line_event(self, event):
373 text = self.text
374 lineno = tkSimpleDialog.askinteger("Goto",
375 "Go to line number:",parent=text)
376 if lineno is None:
377 return "break"
378 if lineno <= 0:
379 text.bell()
380 return "break"
381 text.mark_set("insert", "%d.0" % lineno)
382 text.see("insert")
383
David Scherer7aced172000-08-15 01:13:23 +0000384 def open_module(self, event=None):
385 # XXX Shouldn't this be in IOBinding or in FileList?
386 try:
387 name = self.text.get("sel.first", "sel.last")
388 except TclError:
389 name = ""
390 else:
391 name = string.strip(name)
392 if not name:
393 name = tkSimpleDialog.askstring("Module",
394 "Enter the name of a Python module\n"
395 "to search on sys.path and open:",
396 parent=self.text)
397 if name:
398 name = string.strip(name)
399 if not name:
400 return
401 # XXX Ought to support package syntax
402 # XXX Ought to insert current file's directory in front of path
403 try:
404 (f, file, (suffix, mode, type)) = imp.find_module(name)
405 except (NameError, ImportError), msg:
406 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
407 return
408 if type != imp.PY_SOURCE:
409 tkMessageBox.showerror("Unsupported type",
410 "%s is not a source module" % name, parent=self.text)
411 return
412 if f:
413 f.close()
414 if self.flist:
415 self.flist.open(file)
416 else:
417 self.io.loadfile(file)
418
419 def open_class_browser(self, event=None):
420 filename = self.io.filename
421 if not filename:
422 tkMessageBox.showerror(
423 "No filename",
424 "This buffer has no associated filename",
425 master=self.text)
426 self.text.focus_set()
427 return None
428 head, tail = os.path.split(filename)
429 base, ext = os.path.splitext(tail)
430 import ClassBrowser
431 ClassBrowser.ClassBrowser(self.flist, base, [head])
432
433 def open_path_browser(self, event=None):
434 import PathBrowser
435 PathBrowser.PathBrowser(self.flist)
436
437 def gotoline(self, lineno):
438 if lineno is not None and lineno > 0:
439 self.text.mark_set("insert", "%d.0" % lineno)
440 self.text.tag_remove("sel", "1.0", "end")
441 self.text.tag_add("sel", "insert", "insert +1l")
442 self.center()
443
444 def ispythonsource(self, filename):
445 if not filename:
446 return 1
447 base, ext = os.path.splitext(os.path.basename(filename))
448 if os.path.normcase(ext) in (".py", ".pyw"):
449 return 1
450 try:
451 f = open(filename)
452 line = f.readline()
453 f.close()
454 except IOError:
455 return 0
456 return line[:2] == '#!' and string.find(line, 'python') >= 0
457
458 def close_hook(self):
459 if self.flist:
460 self.flist.close_edit(self)
461
462 def set_close_hook(self, close_hook):
463 self.close_hook = close_hook
464
465 def filename_change_hook(self):
466 if self.flist:
467 self.flist.filename_changed_edit(self)
468 self.saved_change_hook()
469 if self.ispythonsource(self.io.filename):
470 self.addcolorizer()
471 else:
472 self.rmcolorizer()
473
474 def addcolorizer(self):
475 if self.color:
476 return
477 ##print "Add colorizer"
478 self.per.removefilter(self.undo)
479 self.color = self.ColorDelegator()
480 self.per.insertfilter(self.color)
481 self.per.insertfilter(self.undo)
482
483 def rmcolorizer(self):
484 if not self.color:
485 return
486 ##print "Remove colorizer"
487 self.per.removefilter(self.undo)
488 self.per.removefilter(self.color)
489 self.color = None
490 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000491
492 def ResetColorizer(self):
493 #this function is called from configDialog.py
494 #to update the colour theme if it is changed
495 if self.color:
496 self.color = self.ColorDelegator()
497 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000498
Steven M. Gavab1585412002-03-12 00:21:56 +0000499 def ResetFont(self):
500 #this function is called from configDialog.py
501 #to update the text widgets' font if it is changed
502 fontWeight='normal'
503 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
504 fontWeight='bold'
505 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
506 idleConf.GetOption('main','EditorWindow','font-size'),
507 fontWeight))
508
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000509 def ResetKeybindings(self):
510 #this function is called from configDialog.py
511 #to update the keybindings if they are changed
512 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
513 keydefs = self.Bindings.default_keydefs
514 for event, keylist in keydefs.items():
515 self.text.event_delete(event)
516 self.apply_bindings()
517 #update menu accelerators
518 menuEventDict={}
519 for menu in self.Bindings.menudefs:
520 menuEventDict[menu[0]]={}
521 for item in menu[1]:
522 if item:
523 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
524 for menubarItem in self.menudict.keys():
525 menu=self.menudict[menubarItem]
526 end=menu.index(END)+1
527 for index in range(0,end):
528 if menu.type(index)=='command':
529 accel=menu.entrycget(index,'accelerator')
530 if accel:
531 itemName=menu.entrycget(index,'label')
532 event=''
533 if menuEventDict.has_key(menubarItem):
534 if menuEventDict[menubarItem].has_key(itemName):
535 event=menuEventDict[menubarItem][itemName]
536 if event:
537 #print 'accel was:',accel
538 accel=get_accelerator(keydefs, event)
539 menu.entryconfig(index,accelerator=accel)
540 #print 'accel now:',accel,'\n'
541
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000542 def ResetExtraHelpMenu(self):
543 #load or update the Extra Help menu if required
544 menuList=idleConf.GetAllExtraHelpSourcesList()
545 helpMenu=self.menudict['help']
546 cascadeIndex=helpMenu.index(END)-1
547 if menuList:
548 if not hasattr(self,'menuExtraHelp'):
549 self.menuExtraHelp=Menu(self.menubar)
550 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
551 underline=1,menu=self.menuExtraHelp)
552 self.menuExtraHelp.delete(1,END)
553 for menuItem in menuList:
554 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000555 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000556 else: #no extra help items
557 if hasattr(self,'menuExtraHelp'):
558 helpMenu.delete(cascadeIndex-1)
559 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000560
561 def __DisplayExtraHelpCallback(self,helpFile):
562 def DisplayExtraHelp(helpFile=helpFile):
563 self.display_docs(helpFile)
564 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000565
Steven M. Gava1d46e402002-03-27 08:40:46 +0000566 def UpdateRecentFilesList(self,newFile=None):
567 #load or update the recent files list, and menu if required
568 rfList=[]
569 if os.path.exists(self.recentFilesPath):
570 RFfile=open(self.recentFilesPath,'r')
571 try:
572 rfList=RFfile.readlines()
573 finally:
574 RFfile.close()
575 if newFile:
576 newFile=os.path.abspath(newFile)+'\n'
577 if newFile in rfList:
578 rfList.remove(newFile)
579 rfList.insert(0,newFile)
580 rfList=self.__CleanRecentFiles(rfList)
581 print self.top.instanceDict
582 print self
583 if rfList:
584 for instance in self.top.instanceDict.keys():
585 instance.menuRecentFiles.delete(1,END)
586 for file in rfList:
587 fileName=file[0:-1]
588 instance.menuRecentFiles.add_command(label=fileName,
589 command=instance.__RecentFileCallback(fileName))
590
591 def __CleanRecentFiles(self,rfList):
592 origRfList=rfList[:]
593 count=0
594 nonFiles=[]
595 for path in rfList:
596 if not os.path.exists(path[0:-1]):
597 nonFiles.append(count)
598 count=count+1
599 if nonFiles:
600 nonFiles.reverse()
601 for index in nonFiles:
602 del(rfList[index])
603 if len(rfList)>19:
604 rfList=rfList[0:19]
605 #if rfList != origRfList:
606 RFfile=open(self.recentFilesPath,'w')
607 try:
608 RFfile.writelines(rfList)
609 finally:
610 RFfile.close()
611 return rfList
612
613 def __RecentFileCallback(self,fileName):
614 def OpenRecentFile(fileName=fileName):
615 self.io.open(editFile=fileName)
616 return OpenRecentFile
617
David Scherer7aced172000-08-15 01:13:23 +0000618 def saved_change_hook(self):
619 short = self.short_title()
620 long = self.long_title()
621 if short and long:
622 title = short + " - " + long
623 elif short:
624 title = short
625 elif long:
626 title = long
627 else:
628 title = "Untitled"
629 icon = short or long or title
630 if not self.get_saved():
631 title = "*%s*" % title
632 icon = "*%s" % icon
633 self.top.wm_title(title)
634 self.top.wm_iconname(icon)
635
636 def get_saved(self):
637 return self.undo.get_saved()
638
639 def set_saved(self, flag):
640 self.undo.set_saved(flag)
641
642 def reset_undo(self):
643 self.undo.reset_undo()
644
645 def short_title(self):
646 filename = self.io.filename
647 if filename:
648 filename = os.path.basename(filename)
649 return filename
650
651 def long_title(self):
652 return self.io.filename or ""
653
654 def center_insert_event(self, event):
655 self.center()
656
657 def center(self, mark="insert"):
658 text = self.text
659 top, bot = self.getwindowlines()
660 lineno = self.getlineno(mark)
661 height = bot - top
662 newtop = max(1, lineno - height/2)
663 text.yview(float(newtop))
664
665 def getwindowlines(self):
666 text = self.text
667 top = self.getlineno("@0,0")
668 bot = self.getlineno("@0,65535")
669 if top == bot and text.winfo_height() == 1:
670 # Geometry manager hasn't run yet
671 height = int(text['height'])
672 bot = top + height - 1
673 return top, bot
674
675 def getlineno(self, mark="insert"):
676 text = self.text
677 return int(float(text.index(mark)))
678
679 def close_event(self, event):
680 self.close()
681
682 def maybesave(self):
683 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000684 if not self.get_saved():
685 if self.top.state()!='normal':
686 self.top.deiconify()
687 self.top.lower()
688 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000689 return self.io.maybesave()
690
691 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000692 reply = self.maybesave()
693 if reply != "cancel":
694 self._close()
695 return reply
696
697 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000698 print self.io.filename
699 if self.io.filename:
700 self.UpdateRecentFilesList(newFile=self.io.filename)
701
David Scherer7aced172000-08-15 01:13:23 +0000702 WindowList.unregister_callback(self.postwindowsmenu)
703 if self.close_hook:
704 self.close_hook()
705 self.flist = None
706 colorizing = 0
707 self.unload_extensions()
708 self.io.close(); self.io = None
709 self.undo = None # XXX
710 if self.color:
711 colorizing = self.color.colorizing
712 doh = colorizing and self.top
713 self.color.close(doh) # Cancel colorization
714 self.text = None
715 self.vars = None
716 self.per.close(); self.per = None
717 if not colorizing:
718 self.top.destroy()
719
720 def load_extensions(self):
721 self.extensions = {}
722 self.load_standard_extensions()
723
724 def unload_extensions(self):
725 for ins in self.extensions.values():
726 if hasattr(ins, "close"):
727 ins.close()
728 self.extensions = {}
729
730 def load_standard_extensions(self):
731 for name in self.get_standard_extension_names():
732 try:
733 self.load_extension(name)
734 except:
735 print "Failed to load extension", `name`
736 import traceback
737 traceback.print_exc()
738
739 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000740 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000741
742 def load_extension(self, name):
743 mod = __import__(name, globals(), locals(), [])
744 cls = getattr(mod, name)
745 ins = cls(self)
746 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000747 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000748 if keydefs:
749 self.apply_bindings(keydefs)
750 for vevent in keydefs.keys():
751 methodname = string.replace(vevent, "-", "_")
752 while methodname[:1] == '<':
753 methodname = methodname[1:]
754 while methodname[-1:] == '>':
755 methodname = methodname[:-1]
756 methodname = methodname + "_event"
757 if hasattr(ins, methodname):
758 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000759
David Scherer7aced172000-08-15 01:13:23 +0000760 if hasattr(ins, "menudefs"):
761 self.fill_menus(ins.menudefs, keydefs)
762 return ins
763
764 def apply_bindings(self, keydefs=None):
765 if keydefs is None:
766 keydefs = self.Bindings.default_keydefs
767 text = self.text
768 text.keydefs = keydefs
769 for event, keylist in keydefs.items():
770 if keylist:
771 apply(text.event_add, (event,) + tuple(keylist))
772
773 def fill_menus(self, defs=None, keydefs=None):
774 # Fill the menus. Menus that are absent or None in
775 # self.menudict are ignored.
776 if defs is None:
777 defs = self.Bindings.menudefs
778 if keydefs is None:
779 keydefs = self.Bindings.default_keydefs
780 menudict = self.menudict
781 text = self.text
782 for mname, itemlist in defs:
783 menu = menudict.get(mname)
784 if not menu:
785 continue
786 for item in itemlist:
787 if not item:
788 menu.add_separator()
789 else:
790 label, event = item
791 checkbutton = (label[:1] == '!')
792 if checkbutton:
793 label = label[1:]
794 underline, label = prepstr(label)
795 accelerator = get_accelerator(keydefs, event)
796 def command(text=text, event=event):
797 text.event_generate(event)
798 if checkbutton:
799 var = self.getrawvar(event, BooleanVar)
800 menu.add_checkbutton(label=label, underline=underline,
801 command=command, accelerator=accelerator,
802 variable=var)
803 else:
804 menu.add_command(label=label, underline=underline,
805 command=command, accelerator=accelerator)
806
807 def getvar(self, name):
808 var = self.getrawvar(name)
809 if var:
810 return var.get()
811
812 def setvar(self, name, value, vartype=None):
813 var = self.getrawvar(name, vartype)
814 if var:
815 var.set(value)
816
817 def getrawvar(self, name, vartype=None):
818 var = self.vars.get(name)
819 if not var and vartype:
820 self.vars[name] = var = vartype(self.text)
821 return var
822
823 # Tk implementations of "virtual text methods" -- each platform
824 # reusing IDLE's support code needs to define these for its GUI's
825 # flavor of widget.
826
827 # Is character at text_index in a Python string? Return 0 for
828 # "guaranteed no", true for anything else. This info is expensive
829 # to compute ab initio, but is probably already known by the
830 # platform's colorizer.
831
832 def is_char_in_string(self, text_index):
833 if self.color:
834 # Return true iff colorizer hasn't (re)gotten this far
835 # yet, or the character is tagged as being in a string
836 return self.text.tag_prevrange("TODO", text_index) or \
837 "STRING" in self.text.tag_names(text_index)
838 else:
839 # The colorizer is missing: assume the worst
840 return 1
841
842 # If a selection is defined in the text widget, return (start,
843 # end) as Tkinter text indices, otherwise return (None, None)
844 def get_selection_indices(self):
845 try:
846 first = self.text.index("sel.first")
847 last = self.text.index("sel.last")
848 return first, last
849 except TclError:
850 return None, None
851
852 # Return the text widget's current view of what a tab stop means
853 # (equivalent width in spaces).
854
855 def get_tabwidth(self):
856 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
857 return int(current)
858
859 # Set the text widget's current view of what a tab stop means.
860
861 def set_tabwidth(self, newtabwidth):
862 text = self.text
863 if self.get_tabwidth() != newtabwidth:
864 pixels = text.tk.call("font", "measure", text["font"],
865 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000866 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000867 text.configure(tabs=pixels)
868
869def prepstr(s):
870 # Helper to extract the underscore from a string, e.g.
871 # prepstr("Co_py") returns (2, "Copy").
872 i = string.find(s, '_')
873 if i >= 0:
874 s = s[:i] + s[i+1:]
875 return i, s
876
877
878keynames = {
879 'bracketleft': '[',
880 'bracketright': ']',
881 'slash': '/',
882}
883
884def get_accelerator(keydefs, event):
885 keylist = keydefs.get(event)
886 if not keylist:
887 return ""
888 s = keylist[0]
889 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
890 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
891 s = re.sub("Key-", "", s)
892 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
893 s = re.sub("Control-", "Ctrl-", s)
894 s = re.sub("-", "+", s)
895 s = re.sub("><", " ", s)
896 s = re.sub("<", "", s)
897 s = re.sub(">", "", s)
898 return s
899
900
901def fixwordbreaks(root):
902 # Make sure that Tk's double-click and next/previous word
903 # operations use our definition of a word (i.e. an identifier)
904 tk = root.tk
905 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
906 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
907 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
908
909
910def test():
911 root = Tk()
912 fixwordbreaks(root)
913 root.withdraw()
914 if sys.argv[1:]:
915 filename = sys.argv[1]
916 else:
917 filename = None
918 edit = EditorWindow(root=root, filename=filename)
919 edit.set_close_hook(root.quit)
920 root.mainloop()
921 root.destroy()
922
923if __name__ == '__main__':
924 test()