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