Package param :: Module tk
[hide private]
[frames] | no frames]

Source Code for Module param.tk

   1  """ 
   2  Classes linking Parameters to Tkinter. 
   3   
   4  TkParameterized allows flexible graphical representation and 
   5  manipulation of the parameters of a Parameterized instance or class. 
   6   
   7  ParametersFrame and ParametersFrameWithApply extend TkParameterized, 
   8  displaying all the Parameters together in a list. Both allow these 
   9  Parameters to be edited; ParametersFrame applies changes immediately 
  10  as they are made, whereas ParametersFrameWithApply makes no changes 
  11  until a confirmation is given (by pressing the 'Apply' button). 
  12   
  13  Using these classes to display parameters has several benefits, 
  14  including that, automatically: 
  15   
  16  - changes to the Parameters in the GUI are reflected in the objects 
  17    themselves without any additional code; 
  18   
  19  - each Parameter is represented by an appropriate widget (e.g. slider 
  20    for a Number); 
  21   
  22  - type and range checking is handled (by virtue of using parameters); 
  23   
  24  - doc strings are displayed as pop-up help for each parameter; 
  25   
  26  - parameter names are formatted nicely for display; 
  27   
  28  - changes to parameter values in the GUI can be associated with 
  29    function calls. 
  30   
  31   
  32  Examples 
  33  ======== 
  34   
  35  (1) Display the parameters of an object inside an existing window 
  36   
  37  You want to display all parameters of a parameterized instance 
  38  inside an existing containter (e.g. a window or a frame): 
  39   
  40    # existing Parameterized instance g 
  41    from topo import pattern 
  42    g = pattern.Gaussian() 
  43     
  44    # existing window t 
  45    import Tkinter 
  46    t = Tkinter.Toplevel() 
  47     
  48    # display all the parameters of g in t 
  49    from topo.param.tk import ParametersFrame 
  50    ParametersFrame(t,g) 
  51    #should be ParametersFrame(t,g).pack(); see ALERT in ParametersFrame 
  52     
  53   
  54  (2) Display the parameters of an object in a standalone window 
  55   
  56  You want a new window displaying only the parameters of your object: 
  57   
  58    # existing Parameterized instance g 
  59    from topo import pattern 
  60    g = pattern.Gaussian() 
  61     
  62    # display all the parameters of g in a new window 
  63    from topo.param.tk import edit_parameters 
  64    edit_parameters(g) 
  65     
  66   
  67  (3) Flexible GUI layout using TkParameterized 
  68   
  69  You want to display only some of the parameters of one or more 
  70  Parameterized instances: 
  71   
  72   ## Existing, non-GUI code 
  73   from topo import param 
  74   
  75   class Object1(param.Parameterized): 
  76       duration = param.Number(2.0,bounds=(0,None),doc='Duration of measurement') 
  77       displacement = param.Number(0.0,bounds=(-1,1),doc='Displacement from point A') 
  78   
  79   class Object2(param.Parameterized): 
  80       active_today = param.Boolean(True,doc='Whether or not to count today') 
  81       operator_name = param.String('A. Person',doc='Operator today') 
  82   
  83   o1 = Object1() 
  84   o2 = Object2() 
  85   
  86   ## Existing window 
  87   import Tkinter 
  88   t = Tkinter.Toplevel() 
  89   
  90   ## Flexible GUI representation: display o1.duration, o1.displacement, 
  91   ## and o2.active_today inside t, ignoring o2.operator_name 
  92   from topo.param.tk import TkParameterized 
  93   
  94   t1 = TkParameterized(t,o1) 
  95   t2 = TkParameterized(t,o2) 
  96   
  97   t1.pack_param('duration',side='left') 
  98   t1.pack_param('displacement',side='bottom') 
  99   t2.pack_param('active_today',side='right') 
 100   
 101   
 102  (4) ? 
 103   
 104  TkParameterized is itself a subclass of Parameterized, so a 
 105  TkParameterized class can have its own parameters (in addition to 
 106  representing those of an external parameterized instance or class). 
 107   
 108    ## Existing class 
 109    from topo import params 
 110     
 111    class X(param.Parameterized): 
 112        one = param.Boolean(True) 
 113        two = param.Boolean(True) 
 114     
 115                                                         
 116    ## Panel to represent an instance of X  
 117    from Tkinter import Frame 
 118    from topo.param.tk import TkParameterized 
 119   
 120    class XPanel(TkParameterized,Frame): 
 121     
 122        dock = param.Boolean(False,doc='Whether to attach this Panel') 
 123         
 124        def __init__(self,master,x): 
 125            self.pack_param('dock',side='top',on_set=self.handle_dock) 
 126            self.pack_param('one',side='left') 
 127            self.pack_param('two',side='right') 
 128   
 129        def handle_dock(self): 
 130            if self.dock: 
 131                # dock the window 
 132            else: 
 133                # undock the window         
 134     
 135   
 136    ## Running the code 
 137    t = Tkinter.Toplevel() 
 138    x = X() 
 139    XPanel(t,x) 
 140   
 141   
 142  $Id: tk.py 11264 2010-07-21 18:12:49Z ceball $ 
 143  """ 
 144   
 145  # CB: This file is too long because the param/gui interface code has 
 146  # become too long, and needs cleaning up.  I'm still working on it 
 147  # (still have to attend to simple ALERTs and do a one-pass cleanup) 
 148   
 149  # It's quite likely that the way TkParameterizedBase is implemented 
 150  # could be simplified. Right now, it still contains leftovers of 
 151  # various attempts to get everything working. But it does seem to 
 152  # work! 
 153   
 154   
 155  ## import logging 
 156  ## import string,time 
 157  ## log_name= 'guilog_%s'%string.join([str(i) for i in list(time.gmtime())],"") 
 158   
 159  ## logging.basicConfig(level=logging.DEBUG, 
 160  ##                     format='%(asctime)s %(levelname)s %(message)s', 
 161  ##                     filename='topo/tkgui/%s'%log_name, 
 162  ##                     filemode='w') 
 163  ## from os import popen 
 164  ## version = popen('svnversion -n').read() 
 165  ## logging.info("tkgui logging started for %s"%version) 
 166   
 167  import __main__ 
 168  import copy 
 169  import decimal 
 170  import os.path 
 171  import ImageTk, Image, ImageOps 
 172  import Tkinter as T 
 173   
 174  from inspect import getdoc 
 175   
 176  from tkMessageBox import _show,QUESTION,YESNO 
 177   
 178  from parameterized import Parameterized,ParameterizedMetaclass,\ 
 179       classlist 
 180   
 181  import param 
 182   
 183  from external import Combobox,OrderedDict,Progressbar 
 184   
 185  from . import Boolean,String,Number,Selector,ClassSelector,\ 
 186       ObjectSelector,Callable,Dynamic,Parameter,List,HookList,\ 
 187       Filename,resolve_path 
 188   
 189   
 190  _last_one_set = None 
 191   
 192   
 193   
 194  # Until tklib, tcllib, and scrodget become more commonly 
 195  # available, we include them in tkgui. 
 196  externaltk_path = os.path.join(os.path.split(param.__file__)[0],"externaltk") 
 197   
 198  # CEBALERT: need to document this; allows param.tk to be used with 
 199  # existing Tk instance. (And allows param.tk to be imported 
 200  # even if there is no DISPLAY.) 
 201  root = None 
