| Home | 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_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
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
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
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
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
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
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
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).
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...
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
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.
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).
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.
493
494
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.
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
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
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
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
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
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?
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
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.
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
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
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
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 #####
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.
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
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
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
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
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
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
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
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
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
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
1166
1167
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
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
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
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
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
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.
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
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
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
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
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
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
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
1564
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
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
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
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
1704 """Return a Tkinter.Checkbutton to represent parameter 'name'."""
1705 return T.Checkbutton(frame,variable=self._tkvars[name],**widget_options)
1706
1707
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
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!)
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
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
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
1795
1798
1801
1805
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
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 """
1822
1825
1826 # seems to be necessary after switching to tk 8.5
1827 # need to check it's really necessary
1831
1832
1833 # Error messages: need to change how they're reported
1834
1835
1836
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
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
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
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
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
1889
1890 cache = {}
1891
1893 super(String_ObjectTranslator,self).__init__(param,initial_value)
1894 self.cache = {}
1895 self.update()
1896
1902
1904 inverse_cache = inverse(self.cache)
1905 if object_ in inverse_cache:
1906 return inverse_cache[object_]
1907 else:
1908 return object_
1909
1910
1913
1918
1919
1920
1922
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
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
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
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
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
2032
2033
2087
2088
2093
2094
2108
2109
2114
2115
2116
2117 # CEBALERT: all these 'on_change=None's mean someone could lose
2118 # their initial setting: cleanup
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
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
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
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
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
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
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
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
2355
2356
2357
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
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
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
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
2431
2432 if self._tkvar_changed(param_name):
2433 status = 'changed'
2434
2435 super(ParametersFrameWithApply,self)._indicate_tkvar_status(param_name,status)
2436
2437
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
2460
2461
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
2476
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
2493
2494
2495
2509
2510
2511 # CB: can override tracefn so that Apply/Refresh buttons are enabled/disabled as appropriate
2512
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
2543
2544 _tkname = '::tooltip::tooltip'
2545
2547 master.tk.call("package","require","tooltip")
2548 T.Widget.__init__(self, master,self._tkname, cnf, kw)
2549
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).
2573 """
2574 Tkinter Menu, but with a way to access entries by name.
2575
2576 Supply indexname to any of the add/insert/delete methods and that
2577 indexname can be used to get the index of the entry later on (if
2578 'indexname' not supplied, uses 'label' instead, if that was
2579 supplied).
2580 """
2581 ## (Original Menu class is in lib/python2.4/lib-tk/Tkinter.py)
2582
2583 ## Note that I added a separate indexname rather than using label
2584 ## or some other existing name because those could change, and
2585 ## they're different for different widgets. Unfortunately this
2586 ## means sometimes specifying label="Something" and
2587 ## indexname="Something".
2588 ## CB: It's probably possible to remove the requirement
2589 ## for a separate indexname, taking label or whatever instead. I've tried
2590 ## that (taking label if indexname not supplied).
2591
2593 for name,i in self.indexname2index.items():
2594 if index==i: return name
2595 raise ValueError("%s not in Tkinter.Menu %s"%(index,self))
2596
2598 """Return the Tkinter index, whether given an indexname or index."""
2599 if isinstance(index,str):
2600 i=self.indexname2index[index]
2601 else:
2602 i=index
2603 return i
2604
2605
2606 ########## METHODS OVERRIDDEN to keep track of contents
2608 self.indexname2index = {} # rename to indexnames2index or similar
2609 self.entries = {}
2610 T.Menu.__init__(self,master,cnf,**kw)
2611
2612
2614 indexname = cnf.pop('indexname',kw.pop('indexname',None))
2615
2616 # take indexname as label if indexname isn't actually specified
2617 if indexname is None:
2618 indexname = cnf.get('label',kw.get('label',None))
2619
2620 T.Menu.add(self,itemType,cnf,**kw)
2621 i = self.index("last")
2622 self.indexname2index[indexname or i] = i
2623
2624 # this pain is to keep the actual item, if it's a menu or a command, available to access
2625 self.entries[indexname or i] = cnf.get('menu',kw.get('menu',cnf.get('command',kw.get('command',None))))
2626
2627
2629 indexname = cnf.pop('indexname',kw.pop('indexname',index))
2630
2631 # take indexname as label if indexname isn't actually specified
2632 if indexname is None:
2633 indexname = cnf.get('label',kw.get('label',None))
2634
2635 self.indexname2index[indexname] = index
2636 T.Menu.insert(self,index,itemType,cnf,**kw)
2637
2638 # this pain is to keep the actual item, if it's a menu or a command, available to access
2639 self.entries[indexname] = cnf.get('menu',kw.get('menu',cnf.get('command',kw.get('command',None))))
2640
2641
2643 assert index2 is None, "I only thought about single-item deletions: code needs to be upgraded..."
2644
2645 i1 = self.index(index1)
2646 self.indexname2index.pop(self.index2name(i1))
2647 self.entries.pop(self.index2name(i1))
2648
2649 T.Menu.delete(self,index1,index2)
2650
2651
2652 ########## METHODS OVERRIDDEN FOR CONVENIENCE
2654 """Configure a menu item at INDEX."""
2655 i = self.index_convert(index)
2656 T.Menu.entryconfigure(self,i,cnf,**kw)
2657
2658 entryconfig = entryconfigure
2659
2661 """Invoke a menu item identified by INDEX and execute
2662 the associated command."""
2663 return T.Menu.invoke(self,self.index_convert(index))
2664
2665 # other methods can be overriden if they're needed
2666
2667
2668
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
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
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?
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
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>>?
2803
2804
2808
2809
2813
2814
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
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
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 """
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.
2884 """
2885 XXXX
2886
2887 Content to be scrolled should go in the 'content' frame.
2888 """
2889
2894 status = property(_getstatus,_setstatus)
2895
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
2935
2936
2937
2938
2939
2941
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
2962 self._scrolledframe.sizeright()
2963
2964
2966
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
2974
2978
2982
2983
2984 # CEBALERT: rename
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
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
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
3029
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 11 20:50:22 2008 | http://epydoc.sourceforge.net |