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
15from Tkinter import *
16
17class 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
48class 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
63class 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
79class 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
90class 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
97class 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
143class 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
197class 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
252class 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
361class 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
411def 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
432def 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
438def 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
452test()
453