| 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 = map(None, names) # convert tuple to list
|
|---|
| 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()
|
|---|