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 14 from Tkinter import * 15 import sys 16 17 # 1. Create basic application structure: menu bar on top of 18 # text widget, scrollbar on right. 19 20 root = Tk() 21 tk = root.tk 22 mBar = Frame(root, relief=RAISED, borderwidth=2) 23 mBar.pack(fill=X) 24 25 f = Frame(root) 26 f.pack(expand=1, fill=BOTH) 27 s = Scrollbar(f, relief=FLAT) 28 s.pack(side=RIGHT, fill=Y) 29 t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1) 30 t.pack(side=LEFT, fill=BOTH, expand=1) 31 t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*') 32 s['command'] = t.yview 33 34 root.title('Tk Remote Controller') 35 root.iconname('Tk Remote') 36 37 # 2. Create menu button and menus. 38 39 file = Menubutton(mBar, text='File', underline=0) 40 file.pack(side=LEFT) 41 file_m = Menu(file) 42 file['menu'] = file_m 43 file_m_apps = Menu(file_m, tearoff=0) 44 file_m.add_cascade(label='Select Application', underline=0, 45 menu=file_m_apps) 46 file_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 53 def 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 59 t.bind('<1>', single1) 60 61 def double1(e): 62 x = e.x 63 y = e.y 64 t.setvar('tk_priv(selectMode)', 'word') 65 t.tk_textSelectTo(At(x, y)) 66 t.bind('<Double-1>', double1) 67 68 def triple1(e): 69 x = e.x 70 y = e.y 71 t.setvar('tk_priv(selectMode)', 'line') 72 t.tk_textSelectTo(At(x, y)) 73 t.bind('<Triple-1>', triple1) 74 75 def returnkey(e): 76 t.insert(AtInsert(), '\n') 77 invoke() 78 t.bind('<Return>', returnkey) 79 80 def controlv(e): 81 t.insert(AtInsert(), t.selection_get()) 82 t.yview_pickplace(AtInsert()) 83 if t.index(AtInsert())[-2:] == '.0': 84 invoke() 85 t.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 90 def backspace(e): 91 if t.index('promptEnd') != t.index('insert - 1 char'): 92 t.delete('insert - 1 char', AtInsert()) 93 t.yview_pickplace(AtInsert()) 94 t.bind('<BackSpace>', backspace) 95 t.bind('<Control-h>', backspace) 96 t.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 105 def 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 117 def 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 126 def 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 133 def 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 150 file_m_apps['postcommand'] = fillAppsMenu 151 mBar.tk_menuBar(file) 152 153 # 7. Miscellaneous initialization. 154 155 app = root.winfo_name() 156 prompt() 157 t.focus() 158 159 root.mainloop() 160