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

Source Code for Module topo.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_change=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 9416 2008-10-08 19:36:54Z 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 sys 
 169  import copy 
 170  import decimal 
 171  import os.path 
 172  import ImageTk, Image, ImageOps 
 173  import Tkinter as T 
 174   
 175  from inspect import getdoc 
 176   
 177  from tkMessageBox import _show,QUESTION,YESNO 
 178  from Tile import Combobox 
 179  from scrodget import Scrodget 
 180   
 181  from parameterized import Parameterized,ParameterizedMetaclass,\ 
 182       classlist 
 183        
 184  from . import Boolean,String,Number,Selector,ClassSelector,\ 
 185       ObjectSelector,Callable,Dynamic,Parameter 
 186   
 187   
 188  # CEBALERT: copied from topo.misc.filepath, to make it clear what we 
 189  # need. I guess we should consider how much of topo.misc.filepath 
 190  # we might want in topo/param... 
 191  ######################################## 
 192  application_path = os.path.split(os.path.split(sys.executable)[0])[0] 
 193  output_path = application_path 
 194   
195 -def resolve_path(path,search_paths=[]):
196 """ 197 Find the path to an existing file, searching in the specified 198 search paths if the filename is not absolute, and converting a 199 UNIX-style path to the current OS's format if necessary. 200 201 To turn a supplied relative path into an absolute one, the path is 202 appended to each path in (search_paths+the current working 203 directory+the application's base path), in that order, until the 204 file is found. 205 206 (Similar to Python's os.path.abspath(), except more search paths 207 than just os.getcwd() can be used, and the file must exist.) 208 209 An IOError is raised if the file is not found anywhere. 210 """ 211 path = os.path.normpath(path) 212 213 if os.path.isabs(path): return path 214 215 all_search_paths = search_paths + [os.getcwd()] + [application_path] 216 217 paths_tried = [] 218 for prefix in set(all_search_paths): # does set() keep order? 219 try_path = os.path.join(os.path.normpath(prefix),path) 220 if os.path.isfile(try_path): return try_path 221 paths_tried.append(try_path) 222 223 raise IOError('File "'+os.path.split(path)[1]+'" was not found in the following place(s): '+str(paths_tried)+'.')
224 225 ######################################## 226 227 228
229 -def inverse(dict_):
230 """ 231 Return the inverse of dictionary dict_. 232 233 (I.e. return a dictionary with keys that are the values of dict_, 234 and values that are the corresponding keys from dict_.) 235 236 The values of dict_ must be unique. 237 """ 238 idict = dict([(value,key) for key,value in dict_.iteritems()]) 239 if len(idict)!=len(dict_): 240 raise ValueError("Dictionary has no inverse (values not unique).") 241 return idict
242 243
244 -def lookup_by_class(dict_,class_):
245 """ 246 Look for class_ or its superclasses in the keys of dict_; on 247 finding a match, return the value (return None if no match found). 248 249 Searches from the most immediate class to the most distant 250 (i.e. from class_ to the final superclass of class_). 251 """ 252 v = None 253 for c in classlist(class_)[::-1]: 254 if c in dict_: 255 v = dict_[c] 256 break 257 return v
258 259
260 -def keys_sorted_by_value(d):
261 """ 262 Return the keys of dictionary d sorted by value. 263 """ 264 # By Daniel Schult, 2004/01/23 265 # http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306 266 items=d.items() 267 backitems=[ [v[1],v[0]] for v in items] 268 backitems.sort() 269 return [ backitems[i][1] for i in range(0,len(backitems))]
270 271
272 -def keys_sorted_by_value_unique(d, **sort_kwargs):
273 """ 274 Return the keys of d, sorted by value. 275 276 The values of d must be unique (see inverse) 277 """ 278 values = d.values() 279 values.sort(**sort_kwargs) 280 i = inverse(d) 281 return [i[val] for val in values]
282 283
284 -def keys_sorted_by_value_unique_None_safe(d, **sort_kwargs):
285 """ 286 None is handled separately: if present, it always comes out at the 287 end of the list. Allows callers to use any sort function without 288 worrying that None will not match the other objects (e.g. for 289 using an attribute present on all the other objects, such as 290 precedence of sheets). 291 """ 292 if 'None' in d: 293 assert d['None'] is None 294 None_in_d = True 295 else: 296 None_in_d = False 297 298 if None_in_d: 299 del d['None'] 300 301 sorted_keys = keys_sorted_by_value_unique(d,**sort_kwargs) 302 303 if None_in_d: 304 d['None']=None 305 sorted_keys.append('None') 306 307 return sorted_keys
308 309
310 -def is_button(widget):
311 """ 312 Simple detection of Button-like widgets that are not Checkbuttons 313 (i.e. widgets that do not require a separate label). 314 """ 315 return 'command' in widget.config() and not hasattr(widget,'toggle')
316 317 318 # CEB: workaround for tkinter lagging behind tk (tk must have changed 319 # the type of a returned value). This is copied almost exactly from 320 # tkMessageBox. If there are other things like this, we could have the 321 # gui load some 'dynamic patches' to tkinter on startup, which could 322 # then be removed when tkinter is updated (they'd all be in one place, 323 # and no tkgui code would have to change).
324 -def askyesno(title=None, message=None, **options):
325 "Ask a question; return true if the answer is yes" 326 s = _show(title, message, QUESTION, YESNO, **options) 327 return str(s) == "yes"
328 329 330 331 # Buttons are not naturally represented by parameters? 332 # 333 # Maybe we should have a parent class that implements the 334 # non-Parameter specific stuff, then one that bolts on the 335 # Parameter-specific stuff, and then instead of Button we'd 336 # have TopoButton, or something like that...
337 -class Button(Callable):
338 """ 339 A GUI-specific parameter to display a button. 340 341 Can be associated with an image by specifying an image_path 342 (i.e. location of an image suitable for PIL, e.g. a PNG, TIFF, or 343 JPEG image) and optionally a size (width,height) tuple. 344 345 Note that the button size can also be set when there is no image, 346 but instead of being presumed to be in pixels, it is presumed to 347 be in text units (a Tkinter feature: see 348 e.g. http://effbot.org/tkinterbook/button.htm). Therefore, to 349 place two identically sized buttons next to each other, with one 350 displaying text and the other an image, you first have to convert 351 one of the sizes to the other's units. 352 """ 353 # CEBALERT: we should probably solve the above for users, 354 # but what a pain! 355 __slots__ = ['image_path','size','_hack'] 356
357 - def __init__(self,default=None,image_path=None,size=None,**params):
358 Callable.__init__(self,default=default,**params) 359 self.image_path = image_path 360 self.size = size 361 self._hack = []
362 363 # CB: would create the photoimage on construction and store it as an 364 # attribute, except that it gives "RuntimeError: Too early to create 365 # image". Must be happening before tk starts, or something. So 366 # instead, return image on demand. Also, because of PIL bug (see 367 # topoconsole.py "got to keep references to the images") we store 368 # a reference to the image each time in _hack.
369 - def get_image(self):
370 """ 371 Return an ImageTk.PhotoImage of the image at image_path 372 (or None if image_path is None or an Image cannot be created). 373 """ 374 image = None 375 if self.image_path: 376 image=ImageTk.PhotoImage(ImageOps.fit( 377 Image.open(resolve_path(self.image_path)),self.size or (32,32))) 378 self._hack.append(image) 379 380 return image
381 382 383 # Note that TkParameterized extends TkParameterizedBase by adding 384 # widget-drawing abilities; documentation for using these classes 385 # begins at a more useful and simple level in TkParameterized (the 386 # documentation for TkParameterizedBase is more complex).
387 -class TkParameterizedBase(Parameterized):
388 """ 389 A Parameterized subclass that maintains Tkinter.Variable shadows 390 (proxies) of its Parameters. The Tkinter Variable shadows are kept 391 in sync with the Parameter values, and vice versa. 392 393 Optionally performs the same for an *additional* shadowed 394 Parameterized (extraPO). The Parameters of the extra 395 shadowed PO are available via this object (via both the usual 396 'dot' attribute access and dedicated parameter accessors 397 declared in this class). 398 399 The Tkinter.Variable shadows for this Parameterized and any 400 extra shadowed one are available under their corresponding 401 parameter names in the _tkvars dictionary. 402 403 (See note 1 for complications arising from name clashes.) 404 405 406 Parameters being represented by TkParameterizedBase also 407 have a 'translators' dictionary, allowing mapping between string 408 representations of the objects and the objects themselves (for use 409 with e.g. a Tkinter.OptionMenu). More information about the 410 translators is available from specific translator-related methods 411 in this class. 412 413 414 Notes 415 ===== 416 417 (1) (a) There is an order of precedance for parameter lookup: 418 this PO > shadowed PO. 419 420 Therefore, if you create a Parameter for this PO 421 with the same name as one of the shadowed PO's Parameters, only 422 the Parameter on this PO will be shadowed. 423 424 Example: 'name' is a common attribute. As a 425 Parameterized, this object has a 'name' Parameter. Any 426 shadowed PO will also have a 'name' Parameter. By default, 427 this object's name will be shadowed at the expense of the name 428 of the extra shadowed PO. 429 430 The precedence order can be reversed by setting the attribute 431 'self_first' on this object to False. 432 433 434 (b) Along the same lines, an additional complication can arise 435 relating specifically to 'dot' attribute lookup. For 436 instance, a sublass of TkParameterized might also 437 inherit from Tkinter.Frame. Frame has many of its own 438 attributes, including - for example - 'size'. If we shadow a 439 Parameterized that has a 'size' Parameter, the 440 Parameterized's size Parameter will not be available as 441 .size because ('dot') attribute lookup begins on the local 442 object and is not overridden by 'self_first'. Using the 443 parameter accessors .get_parameter_object('size') or 444 .get_parameter_value('size') (and the equivalent set versions) 445 avoids this problem. 446 447 448 449 (2) If a shadowed PO's Parameter value is modified elsewhere, the 450 Tkinter Variable shadow is NOT updated until that Parameter value 451 or shadow value is requested from this object. Thus requesting the 452 value will always return an up-to-date result, but any GUI display 453 of the Variable might display a stale value (until a GUI refresh 454 takes place). 455 """ 456 457 # CEBNOTE: avoid (as far as possible) defining Parameters for this 458 # object because they might clash with Parameters of objects it 459 # eventually represents. 460 461 462 # CEBNOTE: Regarding note 1a above...it would be possible - with 463 # some extra work - to shadow Parameters that are duplicated on 464 # this PO and extraPO. Among other changes, things like the 465 # _tkvars dict would need different (e.g. name on this PO and 466 # name on extraPO) 467 468 # CEBNOTE: Regarding note 1b...might be less confusing if we stop 469 # parameters of shadowed POs being available as attributes (and 470 # use only the parameter access methods instead). But having them 471 # available as attributes is really convenient. 472 473 # CEBNOTE: Regarding note 2 above...if it becomes a problem, we 474 # could have some autorefresh of the vars or a callback of some 475 # kind in the parameterized object itself. E.g a 476 # refresh-the-widgets-on-focus-in method could make the gui in 477 # sync with the actual object (so changes outside the gui could 478 # show up when a frame takes focus). Or there could be timer 479 # process. 480 481 # CEB: because of note 1, attributes of this class should have 482 # names that are unlikely to clash (or they should be private); 483 # this will make it easier for creators and users of subclasses to 484 # avoid name clashes. 485 486 # must exist *before* an instance is init'd 487 # (for __getattribute__) 488 _extraPO = None 489 490 491 # CEBALERT: Parameterized repr method leads to recursion error.
492 - def __repr__(self): return object.__repr__(self)
493 494
495 - def _setup_params(self,**params):
496 """ 497 Parameters that are not in this object itself but are in the 498 extraPO get set on extraPO. Then calls Parameterized's 499 _setup_params(). 500 """ 501 ### a parameter might be passed in for one of the extra_pos; 502 ### if a key in the params dict is not a *parameter* of this 503 ### PO, then try it on the extra_pos 504 for n,p in params.items(): 505 if n not in self.params(): 506 self.set_parameter_value(n,p) 507 del params[n] 508 509 Parameterized._setup_params(self,**params)
510 511 # CEBALERT: rename extraPO...but to what? 512 # Rename change_PO() and anything else related.
513 - def __init__(self,extraPO=None,self_first=True,live_update=True,**params):
514 """ 515 516 517 Translation between displayed values and objects 518 ------------------------------------------------ 519 520 A Parameter has a value, but that might need some processing 521 to become a value suitable for display. For instance, the 522 SheetMask() object <SheetMask SheetMask0001923> ... 523 524 525 526 Important attributes 527 -------------------- 528 529 * _extraPO 530 531 532 * self_first 533 Determines precedence order for Parameter lookup: 534 if True, Parameters of this object take priority whenever 535 there is a name clash; if False, Parameters of extraPO take 536 priority. 537 538 539 540 541 * obj2str_fn & str2obj_fn 542 543 * translator_creators 544 545 (Note that in the various dictionaries above, the entry for 546 Parameter serves as a default, since keys are looked up by 547 class, so any Parameter type not specifically listed will be 548 covered by the Parameter entry.) 549 550 551 552 * _tkvars 553 554 555 """ 556 if not (extraPO is None or isinstance(extraPO,ParameterizedMetaclass) \ 557 or isinstance(extraPO,Parameterized)): 558 raise TypeError("%s is not a Parameterized instance or class."%extraPO) 559 560 # make self.first etc private 561 562 self._live_update = live_update 563 self.self_first = self_first 564 565 ## Which Tkinter Variable to use for each Parameter type 566 # (Note that, for instance, we don't include Number:DoubleVar. 567 # This is because we use Number to control the type, so we 568 # don't need restrictions from DoubleVar.) 569 self.__param_to_tkvar = {Boolean:T.BooleanVar, 570 Parameter:T.StringVar} 571 572 # CEBALERT: Parameter is the base parameter class, but ... 573 # at least need a test that will fail when a new param type added 574 # Rename 575 self.trans={Parameter:Eval_ReprTranslator, 576 Dynamic:Eval_ReprTranslator, 577 ObjectSelector:String_ObjectTranslator, 578 ClassSelector:CSPTranslator, 579 Number:Eval_ReprTranslator, 580 Boolean:BoolTranslator, 581 String:DoNothingTranslator} 582 583 self.change_PO(extraPO) 584 super(TkParameterizedBase,self).__init__(**params)
585 586
587 - def change_PO(self,extraPO):
588 """ 589 Shadow the Parameters of extraPO. 590 """ 591 self._extraPO = extraPO 592 self._tkvars = {} 593 self.translators = {} 594 595 # (reverse list to respect precedence) 596 for PO in self._source_POs()[::-1]: 597 self._init_tkvars(PO)
598 599
600 - def _init_tkvars(self,PO):
601 """ 602 Create Tkinter Variable shadows of all Parameters of PO. 603 """ 604 for name,param in PO.params().items(): 605 self._create_tkvar(PO,name,param)
606 607 608 # rename param to param_obj
609 - def _create_tkvar(self,PO,name,param):
610 """ 611 Add _tkvars[name] to represent param. 612 613 The appropriate Variable is used for each Parameter type. 614 615 Also adds tracing mechanism to keep the Variable and Parameter 616 values in sync, and updates the translator dictionary to map 617 string representations to the objects themselves. 618 """ 619 # CEBALERT: should probably delete any existing tkvar for name 620 self._create_translator(name,param) 621 622 tkvar = lookup_by_class(self.__param_to_tkvar,type(param))() 623 self._tkvars[name] = tkvar 624 625 # overwrite Variable's set() with one that will handle 626 # transformations to string 627 tkvar._original_set = tkvar.set 628 tkvar.set = lambda v,x=name: self._tkvar_set(x,v) 629 630 tkvar.set(self.get_parameter_value(name,PO)) 631 tkvar._last_good_val=tkvar.get() # for reverting 632 tkvar.trace_variable('w',lambda a,b,c,p_name=name: self._handle_gui_set(p_name)) 633 # CB: Instead of a trace, could we override the Variable's 634 # set() method i.e. trace it ourselves? Or does too much 635 # happen in tcl/tk for that to work? 636 637 # Override the Variable's get() method to guarantee an 638 # out-of-date value is never returned. In cases where the 639 # tkinter val is the most recently changed (i.e. when it's 640 # edited in the gui, resulting in a trace_variable being 641 # called), the _original_get() method is used. 642 # CEBALERT: what about other users of the variable? Could they 643 # be surprised by the result from get()? 644 tkvar._original_get = tkvar.get 645 tkvar.get = lambda x=name: self._tkvar_get(x)
646 647 648 ################################################################################ 649 # 650 ################################################################################ 651
652 - def _handle_gui_set(self,p_name):
653 """ 654 * The callback to use for all GUI variable traces/binds * 655 """ 656 if self._live_update: self._update_param_from_tkvar(p_name)
657 658
659 - def _tkvar_set(self,param_name,val):
660 """ 661 Set the tk variable to (the possibly transformed-to-string) val. 662 """ 663 self.debug("_tkvar_set(%s,%s)"%(param_name,val)) 664 val = self._object2string(param_name,val) 665 tkvar = self._tkvars[param_name] 666 tkvar._original_set(val) # trace not called because we're already in trace,
667 # and tk disables trace activation during trace 668 669 670 # CB: separate into update and get?
671 - def _tkvar_get(self,param_name):
672 """ 673 Return the value of the tk variable representing param_name. 674 675 (Before returning the variable's value, ensures it's up to date.) 676 """ 677 tk_val = self._tkvars[param_name]._original_get() 678 po_val = self.get_parameter_value(param_name) 679 680 po_stringrep = self._object2string(param_name,po_val) 681 682 if not self.translators[param_name].last_string2object_failed and not tk_val==po_stringrep: 683 self._tkvars[param_name]._original_set(po_stringrep) 684 return tk_val
685 686
687 - def _tkvar_changed(self,name):
688 """ 689 Return True if the displayed value does not equal the object's 690 value (and False otherwise). 691 """ 692 self.debug("_tkvar_changed(%s)"%name) 693 displayed_value = self._string2object(name,self._tkvars[name]._original_get()) 694 object_value = self.get_parameter_value(name) #getattr(self._extraPO,name) 695 696 # use equality check then identity check because e.g. val 697 # starts at 0.5, type 0.8, then type 0.5, need that to be 698 # changed is False, but some types cannot be equality compared 699 # (can be identity compared). 700 # CEBALERT: need to add a unit test to ensure this keeps working. 701 # Plus, I need to think about this, because while the above is 702 # true for floats, identity tests make more sense for many types 703 # (i.e. you want to know the object is the same). 704 try: 705 if displayed_value != object_value: 706 changed = True 707 else: 708 changed = False 709 except: 710 if displayed_value is not object_value: 711 changed = True 712 else: 713 changed = False 714 715 self.debug("..._v_c return %s"%changed) 716 return changed
717 718 719 # CEBALERT: now I've added on_modify, need to go through and rename 720 # on_change and decide whether each use should be for alteration of 721 # value or just gui set. Probably can remove on_change.
722 - def _update_param_from_tkvar(self,param_name):
723 """ 724 Attempt to set the parameter represented by param_name to the 725 value of its corresponding Tkinter Variable. 726 727 If setting the parameter fails (e.g. an inappropriate value 728 is set for that Parameter type), the Variable is reverted to 729 its previous value. 730 731 (Called by the Tkinter Variable's trace_variable() method.) 732 """ 733 self.debug("TkPOb._update_param_from_tkvar(%s)"%param_name) 734 735 parameter,sourcePO=self.get_parameter_object(param_name,with_location=True) 736 737 ### can only edit constant parameters for class objects 738 if parameter.constant==True and not isinstance(sourcePO,ParameterizedMetaclass): 739 return ### HIDDEN 740 741 tkvar = self._tkvars[param_name] 742 743 if self._tkvar_changed(param_name): 744 745 # don't attempt to set if there was a string-to-object translation error 746 if self.translators[param_name].last_string2object_failed: 747 return ### HIDDEN 748 749 # (use _original_get() because we don't want the tkvar to be reset to 750 # the parameter's current value!) 751 val = self._string2object(param_name,tkvar._original_get()) 752 753 try: 754 self.__set_parameter(param_name,val) 755 except: # everything 756 tkvar.set(tkvar._last_good_val) 757 raise # whatever the parameter-setting error was 758 759 self.debug("set %s to %s"%(param_name,val)) 760 761 if hasattr(tkvar,'_on_modify'): tkvar._on_modify() 762 763 ### call any function associated with GUI set() 764 if hasattr(tkvar,'_on_change'): tkvar._on_change()
765 766 767 ################################################################################ 768 # 769 ################################################################################ 770 771 772
773 - def _source_POs(self):
774 """ 775 Return a list of Parameterizeds in which to find 776 Parameters. 777 778 The list is ordered by precedence, as defined by self_first. 779 """ 780 if not self._extraPO: 781 sources = [self] 782 elif self.self_first: 783 sources = [self,self._extraPO] 784 else: 785 sources = [self._extraPO,self] 786 return sources
787 788 789 790
791 - def get_source_po(self,name):
792 """ 793 Return the Parameterized which contains the parameter 'name'. 794 """ 795 sources = self._source_POs() 796 797 for po in sources: 798 if name in po.params(): 799 return po 800 801 raise AttributeError(self.__attr_err_msg(name,sources))
802 803 804 # CB: change with_location to with_source
805 - def get_parameter_object(self,name,parameterized_object=None,with_location=False):
806 """ 807 Return the Parameter *object* (not value) specified by name, 808 from the source_POs in this object (or the 809 specified parameterized_object). 810 811 If with_location=True, returns also the source parameterizedobject. 812 """ 813 source = parameterized_object or self.get_source_po(name) 814 parameter_object = source.params()[name] 815 816 if not with_location: 817 return parameter_object 818 else: 819 return parameter_object,source
820 821 822 823 ###################################################################### 824 # Attribute lookup 825 826 ##### these guarantee only to get/set parameters #####
827 - def get_parameter_value(self,name,parameterized_object=None):
828 """ 829 Return the value of the parameter specified by name. 830 831 If a parameterized_object is specified, looks for the parameter there. 832 Otherwise, looks in the source_POs of this object. 833 """ 834 source = parameterized_object or self.get_source_po(name) 835 return source.get_value_generator(name)
836 837 # CEBALERT: shouldn't this use __set_parameter? Presumably doing 838 # that kind of thing is part of the cleanup required in this file.
839 - def set_parameter_value(self,name,val,parameterized_object=None):
840 """ 841 Set the value of the parameter specified by name to val. 842 843 Updates the corresponding tkvar. 844 """ 845 source = parameterized_object or self.get_source_po(name) 846 object.__setattr__(source,name,val) 847 848 # update the tkvar 849 if name in self._tkvars: 850 self._tkvars[name]._original_set(self._object2string(name,val))
851 852 853 ###################################################### 854 855 ########## these lookup attributes in order ########## 856 # (i.e. you could get attribute of self rather than a parameter) 857 # (might remove these to save confusion: they are useful except when 858 # someone would be surprised to get an attribute of e.g. a Frame (like 'size') when 859 # they were expecting to get one of their parameters. Also, means you can't set 860 # an attribute a on self if a exists on one of the shadowed objects) 861 # (also they (have to) ignore self_first) 862
863 - def __getattribute__(self,name):
864 """ 865 If the attribute is found on this object, return it. Otherwise, 866 return the attribute from the extraPO, if it exists. 867 If there is no match, raise an attribute error. 868 """ 869 try: 870 return object.__getattribute__(self,name) 871 except AttributeError: 872 extraPO = object.__getattribute__(self,'_extraPO') 873 874 if hasattr(extraPO,name): 875 return getattr(extraPO,name) # HIDDEN! 876 877 raise AttributeError(self.__attr_err_msg(name,[self,extraPO]))
878 879
880 - def __setattr__(self,name,val):
881 """ 882 If the attribute already exists on this object, set it. If the attribute 883 is found on the extraPO, set it there. Otherwise, set the 884 attribute on this object (i.e. add a new attribute). 885 """ 886 # use dir() not hasattr() because hasattr uses __getattribute__ 887 if name in dir(self): 888 889 if name in self.params(): 890 self.set_parameter_value(name,val,self) 891 else: 892 object.__setattr__(self,name,val) 893 894 elif name in dir(self._extraPO): 895 896 if name in self._extraPO.params(): 897 self.set_parameter_value(name,val,self._extraPO) 898 else: 899 object.__setattr__(self._extraPO,name,val) 900 901 else: 902 903 # name not found, so set on this object 904 object.__setattr__(self,name,val)
905 ####################################################### 906 907 908 ###################################################################### 909 910 911 912 913 914 ###################################################################### 915 # Translation between GUI (strings) and true values 916
917 - def _create_translator(self,name,param):
918 self.debug("_create_translator(%s,%s)"%(name,param)) 919 920 translator_type = lookup_by_class(self.trans,type(param)) 921 922 # Dynamic parameters only *might* contain a 923 # dynamic value; if such a parameter really is dynamic, we 924 # overwrite any more specific class found above 925 # (e.g. a Number with a dynamic value will have a numeric 926 # translator from above, so we replace that) 927 if param_is_dynamically_generated(param,self.get_source_po(name)) or name in self.allow_dynamic: 928 translator_type = self.trans[Dynamic] 929 930 self.translators[name]=translator_type(param,initial_value=self.get_parameter_value(name)) 931 932 self.translators[name].msg_handler = self.msg_handler
933 934 935 936 937 # CEBALERT: doc replace & rename to plastic or something
938 - def _object2string(self,param_name,obj,replace=True):
939 """ 940 If val is one of the objects in param_name's translator, 941 translate to the string. 942 """ 943 self.debug("object2string(%s,%s)"%(param_name,obj)) 944 translator = self.translators[param_name] 945 946 if not replace: 947 translator=copy.copy(translator) 948 949 return translator.object2string(obj)
950 951
952 - def _string2object(self,param_name,string):
953 """ 954 Change the given string for the named parameter into an object. 955 956 If there is a translator for param_name, translate the string 957 to the object; otherwise, call convert_string2obj on the 958 string. 959 """ 960 self.debug("string2object(%s,%s)"%(param_name,string)) 961 translator = self.translators[param_name] 962 o = translator.string2object(string) 963 self.debug("...s2o return %s, type %s"%(o,type(o))) 964 return o
965 966 ###################################################################### 967
968 - def __attr_err_msg(self,attr_name,objects):
969 """ 970 Helper method: return the 'attr_name is not in objects' message. 971 """ 972 if not hasattr(objects,'__len__'):objects=[objects] 973 974 error_string = "'%s' not in %s"%(attr_name,str(objects.pop(0))) 975 976 for o in objects: 977 error_string+=" or %s"%str(o) 978 979 return error_string
980 981
982 - def __set_parameter(self,param_name,val):
983 """ 984 Helper method: 985 """ 986 # use set_in_bounds if it exists 987 # i.e. users of widgets get their values cropped 988 # (no warnings/errors, so e.g. a string in a 989 # tagged slider just goes to the default value) 990 # CEBALERT: set_in_bounds not valid for POMetaclass? 991 parameter,sourcePO=self.get_parameter_object(param_name,with_location=True) 992 993 # CEBHACKALERT: GeneratorSheet.input_generator should be a 994 # property? But it's a Parameter... 995 # Setting input_generator rather than calling set_input_generator() 996 # is probably a trap... 997 if param_name=='input_generator' and hasattr(sourcePO,'set_input_generator'): 998 sourcePO.set_input_generator(val) 999 elif hasattr(parameter,'set_in_bounds') and isinstance(sourcePO,Parameterized): 1000 parameter.set_in_bounds(sourcePO,val) 1001 else: 1002 setattr(sourcePO,param_name,val)
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
1013 -class TkParameterized(TkParameterizedBase):
1014 """ 1015 Provide widgets for Parameters of itself and up to one additional 1016 Parameterized instance or class. 1017 1018 A subclass that defines a Parameter p can display it appropriately 1019 for manipulation by the user simply by calling 1020 pack_param('p'). The GUI display and the actual Parameter value 1021 are automatically synchronized (though see technical notes in 1022 TkParameterizedBase's documentation for more details). 1023 1024 In general, pack_param() adds a Tkinter.Frame containing a label 1025 and a widget: 1026 1027 --------------------- The Parameter's 1028 | | 'representation' 1029 | [label] [widget] |<----frame 1030 | | 1031 --------------------- 1032 1033 In the same way, an instance of this class can be used to display 1034 the Parameters of an existing object. By passing in extraPO=x, 1035 where x is an existing Parameterized instance or class, a 1036 Parameter q of x can also be displayed in the GUI by calling 1037 pack_param('q'). 1038 1039 For representation in the GUI, Parameter values might need to be 1040 converted between their real values and strings used for display 1041 (e.g. for a ClassSelector, the options are really class objects, 1042 but the user is presented with a list of strings to choose 1043 from). Such translation is handled and documented in the 1044 TkParameterizedBase; the default behaviors can be overridden if 1045 required. 1046 1047 (Note that this class simply adds widget drawing to 1048 TkParameterizedBase. More detail about the shadowing of 1049 Parameters is available in the documentation for 1050 TkParameterizedBase.) 1051 """ 1052 1053 # CEBNOTE: as for TkParameterizedBase, avoid declaring 1054 # Parameters here (to avoid name clashes with any additional 1055 # Parameters this might eventually be representing). 1056 1057 pretty_parameters = Boolean(default=True,precedence=-1, 1058 doc="""Whether to format parameter names, or display the 1059 variable names instead. 1060 1061 Example use: 1062 TkParameterized.pretty_parameters=False 1063 1064 (This causes all Parameters throughout the GUI to be displayed 1065 with variable names.) 1066 """) 1067 1068
1069 - def __init__(self,master,extraPO=None,self_first=True, 1070 msg_handler=None,**params):
1071 """ 1072 Initialize this object with the arguments and attributes 1073 described below: 1074 1075 extraPO: optional Parameterized object for which to shadow 1076 Parameters (in addition to Parameters of this object; see 1077 superclass) 1078 1079 self_first: if True, Parameters on this object take precedence 1080 over any ones with the same names in the extraPO (i.e. what 1081 to do if there are name clashes; see superclass) 1082 1083 1084 Important attributes 1085 ==================== 1086 1087 * param_immediately_apply_change 1088 1089 Some types of Parameter are represented with widgets where 1090 a complete change can be instantaneous (e.g. when 1091 selecting from an option list, the selection should be 1092 applied straightaway). Others are represented with widgets 1093 that do not finish their changes instantaneously 1094 (e.g. entry into a text box is not considered finished 1095 until Return is pressed). 1096 1097 * widget_creators 1098 1099 A dictionary of methods to create a widget for each Parameter 1100 type. For special widget options (specific to each particular 1101 parameter), see the corresponding method's docstring. 1102 1103 * representations 1104 1105 After pack_param() is called, a Parameter's representation 1106 consists of the tuple (frame,widget,label,pack_options) - the 1107 enclosing frame, the value-containing widget, the label 1108 holding the Parameter's name, and any options supplied for 1109 pack(). These can all be accessed through the representations 1110 dictionary, under the Parameter's name. 1111 """ 1112 self.master = master 1113 self.msg_handler = msg_handler 1114 1115 # CEBALER: doc 1116 self.allow_dynamic = [] 1117 1118 self.param_immediately_apply_change = {Boolean:True, 1119 Selector:True, 1120 Number:False, 1121 Parameter:False} 1122 1123 TkParameterizedBase.__init__(self,extraPO=extraPO, 1124 self_first=self_first, 1125 **params) 1126 1127 self.balloon = Balloon(master) 1128 1129 # CEBALERT: what about subclasses of Number (e.g. Integer, 1130 # which should get a slider that jumps between integers... 1131 # maybe that already happens)? 1132 self.widget_creators = { 1133 Boolean:self._create_boolean_widget, 1134 Dynamic:self._create_string_widget, 1135 Number:self._create_number_widget, 1136 Button:self._create_button_widget, 1137 String:self._create_string_widget, 1138 Selector:self._create_selector_widget} 1139 1140 self.representations = {} 1141 1142 1143 # CEBNOTE: it would be nice to sort out menus properly 1144 # (i.e. parameterize them) 1145 self.popup_menu = Menu(master, tearoff=0) 1146 self.dynamic_var = T.BooleanVar() 1147 self.popup_menu.add("checkbutton",indexname="dynamic", 1148 label="Enter dynamic value?", 1149 state="disabled",command=self._switch_dynamic, 1150 variable=self.dynamic_var)
1151 1152
1153 - def __update_dynamic_menu_entry(self,param_name):
1154 """Keep track of status of dynamic entry.""" 1155 param,po = self.get_parameter_object(param_name,with_location=True) 1156 currently_dynamic = param_is_dynamically_generated(param,po) 1157 if hasattr(param,'_value_is_dynamic') and not currently_dynamic: 1158 self._right_click_param = param_name 1159 state = "normal" 1160 else: 1161 self._right_click_param = None 1162 state = "disabled" 1163 self.popup_menu.entryconfig("dynamic",state=state) 1164 self.dynamic_var.set(currently_dynamic or \ 1165 param_name in self.allow_dynamic)
1166 1167
1168 - def _param_right_click(self,event,param_name):
1169 """Display a popup menu when user right clicks on a parameter.""" 1170 self.__update_dynamic_menu_entry(param_name) 1171 self.popup_menu.tk_popup(event.x_root,event.y_root)
1172 1173 1174 1175 ################################################################################ 1176 # 1177 ################################################################################ 1178
1179 - def _update_param_from_tkvar(self,param_name,force=False):
1180 """ 1181 Prevents the superclass's _update_param_from_tkvar() method from being 1182 called unless: 1183 1184 * param_name is a Parameter type that has changes immediately 1185 applied (see doc for param_immediately_apply_change 1186 dictionary); 1187 1188 * force is True. 1189 1190 (I.e. to update a parameter for which 1191 param_immediately_apply_change[type(parameter)]==False, call 1192 this method with force=True. E.g. when <Return> is pressed in 1193 a text box, this method is called with force=True.) 1194 """ 1195 self.debug("TkPO._update_param_from_tkvar(%s)"%param_name) 1196 1197 param_obj = self.get_parameter_object(param_name) 1198 1199 if not lookup_by_class(self.param_immediately_apply_change, 1200 type(param_obj)) and not force: 1201 return 1202 else: 1203 super(TkParameterized,self)._update_param_from_tkvar(param_name)
1204 1205 1206
1207 - def _tkvar_set(self,param_name,val):
1208 """ 1209 Calls superclass's version, but adds help text for the 1210 currently selected item of SelectorParameters. 1211 """ 1212 super(TkParameterized,self)._tkvar_set(param_name,val) 1213 1214 if isinstance(self.get_parameter_object(param_name),Selector): 1215 try: 1216 w = self.representations[param_name]['widget'] 1217 help_text = getdoc(self._string2object( 1218 param_name, 1219 self._tkvars[param_name]._original_get())) 1220 1221 1222 ###################################################### 1223 # CEBALERT: for projectionpanel, which currently has 1224 # to destroy a widget (because Tkinter.OptionMenu's 1225 # list of choices cannot easily be replaced). See ALERT 1226 # 'How do you change list [...]' in projectionpanel.py. 1227 try: 1228 self.balloon.bind(w,help_text) 1229 except T.TclError: 1230 pass 1231 ###################################################### 1232 1233 except (AttributeError,KeyError): 1234 # could be called before self.representations exists, 1235 # or before param in _tkvars dict 1236 pass
1237 1238 1239 1240 1241 ### Use in callbacks 1242
1243 - def _handle_gui_set(self,p_name,force=False):
1244 """Override the superclass's method to X and allow status indications.""" 1245 #logging.info("%s._handle_gui_set(%s,force=%s)"%(self,p_name,force)) 1246 if self._live_update: 1247 self._update_param_from_tkvar(p_name,force) 1248 1249 self._indicate_tkvar_status(p_name)
1250 1251 1252 1253 ### Simulate GUI actions 1254
1255 - def gui_set_param(self,param_name,val):
1256 """Simulate setting the parameter in the GUI.""" 1257 self._tkvar_set(param_name,val) # ERROR: presumably calls trace stuff twice 1258 self._handle_gui_set(param_name,force=True)
1259
1260 - def gui_get_param(self,param_name):
1261 """Simulate getting the parameter in the GUI.""" 1262 return self._tkvars[param_name].get()
1263 1264 1265 1266 ################################################################################ 1267 # End 1268 ################################################################################ 1269 1270 1271 1272 1273 1274 ################################################################################ 1275 # 1276 ################################################################################ 1277 1278 # some refactoring required: should be a base method that's to do 1279 # with adding a representation for a parameter. then this stuff 1280 # would go in it.
1281 - def _post_add_param(self,param_name):
1282 l = self.representations[param_name]['label'] 1283 if l is not None: 1284 l.bind('<<right-click>>',lambda event: \ 1285 self._param_right_click(event,param_name))
1286 1287 1288 # CEBALERT: rename on_change and on_modify
1289 - def pack_param(self,name,parent=None,widget_options={}, 1290 on_change=None,on_modify=None,**pack_options):
1291 """ 1292 Create a widget for the Parameter name, configured according 1293 to widget_options, and pack()ed according to the pack_options. 1294 1295 Pop-up help is automatically set from the Parameter's doc. 1296 1297 The widget and label (if appropriate) are enlosed in a Frame 1298 so they can be manipulated as a single unit (see the class 1299 docstring). The representation 1300 (frame,widget,label,pack_options) is returned (as well as 1301 being stored in the representations dictionary). 1302 1303 1304 * parent is an existing widget that is to be the parent 1305 (master) of the widget created for this paramter. If no parent 1306 is specified, defaults to the originally supplied master 1307 (i.e. that set during __init__). 1308 1309 * on_change is an optional function to call whenever the 1310 Parameter's corresponding Tkinter Variable's trace_variable 1311 indicates that it has been set (this does not necessarily mean 1312 that the widget's value has changed). When the widget is created, 1313 the on_change method will be called (because the creation of the 1314 widget triggers a set in Tkinter). 1315 1316 * on_modify is an optional function to call whenever the 1317 corresponding Tkinter Variable is actually changed. 1318 1319 1320 widget_options specified here override anything that might have been 1321 set elsewhere (e.g. Button's size can be overridden here 1322 if required). 1323 1324 1325 1326 Examples of use: 1327 pack_param(name) 1328 pack_param(name,side='left') 1329 pack_param(name,parent=frame3,on_change=some_func) 1330 pack_param(name,widget_options={'width':50},side='top',expand='yes',fill='y') 1331 """ 1332 frame = T.Frame(parent or self.master) 1333 1334 widget,label = self._create_widget(name,frame,widget_options,on_change,on_modify) 1335 1336 # checkbuttons are 'widget label' rather than 'label widget' 1337 if widget.__class__ is T.Checkbutton: # type(widget) doesn't seem to work 1338 widget_side='left'; label_side='right' 1339 else: 1340 label_side='left'; widget_side='right' 1341 1342 if label: label.pack(side=label_side) # label can be None (e.g. for Button) 1343 widget.pack(side=widget_side,expand='yes',fill='x') 1344 1345 representation = {"frame":frame,"widget":widget, 1346 "label":label,"pack_options":pack_options, 1347 "on_change":on_change,"on_modify":on_modify, 1348 "widget_options":widget_options} 1349 self.representations[name] = representation 1350 1351 # If there's a label, balloon's bound to it - otherwise, bound 1352 # to enclosing frame. 1353 # (E.g. when there's [label] [text_box], only want balloon for 1354 # label (because maybe more help will be present for what's in 1355 # the box) but when there's [button], want popup help over the 1356 # button.) 1357 param_obj = self.get_parameter_object(name) 1358 help_text = getdoc(param_obj) 1359 1360 if param_obj.default is not None: 1361 # some params appear to have no docs!!! 1362 if help_text is not None: 1363 help_text+="\n\nDefault: %s"%self._object2string(name,param_obj.default,replace=False) 1364 1365 self.balloon.bind(label or frame,help_text) 1366 1367 frame.pack(pack_options) 1368 1369 self._indicate_tkvar_status(name) 1370 1371 self._post_add_param(name) 1372 return representation
1373 1374
1375 - def hide_param(self,name):
1376 """Hide the representation of Parameter 'name'.""" 1377 if name in self.representations: 1378 self.representations[name]['frame'].pack_forget()
1379 # CEBNOTE: forgetting label and widget rather than frame would 1380 # just hide while still occupying space (i.e. the empty frame 1381 # stays in place, and so widgets could later be inserted at 1382 # exact same place) 1383 #self.representations[name]['label'].pack_forget() 1384 #self.representations[name]['widget'].pack_forget() 1385 # unhide_param would need modifying too 1386 1387
1388 - def unhide_param(self,name,new_pack_options={}):
1389 """ 1390 Un-hide the representation of Parameter 'name'. 1391 1392 Any new pack options supplied overwrite the originally 1393 supplied ones, but the parent of the widget remains the same. 1394 """ 1395 # CEBNOTE: new_pack_options not really tested. Are they useful anyway? 1396 if name in self.representations: 1397 pack_options = self.representations[name]['pack_options'] 1398 pack_options.update(new_pack_options) 1399 self.representations[name]['frame'].pack(pack_options)
1400 1401
1402 - def unpack_param(self,name):
1403 """ 1404 Destroy the representation of Parameter 'name'. 1405 1406 (unpack and then pack a param if you want to put it in a different 1407 frame; otherwise simply use hide and unhide) 1408 """ 1409 f = self.representations[name]['frame'] 1410 w = self.representations[name]['widget'] 1411 l = self.representations[name]['label'] 1412 1413 del self.representations[name] 1414 1415 for x in [f,w,l]: 1416 x.destroy()
1417 1418
1419 - def repack_param(self,name):
1420 1421 f = self.representations[name]['frame'] 1422 w = self.representations[name]['widget'] 1423 l = self.representations[name]['label'] 1424 o = self.representations[name]['pack_options'] 1425 on_change = self.representations[name]['on_change'] 1426 on_modify = self.representations[name]['on_modify'] 1427 1428 w.destroy(); l.destroy() 1429 1430 param_obj,PO = self.get_parameter_object(name,with_location=True) 1431 self._create_tkvar(PO,name,param_obj) 1432 1433 self.pack_param(name,f,on_change=on_change,on_modify=on_modify,**o)
1434 1435
1436 - def _switch_dynamic(self,name=None,dynamic=False):
1437 1438 param_name = name or self._right_click_param 1439 param,po = self.get_parameter_object(param_name,with_location=True) 1440 if not hasattr(param,'_value_is_dynamic'): 1441 return 1442 1443 if param_name in self.allow_dynamic: 1444 self.allow_dynamic.remove(param_name) 1445 else: 1446 self.allow_dynamic.append(param_name) 1447 1448 self.repack_param(param_name)
1449 1450 1451 1452 1453 1454 ################################################################################ 1455 # 1456 ################################################################################ 1457 1458 1459 1460 1461 1462 ################################################################################ 1463 # WIDGET CREATION 1464 ################################################################################ 1465
1466 - def _create_widget(self,name,master,widget_options={},on_change=None,on_modify=None):
1467 """ 1468 Return widget,label for parameter 'name', each having the master supplied 1469 1470 The appropriate widget creation method is found from the 1471 widget_creators dictionary; see individual widget creation 1472 methods for details to each type of widget. 1473 """ 1474 # select the appropriate widget-creation method; 1475 # default is self._create_string_widget... 1476 widget_creation_fn = self._create_string_widget 1477 1478 param_obj,source_po = self.get_parameter_object(name,with_location=True) 1479 1480 if not (param_is_dynamically_generated(param_obj,source_po) or name in self.allow_dynamic): 1481 # ...but overwrite that with a more specific one, if possible 1482 for c in classlist(type(param_obj))[::-1]: 1483 if self.widget_creators.has_key(c): 1484 widget_creation_fn = self.widget_creators[c] 1485 break 1486 elif name not in self.allow_dynamic: 1487 self.allow_dynamic.append(name) 1488 1489 1490 if on_change is not None: 1491 self._tkvars[name]._on_change=on_change 1492 1493 if on_modify is not None: 1494 self._tkvars[name]._on_modify=on_modify 1495 1496 widget=widget_creation_fn(master,name,widget_options) 1497 1498 # Is widget a button (but not a checkbutton)? If so, no label wanted. 1499 # CEBALERT 'notNonelabel': change to have a label with no text 1500 if is_button(widget): 1501 label = None 1502 else: 1503 label = T.Label(master,text=self.__pretty_print(name)) 1504 1505 # disable widgets for constant params 1506 if param_obj.constant and isinstance(source_po,Parameterized): 1507 # (need to be able to set on class, hence check it's PO not POMetaclass 1508 widget.config(state='disabled') 1509 1510 return widget,label
1511 1512
1513 - def _create_button_widget(self,frame,name,widget_options):
1514 """ 1515 Return a FocusTakingButton to represent Parameter 'name'. 1516 1517 Buttons require a command, which should have been specified as the 1518 'on_change' function passed to pack_param(). 1519 1520 After creating a button for a Parameter param, self.param() also 1521 executes the button's command. 1522 1523 If the Button was declared with an image, the button will 1524 have that image (and no text); otherwise, the button will display 1525 the (possibly pretty_print()ed) name of the Parameter. 1526 """ 1527 try: 1528 command = self._tkvars[name]._on_change 1529 except AttributeError: 1530 raise TypeError("No command given for '%s' button."%name) 1531 1532 del self._tkvars[name]._on_change # because we use Button's command instead 1533 1534 # Calling the parameter (e.g. self.Apply()) is like pressing the button: 1535 self.__dict__["_%s_param_value"%name]=command 1536 # like setattr(self,name,command) but without tracing etc 1537 1538 # (...CEBNOTE: and so you can't edit a tkparameterizedobject 1539 # w/buttons with another tkparameterizedobject because the 1540 # button parameters all skip translation etc. Instead should 1541 # handle their translation. But we're not offering a GUI 1542 # builder so it doesn't matter.) 1543 1544 button = FocusTakingButton(frame,command=command) 1545 1546 button_param = self.get_parameter_object(name) 1547 1548 image = button_param.get_image() 1549 if image: 1550 button['image']=image 1551 #button['relief']='flat' 1552 else: 1553 button['text']=self.__pretty_print(name) 1554 1555 1556 # and set size from Button 1557 size = button_param.size 1558 #if size: 1559 # button['width']=size[0] 1560 # button['height']=size[1] 1561 1562 button.config(**widget_options) # widget_options override things from parameter 1563 return button
1564
1565 - def update_selector(self,name):
1566 1567 if name in self.representations: 1568 widget_options = self.representations[name]["widget_options"] 1569 1570 new_range,widget_options = self._X(name,widget_options) 1571 1572 w = self.representations[name]['widget'] 1573 # hACK: tuple to work around strange list parsing tkinter/tcl 1574 w.configure(values=tuple(new_range)) # what a mess
1575 #w.configure(state='readonly') # CEBALERT: why necessary? 1576 # does it get changed somewhere else by mistake? e.g. does 1577 # plotgrouppanel switch disabled to normal and normal to 1578 # disabled without checking that normal isn't readonly? 1579 1580
1581 - def _X(self,name,widget_options):
1582 1583 # CEBALERT: need to document how & why people should use 'sort_fn_args' 1584 # and 'new_default' when calling pack_param(). Also, simplify it if 1585 # possible. 1586 1587 self.translators[name].update() 1588 1589 new_range = self.translators[name].cache.keys() 1590 1591 if 'sort_fn_args' not in widget_options: 1592 # no sort specified: defaults to sort() 1593 new_range.sort() 1594 else: 1595 sort_fn_args = widget_options['sort_fn_args'] 1596 del widget_options['sort_fn_args'] 1597 if sort_fn_args is not None: 1598 new_range = keys_sorted_by_value_unique_None_safe(self.translators[name].cache,**sort_fn_args) 1599 1600 assert len(new_range)>0 # CB: remove 1601 1602 tkvar = self._tkvars[name] 1603 1604 1605 if 'new_default' in widget_options: 1606 if widget_options['new_default']: 1607 current_value = new_range[0] 1608 del widget_options['new_default'] 1609 else: 1610 current_value = self._object2string(name,self.get_parameter_value(name)) 1611 if current_value not in new_range: 1612 current_value = new_range[0] # whatever was there is out of date now 1613 1614 tkvar.set(current_value) 1615 1616 return new_range,widget_options
1617 1618
1619 - def _create_selector_widget(self,frame,name,widget_options):
1620 """ 1621 Return a Tkinter.OptionMenu to represent Parameter 'name'. 1622 1623 In addition to Tkinter.OptionMenu's usual options, the 1624 following additional ones may be included in widget_options: 1625 1626 * sort_fn_args: if widget_options includes 'sort_fn_args', 1627 these are passed to the sort() method of the list of 1628 *objects* available for the parameter, and the names are 1629 displayed sorted in that order. If 'sort_fn_args' is not 1630 present, the default is to sort the list of names using its 1631 sort() method. 1632 1633 * new_default: if widget_options includes 'new_default':True, 1634 the currently selected value for the widget will be set 1635 to the first item in the (possibly sorted as above) range. 1636 Otherwise, the currently selected value will be left as the 1637 current value. 1638 """ 1639 #param = self.get_parameter_object(name) 1640 #self._update_translator(name,param) 1641 1642 ## sort the range for display 1643 # CEBALERT: extend OptionMenu so that it 1644 # (a) supports changing its option list (subject of a previous ALERT) 1645 # (b) supports sorting of its option list 1646 # (c) supports selecting a new default 1647 1648 new_range,widget_options = self._X(name,widget_options) 1649 1650 tkvar = self._tkvars[name] 1651 1652 # Combobox looks bad with standard theme on my ubuntu 1653 # (and 'changed' marker - blue text - not visible). 1654 w = Combobox(frame,textvariable=tkvar, 1655 values=new_range,state='readonly', 1656 **widget_options) 1657 1658 # CEBALERT: somehow Combobox sets textvariable without calling 1659 # its set() method! So how can I possibly track that the 1660 # variable's been set? So (hack) track the ComboBox event 1661 # itself. Shouldn't be necessary, should be checked as tk 1662 # versions are upgraded... 1663 def f(event,name=name): 1664 v = self._tkvars[name].get() 1665 self._tkvar_set(name,v) 1666 # CEBSUPERHACKALERT: variable traces vanish so need this 1667 self._handle_gui_set(name)
1668 1669 w.bind("<<ComboboxSelected>>",f) 1670 1671 # CEBALERT: hack to 1672 w._readonly_=True 1673 1674 1675 help_text = getdoc(self._string2object(name,tkvar._original_get())) 1676 self.balloon.bind(w,help_text) 1677 return w
1678 1679
1680 - def _create_number_widget(self,frame,name,widget_options):
1681 """ 1682 Return a TaggedSlider to represent parameter 'name'. 1683 1684 The slider's bounds are set to those of the Parameter. 1685 """ 1686 w = TaggedSlider(frame,variable=self._tkvars[name],**widget_options) 1687 param = self.get_parameter_object(name) 1688 1689 lower_bound,upper_bound = param.get_soft_bounds() 1690 if upper_bound is not None and lower_bound is not None: 1691 # TaggedSlider needs BOTH bounds (neither can be None) 1692 w.set_bounds(lower_bound,upper_bound) 1693 1694 # have to do the lookup because subclass might override default 1695 if not lookup_by_class(self.param_immediately_apply_change,type(param)): 1696 w.bind('<<TagReturn>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1697 w.bind('<<TagFocusOut>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1698 w.bind('<<SliderSet>>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1699 1700 return w
1701 1702
1703 - def _create_boolean_widget(self,frame,name,widget_options):
1704 """Return a Tkinter.Checkbutton to represent parameter 'name'.""" 1705 return T.Checkbutton(frame,variable=self._tkvars[name],**widget_options)
1706 1707
1708 - def _create_string_widget(self,frame,name,widget_options):
1709 """Return a Tkinter.Entry to represent parameter 'name'.""" 1710 widget = T.Entry(frame,textvariable=self._tkvars[name],**widget_options) 1711 param = self.get_parameter_object(name) 1712 if not lookup_by_class(self.param_immediately_apply_change,type(param)): 1713 widget.bind('<Return>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1714 widget.bind('<FocusOut>', lambda e=None,x=name: self._handle_gui_set(x,force=True)) 1715 return widget
1716 1717 ################################################################################ 1718 # End WIDGET CREATION 1719 ################################################################################ 1720 1721 1722 1723 # CB: the colors etc for indication are only temporary
1724 - def _indicate_tkvar_status(self,param_name,status=None):
1725 """ 1726 Set the parameter's label to: 1727 - blue if the GUI value differs from that set on the object 1728 - red if the text doesn't translate to a correct value 1729 - black if the GUI and object have the same value 1730 """ 1731 if self.translators[param_name].last_string2object_failed: 1732 status = 'error' 1733 1734 self._set_widget_status(param_name,status)
1735 1736 1737 # this scheme is incompatible with tile 1738 # (tile having the right idea about how to do this kind of thing!)
1739 - def _set_widget_status(self,param_name,status):
1740 1741 if hasattr(self,'representations') and param_name in self.representations: 1742 1743 widget = self.representations[param_name]['widget'] 1744 1745 states = {'error':'red','changed':'blue',None:'black'} 1746 1747 try: 1748 if is_button(widget): 1749 return 1750 else: 1751 widget.config(foreground=states[status]) 1752 except T.TclError: #CEBALERT uh-oh 1753 pass
1754 1755 1756 1757 1758
1759 - def __pretty_print(self,s):
1760 """ 1761 Convert a Parameter name s to a string suitable for display, 1762 if pretty_parameters is True. 1763 """ 1764 if not self.pretty_parameters: 1765 return s 1766 else: 1767 n = s.replace("_"," ") 1768 n = n.capitalize() 1769 return n
1770 1771 1772 1773 1774 ###################################################################### 1775 ###################################################################### 1776
1777 -class Translator(object):
1778 """ 1779 Abstract class that 1780 1781 Translators handle translation between objects and their 1782 string representations in the GUI. 1783 1784 last_string2object_failed is a flag that can be set to indicate that 1785 the last string-to-object translation failed. 1786 (TkParameterized checks this attribute for indicating errors to 1787 the user.) 1788 1789 """ 1790 last_string2object_failed = False # CEBALERT: why class attr? 1791
1792 - def __init__(self,param,initial_value=None):
1793 self.param = param 1794 self.msg_handler = None
1795
1796 - def string2object(self,string_):
1797 raise NotImplementedError
1798
1799 - def object2string(self,object_):
1800 raise NotImplementedError
1801
1802 - def _pass_out_msg(self,msg):
1803 if self.msg_handler: 1804 self.msg_handler.message(None,msg)
1805
1806 - def __copy__(self):
1807 """Copy only translator-specific state.""" 1808 new = self.__class__(self.param) 1809 new.last_string2object_failed = self.last_string2object_failed 1810 new.msg_handler = self.msg_handler 1811 return new
1812 1813
1814 -class DoNothingTranslator(Translator):
1815 """ 1816 Performs no translation. For use with Parameter types such as 1817 Boolean and String, where the representation 1818 of the object in the GUI is the object itself. 1819 """
1820 - def string2object(self,string_):
1821 return string_
1822
1823 - def object2string(self,object_):
1824 return object_
1825 1826 # seems to be necessary after switching to tk 8.5 1827 # need to check it's really necessary
1828 -class BoolTranslator(DoNothingTranslator):
1829 - def string2object(self,string_):
1830 return bool(string_)
1831 1832 1833 # Error messages: need to change how they're reported 1834 1835 1836
1837 -class Eval_ReprTranslator(Translator):
1838 """ 1839 Translates a string to an object by eval()ing the string in 1840 __main__.__dict__ (i.e. as if the string were typed at the 1841 commandline), and translates an object to a string by 1842 repr(object). 1843 """ 1844 1845 last_object = None 1846 last_string = None 1847
1848 - def __init__(self,param,initial_value=None):
1849 super(Eval_ReprTranslator,self).__init__(param,initial_value) 1850 self.last_string = self.object2string(initial_value) 1851 self.last_object = initial_value
1852 1853 # the whole last_string deal is required because of execing in main
1854 - def string2object(self,string_):
1855 if string_!=self.last_string: 1856 try: 1857 self.last_object = eval(string_,__main__.__dict__) 1858 self.last_string = string_ 1859 self._pass_out_msg(" ") 1860 #print "OUT:" 1861 except Exception,inst: 1862 #print "OUT:", str(sys.exc_info()[0])[11::]+" ("+str(inst)+")" 1863 m = str(inst) # CEBALERT: clean up 1864 self._pass_out_msg(m) 1865 self.last_string2object_failed=True 1866 return string_ # HIDDEN 1867 1868 self.last_string2object_failed=False 1869 return self.last_object
1870 1871
1872 - def object2string(self,object_):
1873 if object_==self.last_object: 1874 return self.last_string 1875 else: 1876 string_ = repr(object_) 1877 self.last_object = object_ 1878 self.last_string = string_ 1879 return string_
1880
1881 - def __copy__(self):
1882 n = super(Eval_ReprTranslator,self).__copy__() 1883 n.last_string = self.last_string 1884 n.last_object = self.last_object 1885 return n
1886 1887
1888 -class String_ObjectTranslator(Translator):
1889 1890 cache = {} 1891
1892 - def __init__(self,param,initial_value=None):
1893 super(String_ObjectTranslator,self).__init__(param,initial_value) 1894 self.cache = {} 1895 self.update()
1896
1897 - def string2object(self,string_):
1898 if string_ in self.cache: 1899 return self.cache[string_] 1900 else: 1901 return string_
1902
1903 - def object2string(self,object_):
1904 inverse_cache = inverse(self.cache) 1905 if object_ in inverse_cache: 1906 return inverse_cache[object_] 1907 else: 1908 return object_
1909 1910
1911 - def update(self):
1912 self.cache = self.param.get_range()
1913
1914 - def __copy__(self):
1915 n = super(String_ObjectTranslator,self).__copy__() 1916 n.cache = self.cache 1917 return n
1918 1919 1920
1921 -class CSPTranslator(String_ObjectTranslator):
1922
1923 - def string2object(self,string_):
1924 obj = super(CSPTranslator,self).string2object(string_) 1925 ## instantiate if it's just a class 1926 if isinstance(obj,type) and isinstance(string_,str): 1927 obj = obj() 1928 self.cache[string_]=obj 1929 1930 return obj
1931
1932 - def object2string(self,object_):
1933 ## replace class if we already have object 1934 for name,obj in self.cache.items(): 1935 if type(object_)==obj or type(object_)==type(obj): 1936 self.cache[name]=object_ 1937 ## 1938 return super(CSPTranslator,self).object2string(object_)
1939 1940
1941 - def __copy__(self):
1942 # because this one's object2string can modify cache 1943 n = Translator.__copy__(self) 1944 n.cache = copy.copy(self.cache) 1945 return n
1946 1947 1948
1949 -def param_is_dynamically_generated(param,po):
1950 1951 if not hasattr(param,'_value_is_dynamic'): 1952 return False 1953 1954 if isinstance(po,Parameterized): 1955 return param._value_is_dynamic(po) 1956 elif isinstance(po,ParameterizedMetaclass): 1957 return param._value_is_dynamic(None) 1958 else: 1959 raise ValueError("po must be a Parameterized or ParameterizedMetaclass.")
1960 1961
1962 -class ParametersFrame(TkParameterized,T.Frame):
1963 """ 1964 Displays and allows instantaneous editing of the Parameters 1965 of a supplied Parameterized. 1966 1967 Changes made to Parameter representations on the GUI are 1968 immediately applied to the underlying object. 1969 """ 1970 Defaults = Button(doc="""Return values to class defaults.""") 1971 1972 Refresh = Button(doc="Return values to those currently set on the object (or, if editing a class, to those currently set on the class).") 1973 1974 # CEBALERT: this is a Frame, so close isn't likely to make 1975 # sense. But fortunately the close button acts on master. 1976 # Just be sure not to use this button when you don't want 1977 # the master window to vanish (e.g. in the model editor). 1978 Close = Button(doc="Close the window. (If applicable, asks if unsaved changes should be saved).") 1979 1980 display_threshold = Number(default=0,precedence=-10,doc="Parameters with precedence below this value are not displayed.") 1981
1982 - def __init__(self,master,parameterized_object=None,on_change=None, 1983 on_modify=None,msg_handler=None,**params):
1984 """ 1985 Create a ParametersFrame with the specifed master, and 1986 representing the Parameters of parameterized_object. 1987 1988 on_change is an optional function to call whenever any of the 1989 GUI variables representing Parameter values is set() by the 1990 GUI (i.e. by the user). Since a variable's value is not 1991 necessarily changed by such a set(), on_modify is another 1992 optional function to call only when a GUI variable's value 1993 actually changes. (See TkParameterized for more detail.) 1994 """ 1995 T.Frame.__init__(self,master,borderwidth=1,relief='raised') 1996 1997 TkParameterized.__init__(self,master, 1998 extraPO=parameterized_object, 1999 self_first=False, 2000 msg_handler=msg_handler, 2001 **params) 2002 2003 self.on_change = on_change 2004 self.on_modify = on_modify 2005 2006 ## Frame for the Parameters 2007 self._params_frame = T.Frame(self) 2008 self._params_frame.pack(side='top',expand='yes',fill='both') 2009 2010 if parameterized_object: 2011 self.set_PO(parameterized_object,on_change=on_change, 2012 on_modify=on_modify) 2013 2014 self.__create_button_panel() 2015 2016 ### Right-click menu for widgets 2017 self.option_add("*Menu.tearOff", "0") 2018 self.menu = Menu(self) 2019 self.menu.insert_command('end',label='Properties', 2020 command=lambda:self.__edit_PO_in_currently_selected_widget()) 2021 2022 # CEBALERT: just because callers assume this pack()s itself. 2023 # Really it should be left to callers i.e. this should be removed. 2024 self.pack(expand='yes',fill='both')
2025 2026
2027 - def hidden_param(self,name):
2028 """Return True if a parameter's precedence is below the display threshold.""" 2029 # interpret a precedence of None as 0 2030 precedence = self.get_parameter_object(name).precedence or 0 2031 return precedence<self.display_threshold
2032 2033
2034 - def __create_button_panel(self):
2035 """ 2036 Add the buttons in their own panel (frame). 2037 """ 2038 ## Buttons 2039 # 2040 # Our button order (when all buttons present): 2041 # [Defaults] [Refresh] [Apply] [Close] 2042 # 2043 # Our button - Windows 2044 # Close(yes) - OK 2045 # Close(no ) - Cancel 2046 # [X] - Cancel 2047 # Apply - Apply 2048 # Defaults - 2049 # Refresh - Reset 2050 # 2051 # I think Windows users will head for the window's [X] 2052 # when they want to close and cancel their changes, 2053 # because they won't know if [Close] saves their changes 2054 # or not (until they press it, and find that it asks). 2055 # 2056 # 2057 # Some links that discuss and address what order to use for buttons: 2058 # 2059 # http://java.sun.com/products/jlf/ed2/book/HIG.Dialogs2.html 2060 # http://developer.kde.org/documentation/books/kde-2.0-development/ch08lev1sec6.html 2061 # http://developer.kde.org/documentation/standards/kde/style/dialogs/index.html 2062 # http://doc.trolltech.com/qq/qq19-buttons.html 2063 2064 2065 # Catch click on the [X]: like clicking [Close] 2066 # CEBALERT: but what if this frame isn't in its own window! 2067 try: 2068 self.master.protocol("WM_DELETE_WINDOW",self._close_button) 2069 except AttributeError: 2070 pass 2071 2072 buttons_frame = T.Frame(self,borderwidth=1,relief='sunken') 2073 buttons_frame.pack(side="bottom",expand="no") 2074 2075 self._buttons_frame_left = T.Frame(buttons_frame) 2076 self._buttons_frame_left.pack(side='left',expand='yes',fill='x') 2077 2078 self._buttons_frame_right = T.Frame(buttons_frame) 2079 self._buttons_frame_right.pack(side='right',expand='yes',fill='x') 2080 2081 self.pack_param('Defaults',parent=self._buttons_frame_left, 2082 on_change=self._defaults_button,side='left') 2083 self.pack_param('Refresh',parent=self._buttons_frame_left, 2084 on_change=self._refresh_button,side='left') 2085 self.pack_param('Close',parent=self._buttons_frame_right, 2086 on_change=self._close_button,side='right')
2087 2088
2089 - def _refresh_button(self):
2090 """See Refresh parameter.""" 2091 for name in self.displayed_params.keys(): 2092 self._tkvars[name].get()
2093 2094
2095 - def _defaults_button(self):
2096 """See Defaults parameter.""" 2097 assert isinstance(self._extraPO,Parameterized) 2098 2099 defaults = self._extraPO.defaults() 2100 2101 for param_name,val in defaults.items(): 2102 if not self.hidden_param(param_name): 2103 self.gui_set_param(param_name,val)#_tkvars[param_name].set(val) 2104 2105 if self.on_modify: self.on_modify() 2106 if self.on_change: self.on_change() 2107 self.update_idletasks()
2108 2109
2110 - def _close_button(self):
2111 """See Close parameter.""" 2112 T.Frame.destroy(self) # hmm 2113 self.master.destroy()
2114 2115 2116 2117 # CEBALERT: all these 'on_change=None's mean someone could lose 2118 # their initial setting: cleanup
2119 - def set_PO(self,parameterized_object,on_change=None,on_modify=None):
2120 """ 2121 2122 """ 2123 2124 self.change_PO(parameterized_object) 2125 2126 title = "Parameters of "+ (parameterized_object.name or str(parameterized_object)) # (name for class is None) 2127 2128 try: 2129 self.master.title(title) 2130 except AttributeError: 2131 # can only set window title on a window (model editor puts frame in another frame) 2132 pass 2133 2134 # CB: need a method for this! 2135 self.__dict__['_name_param_value'] = title 2136 2137 2138 ### Pack all of the non-hidden Parameters 2139 self.displayed_params = {} 2140 for n,p in parameterized_object.params().items(): 2141 if not self.hidden_param(n): 2142 self.displayed_params[n]=p 2143 2144 self.pack_displayed_params(on_change=on_change,on_modify=on_modify) 2145 2146 # hide Defaults button for classes 2147 if isinstance(parameterized_object,ParameterizedMetaclass): 2148 self.hide_param('Defaults') 2149 else: 2150 self.unhide_param('Defaults')
2151 2152
2153 - def _wipe_currently_displaying(self):
2154 """Wipe old labels and widgets from screen.""" 2155 if hasattr(self,'currently_displaying'): 2156 for rep in self.currently_displaying.values(): 2157 try: 2158 rep['label'].destroy() 2159 except AttributeError: 2160 # e.g. buttons have None for label ('notNonelabel') 2161 pass 2162 rep['widget'].destroy()
2163 2164 2165 ## def pack_param(self): 2166 ## raise TypeError("ParametersFrame arranges all parameters together in a grid.") 2167 2168
2169 - def __grid_param(self,parameter_name,row):
2170 widget = self.representations[parameter_name]['widget'] 2171 label = self.representations[parameter_name]['label'] 2172 2173 # CB: (I know about the code duplication here & in tkpo) 2174 param_obj = self.get_parameter_object(parameter_name) 2175 help_text = getdoc(param_obj) 2176 2177 if param_obj.default is not None: 2178 # some params appear to have no docs!!! 2179 if help_text is not None: 2180 help_text+="\n\nDefault: %s"%self._object2string(parameter_name,param_obj.default,replace=False) 2181 2182 2183 label.grid(row=row,column=0, 2184 padx=2,pady=2,sticky=T.E) 2185 2186 self.balloon.bind(label, help_text) 2187 2188 # We want widgets to stretch to both sides... 2189 posn=T.E+T.W 2190 # ...except Checkbuttons, which should be left-aligned. 2191 if widget.__class__==T.Checkbutton: 2192 posn=T.W 2193 2194 widget.grid(row=row, 2195 column=1, 2196 padx=2, 2197 pady=2, 2198 sticky=posn) 2199 2200 # widgets expand to fill frame 2201 widget.master.grid_columnconfigure(1,weight=2) 2202 2203 self.representations[parameter_name]['row']=row 2204 2205 self._post_add_param(parameter_name)
2206 2207 2208 2209
2210 - def __make_representation(self,name,on_change=None,on_modify=None):
2211 widget,label = self._create_widget(name,self._params_frame, 2212 on_change=on_change or self.on_change, 2213 on_modify=on_modify or self.on_modify) 2214 2215 label.bind("<Double-Button-1>",lambda event=None,x=name: self.switch_dynamic(x)) 2216 self.representations[name]={'widget':widget,'label':label} 2217 self._indicate_tkvar_status(name)
2218 2219 2220 2221 # CEBALERT: name doesn't make sense! change displayed_params to 2222 # something else e.g. params_to_display
2223 - def pack_displayed_params(self,on_change=None,on_modify=None):
2224 2225 self._wipe_currently_displaying() 2226 2227 ### sort Parameters by reverse precedence 2228 parameter_precedences = {} 2229 for name,parameter in self.displayed_params.items(): 2230 parameter_precedences[name]=parameter.precedence 2231 sorted_parameter_names = keys_sorted_by_value(parameter_precedences) 2232 2233 ### create the labels & widgets 2234 for name in self.displayed_params: 2235 self.__make_representation(name,on_change,on_modify) 2236 2237 ### add widgets & labels to screen in a grid 2238 rows = range(len(sorted_parameter_names)) 2239 for row,parameter_name in zip(rows,sorted_parameter_names): 2240 self.__grid_param(parameter_name,row) 2241 2242 self.currently_displaying = dict([(param_name,self.representations[param_name]) 2243 for param_name in self.displayed_params])
2244 #self.event_generate("<<SizeRight>>") 2245 2246
2247 - def _create_selector_widget(self,frame,name,widget_options):
2248 """As for the superclass, but binds <<right-click>> event for opening menu.""" 2249 w = TkParameterized._create_selector_widget(self,frame,name,widget_options) 2250 w.bind('<<right-click>>',lambda event: self.__right_click(event, w)) 2251 return w
2252 2253
2254 - def __right_click(self, event, widget):
2255 """ 2256 Popup the right-click menu. 2257 """ 2258 self.__currently_selected_widget = widget 2259 self.menu.tk_popup(event.x_root, event.y_root)
2260 2261 2262 # CEBALERT: rename
2264 """ 2265 Open a new window containing a ParametersFrame (actually, a 2266 type(self)) for the PO in __currently_selected_widget. 2267 """ 2268 # CEBALERT: simplify this lookup by value 2269 param_name = None 2270 for name,representation in self.representations.items(): 2271 if self.__currently_selected_widget is representation['widget']: 2272 param_name=name 2273 break 2274 2275 # CEBALERT: should have used get_parameter_value(param_name)? 2276 PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get()) 2277 2278 parameter_window = AppWindow(self) 2279 parameter_window.title(PO_to_edit.name+' parameters') 2280 2281 ### CEBALERT: confusing? ### 2282 title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")") 2283 title.pack(side = "top") 2284 self.balloon.bind(title,getdoc(self.get_parameter_object(param_name,self._extraPO))) 2285 ############################ 2286 2287 parameter_frame = type(self)(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler,on_change=self.on_change,on_modify=self.on_modify) 2288 parameter_frame.pack()
2289 2290 2291 ## def unpack_param(self,param_name): 2292 ## if param_name in self.currently_displaying: 2293 ## raise NotImplementedError("yet") 2294 ## super(ParametersFrame,self).unpack_param(param_name) 2295 2296 ## def hide_param(self,param_name): 2297 ## if param_name in self.currently_displaying: 2298 ## raise NotImplementedError("yet") 2299 ## super(ParametersFrame,self).hide_param(param_name) 2300 2301 ## def unhide_param(self,param_name): 2302 ## if param_name in self.currently_displaying: 2303 ## raise NotImplementedError("yet") 2304 ## super(ParametersFrame,self).unhide_param(param_name) 2305 2306 2307 # ERROR need to sort out all this stuff in the tkpo/pf hierarchy 2308
2309 - def repack_param(self,param_name):
2310 2311 self._refresh_value(param_name) 2312 2313 r = self.representations[param_name] 2314 widget,label,row = r['widget'],r['label'],r['row'] 2315 2316 widget.destroy() 2317 label.destroy() 2318 2319 param = self.get_parameter_object(param_name) 2320 2321 self._create_translator(param_name,param) 2322 self.__make_representation(param_name) 2323 self.__grid_param(param_name,row)
2324 2325 2326 2327 ## def _indicate_tkvar_status(self,param_name): 2328 ## """ 2329 ## Calls the superclass's method, then additionally indicates if a parameter 2330 ## differs from the class default (by giving label green background). 2331 ## """ 2332 ## TkParameterized._indicate_tkvar_status(self,param_name) 2333 2334 ## b = 'white' 2335 2336 ## param,sourcePO = self.get_parameter_object(param_name,with_location=True) 2337 2338 ## if sourcePO is not self and self.get_parameter_value(param_name) is not self.get_parameter_object(param_name).default: 2339 ## b = "green" 2340 2341 2342 ## if hasattr(self,'representations') and param_name in self.representations: 2343 ## try: 2344 ## label = self.representations[param_name]['label'] 2345 ## if label is None: # HACK about the label being none 2346 ## return 2347 ## label['background']=b 2348 ## except T.TclError: 2349 ## pass 2350 2351 2352
2353 - def _refresh_value(self,param_name):
2354 pass
2355 2356 2357
2358 -class ParametersFrameWithApply(ParametersFrame):
2359 """ 2360 Displays and allows editing of the Parameters of a supplied 2361 Parameterized. 2362 2363 Changes made to Parameter representations in the GUI are not 2364 applied to the underlying object until Apply is pressed. 2365 """ 2366 2367 # CEBENHANCEMENT: might be nice to make Apply button blue like the 2368 # unapplied changes, but can't currently set button color. 2369 Apply = Button(doc="""Set object's Parameters to displayed values.\n 2370 When editing a class, sets the class defaults 2371 (i.e. acts on the class object).""") 2372
2373 - def __init__(self,master,parameterized_object=None, 2374 on_change=None,on_modify=None,**params):
2375 super(ParametersFrameWithApply,self).__init__(master, 2376 parameterized_object, 2377 on_change,on_modify, 2378 **params) 2379 self._live_update = False 2380 2381 ### CEBALERT: describe why this apply is different from Apply 2382 for p in self.param_immediately_apply_change: 2383 self.param_immediately_apply_change[p]=True 2384 2385 2386 self.pack_param('Apply',parent=self._buttons_frame_right, 2387 on_change=self._apply_button,side='left') 2388 2389 # this check for displayed_params, like elsewhere it exists, 2390 # is to get round the fact that parametersframes can be opened 2391 # without any associated object. Need to clean this 2392 # up. (displayed_params should probably start as an empty 2393 # dict.) 2394 if hasattr(self,'displayed_params'): 2395 assert self.has_unapplied_change() is False, "ParametersFrame altered a value on opening. If possible, please email ceball at users.sf.net describing what you were doing when you received this error." 2396 # should use existing code 2397 self.representations['Apply']['widget']['state']='disabled'
2398 2399
2400 - def _create_string_widget(self,frame,name,widget_options):
2401 # CEBALERT: why do I unbind those events? 2402 w= super(ParametersFrameWithApply,self)._create_string_widget(frame,name,widget_options) 2403 w.unbind('<Return>') 2404 w.unbind('<FocusOut>') 2405 return w
2406 2407
2408 - def set_PO(self,parameterized_object,on_change=None,on_modify=None):
2409 2410 super(ParametersFrameWithApply,self).set_PO(parameterized_object, 2411 on_change=on_change, 2412 on_modify=on_modify) 2413 2414 # (don't want to update parameters immediately) 2415 for v in self._tkvars.values(): 2416 v._checking_get = v.get 2417 v.get = v._original_get
2418 2419
2420 - def has_unapplied_change(self):
2421 """Return True if any one of the packed parameters' displayed values is different from 2422 that on the object.""" 2423 for name in self.displayed_params.keys(): 2424 if self._tkvar_changed(name): 2425 return True 2426 return False
2427 2428 2429
2430 - def _indicate_tkvar_status(self,param_name,status=None):
2431 2432 if self._tkvar_changed(param_name): 2433 status = 'changed' 2434 2435 super(ParametersFrameWithApply,self)._indicate_tkvar_status(param_name,status)
2436 2437
2438 - def _handle_gui_set(self,p_name,force=False):
2439 TkParameterized._handle_gui_set(self,p_name,force) 2440 2441 if hasattr(self,'representations') and 'Apply' in self.representations: 2442 w=self.representations['Apply']['widget'] 2443 if self.has_unapplied_change(): 2444 state='normal' 2445 else: 2446 state='disable' 2447 2448 w.config(state=state)
2449 2450 2451 2452
2453 - def _close_button(self):
2454 # CEBALERT: dialog box should include a cancel button 2455 # Also, changes are *not* applied if one of the boxes is in error. 2456 if self.has_unapplied_change() \ 2457 and askyesno("Close","Apply changes before closing?"): 2458 self.update_parameters() 2459 super(ParametersFrameWithApply,self)._close_button()
2460 2461
2462 - def update_parameters(self):
2463 if isinstance(self._extraPO,ParameterizedMetaclass): 2464 for name in self.displayed_params.keys(): 2465 #if self._tkvar_changed(name): 2466 self._update_param_from_tkvar(name) 2467 else: 2468 for name,param in self.displayed_params.items(): 2469 if not param.constant: #and self._tkvar_changed(name): 2470 self._update_param_from_tkvar(name)
2471 2472
2473 - def _apply_button(self):
2474 self.update_parameters() 2475 self._refresh_button(overwrite_error=False)
2476
2477 - def _refresh_value(self,param_name):
2478 po_val = self.get_parameter_value(param_name) 2479 po_stringrep = self._object2string(param_name,po_val) 2480 self._tkvars[param_name]._original_set(po_stringrep)
2481 2482
2483 - def _refresh_button(self,overwrite_error=True):
2484 for name in self.displayed_params.keys(): 2485 if self.translators[name].last_string2object_failed and not overwrite_error: 2486 pass 2487 else: 2488 self._refresh_value(name) 2489 #print self._tkvars[name]._checking_get() 2490 # CEBALERT: taggedsliders need to have tag_set() called to update slider 2491 w = self.representations[name]['widget'] 2492 if hasattr(w,'tag_set'):w.tag_set()
2493 2494 2495
2496 - def _defaults_button(self):
2497 """See Defaults parameter.""" 2498 assert isinstance(self._extraPO,Parameterized) 2499 2500 defaults = self._extraPO.defaults() 2501 2502 for param_name,val in defaults.items(): 2503 if not self.hidden_param(param_name): 2504 self._tkvars[param_name].set(val) 2505 2506 if self.on_modify: self.on_modify() 2507 if self.on_change: self.on_change() 2508 self.update_idletasks()
2509 2510 2511 # CB: can override tracefn so that Apply/Refresh buttons are enabled/disabled as appropriate 2512
2513 -def edit_parameters(parameterized,with_apply=True,**params):
2514 """ 2515 Edit the Parameters of a supplied parameterized instance or class. 2516 2517 Specify with_apply=False for a ParametersFrame (which immediately 2518 updates the object - no need to press the Apply button). 2519 2520 Extra params are passed to the ParametersFrame constructor. 2521 """ 2522 if not with_apply: 2523 pf_class = ParametersFrame 2524 else: 2525 pf_class = ParametersFrameWithApply 2526 2527 return pf_class(T.Toplevel(),parameterized,**params)
2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 # Barely wrapped tooltip from tklib. 2539 # CB: this isn't the right way to do it, and it breaks menubar 2540 # tips for some reason, but user code didn't have to change. 2541
2542 -class Balloon(T.Widget):
2543 2544 _tkname = '::tooltip::tooltip' 2545
2546 - def __init__(self,master,cnf={},**kw):
2547 master.tk.call("package","require","tooltip") 2548 T.Widget.__init__(self, master,self._tkname, cnf, kw)
2549
2550 - def bind(self,*args):
2551 """ 2552 e.g. for a Button b and a Menu m with item 'Quit' in a T.Toplevel t 2553 2554 balloon = Balloon(t) 2555 balloon.bind(b,'some guidance') 2556 balloon.bind(m,'Quit','more guidance') 2557 """ 2558 if len(args)>2: 2559 self.tk.call(self._tkname,args[0]._w,'-index',args[1],args[2]) 2560 else: 2561 self.tk.call(self._tkname,*args)
2562 2563 ## def tagbind(self,*args,**kw): 2564 ## print "### Balloon.tagbind(): not yet implemented error ###" 2565 2566 2567 2568 # CEBALERT: should be renamed to something like IndexMenu, but then 2569 # I am not sure if some of our tk hacks would work (e.g. in topoconsole, 2570 # we set something for Menu on linux so that the popup behavior is 2571 # reasonable). 2664 2665 # other methods can be overriden if they're needed 2666 2667 2668
2669 -class TaggedSlider(T.Frame):
2670 """ 2671 Widget for manipulating a numeric value using either a slider or a 2672 text-entry box, keeping the two values in sync. 2673 2674 Generates a number of Events: 2675 2676 <<TagFocusOut>> - tag loses focus 2677 <<TagReturn>> - Return pressed in tag 2678 <<SliderSet>> - slider is clicked/dragged 2679 """
2680 - def __init__(self,master,variable,bounds=(0,1), 2681 slider_length=100,tag_width=10, 2682 tag_extra_config={},slider_extra_config={}):
2683 """ 2684 On clicking or dragging the slider, the tag value is set 2685 to the slider's value. 2686 2687 On pressing Return in or moving focus from the tag, the slider 2688 value is set, but also: 2689 2690 * the range of the slider is adjusted (e.g. to fit a larger 2691 max value) 2692 2693 * the resolution of the slider is adjusted based on the 2694 the value in the tag (e.g. 0.01 in the tag gives a 2695 resolution of 0.01), also taking into account the precision 2696 of the value in the tag (e.g. 0.0100 gives a resolution 2697 of 0.0001). 2698 """ 2699 # CEBALERT: ...although respecting the precision isn't always 2700 # helpful because the slider can still have a limited 2701 # resolution anyway (from its length and the range, given the 2702 # size of a pixel...) 2703 T.Frame.__init__(self,master) 2704 2705 self.variable= variable 2706 self.bounds = bounds 2707 2708 self.tag = T.Entry(self,textvariable=self.variable, 2709 width=tag_width,**tag_extra_config) 2710 self.tag.pack(side='left') 2711 self.tag.bind('<Return>', self._tag_press_return) 2712 self.tag.bind('<FocusOut>', self._tag_focus_out) 2713 self.tag.bind('<Leave>', self._tag_focus_out) 2714 2715 # no variable: we control the slider ourselves 2716 self.slider = T.Scale(self,variable=None, 2717 from_=self.bounds[0],to=self.bounds[1], 2718 orient='horizontal',length=slider_length, 2719 showvalue=0,**slider_extra_config) 2720 2721 self.slider.pack(side='right',expand="yes",fill='x') 2722 self.slider.bind('<ButtonRelease-1>', self._slider_used) 2723 self.slider.bind('<B1-Motion>', self._slider_used) 2724 2725 self.tag_set()
2726 2727
2728 - def tag_set(self):
2729 """ 2730 After entering a value into the tag, this method should be 2731 called to set the slider correctly. 2732 2733 (Called automatically for tag's <Return> and <FocusOut> 2734 events.) 2735 """ 2736 # Set slider resolution. This is important because 2737 # we want the slider to be positioned at the exact 2738 # tag value. 2739 self._try_to_set_slider_resolution() 2740 self._try_to_set_slider()
2741 2742
2743 - def set_slider_bounds(self,lower,upper):
2744 """ 2745 Set new lower and upper bounds for the slider. 2746 """ 2747 self.bounds = (lower,upper) 2748 self.slider.config(from_=lower,to=upper)
2749 set_bounds = set_slider_bounds 2750 2751 2752 # CB: why isn't this used for [] access? What should this 2753 # be called? Is it configure?
2754 - def config(self,**options):
2755 """ 2756 TaggedSlider is a compound widget. In most cases, config 2757 options should be passed to one of the component widgets 2758 (i.e. the tag or the slider). For some options, however, 2759 we need to handle them being set on the widget as a whole; 2760 further, some of these should be applied to both component 2761 widgets, but some should just be applied to one. 2762 2763 Options handled: 2764 * state (applied to both) 2765 * background, foreground (applied to tag only) 2766 """ 2767 if 'state' in options: 2768 self.tag['state']=options['state'] 2769 self.slider['state']=options['state'] 2770 del options['state'] 2771 if 'background' in options: 2772 self.tag['background']=options['background'] 2773 del options['background'] 2774 if 'foreground' in options: 2775 self.tag['foreground']=options['foreground'] 2776 del options['foreground'] 2777 if len(options)>0: 2778 raise NotImplementedError( 2779 """Only state, background, and foreground options are 2780 currently supported for this widget; set options on 2781 either the component tag or slider instead.""") 2782 2783 return {} # CEBALERT: need to return same object as Tkinter would.
2784 2785
2786 - def get(self):
2787 """ 2788 Calls the tag's get() method. 2789 2790 Helps to match behavior of other Tkinter Widgets. 2791 """ 2792 return self.tag.get()
2793 2794 2795 # CEBALERT: three different events for max. flexibility...but 2796 # often a user will just want to know if the value was set. Could 2797 # also generate a "<<TaggedSliderSet>>" event each time, which a 2798 # user could just look for. Or could these events all be children 2799 # of a <<TaggedSliderSet>>?
2800 - def _slider_used(self,event=None):
2801 self.variable.set(self.slider.get()) 2802 self.event_generate("<<SliderSet>>")
2803 2804
2805 - def _tag_press_return(self,event=None):
2806 self.event_generate("<<TagReturn>>") 2807 self.tag_set()
2808 2809
2810 - def _tag_focus_out(self,event=None):
2811 self.event_generate("<<TagFocusOut>>") 2812 self.tag_set()
2813 2814
2815 - def _set_slider_resolution(self,value):
2816 # CEBALERT: how to find n. dp simply? 2817 p = decimal.Decimal(str(value)).as_tuple()[2] 2818 self.slider['resolution']=10**p
2819 2820
2822 """ 2823 Use the actual number in the box to set the slider's resolution, 2824 so that user-entered resolutions are respected (e.g. 0.1 vs 0.1000). 2825 2826 If that's not possible (e.g. there's text in the box), use the 2827 value contained in the variable (because whatever owns the 2828 variable could have performed a conversion of the text - 2829 TkParameterized does this, for instance). 2830 2831 Leaves the resolution as it was if no number is available. 2832 """ 2833 try: 2834 # 1st choice is to get the actual number in the box: 2835 # allows us to respect user-entered resolution 2836 # (e.g. 0.010000) 2837 self._set_slider_resolution(self.tag.get()) 2838 return True 2839 except: # probably tclerror 2840 try: 2841 # ...but that might have been text. 2nd choice is to 2842 # get the value from the variable (e.g. 0.01) 2843 self._set_slider_resolution(self.variable.get()) 2844 return True 2845 except: # probably tclerror 2846 return False # can't set a new resolution
2847 2848
2849 - def _try_to_set_slider(self):
2850 """ 2851 If the value in the box can't be converted to a float, the slider doesn't get set. 2852 """ 2853 tagvar_val = self.variable.get() 2854 try: 2855 val = float(tagvar_val) 2856 self.set_slider_bounds(min(self.bounds[0],val), 2857 max(self.bounds[1],val)) 2858 self.slider.set(val) 2859 except ValueError: 2860 pass
2861 2862
2863 -class FocusTakingButton(T.Button):
2864 """ 2865 A Tkinter Button that takes the focus when the mouse <Enter>s. 2866 2867 (Tkinter.Button doesn't get focus even when it's clicked, 2868 and only <Enter> and <Leave> events work for buttons.) 2869 """
2870 - def __init__(self, master=None, cnf={}, **kw):
2871 T.Button.__init__(self,master=master,cnf=cnf,**kw) 2872 self.bind("<Enter>", lambda e=None,x=self: x.focus_set())
2873 #self['highlightthickness']=0 2874 2875 2876 # CB: tklib has status bar and scrolled window (among other widgets); 2877 # should try them out (although they're tk only...) because we might 2878 # not need the scrodget pacakge. 2879 2880 # Might wonder why we need <<SizeRight>> event, and don't just use the 2881 # <Configure> event for calling sizeright: Can't distinguish manual 2882 # resize from autoresizing.
2883 -class ScrolledFrame(T.Frame):
2884 """ 2885 XXXX 2886 2887 Content to be scrolled should go in the 'content' frame. 2888 """ 2889
2890 - def _getstatus(self):
2891 return self._status
2892 - def _setstatus(self,s):
2893 self._status = s
2894 status = property(_getstatus,_setstatus) 2895
2896 - def __init__(self,parent,status=False,**config):
2897 T.Frame.__init__(self,parent,**config) 2898 2899 topframe = T.Frame(self)#,bd=2,relief='groove') 2900 botframe = T.Frame(self)#,bd=2,relief='groove') 2901 2902 botframe.pack(side="bottom",expand="yes",fill="x") 2903 topframe.pack(side="top",expand="yes",fill="both") 2904 2905 2906 2907 self.canvas = T.Canvas(topframe) 2908 self.canvas.pack() 2909 self.canvas.configure(width=0,height=0) 2910 2911 self.sc = Scrodget(topframe,autohide=1) 2912 self.sc.associate(self.canvas) 2913 self.sc.pack(expand=1,fill="both") 2914 2915 self.content = T.Frame(self.canvas) 2916 self.content.title = lambda x: self.title(x) 2917 self.content.container = self 2918 2919 self.canvas.create_window(0,0,window=self.content,anchor='nw') 2920 2921 self.bind("<<SizeRight>>",self.sizeright) 2922 2923 self.status = StatusBar(botframe) 2924 if status: 2925 self.status.pack(side="bottom",fill="both",expand="yes")
2926 2927 2928
2929 - def sizeright(self,event=None):
2930 self.content.update_idletasks() 2931 self.canvas.configure(scrollregion=(0, 0, self.content.winfo_width(), 2932 self.content.winfo_height())) 2933 self.canvas.configure(width=self.content.winfo_width(), 2934 height=self.content.winfo_height())
2935 2936 2937 2938 2939
2940 -class ScrolledWindow(T.Toplevel):
2941
2942 - def __init__(self,parent,**config):
2943 T.Toplevel.__init__(self,parent,**config) 2944 self.maxsize(self.winfo_screenwidth(),self.winfo_screenheight()) 2945 2946 self.topframe = T.Frame(self) 2947 # CEBALERT: specifying 20 necessary because otherwise nothing 2948 # requests any height for the frame. I feel there should be 2949 # way to have the (status) label request the right height but 2950 # make no request about the width. 2951 self.botframe = T.Frame(self,height=20) 2952 2953 self.botframe.pack(side='bottom',expand='no',fill='x') 2954 self.topframe.pack(side='top',expand='yes',fill='both') 2955 2956 self._scrolledframe = ScrolledFrame(self.topframe) 2957 self._scrolledframe.pack(expand=1,fill='both') 2958 self.content = self._scrolledframe.content
2959 2960 # required? presumably should be deleted
2961 - def sizeright(self,event=None):
2962 self._scrolledframe.sizeright()
2963 2964
2965 -class StatusBar(T.Frame):
2966
2967 - def __init__(self, master):
2968 T.Frame.__init__(self, master) 2969 self.label = T.Label(self, borderwidth=1, relief='sunken', anchor='w') 2970 self.label.pack(fill='x')
2971
2972 - def message(self,chuck=None,message=None):
2973 self.set(message)
2974
2975 - def set(self, format, *args):
2976 self.label.config(text=format % args) 2977 self.label.update_idletasks()
2978
2979 - def clear(self):
2980 self.label.config(text="") 2981 self.label.update_idletasks()
2982 2983 2984 # CEBALERT: rename
2985 -class AppWindow(ScrolledWindow):
2986 """ 2987 A ScrolledWindow with extra features intended to be common to all 2988 windows of an application. 2989 2990 Currently this only includes a window icon, but we intend to 2991 add a right-click menu and possibly more. 2992 """ 2993 window_icon_path = None 2994
2995 - def __init__(self,parent,status=False,**config):
2996 2997 ScrolledWindow.__init__(self,parent) 2998 self.content.title = self.title 2999 3000 self.renew() 3001 ### Universal right-click menu 3002 # CB: not currently used by anything but the plotgrouppanels 3003 # self.context_menu = Tkinter.Menu(self, tearoff=0) 3004 # self.bind("<<right-click>>",self.display_context_menu) 3005 3006 # status bar is currenlty inside scrolled area (a feature 3007 # request is to move it outside ie replace self.content with 3008 # just self) 3009 self.status = StatusBar(self.botframe) 3010 if status: 3011 # place doesn't interfere with parent's geometry 3012 # (don't want status bar to cause horizontal resizing, 3013 # but DO want it to cause vertical resizing - the vertical 3014 # part doesn't work, see earlier HACKALERT by botframe) 3015 self.status.place(relx=0,rely=0,relwidth=1.0,height='')
3016
3017 - def renew(self):
3018 # CEBALERT: doesn't work on OS X, and is a strange color on 3019 # Windows. To get a proper icon on OS X, we probably have to 3020 # bundle into an application package or something like that. 3021 # On OS X, could possibly alter the small titlebar icon with something like: 3022 # self.attributes("-titlepath","/Users/x/topographica/AppIcon.icns") 3023 self.iconbitmap('@'+self.window_icon_path) 3024 3025 self.protocol("WM_DELETE_WINDOW",self.mydestroy)
3026
3027 - def mydestroy(self):
3029