| #! /usr/bin/env python | |
| # A Python program implementing rmt, an application for remotely | |
| # controlling other Tk applications. | |
| # Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276. | |
| # Note that because of forward references in the original, we | |
| # sometimes delay bindings until after the corresponding procedure is | |
| # defined. We also introduce names for some unnamed code blocks in | |
| # the original because of restrictions on lambda forms in Python. | |
| # XXX This should be written in a more Python-like style!!! | |
| from Tkinter import * | |
| import sys | |
| # 1. Create basic application structure: menu bar on top of | |
| # text widget, scrollbar on right. | |
| root = Tk() | |
| tk = root.tk | |
| mBar = Frame(root, relief=RAISED, borderwidth=2) | |
| mBar.pack(fill=X) | |
| f = Frame(root) | |
| f.pack(expand=1, fill=BOTH) | |
| s = Scrollbar(f, relief=FLAT) | |
| s.pack(side=RIGHT, fill=Y) | |
| t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1) | |
| t.pack(side=LEFT, fill=BOTH, expand=1) | |
| t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*') | |
| s['command'] = t.yview | |
| root.title('Tk Remote Controller') | |
| root.iconname('Tk Remote') | |
| # 2. Create menu button and menus. | |
| file = Menubutton(mBar, text='File', underline=0) | |
| file.pack(side=LEFT) | |
| file_m = Menu(file) | |
| file['menu'] = file_m | |
| file_m_apps = Menu(file_m, tearoff=0) | |
| file_m.add_cascade(label='Select Application', underline=0, | |
| menu=file_m_apps) | |
| file_m.add_command(label='Quit', underline=0, command=sys.exit) | |
| # 3. Create bindings for text widget to allow commands to be | |
| # entered and information to be selected. New characters | |
| # can only be added at the end of the text (can't ever move | |
| # insertion point). | |
| def single1(e): | |
| x = e.x | |
| y = e.y | |
| t.setvar('tk_priv(selectMode)', 'char') | |
| t.mark_set('anchor', At(x, y)) | |
| # Should focus W | |
| t.bind('<1>', single1) | |
| def double1(e): | |
| x = e.x | |
| y = e.y | |
| t.setvar('tk_priv(selectMode)', 'word') | |
| t.tk_textSelectTo(At(x, y)) | |
| t.bind('<Double-1>', double1) | |
| def triple1(e): | |
| x = e.x | |
| y = e.y | |
| t.setvar('tk_priv(selectMode)', 'line') | |
| t.tk_textSelectTo(At(x, y)) | |
| t.bind('<Triple-1>', triple1) | |
| def returnkey(e): | |
| t.insert(AtInsert(), '\n') | |
| invoke() | |
| t.bind('<Return>', returnkey) | |
| def controlv(e): | |
| t.insert(AtInsert(), t.selection_get()) | |
| t.yview_pickplace(AtInsert()) | |
| if t.index(AtInsert())[-2:] == '.0': | |
| invoke() | |
| t.bind('<Control-v>', controlv) | |
| # 4. Procedure to backspace over one character, as long as | |
| # the character isn't part of the prompt. | |
| def backspace(e): | |
| if t.index('promptEnd') != t.index('insert - 1 char'): | |
| t.delete('insert - 1 char', AtInsert()) | |
| t.yview_pickplace(AtInsert()) | |
| t.bind('<BackSpace>', backspace) | |
| t.bind('<Control-h>', backspace) | |
| t.bind('<Delete>', backspace) | |
| # 5. Procedure that's invoked when return is typed: if | |
| # there's not yet a complete command (e.g. braces are open) | |
| # then do nothing. Otherwise, execute command (locally or | |
| # remotely), output the result or error message, and issue | |
| # a new prompt. | |
| def invoke(): | |
| cmd = t.get('promptEnd + 1 char', AtInsert()) | |
| if t.getboolean(tk.call('info', 'complete', cmd)): # XXX | |
| if app == root.winfo_name(): | |
| msg = tk.call('eval', cmd) # XXX | |
| else: | |
| msg = t.send(app, cmd) | |
| if msg: | |
| t.insert(AtInsert(), msg + '\n') | |
| prompt() | |
| t.yview_pickplace(AtInsert()) | |
| def prompt(): | |
| t.insert(AtInsert(), app + ': ') | |
| t.mark_set('promptEnd', 'insert - 1 char') | |
| t.tag_add('bold', 'insert linestart', 'promptEnd') | |
| # 6. Procedure to select a new application. Also changes | |
| # the prompt on the current command line to reflect the new | |
| # name. | |
| def newApp(appName): | |
| global app | |
| app = appName | |
| t.delete('promptEnd linestart', 'promptEnd') | |
| t.insert('promptEnd', appName + ':') | |
| t.tag_add('bold', 'promptEnd linestart', 'promptEnd') | |
| def fillAppsMenu(): | |
| file_m_apps.add('command') | |
| file_m_apps.delete(0, 'last') | |
| names = root.winfo_interps() | |
| names = list(names) | |
| names.sort() | |
| for name in names: | |
| try: | |
| root.send(name, 'winfo name .') | |
| except TclError: | |
| # Inoperative window -- ignore it | |
| pass | |
| else: | |
| file_m_apps.add_command( | |
| label=name, | |
| command=lambda name=name: newApp(name)) | |
| file_m_apps['postcommand'] = fillAppsMenu | |
| mBar.tk_menuBar(file) | |
| # 7. Miscellaneous initialization. | |
| app = root.winfo_name() | |
| prompt() | |
| t.focus() | |
| root.mainloop() |