Home | History | Annotate | Download | only in guido
      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()
    453