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