1#! /usr/bin/env python 2 3# A Python program implementing rmt, an application for remotely 4# controlling other Tk applications. 5# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276. 6 7# Note that because of forward references in the original, we 8# sometimes delay bindings until after the corresponding procedure is 9# defined. We also introduce names for some unnamed code blocks in 10# the original because of restrictions on lambda forms in Python. 11 12# XXX This should be written in a more Python-like style!!! 13 14from Tkinter import * 15import sys 16 17# 1. Create basic application structure: menu bar on top of 18# text widget, scrollbar on right. 19 20root = Tk() 21tk = root.tk 22mBar = Frame(root, relief=RAISED, borderwidth=2) 23mBar.pack(fill=X) 24 25f = Frame(root) 26f.pack(expand=1, fill=BOTH) 27s = Scrollbar(f, relief=FLAT) 28s.pack(side=RIGHT, fill=Y) 29t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1) 30t.pack(side=LEFT, fill=BOTH, expand=1) 31t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*') 32s['command'] = t.yview 33 34root.title('Tk Remote Controller') 35root.iconname('Tk Remote') 36 37# 2. Create menu button and menus. 38 39file = Menubutton(mBar, text='File', underline=0) 40file.pack(side=LEFT) 41file_m = Menu(file) 42file['menu'] = file_m 43file_m_apps = Menu(file_m, tearoff=0) 44file_m.add_cascade(label='Select Application', underline=0, 45 menu=file_m_apps) 46file_m.add_command(label='Quit', underline=0, command=sys.exit) 47 48# 3. Create bindings for text widget to allow commands to be 49# entered and information to be selected. New characters 50# can only be added at the end of the text (can't ever move 51# insertion point). 52 53def single1(e): 54 x = e.x 55 y = e.y 56 t.setvar('tk_priv(selectMode)', 'char') 57 t.mark_set('anchor', At(x, y)) 58 # Should focus W 59t.bind('<1>', single1) 60 61def double1(e): 62 x = e.x 63 y = e.y 64 t.setvar('tk_priv(selectMode)', 'word') 65 t.tk_textSelectTo(At(x, y)) 66t.bind('<Double-1>', double1) 67 68def triple1(e): 69 x = e.x 70 y = e.y 71 t.setvar('tk_priv(selectMode)', 'line') 72 t.tk_textSelectTo(At(x, y)) 73t.bind('<Triple-1>', triple1) 74 75def returnkey(e): 76 t.insert(AtInsert(), '\n') 77 invoke() 78t.bind('<Return>', returnkey) 79 80def controlv(e): 81 t.insert(AtInsert(), t.selection_get()) 82 t.yview_pickplace(AtInsert()) 83 if t.index(AtInsert())[-2:] == '.0': 84 invoke() 85t.bind('<Control-v>', controlv) 86 87# 4. Procedure to backspace over one character, as long as 88# the character isn't part of the prompt. 89 90def backspace(e): 91 if t.index('promptEnd') != t.index('insert - 1 char'): 92 t.delete('insert - 1 char', AtInsert()) 93 t.yview_pickplace(AtInsert()) 94t.bind('<BackSpace>', backspace) 95t.bind('<Control-h>', backspace) 96t.bind('<Delete>', backspace) 97 98 99# 5. Procedure that's invoked when return is typed: if 100# there's not yet a complete command (e.g. braces are open) 101# then do nothing. Otherwise, execute command (locally or 102# remotely), output the result or error message, and issue 103# a new prompt. 104 105def invoke(): 106 cmd = t.get('promptEnd + 1 char', AtInsert()) 107 if t.getboolean(tk.call('info', 'complete', cmd)): # XXX 108 if app == root.winfo_name(): 109 msg = tk.call('eval', cmd) # XXX 110 else: 111 msg = t.send(app, cmd) 112 if msg: 113 t.insert(AtInsert(), msg + '\n') 114 prompt() 115 t.yview_pickplace(AtInsert()) 116 117def prompt(): 118 t.insert(AtInsert(), app + ': ') 119 t.mark_set('promptEnd', 'insert - 1 char') 120 t.tag_add('bold', 'insert linestart', 'promptEnd') 121 122# 6. Procedure to select a new application. Also changes 123# the prompt on the current command line to reflect the new 124# name. 125 126def newApp(appName): 127 global app 128 app = appName 129 t.delete('promptEnd linestart', 'promptEnd') 130 t.insert('promptEnd', appName + ':') 131 t.tag_add('bold', 'promptEnd linestart', 'promptEnd') 132 133def fillAppsMenu(): 134 file_m_apps.add('command') 135 file_m_apps.delete(0, 'last') 136 names = root.winfo_interps() 137 names = list(names) 138 names.sort() 139 for name in names: 140 try: 141 root.send(name, 'winfo name .') 142 except TclError: 143 # Inoperative window -- ignore it 144 pass 145 else: 146 file_m_apps.add_command( 147 label=name, 148 command=lambda name=name: newApp(name)) 149 150file_m_apps['postcommand'] = fillAppsMenu 151mBar.tk_menuBar(file) 152 153# 7. Miscellaneous initialization. 154 155app = root.winfo_name() 156prompt() 157t.focus() 158 159root.mainloop() 160