202 -def initialize(external_root=None):
203 global root 204 205 if root is not None: 206 print "param.tk already initialized; ignorning call to param.tk.initialize()" 207 return 208 209 # Creating an initial Tk() instance and then withdrawing the 210 # window is a common technique. 211 if external_root is None: 212 root = T.Tk() 213 root.withdraw() 214 else: 215 root = external_root 216 217 root.tk.call("lappend","auto_path",externaltk_path)
218 219 220
221 -def inverse(dict_):
222 """ 223 Return the inverse of dictionary dict_. 224 225 (I.e. return a dictionary with keys that are the values of dict_, 226 and values that are the corresponding keys from dict_.) 227 228 The values of dict_ must be unique. 229 """ 230 idict = dict([(value,key) for key,value in dict_.iteritems()]) 231 if len(idict)!=len(dict_): 232 raise ValueError("Dictionary has no inverse (values not unique).") 233 return idict
234 235
236 -def lookup_by_class(dict_,class_):
237 """ 238 Look for class_ or its superclasses in the keys of dict_; on 239 finding a match, return the value (return None if no match found). 240 241 Searches from the most immediate class to the most distant 242 (i.e. from class_ to the final superclass of class_). 243 """ 244 v = None 245 for c in classlist(class_)[::-1]: 246 if c in dict_: 247 v = dict_[c] 248 break 249 return v
250 251
252 -def keys_sorted_by_value(d):
253 """ 254 Return the keys of dictionary d sorted by value. 255 """ 256 # By Daniel Schult, 2004/01/23 257 # http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306 258 items=d.items() 259 backitems=[ [v[1],v[0]] for v in items] 260 backitems.sort() 261 return [ backitems[i][1] for i in range(0,len(backitems))]
262 263
264 -def keys_sorted_by_value_unique(d, **sort_kwargs):
265 """ 266 Return the keys of d, sorted by value. 267 268 The values of d must be unique (see inverse) 269 """ 270 values = d.values() 271 values.sort(**sort_kwargs) 272 i = inverse(d) 273 return [i[val] for val in values]
274 275
276 -def keys_sorted_by_value_unique_None_safe(d, **sort_kwargs):
277 """ 278 None is handled separately: if present, it always comes out at the 279 end of the list. Allows callers to use any sort function without 280 worrying that None will not match the other objects (e.g. for 281 using an attribute present on all the other objects, such as 282 precedence of sheets). 283 """ 284 if 'None' in d: 285 assert d['None'] is None 286 None_in_d = True 287 else: 288 None_in_d = False 289 290 if None_in_d: 291 del d['None'] 292 293 sorted_keys = keys_sorted_by_value_unique(d,**sort_kwargs) 294 295 if None_in_d: 296 d['None']=None 297 sorted_keys.append('None') 298 299 return sorted_keys
300 301
302 -def is_button(widget):
303 """ 304 Simple detection of Button-like widgets that are not Checkbuttons 305 (i.e. widgets that do not require a separate label). 306 """ 307 # CEBALERT: document why try/except is needed 308 try: 309 button = 'command' in widget.config() and not hasattr(widget,'toggle') 310 except T.TclError: 311 button = False 312 return button
313 314 315 ################################################################# 316 # Very basic wrapper for scrodget
317 -class ScrodgetWidget:
318 - def _require(self, master):
319 master.tk.call("package", "require", "scrodget")
320
321 - def __init__(self, master, cnf={}, **kw):
322 self._require(master) 323 T.Widget.__init__(self, master, 'scrodget', cnf, kw)
324 325
326 -class Scrodget(ScrodgetWidget, T.Widget):
327 - def associate(self,widget=None,*args):
328 return self.tk.call(self._w,"associate",widget)
329 ################################################################# 330 331 332 333 # CEB: workaround for tkinter lagging behind tk (tk must have changed 334 # the type of a returned value). This is copied almost exactly from 335 # tkMessageBox. If there are other things like this, we could have the 336 # gui load some 'dynamic patches' to tkinter on startup, which could 337 # then be removed when tkinter is updated (they'd all be in one place, 338 # and no tkgui code would have to change).
339 -def askyesno(title=None, message=None, **options):
340 "Ask a question; return true if the answer is yes" 341 s = _show(title, message, QUESTION, YESNO, **options) 342 return str(s) == "yes"
343 344 345 346 # Buttons are not naturally represented by parameters? 347 # 348 # Maybe we should have a parent class that implements the 349 # non-Parameter specific stuff, then one that bolts on the 350 # Parameter-specific stuff, and then instead of Button we'd 351 # have TopoButton, or something like that...
352 -class Button(Callable):
353 """ 354 A GUI-specific parameter to display a button. 355 356 Can be associated with an image by specifying an image_path 357 (i.e. location of an image suitable for PIL, e.g. a PNG, TIFF, or 358 JPEG image) and optionally a size (width,height) tuple. 359 360 Note that the button size can also be set when there is no image, 361 but instead of being presumed to be in pixels, it is presumed to 362 be in text units (a Tkinter feature: see 363 e.g. http://effbot.org/tkinterbook/button.htm). Therefore, to 364 place two identically sized buttons next to each other, with one 365 displaying text and the other an image, you first have to convert 366 one of the sizes to the other's units. 367 """ 368 # CEBALERT: we should probably solve the above for users, 369 # but what a pain! 370 __slots__ = ['image_path','size','_hack'] 371
372 - def __init__(self,default=None,image_path=None,size=None,**params):
373 Callable.__init__(self,default=default,**params) 374 self.image_path = image_path 375 self.size = size 376 self._hack = []
377 378 # CB: would create the photoimage on construction and store it as an 379 # attribute, except that it gives "RuntimeError: Too early to create 380 # image". Must be happening before tk starts, or something. So 381 # instead, return image on demand. Also, because of PIL bug (see 382 # topoconsole.py "got to keep references to the images") we store 383 # a reference to the image each time in _hack.
384 - def get_image(self):
385 """ 386 Return an ImageTk.PhotoImage of the image at image_path 387 (or None if image_path is None or an Image cannot be created). 388 """ 389 image = None 390 if self.image_path: 391 image=ImageTk.PhotoImage(ImageOps.fit( 392 Image.open(resolve_path(self.image_path)),self.size or (32,32))) 393 self._hack.append(image) 394 395 return image
396 397 398 399 400 # Note that TkParameterized extends TkParameterizedBase by adding 401 # widget-drawing abilities; documentation for using these classes 402 # begins at a more useful and simple level in TkParameterized (the 403 # documentation for TkParameterizedBase is more complex).
404 -class TkParameterizedBase(Parameterized):
405 """ 406 A Parameterized subclass that maintains Tkinter.Variable shadows 407 (proxies) of its Parameters. The Tkinter Variable shadows are kept 408 in sync with the Parameter values, and vice versa. 409 410 Optionally performs the same for an *additional* shadowed 411 Parameterized (extraPO). The Parameters of the extra 412 shadowed PO are available via this object (via both the usual 413 'dot' attribute access and dedicated parameter accessors 414 declared in this class). 415 416 The Tkinter.Variable shadows for this Parameterized and any 417 extra shadowed one are available under their corresponding 418 parameter names in the _tkvars dictionary. 419 420 (See note 1 for complications arising from name clashes.) 421 422 423 Parameters being represented by TkParameterizedBase also 424 have a 'translators' dictionary, allowing mapping between string 425 representations of the objects and the objects themselves (for use 426 with e.g. a Tkinter.OptionMenu). More information about the 427 translators is available from specific translator-related methods 428 in this class. 429 430 431 Notes 432 ===== 433 434 (1) (a) There is an order of precedance for parameter lookup: 435 this PO > shadowed PO. 436 437 Therefore, if you create a Parameter for this PO 438 with the same name as one of the shadowed PO's Parameters, only 439 the Parameter on this PO will be shadowed. 440 441 Example: 'name' is a common attribute. As a 442 Parameterized, this object has a 'name' Parameter. Any 443 shadowed PO will also have a 'name' Parameter. By default, 444 this object's name will be shadowed at the expense of the name 445 of the extra shadowed PO. 446 447 The precedence order can be reversed by setting the attribute 448 'self_first' on this object to False. 449 450 451 (b) Along the same lines, an additional complication can arise 452 relating specifically to 'dot' attribute lookup. For 453 instance, a sublass of TkParameterized might also 454 inherit from Tkinter.Frame. Frame has many of its own 455 attributes, including - for example - 'size'. If we shadow a 456 Parameterized that has a 'size' Parameter, the 457 Parameterized's size Parameter will not be available as 458 .size because ('dot') attribute lookup begins on the local 459 object and is not overridden by 'self_first'. Using the 460 parameter accessors .get_parameter_object('size') or 461 .get_parameter_value('size') (and the equivalent set versions) 462 avoids this problem. 463 464 465 466 (2) If a shadowed PO's Parameter value is modified elsewhere, the 467 Tkinter Variable shadow is NOT updated until that Parameter value 468 or shadow value is requested from this object. Thus requesting the 469 value will always return an up-to-date result, but any GUI display 470 of the Variable might display a stale value (until a GUI refresh 471 takes place). 472 """ 473 474 # CEBNOTE: avoid (as far as possible) defining Parameters for this 475 # object because they might clash with Parameters of objects it 476 # eventually represents. 477 478 479 # CEBNOTE: Regarding note 1a above...it would be possible - with 480 # some extra work - to shadow Parameters that are duplicated on 481 # this PO and extraPO. Among other changes, things like the 482 # _tkvars dict would need different (e.g. name on this PO and 483 # name on extraPO) 484 485 # CEBNOTE: Regarding note 1b...might be less confusing if we stop 486 # parameters of shadowed POs being available as attributes (and 487 # use only the parameter access methods instead). But having them 488 # available as attributes is really convenient. 489 490 # CEBNOTE: Regarding note 2 above...if it becomes a problem, we 491 # could have some autorefresh of the vars or a callback of some 492 # kind in the parameterized object itself. E.g a 493 # refresh-the-widgets-on-focus-in method could make the gui in 494 # sync with the actual object (so changes outside the gui could 495 # show up when a frame takes focus). Or there could be timer 496 # process. 497 498 # CEB: because of note 1, attributes of this class should have 499 # names that are unlikely to clash (or they should be private); 500 # this will make it easier for creators and users of subclasses to 501 # avoid name clashes. 502 503 # must exist *before* an instance is init'd 504 # (for __getattribute__) 505 _extraPO = None 506 507 # CEBALERT: Parameterized repr method leads to recursion error.
508 - def __repr__(self): return object.__repr__(self)
509 510
511 - def _setup_params(self,**params):
512 """ 513 Parameters that are not in this object itself but are in the 514 extraPO get set on extraPO. Then calls Parameterized's 515 _setup_params(). 516 """ 517 ### a parameter might be passed in for one of the extra_pos; 518 ### if a key in the params dict is not a *parameter* of this 519 ### PO, then try it on the extra_pos 520 for n,p in params.items(): 521 if n not in self.params(): 522 self.set_parameter_value(n,p) 523 del params[n] 524 525 Parameterized._setup_params(self,**params)
526 527 # CEBALERT: rename extraPO...but to what? 528 # Rename change_PO() and anything else related.
529 - def __init__(self,extraPO=None,self_first=True,live_update=True,**params):
530 """ 531 532 533 Translation between displayed values and objects 534 ------------------------------------------------ 535 536 A Parameter has a value, but that might need some processing 537 to become a value suitable for display. For instance, the 538 SheetMask() object <SheetMask SheetMask0001923> ... 539 540 541 542 Important attributes 543 -------------------- 544 545 * _extraPO 546 547 548 * self_first 549 Determines precedence order for Parameter lookup: 550 if True, Parameters of this object take priority whenever 551 there is a name clash; if False, Parameters of extraPO take 552 priority. 553 554 555 556 557 * obj2str_fn & str2obj_fn 558 559 * translator_creators 560 561 (Note that in the various dictionaries above, the entry for 562 Parameter serves as a default, since keys are looked up by 563 class, so any Parameter type not specifically listed will be 564 covered by the Parameter entry.) 565 566 567 568 * _tkvars 569 570 571 """ 572 if not (extraPO is None or isinstance(extraPO,ParameterizedMetaclass) \ 573 or isinstance(extraPO,Parameterized)): 574 raise TypeError("%s is not a Parameterized instance or class."%extraPO) 575 576 # make self.first etc private 577 578 self._live_update = live_update 579 self.self_first = self_first 580 581 ## Which Tkinter Variable to use for each Parameter type 582 # (Note that, for instance, we don't include Number:DoubleVar. 583 # This is because we use Number to control the type, so we 584 # don't need restrictions from DoubleVar.) 585 self._param_to_tkvar = {Boolean:T.BooleanVar, 586 Parameter:T.StringVar} 587 588 # CEBALERT: Parameter is the base parameter class, but ... 589 # at least need a test that will fail when a new param type added 590 # Rename 591 self.trans={Parameter:Eval_ReprTranslator, 592 Dynamic:Eval_ReprTranslator, 593 ObjectSelector:String_ObjectTranslator, 594 ClassSelector:CSPTranslator, 595 Number:Eval_ReprTranslator, 596 Boolean:BoolTranslator, 597 String:DoNothingTranslator, 598 List:ListTranslator, 599 HookList:ListTranslator} # CBALERT: sort out inherit. 600 601 self.change_PO(extraPO) 602 super(TkParameterizedBase,self).__init__(**params)
603 604
605 - def change_PO(self,extraPO):
606 """ 607 Shadow the Parameters of extraPO. 608 """ 609 self._extraPO = extraPO 610 self._tkvars = {} 611 self.translators = {} 612 613 # (reverse list to respect precedence) 614 for PO in self._source_POs()[::-1]: 615 self._init_tkvars(PO)
616 617
618 - def _init_tkvars(self,PO):
619 """ 620 Create Tkinter Variable shadows of all Parameters of PO. 621 """ 622 for name,param in PO.params().items(): 623 self._create_tkvar(PO,name,param)
624 625
626 - def _create_tkvar(self,PO,name,param_obj):
627 """ 628 Add _tkvars[name] to represent the parameter object with the specified name. 629 630 The appropriate Variable is used for each Parameter type. 631 632 Also adds tracing mechanism to keep the Variable and Parameter 633 values in sync, and updates the translator dictionary to map 634 string representations to the objects themselves. 635 """ 636 # CEBALERT: should probably delete any existing tkvar for name 637 self._create_translator(name,param_obj) 638 639 tkvar = lookup_by_class(self._param_to_tkvar,type(param_obj))() 640 self._tkvars[name] = tkvar 641 642 # overwrite Variable's set() with one that will handle 643 # transformations to string 644 tkvar._original_set = tkvar.set 645 tkvar.set = lambda v,x=name: self._tkvar_set(x,v) 646 647 tkvar.set(self.get_parameter_value(name,PO)) 648 tkvar._last_good_val=tkvar.get() # for reverting 649 tkvar.trace_variable('w',lambda a,b,c,p_name=name: self._handle_gui_set(p_name)) 650 # CB: Instead of a trace, could we override the Variable's 651 # set() method i.e. trace it ourselves? Or does too much 652 # happen in tcl/tk for that to work? 653 654 # Override the Variable's get() method to guarantee an 655 # out-of-date value is never returned. In cases where the 656 # tkinter val is the most recently changed (i.e. when it's 657 # edited in the gui, resulting in a trace_variable being 658 # called), the _original_get() method is used. 659 # CEBALERT: what about other users of the variable? Could they 660 # be surprised by the result from get()? 661 tkvar._original_get = tkvar.get 662 tkvar.get = lambda x=name: self._tkvar_get(x)
663 664 665 ################################################################################ 666 # 667 ################################################################################ 668
669 - def _handle_gui_set(self,p_name):
670 """ 671 * The callback to use for all GUI variable traces/binds * 672 """ 673 if self._live_update: 674 self._update_param_from_tkvar(p_name)
675 676
677 - def _tkvar_set(self,param_name,val):
678 """ 679 Set the tk variable to (the possibly transformed-to-string) val. 680 """ 681 self.debug("_tkvar_set(%s,%s)"%(param_name,val)) 682 val = self._object2string(param_name,val) 683 tkvar = self._tkvars[param_name] 684 tkvar._original_set(val) # trace not called because we're already in trace,
685 # and tk disables trace activation during trace 686 687 688 # CB: separate into update and get?
689 - def _tkvar_get(self,param_name):
690 """ 691 Return the value of the tk variable representing param_name. 692 693 (Before returning the variable's value, ensures it's up to date.) 694 """ 695 tk_val = self._tkvars[param_name]._original_get() 696 po_val = self.get_parameter_value(param_name) 697 698 po_stringrep = self._object2string(param_name,po_val) 699 700 if not self.translators[param_name].last_string2object_failed and not tk_val==po_stringrep: 701 self._tkvars[param_name]._original_set(po_stringrep) 702 return tk_val
703 704
705 - def _tkvar_changed(self,name):
706 """ 707 Return True if the displayed value does not equal the object's 708 value (and False otherwise). 709 """ 710 self.debug("_tkvar_changed(%s)"%name) 711 displayed_value = self._string2object(name,self._tkvars[name]._original_get()) 712 object_value = self.get_parameter_value(name) #getattr(self._extraPO,name) 713 714 # use equality check then identity check because e.g. val 715 # starts at 0.5, type 0.8, then type 0.5, need that to be 716 # changed is False, but some types cannot be equality compared 717 # (can be identity compared). 718 # CEBALERT: need to add a unit test to ensure this keeps working. 719 # Plus, I need to think about this, because while the above is 720 # true for floats, identity tests make more sense for many types 721 # (i.e. you want to know the object is the same). 722 try: 723 if displayed_value != object_value: 724 changed = True 725 else: 726 changed = False 727 except: 728 if displayed_value is not object_value: 729 changed = True 730 else: 731 changed = False 732 733 self.debug("..._v_c return %s"%changed) 734 return changed
735 736
737 - def _update_param_from_tkvar(self,param_name):
738 """ 739 Attempt to set the parameter represented by param_name to the 740 value of its corresponding Tkinter Variable. 741 742 If setting the parameter fails (e.g. an inappropriate value 743 is set for that Parameter type), the Variable is reverted to 744 its previous value. 745 746 (Called by the Tkinter Variable's trace_variable() method.) 747 """ 748 self.debug("TkPOb._update_param_from_tkvar(%s)"%param_name) 749 750 parameter,sourcePO=self.get_parameter_object(param_name,with_source=True) 751 752 ### can only edit constant parameters for class objects 753 if parameter.constant is True and not isinstance(sourcePO,type): 754 return ### HIDDEN 755 756 tkvar = self._tkvars[param_name] 757 758 if self._tkvar_changed(param_name): 759 # don't attempt to set if there was a string-to-object translation error 760 if self.translators[param_name].last_string2object_failed: 761 return ### HIDDEN 762 763 # (use _original_get() because we don't want the tkvar to be reset to 764 # the parameter's current value!) 765 val = self._string2object(param_name,tkvar._original_get()) 766 767 try: 768 self._set_parameter(param_name,val) 769 except: # everything 770 tkvar.set(tkvar._last_good_val) 771 raise # whatever the parameter-setting error was 772 773 self.debug("set %s to %s"%(param_name,val)) 774 775 if hasattr(tkvar,'_on_modify'): 776 tkvar._on_modify() 777 778 ### call any function associated with GUI set() 779 if hasattr(tkvar,'_on_set'): 780 781 # CEBALERT: provide a way of allowing other gui components 782 # to figure out where a callback error might have come 783 # from. Callback instances (the Callback class is defined 784 # in Tkinter.py) store a widget, but often it appears to 785 # be the Tk instance - which is of no use in later 786 # determining where an error might have originated. 787 global _last_one_set 788 if hasattr(self,'master'): 789 _last_one_set = self.master 790 791 tkvar._on_set()
792 793 794 ################################################################################ 795 # 796 ################################################################################ 797 798 799
800 - def _source_POs(self):
801 """ 802 Return a list of Parameterizeds in which to find 803 Parameters. 804 805 The list is ordered by precedence, as defined by self_first. 806 """ 807 if not self._extraPO: 808 sources = [self] 809 elif self.self_first: 810 sources = [self,self._extraPO] 811 else: 812 sources = [self._extraPO,self] 813 return sources
814 815 816 817
818 - def get_source_po(self,name):
819 """ 820 Return the Parameterized which contains the parameter 'name'. 821 """ 822 sources = self._source_POs() 823 824 for po in sources: 825 if name in po.params(): 826 return po 827 828 829 raise AttributeError(self._attr_err_msg(name,sources))
830 831
832 - def get_parameter_object(self,name,parameterized_object=None,with_source=False):
833 """ 834 Return the Parameter *object* (not value) specified by name, 835 from the source_POs in this object (or the 836 specified parameterized_object). 837 838 If with_source=True, returns also the source parameterizedobject. 839 """ 840 source = parameterized_object or self.get_source_po(name) 841 parameter_object = source.params()[name] 842 843 if not with_source: 844 return parameter_object 845 else: 846 return parameter_object,source
847 848 849 850 ###################################################################### 851 # Attribute lookup 852 853 ##### these guarantee only to get/set parameters #####
854 - def get_parameter_value(self,name,parameterized_object=None):
855 """ 856 Return the value of the parameter specified by name. 857 858 If a parameterized_object is specified, looks for the parameter there. 859 Otherwise, looks in the source_POs of this object. 860 """ 861 source = parameterized_object or self.get_source_po(name) 862 return source.get_value_generator(name)
863 864 # CEBALERT: shouldn't this use __set_parameter? Presumably doing 865 # that kind of thing is part of the cleanup required in this file.
866 - def set_parameter_value(self,name,val,parameterized_object=None):
867 """ 868 Set the value of the parameter specified by name to val. 869 870 Updates the corresponding tkvar. 871 """ 872 source = parameterized_object or self.get_source_po(name) 873 object.__setattr__(source,name,val) 874 875 # update the tkvar 876 if name in self._tkvars: 877 self._tkvars[name]._original_set(self._object2string(name,val))
878 879 880 ###################################################### 881 882 ########## these lookup attributes in order ########## 883 # (i.e. you could get attribute of self rather than a parameter) 884 # (might remove these to save confusion: they are useful except when 885 # someone would be surprised to get an attribute of e.g. a Frame (like 'size') when 886 # they were expecting to get one of their parameters. Also, means you can't set 887 # an attribute a on self if a exists on one of the shadowed objects) 888 # (also they (have to) ignore self_first) 889
890 - def __getattribute__(self,name):
891 """ 892 If the attribute is found on this object, return it. Otherwise, 893 return the attribute from the extraPO, if it exists. 894 If there is no match, raise an attribute error. 895 """ 896 try: 897 return object.__getattribute__(self,name) 898 except AttributeError: 899 extraPO = object.__getattribute__(self,'_extraPO') 900 901 if hasattr(extraPO,name): 902 return getattr(extraPO,name) # HIDDEN! 903 904 _attr_err_msg = object.__getattribute__(self,'_attr_err_msg') 905 906 raise AttributeError(_attr_err_msg(name,[self,extraPO]))
907 908
909 - def __setattr__(self,name,val):
910 """ 911 If the attribute already exists on this object, set it. If the attribute 912 is found on the extraPO, set it there. Otherwise, set the 913 attribute on this object (i.e. add a new attribute). 914 """ 915 # use dir() not hasattr() because hasattr uses __getattribute__ 916 if name in dir(self): 917 918 if name in self.params(): 919 self.set_parameter_value(name,val,self) 920 else: 921 object.__setattr__(self,name,val) 922 923 elif name in dir(self._extraPO): 924 925 if name in self._extraPO.params(): 926 self.set_parameter_value(name,val,self._extraPO) 927 else: 928 object.__setattr__(self._extraPO,name,val) 929 930 else: 931 932 # name not found, so set on this object 933 object.__setattr__(self,name,val)
934 ####################################################### 935 936 937 ###################################################################### 938 939 940 941 942 943 ###################################################################### 944 # Translation between GUI (strings) and true values 945
946 - def _create_translator(self,name,param):
947 self.debug("_create_translator(%s,%s)"%(name,param)) 948 949 translator_type = lookup_by_class(self.trans,type(param)) 950 951 # Dynamic parameters only *might* contain a 952 # dynamic value; if such a parameter really is dynamic, we 953 # overwrite any more specific class found above 954 # (e.g. a Number with a dynamic value will have a numeric 955 # translator from above, so we replace that) 956 if param_is_dynamically_generated(param,self.get_source_po(name)) or name in self.allow_dynamic: 957 translator_type = self.trans[Dynamic] 958 959 self.translators[name]=translator_type(param,initial_value=self.get_parameter_value(name)) 960 961 self.translators[name].msg_handler = self.msg_handler
962 963 964 965 966 # CEBALERT: doc replace & rename to plastic or something
967 - def _object2string(self,param_name,obj,replace=True):
968 """ 969 If val is one of the objects in param_name's translator, 970 translate to the string. 971 """ 972 self.debug("object2string(%s,%s)"%(param_name,obj)) 973 translator = self.translators[param_name] 974 975 if not replace: 976 translator=copy.copy(translator) 977 978 return translator.object2string(obj)
979 980
981 - def _string2object(self,param_name,string):
982 """ 983 Change the given string for the named parameter into an object. 984 985 If there is a translator for param_name, translate the string 986 to the object; otherwise, call convert_string2obj on the 987 string. 988 """ 989 self.debug("string2object(%s,%s)"%(param_name,string)) 990 translator = self.translators[param_name] 991 o = translator.string2object(string) 992 self.debug("...s2o return %s, type %s"%(o,type(o))) 993 return o
994 995 ###################################################################### 996
997 - def _attr_err_msg(self,attr_name,objects):
998 """ 999 Helper method: return the 'attr_name is not in objects' message. 1000 """ 1001 if not hasattr(objects,'__len__'):objects=[objects] 1002 1003 error_string = "'%s' not in %s"%(attr_name,objects.pop(0)) 1004 1005 for o in objects: 1006 error_string+=" or %s"%o 1007 1008 return error_string
1009 1010
1011 - def _set_parameter(self,param_name,val):
1012 """ 1013 Helper method: 1014 """ 1015 # use set_in_bounds if it exists 1016 # i.e. users of widgets get their values cropped 1017 # (no warnings/errors, so e.g. a string in a 1018 # tagged slider just goes to the default value) 1019 # CEBALERT: set_in_bounds not valid for POMetaclass? 1020 parameter,sourcePO=self.get_parameter_object(param_name,with_source=True) 1021 1022 # CEBHACKALERT: GeneratorSheet.input_generator should be a 1023 # property? But it's a Parameter... 1024 # Setting input_generator rather than calling set_input_generator() 1025 # is probably a trap... 1026 if param_name=='input_generator' and hasattr(sourcePO,'set_input_generator'): 1027 sourcePO.set_input_generator(val) 1028 elif hasattr(parameter,'set_in_bounds') and isinstance(sourcePO,Parameterized): 1029 parameter.set_in_bounds(sourcePO,val) 1030 else: 1031 setattr(sourcePO,param_name,val)
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
1042 -class TkParameterized(TkParameterizedBase):
1043 """ 1044 Provide widgets for Parameters of itself and up to one additional 1045 Parameterized instance or class. 1046 1047 A subclass that defines a Parameter p can display it appropriately 1048 for manipulation by the user simply by calling 1049 pack_param('p'). The GUI display and the actual Parameter value 1050 are automatically synchronized (though see technical notes in 1051 TkParameterizedBase's documentation for more details). 1052 1053 In general, pack_param() adds a Tkinter.Frame containing a label 1054 and a widget: 1055 1056 --------------------- The Parameter's 1057 | | 'representation' 1058 | [label] [widget] |<----frame 1059 | | 1060 --------------------- 1061 1062 In the same way, an instance of this class can be used to display 1063 the Parameters of an existing object. By passing in extraPO=x, 1064 where x is an existing Parameterized instance or class, a 1065 Parameter q of x can also be displayed in the GUI by calling 1066 pack_param('q'). 1067 1068 For representation in the GUI, Parameter values might need to be 1069 converted between their real values and strings used for display 1070 (e.g. for a ClassSelector, the options are really class objects, 1071 but the user is presented with a list of strings to choose 1072 from). Such translation is handled and documented in the 1073 TkParameterizedBase; the default behaviors can be overridden if 1074 required. 1075 1076 (Note that this class simply adds widget drawing to 1077 TkParameterizedBase. More detail about the shadowing of 1078 Parameters is available in the documentation for 1079 TkParameterizedBase.) 1080 """ 1081 1082 # CEBNOTE: as for TkParameterizedBase, avoid declaring 1083 # Parameters here (to avoid name clashes with any additional 1084 # Parameters this might eventually be representing). 1085 1086 pretty_parameters = Boolean(default=True,precedence=-1, 1087 doc="""Whether to format parameter names, or display the 1088 variable names instead. 1089 1090 Example use: 1091 TkParameterized.pretty_parameters=False 1092 1093 (This causes all Parameters throughout the GUI to be displayed 1094 with variable names.) 1095 """) 1096 1097
1098 - def __init__(self,master,extraPO=None,self_first=True, 1099 msg_handler=None,**params):
1100 """ 1101 Initialize this object with the arguments and attributes 1102 described below: 1103 1104 extraPO: optional Parameterized object for which to shadow 1105 Parameters (in addition to Parameters of this object; see 1106 superclass) 1107 1108 self_first: if True, Parameters on this object take precedence 1109 over any ones with the same names in the extraPO (i.e. what 1110 to do if there are name clashes; see superclass) 1111 1112 1113 Important attributes 1114 ==================== 1115 1116 * param_immediately_apply_change 1117 1118 Some types of Parameter are represented with widgets where 1119 a complete change can be instantaneous (e.g. when 1120 selecting from an option list, the selection should be 1121 applied straightaway). Others are represented with widgets 1122 that do not finish their changes instantaneously 1123 (e.g. entry into a text box is not considered finished 1124 until Return is pressed). 1125 1126 * widget_creators 1127 1128 A dictionary of methods to create a widget for each Parameter 1129 type. For special widget options (specific to each particular 1130 parameter), see the corresponding method's docstring. 1131 1132 * representations 1133 1134 After pack_param() is called, a Parameter's representation 1135 consists of the tuple (frame,widget,label,pack_options) - the 1136 enclosing frame, the value-containing widget, the label 1137 holding the Parameter's name, and any options supplied for 1138 pack(). These can all be accessed through the representations 1139 dictionary, under the Parameter's name. 1140 """ 1141 self.master = master 1142 self.msg_handler = msg_handler 1143 1144 self.representations = {} 1145 1146 # CEBALER: doc 1147 self.allow_dynamic = [] 1148 1149 self.param_immediately_apply_change = {Boolean:True, 1150 Selector:True, 1151 Number:False, 1152 Parameter:False, 1153 Filename:True} 1154 1155 TkParameterizedBase.__init__(self,extraPO=extraPO, 1156 self_first=self_first, 1157 **params) 1158 1159 self.balloon = Balloon(master) 1160 1161 # CEBALERT: what about subclasses of Number (e.g. Integer, 1162 # which should get a slider that jumps between integers... 1163 # maybe that already happens)? 1164 self.widget_creators = { 1165 Boolean:self._create_boolean_widget, 1166 Dynamic:self._create_string_widget, 1167 Number:self._create_number_widget, 1168 Button:self._create_button_widget, 1169 String:self._create_string_widget, 1170 Selector:self._create_selector_widget, 1171 List:self._create_list_widget, 1172 HookList:self._create_list_widget, 1173 Filename:self._create_fileselector_widget, 1174 } 1175 1176 self.representations = {} 1177 1178 1179 # CEBNOTE: it would be nice to sort out menus properly 1180 # (i.e. parameterize them) 1181 self.popup_menu = Menu(master, tearoff=0) 1182 self.dynamic_var = T.BooleanVar() 1183 self.popup_menu.add("checkbutton",indexname="dynamic", 1184 label="Enter dynamic value?", 1185 state="disabled",command=self._switch_dynamic, 1186 variable=self.dynamic_var) 1187 1188 1189 ### Right-click menu for widgets 1190 master.option_add("*Menu.tearOff", "0") 1191 self.menu = Menu(master) 1192 self.menu.insert_command('end',label='Properties', 1193 command=lambda:self._edit_PO_in_currently_selected_widget())
1194 1195 1196
1197 - def _right_click(self, event, widget):
1198 """ 1199 Popup the right-click menu. 1200 """ 1201 self._currently_selected_widget = widget 1202 1203 # need an actual mechanism for populating the menu, rather than this!! 1204 ### copied from edit_PO_in_currently... 1205 param_name = None 1206 for name,representation in self.representations.items(): 1207 if self._currently_selected_widget is representation['widget']: 1208 param_name=name 1209 break 1210 # CEBALERT: should have used get_parameter_value(param_name)? 1211 PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get()) 1212 ### 1213 1214 if hasattr(PO_to_edit,'params'): 1215 self.menu.tk_popup(event.x_root, event.y_root)
1216 1217 1218 # CEBALERT: rename
1220 """ 1221 Open a new window containing a ParametersFrame (actually, a 1222 type(self)) for the PO in _currently_selected_widget. 1223 """ 1224 # CEBALERT: simplify this lookup by value 1225 param_name = None 1226 for name,representation in self.representations.items(): 1227 if self._currently_selected_widget is representation['widget']: 1228 param_name=name 1229 break 1230 1231 # CEBALERT: should have used get_parameter_value(param_name)? 1232 PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get()) 1233 1234 parameter_window = AppWindow(self) 1235 parameter_window.title(PO_to_edit.name+' parameters') 1236 1237 ### CEBALERT: confusing? ### 1238 title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")") 1239 title.pack(side = "top") 1240 self.balloon.bind(title,getdoc(self.get_parameter_object(param_name))) 1241 ############################ 1242 1243 # uh-oh 1244 if not isinstance(self,ParametersFrame): 1245 p_type = ParametersFrameWithApply 1246 parameter_frame = p_type(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler) 1247 1248 else: 1249 p_type = type(self) 1250 parameter_frame = p_type(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler,on_set=self.on_set,on_modify=self.on_modify) 1251 1252 parameter_frame.pack()
1253 1254 1255
1256 - def _update_dynamic_menu_entry(self,param_name):
1257 """Keep track of status of dynamic entry.""" 1258 param,po = self.get_parameter_object(param_name,with_source=True) 1259 currently_dynamic = param_is_dynamically_generated(param,po) 1260 if hasattr(param,'_value_is_dynamic') and not currently_dynamic: 1261 self._right_click_param = param_name 1262 state = "normal" 1263 else: 1264 self._right_click_param = None 1265 state = "disabled" 1266 self.popup_menu.entryconfig("dynamic",state=state) 1267 self.dynamic_var.set(currently_dynamic or \ 1268 param_name in self.allow_dynamic)
1269 1270
1271 - def _param_right_click(self,event,param_name):
1272 """Display a popup menu when user right clicks on a parameter.""" 1273 self._update_dynamic_menu_entry(param_name) 1274 self.popup_menu.tk_popup(event.x_root,event.y_root)
1275 1276 1277 1278 ################################################################################ 1279 # 1280 ################################################################################ 1281
1282 - def _update_param_from_tkvar(self,param_name,force=False):
1283 """ 1284 Prevents the superclass's _update_param_from_tkvar() method from being 1285 called unless: 1286 1287 * param_name is a Parameter type that has changes immediately 1288 applied (see doc for param_immediately_apply_change 1289 dictionary); 1290 1291 * force is True. 1292 1293 (I.e. to update a parameter for which 1294 param_immediately_apply_change[type(parameter)]==False, call 1295 this method with force=True. E.g. when <Return> is pressed in 1296 a text box, this method is called with force=True.) 1297 """ 1298 self.debug("TkPO._update_param_from_tkvar(%s)"%param_name) 1299 1300 param_obj = self.get_parameter_object(param_name) 1301 1302 if not lookup_by_class(self.param_immediately_apply_change, 1303 type(param_obj)) and not force: 1304 return 1305 else: 1306 super(TkParameterized,self)._update_param_from_tkvar(param_name)
1307 1308
1309 - def _handle_gui_set(self,p_name,force=False):
1310 """Override the superclass's method to X and allow status indications.""" 1311 #logging.info("%s._handle_gui_set(%s,force=%s)"%(self,p_name,force)) 1312 if self._live_update: 1313 self._update_param_from_tkvar(p_name,force) 1314 1315 self._indicate_tkvar_status(p_name)
1316 1317 1318 1319 #### Simulate GUI actions
1320 - def gui_set_param(self,param_name,val):
1321 """Simulate setting the parameter in the GUI.""" 1322 self._tkvar_set(param_name,val) # ERROR: presumably calls trace stuff twice 1323 self._handle_gui_set(param_name,force=True)
1324
1325 - def gui_get_param(self,param_name):
1326 """Simulate getting the parameter in the GUI.""" 1327 return self._tkvars[param_name].get()
1328 #### 1329 1330 1331 ################################################################################ 1332 # End 1333 ################################################################################ 1334 1335 1336 1337 1338 1339 ################################################################################ 1340 # 1341 ################################################################################ 1342 1343 # some refactoring required: should be a base method that's to do 1344 # with adding a representation for a parameter. then this stuff 1345 # would go in it.
1346 - def _post_add_param(self,param_name):
1347 l = self.representations[param_name]['label'] 1348 if l is not None: 1349 l.bind('<<right-click>>',lambda event: \ 1350 self._param_right_click(event,param_name))
1351 1352
1353 - def pack_param(self,name,parent=None,widget_options={}, 1354 on_set=None,on_modify=None,**pack_options):
1355 """ 1356 Create a widget for the Parameter name, configured according 1357 to widget_options, and pack()ed according to the pack_options. 1358 1359 Pop-up help is automatically set from the Parameter's doc. 1360 1361 The widget and label (if appropriate) are enlosed in a Frame 1362 so they can be manipulated as a single unit (see the class 1363 docstring). The representation 1364 (frame,widget,label,pack_options) is returned (as well as 1365 being stored in the representations dictionary). 1366 1367 1368 * parent is an existing widget that is to be the parent 1369 (master) of the widget created for this paramter. If no parent 1370 is specified, defaults to the originally supplied master 1371 (i.e. that set during __init__). 1372 1373 * on_set is an optional function to call whenever the 1374 Parameter's corresponding Tkinter Variable's trace_variable 1375 indicates that it has been set (this does not necessarily mean 1376 that the widget's value has changed). When the widget is created, 1377 the on_set method will be called (because the creation of the 1378 widget triggers a set in Tkinter). 1379 1380 * on_modify is an optional function to call whenever the 1381 corresponding Tkinter Variable is actually changed. 1382 1383 1384 widget_options specified here override anything that might have been 1385 set elsewhere (e.g. Button's size can be overridden here 1386 if required). 1387 1388 1389 1390 Examples of use: 1391 pack_param(name) 1392 pack_param(name,side='left') 1393 pack_param(name,parent=frame3,on_set=some_func) 1394 pack_param(name,widget_options={'width':50},side='top',expand='yes',fill='y') 1395 """ 1396 frame = T.Frame(parent or self.master) 1397 1398 widget,label = self._create_widget(name,frame,widget_options,on_set,on_modify) 1399 1400 # checkbuttons are 'widget label' rather than 'label widget' 1401 if widget.__class__ is T.Checkbutton: # type(widget) doesn't seem to work 1402 widget_side='left'; label_side='right' 1403 else: 1404 label_side='left'; widget_side='right' 1405 1406 if label: label.pack(side=label_side) # label can be None (e.g. for Button) 1407 widget.pack(side=widget_side,expand='yes',fill='x') 1408 1409 representation = {"frame":frame,"widget":widget, 1410 "label":label,"pack_options":pack_options, 1411 "on_set":on_set,"on_modify":on_modify, 1412 "widget_options":widget_options} 1413 self.representations[name] = representation 1414 1415 # If there's a label, balloon's bound to it - otherwise, bound 1416 # to enclosing frame. 1417 # (E.g. when there's [label] [text_box], only want balloon for 1418 # label (because maybe more help will be present for what's in 1419 # the box) but when there's [button], want popup help over the 1420 # button.) 1421 param_obj = self.get_parameter_object(name) 1422 help_text = getdoc(param_obj) 1423 1424 if param_obj.default is not None: 1425 # some params appear to have no docs!!! 1426 if help_text is not None: 1427 help_text+="\n\nDefault: %s"%self._object2string(name,param_obj.default,replace=False) 1428 1429 self.balloon.bind(label or frame,help_text) 1430 1431 frame.pack(pack_options) 1432 1433 self._indicate_tkvar_status(name) 1434 1435 self._post_add_param(name) 1436 return representation
1437 1438
1439 - def hide_param(self,name):
1440 """Hide the representation of Parameter 'name'.""" 1441 if name in self.representations: 1442 self.representations[name]['frame'].pack_forget()
1443 # CEBNOTE: forgetting label and widget rather than frame would 1444 # just hide while still occupying space (i.e. the empty frame 1445 # stays in place, and so widgets could later be inserted at 1446 # exact same place) 1447 #self.representations[name]['label'].pack_forget() 1448 #self.representations[name]['widget'].pack_forget() 1449 # unhide_param would need modifying too 1450 1451
1452 - def unhide_param(self,name,new_pack_options={}):
1453 """ 1454 Un-hide the representation of Parameter 'name'. 1455 1456 Any new pack options supplied overwrite the originally 1457 supplied ones, but the parent of the widget remains the same. 1458 """ 1459 # CEBNOTE: new_pack_options not really tested. Are they useful anyway? 1460 if name in self.representations: 1461 pack_options = self.representations[name]['pack_options'] 1462 pack_options.update(new_pack_options) 1463 self.representations[name]['frame'].pack(pack_options)
1464 1465
1466 - def unpack_param(self,name):
1467 """ 1468 Destroy the representation of Parameter 'name'. 1469 1470 (unpack and then pack a param if you want to put it in a different 1471 frame; otherwise simply use hide and unhide) 1472 """ 1473 f = self.representations[name]['frame'] 1474 w = self.representations[name]['widget'] 1475 l = self.representations[name]['label'] 1476 1477 del self.representations[name] 1478 1479 for x in [f,w,l]: 1480 x.destroy()
1481 1482
1483 - def repack_param(self,name):
1484 1485 f = self.representations[name]['frame'] 1486 w = self.representations[name]['widget'] 1487 l = self.representations[name]['label'] 1488 o = self.representations[name]['pack_options'] 1489 on_set = self.representations[name]['on_set'] 1490 on_modify = self.representations[name]['on_modify'] 1491 1492 w.destroy(); l.destroy() 1493 1494 param_obj,PO = self.get_parameter_object(name,with_source=True) 1495 self._create_tkvar(PO,name,param_obj) 1496 1497 self.pack_param(name,f,on_set=on_set,on_modify=on_modify,**o)
1498 1499
1500 - def _switch_dynamic(self,name=None,dynamic=False):
1501 1502 param_name = name or self._right_click_param 1503 param,po = self.get_parameter_object(param_name,with_source=True) 1504 if not hasattr(param,'_value_is_dynamic'): 1505 return 1506 1507 if param_name in self.allow_dynamic: 1508 self.allow_dynamic.remove(param_name) 1509 else: 1510 self.allow_dynamic.append(param_name) 1511 1512 self.repack_param(param_name)
1513 1514 1515 1516 1517 1518 ################################################################################ 1519 # 1520 ################################################################################ 1521 1522 1523 1524 1525 1526 ################################################################################ 1527 # WIDGET CREATION 1528 ################################################################################ 1529
1530 - def _create_widget(self,name,master,widget_options={},on_set=None,on_modify=None):
1531 """ 1532 Return widget,label for parameter 'name', each having the master supplied 1533 1534 The appropriate widget creation method is found from the 1535 widget_creators dictionary; see individual widget creation 1536 methods for details to each type of widget. 1537 """ 1538 # select the appropriate widget-creation method; 1539 # default is self._create_string_widget... 1540 widget_creation_fn = self._create_string_widget 1541 1542 param_obj,source_po = self.get_parameter_object(name,with_source=True) 1543 1544 if not (param_is_dynamically_generated(param_obj,source_po) or name in self.allow_dynamic): 1545 # ...but overwrite that with a more specific one, if possible 1546 for c in classlist(type(param_obj))[::-1]: 1547 if self.widget_creators.has_key(c): 1548 widget_creation_fn = self.widget_creators[c] 1549 break 1550 elif name not in self.allow_dynamic: 1551 self.allow_dynamic.append(name) 1552 1553 if on_set is not None: 1554 self._tkvars[name]._on_set=on_set 1555 1556 if on_modify is not None: 1557 self._tkvars[name]._on_modify=on_modify 1558 1559 widget=widget_creation_fn(master,name,widget_options) 1560 1561 # Is widget a button (but not a checkbutton)? If so, no label wanted. 1562 # CEBALERT 'notNonelabel': change to have a label with no text 1563 if is_button(widget): 1564 label = None 1565 else: 1566 label = T.Label(master,text=self._pretty_print(name)) 1567 1568 # disable widgets for constant params 1569 if param_obj.constant and isinstance(source_po,Parameterized): 1570 # (need to be able to set on class, hence check it's PO not POMetaclass 1571 widget.config(state='disabled') 1572 1573 widget.bind('<<right-click>>',lambda event: self._right_click(event, widget)) 1574 1575 return widget,label
1576 1577
1578 - def _create_button_widget(self,frame,name,widget_options):
1579 """ 1580 Return a FocusTakingButton to represent Parameter 'name'. 1581 1582 Buttons require a command, which should have been specified as the 1583 'on_set' function passed to pack_param(). 1584 1585 After creating a button for a Parameter param, self.param() also 1586 executes the button's command. 1587 1588 If the Button was declared with an image, the button will 1589 have that image (and no text); otherwise, the button will display 1590 the (possibly pretty_print()ed) name of the Parameter. 1591 """ 1592 try: 1593 command = self._tkvars[name]._on_set 1594 except AttributeError: 1595 raise TypeError("No command given for '%s' button."%name) 1596 1597 del self._tkvars[name]._on_set # because we use Button's command instead 1598 1599 # Calling the parameter (e.g. self.Apply()) is like pressing the button: 1600 self.__dict__["_%s_param_value"%name]=command 1601 # like setattr(self,name,command) but without tracing etc 1602 1603 # (...CEBNOTE: and so you can't edit a tkparameterizedobject 1604 # w/buttons with another tkparameterizedobject because the 1605 # button parameters all skip translation etc. Instead should 1606 # handle their translation. But we're not offering a GUI 1607 # builder so it doesn't matter.) 1608 1609 button = FocusTakingButton(frame,command=command) 1610 1611 button_param = self.get_parameter_object(name) 1612 1613 image = button_param.get_image() 1614 if image: 1615 button['image']=image 1616 #button['relief']='flat' 1617 else: 1618 button['text']=self._pretty_print(name) 1619 1620 1621 # and set size from Button 1622 #if size_param.size: 1623 # button['width']=size[0] 1624 # button['height']=size[1] 1625 1626 button.config(**widget_options) # widget_options override things from parameter 1627 return button
1628 1629
1630 - def _create_fileselector_widget(self,frame,name,widget_options):
1631 widget = HackFileEntry(frame,self._tkvars[name]) 1632 return widget
1633 1634
1635 - def update_selector(self,name):
1636 1637 if name in self.representations: 1638 widget_options = self.representations[name]["widget_options"] 1639 1640 new_range,widget_options = self._X(name,widget_options) 1641 1642 w = self.representations[name]['widget'] 1643 # hACK: tuple to work around strange list parsing tkinter/tcl 1644 w.configure(values=tuple(new_range)) # what a mess
1645 #w.configure(state='readonly') # CEBALERT: why necessary? 1646 # does it get changed somewhere else by mistake? e.g. does 1647 # plotgrouppanel switch disabled to normal and normal to 1648 # disabled without checking that normal isn't readonly? 1649 1650
1651 - def _X(self,name,widget_options):
1652 1653 # CEBALERT: need to document how & why people should use 'sort_fn_args' 1654 # and 'new_default' when calling pack_param(). Also, simplify it if 1655 # possible. 1656 self.translators[name].update() 1657 1658 new_range = self.translators[name].cache.keys() 1659 1660 if 'sort_fn_args' not in widget_options: 1661 # no sort specified: defaults to sort() 1662 new_range.sort() 1663 else: 1664 sort_fn_args = widget_options['sort_fn_args'] 1665 del widget_options['sort_fn_args'] 1666 if sort_fn_args is not None: 1667 new_range = keys_sorted_by_value_unique_None_safe(self.translators[name].cache,**sort_fn_args) 1668 1669 assert len(new_range)>0 # CB: remove 1670 1671 tkvar = self._tkvars[name] 1672 1673 1674 if 'new_default' in widget_options: 1675 if widget_options['new_default']: 1676 current_value = new_range[0] 1677 del widget_options['new_default'] 1678 else: 1679 current_value = self._object2string(name,self.get_parameter_value(name)) 1680 if current_value not in new_range: 1681 current_value = new_range[0] # whatever was there is out of date now 1682 1683 tkvar.set(current_value) 1684 return new_range,widget_options
1685 1686
1687 - def _create_selector_widget(self,frame,name,widget_options):
1688 """ 1689 Return a Tkinter.OptionMenu to represent Parameter 'name'. 1690 1691 In addition to Tkinter.OptionMenu's usual options, the 1692 following additional ones may be included in widget_options: 1693 1694 * sort_fn_args: if widget_options includes 'sort_fn_args', 1695 these are passed to the sort() method of the list of 1696 *objects* available for the parameter, and the names are 1697 displayed sorted in that order. If 'sort_fn_args' is not 1698 present, the default is to sort the list of names using its 1699 sort() method. 1700 1701 * new_default: if widget_options includes 'new_default':True, 1702 the currently selected value for the widget will be set 1703 to the first item in the (possibly sorted as above) range. 1704 Otherwise, the currently selected value will be left as the 1705 current value. 1706 """ 1707 #param = self.get_parameter_object(name) 1708 #self._update_translator(name,param) 1709 1710 ## sort the range for display 1711 # CEBALERT: extend OptionMenu so that it 1712 # (a) supports changing its option list (subject of a previous ALERT) 1713 # (b) supports sorting of its option list 1714 # (c) supports selecting a new default 1715 new_range,widget_options = self._X(name,widget_options) 1716 tkvar = self._tkvars[name] 1717 1718 # Combobox looks bad with standard theme on my ubuntu 1719 # (and 'changed' marker - blue text - not visible). 1720 w = Combobox(frame,textvariable=tkvar, 1721 values=new_range,state='readonly', 1722 **widget_options) 1723 1724 # Combobox (along with Checkbutton?) somehow sets its 1725 # associated textvariable without calling that textvariable's 1726 # set() method. Therefore, to update the Selector's help text 1727 # when an item is selected, we bind to the 1728 # <<ComboboxSelected>> event. 1729 def _combobox_updated(event,name=name): 1730 w = self.representations[name]['widget'] 1731 help_text = getdoc( 1732 self._string2object( 1733 name, 1734 self._tkvars[name]._original_get())) 1735 1736 self.balloon.bind(w,help_text)
1737 1738 w.bind("<<ComboboxSelected>>",_combobox_updated) 1739 1740 help_text = getdoc(self._string2object(name,tkvar._original_get())) 1741 self.balloon.bind(w,help_text) 1742 return w
1743
1744 - def _list_edit(self,param_name):
1745 val = self.get_parameter_value(param_name) 1746 1747 param_obj = self.get_parameter_object(param_name) 1748 class_=None 1749 if hasattr(param_obj,'class_') and hasattr(param_obj.class_,'get_range'): 1750 # i.e. we can represent with ClassSelector 1751 class_ = param_obj.class_ 1752 1753 parameterized_instance = list_to_parameterized(val,class_) 1754 1755 parameter_window = AppWindow(self) 1756 #parameter_window.title(PO_to_edit.name+' parameters') 1757 1758 parameter_frame = EditingParametersFrameWithApply(parameter_window,parameterized_object=parameterized_instance,show_labels=False,on_close=lambda:self._handle_gui_set(param_name)) 1759 1760 # CEBALERT: more dynamic class changes to make it impossible 1761 # to follow what is happening...ParametersFrame needs some 1762 # reorganization... 1763 def _forgotten_why(itself): 1764 ### refresh() 1765 po_val = self.get_parameter_value(param_name) 1766 po_stringrep = self._object2string(param_name,po_val) 1767 self._tkvars[param_name]._original_set(po_stringrep)
1768 ### 1769 1770 if _forgotten_why not in parameter_frame._apply_hooks: 1771 parameter_frame._apply_hooks.append(_forgotten_why) 1772 1773 1774 parameter_frame.pack() 1775 1776
1777 - def _create_list_widget(self,frame,name,widget_options):
1778 #param = self.get_parameter_object(name) 1779 #value = self.get_parameter_value(name) 1780 1781 X = lambda event=None,x=name: self._list_edit(x) 1782 1783 w = ListWidget(frame,variable=self._tkvars[name], 1784 cmd=X,**widget_options) 1785 #w = ListWidget(frame,variable=tkvar,**widget_options) 1786 1787 1788 #help_text = getdoc(self._string2object(name,tkvar._original_get())) 1789 #self.balloon.bind(w,help_text) 1790 return w
1791 1792 1793
1794 - def _create_number_widget(self,frame,name,widget_options):
1795 """ 1796 Return a TaggedSlider to represent parameter 'name'. 1797 1798 The slider's bounds are set to those of the Parameter. 1799 """ 1800 w = TaggedSlider(frame,variable=self._tkvars[name],**widget_options) 1801 param = self.get_parameter_object(name) 1802 1803 lower_bound,upper_bound = param.get_soft_bounds() 1804 1805 if upper_bound is not None and lower_bound is not None: 1806 # TaggedSlider needs BOTH bounds (neither can be None) 1807 w.set_bounds(lower_bound,upper_bound,inclusive_bounds=param.inclusive_bounds) 1808 1809 1810 # have to do the lookup because subclass might override default 1811 if not lookup_by_class(self.param_immediately_apply_change,type(param)): 1812 w.bind('<<TagReturn>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1813 w.bind('<<TagFocusOut>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1814 w.bind('<<SliderSet>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1815 1816 return w
1817 1818
1819 - def _create_boolean_widget(self,frame,name,widget_options):
1820 """Return a Tkinter.Checkbutton to represent parameter 'name'.""" 1821 # CB: might be necessary to pass actions to command option of Checkbutton; 1822 # could be cause of test pattern boolean not working? 1823 return T.Checkbutton(frame,variable=self._tkvars[name],**widget_options)
1824 1825
1826 - def _create_string_widget(self,frame,name,widget_options):
1827 """Return a Tkinter.Entry to represent parameter 'name'.""" 1828 widget = T.Entry(frame,textvariable=self._tkvars[name],**widget_options) 1829 param = self.get_parameter_object(name) 1830 if not lookup_by_class(self.param_immediately_apply_change,type(param)): 1831 widget.bind('<Return>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1832 widget.bind('<FocusOut>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1833 return widget
1834 1835 ################################################################################ 1836 # End WIDGET CREATION 1837 ################################################################################ 1838 1839 1840 1841 # CB: the colors etc for indication are only temporary
1842 - def _indicate_tkvar_status(self,param_name,status=None):
1843 """ 1844 Set the parameter's label to: 1845 - blue if the GUI value differs from that set on the object 1846 - red if the text doesn't translate to a correct value 1847 - black if the GUI and object have the same value 1848 """ 1849 if self.translators[param_name].last_string2object_failed: 1850 status = 'error' 1851 1852 self._set_widget_status(param_name,status)
1853 1854 1855 # this scheme is incompatible with tile 1856 # (tile having the right idea about how to do this kind of thing!)
1857 - def _set_widget_status(self,param_name,status):
1858 1859 if param_name in self.representations: 1860 if 'widget' in self.representations[param_name]: 1861 1862 widget = self.representations[param_name]['widget'] 1863 states = {'error' : 'red', 1864 'changed' : 'blue', 1865 None : 'black'} 1866 1867 if is_button(widget): 1868 # can't change state of button 1869 return 1870 1871 try: 1872 widget.config(foreground=states[status]) 1873 except T.TclError: #CEBALERT uh-oh 1874 pass
1875
1876 - def _pretty_print(self,s):
1877 """ 1878 Convert a Parameter name s to a string suitable for display, 1879 if pretty_parameters is True. 1880 """ 1881 if not self.pretty_parameters: 1882 return s 1883 else: 1884 n = s.replace("_"," ") 1885 n = n.capitalize() 1886 return n
1887 1888 1889 # CEBALERT: is there no "file entry" widget for Tkinter? I see there 1890 # is one for Tix, but while that is listed as being in Python's 1891 # standard library, it's rarely actually included... 1892 import tkFileDialog
1893 -class HackFileEntry(T.Frame):
1894 - def __init__(self,master,var,**config):
1895 T.Frame.__init__(self,master) 1896 self.var = var 1897 self.button = T.Button(self,textvariable=self.var,command=self.cmd) 1898 self.button.pack()
1899
1900 - def cmd(self):
1901 filename = tkFileDialog.askopenfilename() 1902 if filename!="": 1903 self.var.set(filename)
1904 1905 1906 ###################################################################### 1907 ###################################################################### 1908
1909 -class Translator(object):
1910 """ 1911 Abstract class that 1912 1913 Translators handle translation between objects and their 1914 string representations in the GUI. 1915 1916 last_string2object_failed is a flag that can be set to indicate that 1917 the last string-to-object translation failed. 1918 (TkParameterized checks this attribute for indicating errors to 1919 the user.) 1920 1921 """ 1922 last_string2object_failed = False # CEBALERT: why class attr? 1923
1924 - def __init__(self,param,initial_value=None):
1925 self.param = param 1926 self.msg_handler = None
1927
1928 - def string2object(self,string_):
1929 raise NotImplementedError
1930
1931 - def object2string(self,object_):
1932 raise NotImplementedError
1933
1934 - def _pass_out_msg(self,msg):
1935 if self.msg_handler: 1936 self.msg_handler.dynamicinfo(msg)
1937
1938 - def __copy__(self):
1939 """Copy only translator-specific state.""" 1940 new = self.__class__(self.param) 1941 new.last_string2object_failed = self.last_string2object_failed 1942 new.msg_handler = self.msg_handler 1943 return new
1944 1945
1946 -class DoNothingTranslator(Translator):
1947 """ 1948 Performs no translation. For use with Parameter types such as 1949 Boolean and String, where the representation 1950 of the object in the GUI is the object itself. 1951 """
1952 - def string2object(self,string_):
1953 return string_
1954
1955 - def object2string(self,object_):
1956 return object_
1957 1958
1959 -class BoolTranslator(DoNothingTranslator):
1960 - def string2object(self,string_):
1961 if string_=='None': 1962 return None 1963 else: 1964 return bool(string_)
1965 1966 1967 # Error messages: need to change how they're reported 1968 from parameterized import script_repr
1969 -class ListTranslator(Translator):
1970
1971 - def __init__(self,param,initial_value=None):
1972 super(ListTranslator,self).