| |
| # The options of a widget are described by the following attributes |
| # of the Pack and Widget dialogs: |
| # |
| # Dialog.current: {name: value} |
| # -- changes during Widget's lifetime |
| # |
| # Dialog.options: {name: (default, klass)} |
| # -- depends on widget class only |
| # |
| # Dialog.classes: {klass: (v0, v1, v2, ...) | 'boolean' | 'other'} |
| # -- totally static, though different between PackDialog and WidgetDialog |
| # (but even that could be unified) |
| |
| from Tkinter import * |
| |
| class Option: |
| |
| varclass = StringVar # May be overridden |
| |
| def __init__(self, dialog, option): |
| self.dialog = dialog |
| self.option = option |
| self.master = dialog.top |
| self.default, self.klass = dialog.options[option] |
| self.var = self.varclass(self.master) |
| self.frame = Frame(self.master) |
| self.frame.pack(fill=X) |
| self.label = Label(self.frame, text=(option + ":")) |
| self.label.pack(side=LEFT) |
| self.update() |
| self.addoption() |
| |
| def refresh(self): |
| self.dialog.refresh() |
| self.update() |
| |
| def update(self): |
| try: |
| self.current = self.dialog.current[self.option] |
| except KeyError: |
| self.current = self.default |
| self.var.set(self.current) |
| |
| def set(self, e=None): # Should be overridden |
| pass |
| |
| class BooleanOption(Option): |
| |
| varclass = BooleanVar |
| |
| def addoption(self): |
| self.button = Checkbutton(self.frame, |
| text='on/off', |
| onvalue=1, |
| offvalue=0, |
| variable=self.var, |
| relief=RAISED, |
| borderwidth=2, |
| command=self.set) |
| self.button.pack(side=RIGHT) |
| |
| class EnumOption(Option): |
| |
| def addoption(self): |
| self.button = Menubutton(self.frame, |
| textvariable=self.var, |
| relief=RAISED, borderwidth=2) |
| self.button.pack(side=RIGHT) |
| self.menu = Menu(self.button) |
| self.button['menu'] = self.menu |
| for v in self.dialog.classes[self.klass]: |
| self.menu.add_radiobutton( |
| label=v, |
| variable=self.var, |
| value=v, |
| command=self.set) |
| |
| class StringOption(Option): |
| |
| def addoption(self): |
| self.entry = Entry(self.frame, |
| textvariable=self.var, |
| width=10, |
| relief=SUNKEN, |
| borderwidth=2) |
| self.entry.pack(side=RIGHT, fill=X, expand=1) |
| self.entry.bind('<Return>', self.set) |
| |
| class ReadonlyOption(Option): |
| |
| def addoption(self): |
| self.label = Label(self.frame, textvariable=self.var, |
| anchor=E) |
| self.label.pack(side=RIGHT) |
| |
| class Dialog: |
| |
| def __init__(self, master): |
| self.master = master |
| self.fixclasses() |
| self.refresh() |
| self.top = Toplevel(self.master) |
| self.top.title(self.__class__.__name__) |
| self.top.minsize(1, 1) |
| self.addchoices() |
| |
| def refresh(self): pass # Must override |
| |
| def fixclasses(self): pass # May override |
| |
| def addchoices(self): |
| self.choices = {} |
| list = [] |
| for k, dc in self.options.items(): |
| list.append((k, dc)) |
| list.sort() |
| for k, (d, c) in list: |
| try: |
| cl = self.classes[c] |
| except KeyError: |
| cl = 'unknown' |
| if type(cl) == TupleType: |
| cl = self.enumoption |
| elif cl == 'boolean': |
| cl = self.booleanoption |
| elif cl == 'readonly': |
| cl = self.readonlyoption |
| else: |
| cl = self.stringoption |
| self.choices[k] = cl(self, k) |
| |
| # Must override: |
| options = {} |
| classes = {} |
| |
| # May override: |
| booleanoption = BooleanOption |
| stringoption = StringOption |
| enumoption = EnumOption |
| readonlyoption = ReadonlyOption |
| |
| class PackDialog(Dialog): |
| |
| def __init__(self, widget): |
| self.widget = widget |
| Dialog.__init__(self, widget) |
| |
| def refresh(self): |
| self.current = self.widget.info() |
| self.current['.class'] = self.widget.winfo_class() |
| self.current['.name'] = self.widget._w |
| |
| class packoption: # Mix-in class |
| def set(self, e=None): |
| self.current = self.var.get() |
| try: |
| self.dialog.widget.pack(**{self.option: self.current}) |
| except TclError as msg: |
| print(msg) |
| self.refresh() |
| |
| class booleanoption(packoption, BooleanOption): pass |
| class enumoption(packoption, EnumOption): pass |
| class stringoption(packoption, StringOption): pass |
| class readonlyoption(packoption, ReadonlyOption): pass |
| |
| options = { |
| '.class': (None, 'Class'), |
| '.name': (None, 'Name'), |
| 'after': (None, 'Widget'), |
| 'anchor': ('center', 'Anchor'), |
| 'before': (None, 'Widget'), |
| 'expand': ('no', 'Boolean'), |
| 'fill': ('none', 'Fill'), |
| 'in': (None, 'Widget'), |
| 'ipadx': (0, 'Pad'), |
| 'ipady': (0, 'Pad'), |
| 'padx': (0, 'Pad'), |
| 'pady': (0, 'Pad'), |
| 'side': ('top', 'Side'), |
| } |
| |
| classes = { |
| 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), |
| 'Boolean': 'boolean', |
| 'Class': 'readonly', |
| 'Expand': 'boolean', |
| 'Fill': (NONE, X, Y, BOTH), |
| 'Name': 'readonly', |
| 'Pad': 'pixel', |
| 'Side': (TOP, RIGHT, BOTTOM, LEFT), |
| 'Widget': 'readonly', |
| } |
| |
| class RemotePackDialog(PackDialog): |
| |
| def __init__(self, master, app, widget): |
| self.master = master |
| self.app = app |
| self.widget = widget |
| self.refresh() |
| self.top = Toplevel(self.master) |
| self.top.title(self.app + ' PackDialog') |
| self.top.minsize(1, 1) |
| self.addchoices() |
| |
| def refresh(self): |
| try: |
| words = self.master.tk.splitlist( |
| self.master.send(self.app, |
| 'pack', |
| 'info', |
| self.widget)) |
| except TclError as msg: |
| print(msg) |
| return |
| dict = {} |
| for i in range(0, len(words), 2): |
| key = words[i][1:] |
| value = words[i+1] |
| dict[key] = value |
| dict['.class'] = self.master.send(self.app, |
| 'winfo', |
| 'class', |
| self.widget) |
| dict['.name'] = self.widget |
| self.current = dict |
| |
| class remotepackoption: # Mix-in class |
| def set(self, e=None): |
| self.current = self.var.get() |
| try: |
| self.dialog.master.send( |
| self.dialog.app, |
| 'pack', |
| 'config', |
| self.dialog.widget, |
| '-'+self.option, |
| self.dialog.master.tk.merge( |
| self.current)) |
| except TclError as msg: |
| print(msg) |
| self.refresh() |
| |
| class booleanoption(remotepackoption, BooleanOption): pass |
| class enumoption(remotepackoption, EnumOption): pass |
| class stringoption(remotepackoption, StringOption): pass |
| class readonlyoption(remotepackoption, ReadonlyOption): pass |
| |
| class WidgetDialog(Dialog): |
| |
| def __init__(self, widget): |
| self.widget = widget |
| self.klass = widget.winfo_class() |
| Dialog.__init__(self, widget) |
| |
| def fixclasses(self): |
| if self.klass in self.addclasses: |
| classes = {} |
| for c in (self.classes, |
| self.addclasses[self.klass]): |
| for k in c.keys(): |
| classes[k] = c[k] |
| self.classes = classes |
| |
| def refresh(self): |
| self.configuration = self.widget.config() |
| self.update() |
| self.current['.class'] = self.widget.winfo_class() |
| self.current['.name'] = self.widget._w |
| |
| def update(self): |
| self.current = {} |
| self.options = {} |
| for k, v in self.configuration.items(): |
| if len(v) > 4: |
| self.current[k] = v[4] |
| self.options[k] = v[3], v[2] # default, klass |
| self.options['.class'] = (None, 'Class') |
| self.options['.name'] = (None, 'Name') |
| |
| class widgetoption: # Mix-in class |
| def set(self, e=None): |
| self.current = self.var.get() |
| try: |
| self.dialog.widget[self.option] = self.current |
| except TclError as msg: |
| print(msg) |
| self.refresh() |
| |
| class booleanoption(widgetoption, BooleanOption): pass |
| class enumoption(widgetoption, EnumOption): pass |
| class stringoption(widgetoption, StringOption): pass |
| class readonlyoption(widgetoption, ReadonlyOption): pass |
| |
| # Universal classes |
| classes = { |
| 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), |
| 'Aspect': 'integer', |
| 'Background': 'color', |
| 'Bitmap': 'bitmap', |
| 'BorderWidth': 'pixel', |
| 'Class': 'readonly', |
| 'CloseEnough': 'double', |
| 'Command': 'command', |
| 'Confine': 'boolean', |
| 'Cursor': 'cursor', |
| 'CursorWidth': 'pixel', |
| 'DisabledForeground': 'color', |
| 'ExportSelection': 'boolean', |
| 'Font': 'font', |
| 'Foreground': 'color', |
| 'From': 'integer', |
| 'Geometry': 'geometry', |
| 'Height': 'pixel', |
| 'InsertWidth': 'time', |
| 'Justify': (LEFT, CENTER, RIGHT), |
| 'Label': 'string', |
| 'Length': 'pixel', |
| 'MenuName': 'widget', |
| 'Name': 'readonly', |
| 'OffTime': 'time', |
| 'OnTime': 'time', |
| 'Orient': (HORIZONTAL, VERTICAL), |
| 'Pad': 'pixel', |
| 'Relief': (RAISED, SUNKEN, FLAT, RIDGE, GROOVE), |
| 'RepeatDelay': 'time', |
| 'RepeatInterval': 'time', |
| 'ScrollCommand': 'command', |
| 'ScrollIncrement': 'pixel', |
| 'ScrollRegion': 'rectangle', |
| 'ShowValue': 'boolean', |
| 'SetGrid': 'boolean', |
| 'Sliderforeground': 'color', |
| 'SliderLength': 'pixel', |
| 'Text': 'string', |
| 'TickInterval': 'integer', |
| 'To': 'integer', |
| 'Underline': 'index', |
| 'Variable': 'variable', |
| 'Value': 'string', |
| 'Width': 'pixel', |
| 'Wrap': (NONE, CHAR, WORD), |
| } |
| |
| # Classes that (may) differ per widget type |
| _tristate = {'State': (NORMAL, ACTIVE, DISABLED)} |
| _bistate = {'State': (NORMAL, DISABLED)} |
| addclasses = { |
| 'Button': _tristate, |
| 'Radiobutton': _tristate, |
| 'Checkbutton': _tristate, |
| 'Entry': _bistate, |
| 'Text': _bistate, |
| 'Menubutton': _tristate, |
| 'Slider': _bistate, |
| } |
| |
| class RemoteWidgetDialog(WidgetDialog): |
| |
| def __init__(self, master, app, widget): |
| self.app = app |
| self.widget = widget |
| self.klass = master.send(self.app, |
| 'winfo', |
| 'class', |
| self.widget) |
| Dialog.__init__(self, master) |
| |
| def refresh(self): |
| try: |
| items = self.master.tk.splitlist( |
| self.master.send(self.app, |
| self.widget, |
| 'config')) |
| except TclError as msg: |
| print(msg) |
| return |
| dict = {} |
| for item in items: |
| words = self.master.tk.splitlist(item) |
| key = words[0][1:] |
| value = (key,) + words[1:] |
| dict[key] = value |
| self.configuration = dict |
| self.update() |
| self.current['.class'] = self.klass |
| self.current['.name'] = self.widget |
| |
| class remotewidgetoption: # Mix-in class |
| def set(self, e=None): |
| self.current = self.var.get() |
| try: |
| self.dialog.master.send( |
| self.dialog.app, |
| self.dialog.widget, |
| 'config', |
| '-'+self.option, |
| self.current) |
| except TclError as msg: |
| print(msg) |
| self.refresh() |
| |
| class booleanoption(remotewidgetoption, BooleanOption): pass |
| class enumoption(remotewidgetoption, EnumOption): pass |
| class stringoption(remotewidgetoption, StringOption): pass |
| class readonlyoption(remotewidgetoption, ReadonlyOption): pass |
| |
| def test(): |
| import sys |
| root = Tk() |
| root.minsize(1, 1) |
| if sys.argv[1:]: |
| remotetest(root, sys.argv[1]) |
| else: |
| frame = Frame(root, name='frame') |
| frame.pack(expand=1, fill=BOTH) |
| button = Button(frame, name='button', text='button') |
| button.pack(expand=1) |
| canvas = Canvas(frame, name='canvas') |
| canvas.pack() |
| fpd = PackDialog(frame) |
| fwd = WidgetDialog(frame) |
| bpd = PackDialog(button) |
| bwd = WidgetDialog(button) |
| cpd = PackDialog(canvas) |
| cwd = WidgetDialog(canvas) |
| root.mainloop() |
| |
| def remotetest(root, app): |
| from listtree import listtree |
| list = listtree(root, app) |
| list.bind('<Any-Double-1>', opendialogs) |
| list.app = app # Pass it on to handler |
| |
| def opendialogs(e): |
| import string |
| list = e.widget |
| sel = list.curselection() |
| for i in sel: |
| item = list.get(i) |
| widget = string.split(item)[0] |
| RemoteWidgetDialog(list, list.app, widget) |
| if widget == '.': continue |
| try: |
| RemotePackDialog(list, list.app, widget) |
| except TclError as msg: |
| print(msg) |
| |
| test() |