|
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() |