|
1 |
|
2 # The options of a widget are described by the following attributes |
|
3 # of the Pack and Widget dialogs: |
|
4 # |
|
5 # Dialog.current: {name: value} |
|
6 # -- changes during Widget's lifetime |
|
7 # |
|
8 # Dialog.options: {name: (default, klass)} |
|
9 # -- depends on widget class only |
|
10 # |
|
11 # Dialog.classes: {klass: (v0, v1, v2, ...) | 'boolean' | 'other'} |
|
12 # -- totally static, though different between PackDialog and WidgetDialog |
|
13 # (but even that could be unified) |
|
14 |
|
15 from Tkinter import * |
|
16 |
|
17 class Option: |
|
18 |
|
19 varclass = StringVar # May be overridden |
|
20 |
|
21 def __init__(self, dialog, option): |
|
22 self.dialog = dialog |
|
23 self.option = option |
|
24 self.master = dialog.top |
|
25 self.default, self.klass = dialog.options[option] |
|
26 self.var = self.varclass(self.master) |
|
27 self.frame = Frame(self.master) |
|
28 self.frame.pack(fill=X) |
|
29 self.label = Label(self.frame, text=(option + ":")) |
|
30 self.label.pack(side=LEFT) |
|
31 self.update() |
|
32 self.addoption() |
|
33 |
|
34 def refresh(self): |
|
35 self.dialog.refresh() |
|
36 self.update() |
|
37 |
|
38 def update(self): |
|
39 try: |
|
40 self.current = self.dialog.current[self.option] |
|
41 except KeyError: |
|
42 self.current = self.default |
|
43 self.var.set(self.current) |
|
44 |
|
45 def set(self, e=None): # Should be overridden |
|
46 pass |
|
47 |
|
48 class BooleanOption(Option): |
|
49 |
|
50 varclass = BooleanVar |
|
51 |
|
52 def addoption(self): |
|
53 self.button = Checkbutton(self.frame, |
|
54 text='on/off', |
|
55 onvalue=1, |
|
56 offvalue=0, |
|
57 variable=self.var, |
|
58 relief=RAISED, |
|
59 borderwidth=2, |
|
60 command=self.set) |
|
61 self.button.pack(side=RIGHT) |
|
62 |
|
63 class EnumOption(Option): |
|
64 |
|
65 def addoption(self): |
|
66 self.button = Menubutton(self.frame, |
|
67 textvariable=self.var, |
|
68 relief=RAISED, borderwidth=2) |
|
69 self.button.pack(side=RIGHT) |
|
70 self.menu = Menu(self.button) |
|
71 self.button['menu'] = self.menu |
|
72 for v in self.dialog.classes[self.klass]: |
|
73 self.menu.add_radiobutton( |
|
74 label=v, |
|
75 variable=self.var, |
|
76 value=v, |
|
77 command=self.set) |
|
78 |
|
79 class StringOption(Option): |
|
80 |
|
81 def addoption(self): |
|
82 self.entry = Entry(self.frame, |
|
83 textvariable=self.var, |
|
84 width=10, |
|
85 relief=SUNKEN, |
|
86 borderwidth=2) |
|
87 self.entry.pack(side=RIGHT, fill=X, expand=1) |
|
88 self.entry.bind('<Return>', self.set) |
|
89 |
|
90 class ReadonlyOption(Option): |
|
91 |
|
92 def addoption(self): |
|
93 self.label = Label(self.frame, textvariable=self.var, |
|
94 anchor=E) |
|
95 self.label.pack(side=RIGHT) |
|
96 |
|
97 class Dialog: |
|
98 |
|
99 def __init__(self, master): |
|
100 self.master = master |
|
101 self.fixclasses() |
|
102 self.refresh() |
|
103 self.top = Toplevel(self.master) |
|
104 self.top.title(self.__class__.__name__) |
|
105 self.top.minsize(1, 1) |
|
106 self.addchoices() |
|
107 |
|
108 def refresh(self): pass # Must override |
|
109 |
|
110 def fixclasses(self): pass # May override |
|
111 |
|
112 def addchoices(self): |
|
113 self.choices = {} |
|
114 list = [] |
|
115 for k, dc in self.options.items(): |
|
116 list.append((k, dc)) |
|
117 list.sort() |
|
118 for k, (d, c) in list: |
|
119 try: |
|
120 cl = self.classes[c] |
|
121 except KeyError: |
|
122 cl = 'unknown' |
|
123 if type(cl) == TupleType: |
|
124 cl = self.enumoption |
|
125 elif cl == 'boolean': |
|
126 cl = self.booleanoption |
|
127 elif cl == 'readonly': |
|
128 cl = self.readonlyoption |
|
129 else: |
|
130 cl = self.stringoption |
|
131 self.choices[k] = cl(self, k) |
|
132 |
|
133 # Must override: |
|
134 options = {} |
|
135 classes = {} |
|
136 |
|
137 # May override: |
|
138 booleanoption = BooleanOption |
|
139 stringoption = StringOption |
|
140 enumoption = EnumOption |
|
141 readonlyoption = ReadonlyOption |
|
142 |
|
143 class PackDialog(Dialog): |
|
144 |
|
145 def __init__(self, widget): |
|
146 self.widget = widget |
|
147 Dialog.__init__(self, widget) |
|
148 |
|
149 def refresh(self): |
|
150 self.current = self.widget.info() |
|
151 self.current['.class'] = self.widget.winfo_class() |
|
152 self.current['.name'] = self.widget._w |
|
153 |
|
154 class packoption: # Mix-in class |
|
155 def set(self, e=None): |
|
156 self.current = self.var.get() |
|
157 try: |
|
158 apply(self.dialog.widget.pack, (), |
|
159 {self.option: self.current}) |
|
160 except TclError, msg: |
|
161 print msg |
|
162 self.refresh() |
|
163 |
|
164 class booleanoption(packoption, BooleanOption): pass |
|
165 class enumoption(packoption, EnumOption): pass |
|
166 class stringoption(packoption, StringOption): pass |
|
167 class readonlyoption(packoption, ReadonlyOption): pass |
|
168 |
|
169 options = { |
|
170 '.class': (None, 'Class'), |
|
171 '.name': (None, 'Name'), |
|
172 'after': (None, 'Widget'), |
|
173 'anchor': ('center', 'Anchor'), |
|
174 'before': (None, 'Widget'), |
|
175 'expand': ('no', 'Boolean'), |
|
176 'fill': ('none', 'Fill'), |
|
177 'in': (None, 'Widget'), |
|
178 'ipadx': (0, 'Pad'), |
|
179 'ipady': (0, 'Pad'), |
|
180 'padx': (0, 'Pad'), |
|
181 'pady': (0, 'Pad'), |
|
182 'side': ('top', 'Side'), |
|
183 } |
|
184 |
|
185 classes = { |
|
186 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), |
|
187 'Boolean': 'boolean', |
|
188 'Class': 'readonly', |
|
189 'Expand': 'boolean', |
|
190 'Fill': (NONE, X, Y, BOTH), |
|
191 'Name': 'readonly', |
|
192 'Pad': 'pixel', |
|
193 'Side': (TOP, RIGHT, BOTTOM, LEFT), |
|
194 'Widget': 'readonly', |
|
195 } |
|
196 |
|
197 class RemotePackDialog(PackDialog): |
|
198 |
|
199 def __init__(self, master, app, widget): |
|
200 self.master = master |
|
201 self.app = app |
|
202 self.widget = widget |
|
203 self.refresh() |
|
204 self.top = Toplevel(self.master) |
|
205 self.top.title(self.app + ' PackDialog') |
|
206 self.top.minsize(1, 1) |
|
207 self.addchoices() |
|
208 |
|
209 def refresh(self): |
|
210 try: |
|
211 words = self.master.tk.splitlist( |
|
212 self.master.send(self.app, |
|
213 'pack', |
|
214 'info', |
|
215 self.widget)) |
|
216 except TclError, msg: |
|
217 print msg |
|
218 return |
|
219 dict = {} |
|
220 for i in range(0, len(words), 2): |
|
221 key = words[i][1:] |
|
222 value = words[i+1] |
|
223 dict[key] = value |
|
224 dict['.class'] = self.master.send(self.app, |
|
225 'winfo', |
|
226 'class', |
|
227 self.widget) |
|
228 dict['.name'] = self.widget |
|
229 self.current = dict |
|
230 |
|
231 class remotepackoption: # Mix-in class |
|
232 def set(self, e=None): |
|
233 self.current = self.var.get() |
|
234 try: |
|
235 self.dialog.master.send( |
|
236 self.dialog.app, |
|
237 'pack', |
|
238 'config', |
|
239 self.dialog.widget, |
|
240 '-'+self.option, |
|
241 self.dialog.master.tk.merge( |
|
242 self.current)) |
|
243 except TclError, msg: |
|
244 print msg |
|
245 self.refresh() |
|
246 |
|
247 class booleanoption(remotepackoption, BooleanOption): pass |
|
248 class enumoption(remotepackoption, EnumOption): pass |
|
249 class stringoption(remotepackoption, StringOption): pass |
|
250 class readonlyoption(remotepackoption, ReadonlyOption): pass |
|
251 |
|
252 class WidgetDialog(Dialog): |
|
253 |
|
254 def __init__(self, widget): |
|
255 self.widget = widget |
|
256 self.klass = widget.winfo_class() |
|
257 Dialog.__init__(self, widget) |
|
258 |
|
259 def fixclasses(self): |
|
260 if self.addclasses.has_key(self.klass): |
|
261 classes = {} |
|
262 for c in (self.classes, |
|
263 self.addclasses[self.klass]): |
|
264 for k in c.keys(): |
|
265 classes[k] = c[k] |
|
266 self.classes = classes |
|
267 |
|
268 def refresh(self): |
|
269 self.configuration = self.widget.config() |
|
270 self.update() |
|
271 self.current['.class'] = self.widget.winfo_class() |
|
272 self.current['.name'] = self.widget._w |
|
273 |
|
274 def update(self): |
|
275 self.current = {} |
|
276 self.options = {} |
|
277 for k, v in self.configuration.items(): |
|
278 if len(v) > 4: |
|
279 self.current[k] = v[4] |
|
280 self.options[k] = v[3], v[2] # default, klass |
|
281 self.options['.class'] = (None, 'Class') |
|
282 self.options['.name'] = (None, 'Name') |
|
283 |
|
284 class widgetoption: # Mix-in class |
|
285 def set(self, e=None): |
|
286 self.current = self.var.get() |
|
287 try: |
|
288 self.dialog.widget[self.option] = self.current |
|
289 except TclError, msg: |
|
290 print msg |
|
291 self.refresh() |
|
292 |
|
293 class booleanoption(widgetoption, BooleanOption): pass |
|
294 class enumoption(widgetoption, EnumOption): pass |
|
295 class stringoption(widgetoption, StringOption): pass |
|
296 class readonlyoption(widgetoption, ReadonlyOption): pass |
|
297 |
|
298 # Universal classes |
|
299 classes = { |
|
300 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), |
|
301 'Aspect': 'integer', |
|
302 'Background': 'color', |
|
303 'Bitmap': 'bitmap', |
|
304 'BorderWidth': 'pixel', |
|
305 'Class': 'readonly', |
|
306 'CloseEnough': 'double', |
|
307 'Command': 'command', |
|
308 'Confine': 'boolean', |
|
309 'Cursor': 'cursor', |
|
310 'CursorWidth': 'pixel', |
|
311 'DisabledForeground': 'color', |
|
312 'ExportSelection': 'boolean', |
|
313 'Font': 'font', |
|
314 'Foreground': 'color', |
|
315 'From': 'integer', |
|
316 'Geometry': 'geometry', |
|
317 'Height': 'pixel', |
|
318 'InsertWidth': 'time', |
|
319 'Justify': (LEFT, CENTER, RIGHT), |
|
320 'Label': 'string', |
|
321 'Length': 'pixel', |
|
322 'MenuName': 'widget', |
|
323 'Name': 'readonly', |
|
324 'OffTime': 'time', |
|
325 'OnTime': 'time', |
|
326 'Orient': (HORIZONTAL, VERTICAL), |
|
327 'Pad': 'pixel', |
|
328 'Relief': (RAISED, SUNKEN, FLAT, RIDGE, GROOVE), |
|
329 'RepeatDelay': 'time', |
|
330 'RepeatInterval': 'time', |
|
331 'ScrollCommand': 'command', |
|
332 'ScrollIncrement': 'pixel', |
|
333 'ScrollRegion': 'rectangle', |
|
334 'ShowValue': 'boolean', |
|
335 'SetGrid': 'boolean', |
|
336 'Sliderforeground': 'color', |
|
337 'SliderLength': 'pixel', |
|
338 'Text': 'string', |
|
339 'TickInterval': 'integer', |
|
340 'To': 'integer', |
|
341 'Underline': 'index', |
|
342 'Variable': 'variable', |
|
343 'Value': 'string', |
|
344 'Width': 'pixel', |
|
345 'Wrap': (NONE, CHAR, WORD), |
|
346 } |
|
347 |
|
348 # Classes that (may) differ per widget type |
|
349 _tristate = {'State': (NORMAL, ACTIVE, DISABLED)} |
|
350 _bistate = {'State': (NORMAL, DISABLED)} |
|
351 addclasses = { |
|
352 'Button': _tristate, |
|
353 'Radiobutton': _tristate, |
|
354 'Checkbutton': _tristate, |
|
355 'Entry': _bistate, |
|
356 'Text': _bistate, |
|
357 'Menubutton': _tristate, |
|
358 'Slider': _bistate, |
|
359 } |
|
360 |
|
361 class RemoteWidgetDialog(WidgetDialog): |
|
362 |
|
363 def __init__(self, master, app, widget): |
|
364 self.app = app |
|
365 self.widget = widget |
|
366 self.klass = master.send(self.app, |
|
367 'winfo', |
|
368 'class', |
|
369 self.widget) |
|
370 Dialog.__init__(self, master) |
|
371 |
|
372 def refresh(self): |
|
373 try: |
|
374 items = self.master.tk.splitlist( |
|
375 self.master.send(self.app, |
|
376 self.widget, |
|
377 'config')) |
|
378 except TclError, msg: |
|
379 print msg |
|
380 return |
|
381 dict = {} |
|
382 for item in items: |
|
383 words = self.master.tk.splitlist(item) |
|
384 key = words[0][1:] |
|
385 value = (key,) + words[1:] |
|
386 dict[key] = value |
|
387 self.configuration = dict |
|
388 self.update() |
|
389 self.current['.class'] = self.klass |
|
390 self.current['.name'] = self.widget |
|
391 |
|
392 class remotewidgetoption: # Mix-in class |
|
393 def set(self, e=None): |
|
394 self.current = self.var.get() |
|
395 try: |
|
396 self.dialog.master.send( |
|
397 self.dialog.app, |
|
398 self.dialog.widget, |
|
399 'config', |
|
400 '-'+self.option, |
|
401 self.current) |
|
402 except TclError, msg: |
|
403 print msg |
|
404 self.refresh() |
|
405 |
|
406 class booleanoption(remotewidgetoption, BooleanOption): pass |
|
407 class enumoption(remotewidgetoption, EnumOption): pass |
|
408 class stringoption(remotewidgetoption, StringOption): pass |
|
409 class readonlyoption(remotewidgetoption, ReadonlyOption): pass |
|
410 |
|
411 def test(): |
|
412 import sys |
|
413 root = Tk() |
|
414 root.minsize(1, 1) |
|
415 if sys.argv[1:]: |
|
416 remotetest(root, sys.argv[1]) |
|
417 else: |
|
418 frame = Frame(root, name='frame') |
|
419 frame.pack(expand=1, fill=BOTH) |
|
420 button = Button(frame, name='button', text='button') |
|
421 button.pack(expand=1) |
|
422 canvas = Canvas(frame, name='canvas') |
|
423 canvas.pack() |
|
424 fpd = PackDialog(frame) |
|
425 fwd = WidgetDialog(frame) |
|
426 bpd = PackDialog(button) |
|
427 bwd = WidgetDialog(button) |
|
428 cpd = PackDialog(canvas) |
|
429 cwd = WidgetDialog(canvas) |
|
430 root.mainloop() |
|
431 |
|
432 def remotetest(root, app): |
|
433 from listtree import listtree |
|
434 list = listtree(root, app) |
|
435 list.bind('<Any-Double-1>', opendialogs) |
|
436 list.app = app # Pass it on to handler |
|
437 |
|
438 def opendialogs(e): |
|
439 import string |
|
440 list = e.widget |
|
441 sel = list.curselection() |
|
442 for i in sel: |
|
443 item = list.get(i) |
|
444 widget = string.split(item)[0] |
|
445 RemoteWidgetDialog(list, list.app, widget) |
|
446 if widget == '.': continue |
|
447 try: |
|
448 RemotePackDialog(list, list.app, widget) |
|
449 except TclError, msg: |
|
450 print msg |
|
451 |
|
452 test() |