<filename>paramtk/__init__.py
"""
Classes linking Parameters to Tkinter.

TkParameterized allows flexible graphical representation and
manipulation of the parameters of a Parameterized instance or class.

ParametersFrame and ParametersFrameWithApply extend TkParameterized,
displaying all the Parameters together in a list. Both allow these
Parameters to be edited; ParametersFrame applies changes immediately
as they are made, whereas ParametersFrameWithApply makes no changes
until a confirmation is given (by pressing the 'Apply' button).

Using these classes to display parameters has several benefits,
including that, automatically:

- changes to the Parameters in the GUI are reflected in the objects
  themselves without any additional code;

- each Parameter is represented by an appropriate widget (e.g. slider
  for a Number);

- type and range checking is handled (by virtue of using parameters);

- doc strings are displayed as pop-up help for each parameter;

- parameter names are formatted nicely for display;

- changes to parameter values in the GUI can be associated with
  function calls.


Examples
========

(1) Display the parameters of an object inside an existing window

You want to display all parameters of a parameterized instance
inside an existing container (e.g. a window or a frame):

  # existing Parameterized instance g
  from topo import pattern
  g = pattern.Gaussian()

  # existing window t
  import Tkinter
  t = Tkinter.Toplevel()

  # display all the parameters of g in t
  from topo.param.tk import ParametersFrame
  ParametersFrame(t,g)
  #should be ParametersFrame(t,g).pack(); see ALERT in ParametersFrame


(2) Display the parameters of an object in a standalone window

You want a new window displaying only the parameters of your object:

  # existing Parameterized instance g
  from topo import pattern
  g = pattern.Gaussian()

  # display all the parameters of g in a new window
  from topo.param.tk import edit_parameters
  edit_parameters(g)


(3) Flexible GUI layout using TkParameterized

You want to display only some of the parameters of one or more
Parameterized instances:

 ## Existing, non-GUI code
 from topo import param

 class Object1(param.Parameterized):
     duration = param.Number(2.0,bounds=(0,None),doc='Duration of measurement')
     displacement = param.Number(0.0,bounds=(-1,1),doc='Displacement from point A')

 class Object2(param.Parameterized):
     active_today = param.Boolean(True,doc='Whether or not to count today')
     operator_name = param.String('<NAME>',doc='Operator today')

 o1 = Object1()
 o2 = Object2()

 ## Existing window
 import Tkinter
 t = Tkinter.Toplevel()

 ## Flexible GUI representation: display o1.duration, o1.displacement,
 ## and o2.active_today inside t, ignoring o2.operator_name
 from topo.param.tk import TkParameterized

 t1 = TkParameterized(t,o1)
 t2 = TkParameterized(t,o2)

 t1.pack_param('duration',side='left')
 t1.pack_param('displacement',side='bottom')
 t2.pack_param('active_today',side='right')


(4) ?

TkParameterized is itself a subclass of Parameterized, so a
TkParameterized class can have its own parameters (in addition to
representing those of an external parameterized instance or class).

  ## Existing class
  from topo import params

  class X(param.Parameterized):
      one = param.Boolean(True)
      two = param.Boolean(True)


  ## Panel to represent an instance of X
  from Tkinter import Frame
  from topo.param.tk import TkParameterized

  class XPanel(TkParameterized,Frame):

      dock = param.Boolean(False,doc='Whether to attach this Panel')

      def __init__(self,master,x):
          self.pack_param('dock',side='top',on_set=self.handle_dock)
          self.pack_param('one',side='left')
          self.pack_param('two',side='right')

      def handle_dock(self):
          if self.dock:
              # dock the window
          else:
              # undock the window


  ## Running the code
  t = Tkinter.Toplevel()
  x = X()
  XPanel(t,x)
"""

# CEBALERT: moving todo
# (1) move icons from topo/tkgui.
# (2) update documentation


# CB: This file is too long because the param/gui interface code has
# become too long, and needs cleaning up.  I'm still working on it
# (still have to attend to simple ALERTs and do a one-pass cleanup)

# It's quite likely that the way TkParameterizedBase is implemented
# could be simplified. Right now, it still contains leftovers of
# various attempts to get everything working. But it does seem to
# work!


## import logging
## import string,time
## log_name= 'guilog_%s'%string.join([str(i) for i in list(time.gmtime())],"")

## logging.basicConfig(level=logging.DEBUG,
##                     format='%(asctime)s %(levelname)s %(message)s',
##                     filename='topo/tkgui/%s'%log_name,
##                     filemode='w')
## from os import popen
## version = popen('svnversion -n').read()
## logging.info("tkgui logging started for %s"%version)


import __main__
import copy
import decimal
import os.path

import Tkinter as T

try:
    import Image, ImageOps, ImageTk
except ImportError:
    from PIL import Image, ImageOps, ImageTk # pyflakes:ignore (try/except import)


from inspect import getdoc

from tkMessageBox import _show,QUESTION,YESNO

from param.parameterized import Parameterized,ParameterizedMetaclass,\
     classlist

# CEBALERT: try to import from python 2.7's ttk first
from tilewrapper import Combobox,Progressbar

# CEBALERT: try to import python 2.7's ordered dictionary first
from odict import OrderedDict

from param import Boolean,String,Number,Selector,ClassSelector,\
     ObjectSelector,Callable,Dynamic,Parameter,List,HookList,\
     Filename,resolve_path


# (part of an existing ALERT - search this file)
_last_one_set = None


# CEBALERT: Tkinter.BooleanVar.get() doesn't support None. This is a
# hack because there's no way in the GUI to visually distinguish a
# "None" from a "False".
def _BooleanVar_get(instance):
    try:
        return instance._tk.getboolean(instance._tk.globalgetvar(instance._name)) # i.e. BooleanVar.get()
    except T.TclError:
        return instance._tk.globalgetvar(instance._name) # i.e. Variable.get()


root = None
def initialize():
    """
    Add extension Tcl/Tk code to the Tk instance at
    Tkinter._default_root (creating the Tk instance first if
    necessary).
    """
    global root

    if root is not None:
        print "param.tk already initialized; ignoring call to param.tk.initialize()"
        return

    if T._default_root is not None:
        root = T._default_root
    else:
        root = T.Tk()
        root.withdraw()

    # Until tklib, tcllib, and scrodget become more commonly
    # available, we include them in tkgui.
    externaltcl_path = os.path.join(os.path.split(__file__)[0],"tcl")
    root.tk.call("lappend","auto_path",externaltcl_path)

    T.BooleanVar.get = _BooleanVar_get



def inverse(dict_):
    """
    Return the inverse of dictionary dict_.

    (I.e. return a dictionary with keys that are the values of dict_,
    and values that are the corresponding keys from dict_.)

    The values of dict_ must be unique.
    """
    idict = dict([(value,key) for key,value in dict_.iteritems()])
    if len(idict)!=len(dict_):
        raise ValueError("Dictionary has no inverse (values not unique).")
    return idict


def lookup_by_class(dict_,class_):
    """
    Look for class_ or its superclasses in the keys of dict_; on
    finding a match, return the value (return None if no match found).

    Searches from the most immediate class to the most distant
    (i.e. from class_ to the final superclass of class_).
    """
    v = None
    for c in classlist(class_)[::-1]:
        if c in dict_:
            v = dict_[c]
            break
    return v


def keys_sorted_by_value(d):
    """
    Return the keys of dictionary d sorted by value.
    """
    # By <NAME>, 2004/01/23
    # http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306
    items=d.items()
    backitems=[ [v[1],v[0]] for v in items]
    backitems.sort()
    return [ backitems[i][1] for i in range(0,len(backitems))]


def keys_sorted_by_value_unique(d, **sort_kwargs):
    """
    Return the keys of d, sorted by value.

    The values of d must be unique (see inverse)
    """
    values = d.values()
    values.sort(**sort_kwargs)
    i = inverse(d)
    return [i[val] for val in values]


def keys_sorted_by_value_unique_None_safe(d, **sort_kwargs):
    """
    None is handled separately: if present, it always comes out at the
    end of the list. Allows callers to use any sort function without
    worrying that None will not match the other objects (e.g. for
    using an attribute present on all the other objects, such as
    precedence of sheets).
    """
    if 'None' in d:
        assert d['None'] is None
        None_in_d = True
    else:
        None_in_d = False

    if None_in_d:
        del d['None']

    sorted_keys = keys_sorted_by_value_unique(d,**sort_kwargs)

    if None_in_d:
        d['None']=None
        sorted_keys.append('None')

    return sorted_keys


def is_button(widget):
    """
    Simple detection of Button-like widgets that are not Checkbuttons
    (i.e. widgets that do not require a separate label).
    """
    # CEBALERT: document why try/except is needed
    try:
        button = 'command' in widget.config() and not hasattr(widget,'toggle')
    except T.TclError:
        button = False
    return button


#################################################################
# Very basic wrapper for scrodget
class ScrodgetWidget:
    def _require(self, master):
        master.tk.call("package", "require", "scrodget")

    def __init__(self, master, cnf={}, **kw):
        self._require(master)
        T.Widget.__init__(self, master, 'scrodget', cnf, kw)


class Scrodget(ScrodgetWidget, T.Widget):
    def associate(self,widget=None,*args):
        return self.tk.call(self._w,"associate",widget)
#################################################################



# CEB: workaround for tkinter lagging behind tk (tk must have changed
# the type of a returned value).  This is copied almost exactly from
# tkMessageBox. If there are other things like this, we could have the
# gui load some 'dynamic patches' to tkinter on startup, which could
# then be removed when tkinter is updated (they'd all be in one place,
# and no tkgui code would have to change).
def askyesno(title=None, message=None, **options):
    "Ask a question; return true if the answer is yes"
    s = _show(title, message, QUESTION, YESNO, **options)
    return str(s) == "yes"



# Buttons are not naturally represented by parameters?
#
# Maybe we should have a parent class that implements the
# non-Parameter specific stuff, then one that bolts on the
# Parameter-specific stuff, and then instead of Button we'd
# have TopoButton, or something like that...
class Button(Callable):
    """
    A GUI-specific parameter to display a button.

    Can be associated with an image by specifying an image_path
    (i.e. location of an image suitable for PIL, e.g. a PNG, TIFF, or
    JPEG image) and optionally a size (width,height) tuple.

    Note that the button size can also be set when there is no image,
    but instead of being presumed to be in pixels, it is presumed to
    be in text units (a Tkinter feature: see
    e.g. http://effbot.org/tkinterbook/button.htm). Therefore, to
    place two identically sized buttons next to each other, with one
    displaying text and the other an image, you first have to convert
    one of the sizes to the other's units.
    """
    # CEBALERT: we should probably solve the above for users,
    # but what a pain!
    __slots__ = ['image_path','size','_hack']

    def __init__(self,default=None,image_path=None,size=None,**params):
        Callable.__init__(self,default=default,**params)
        self.image_path = image_path
        self.size = size
        self._hack = []

# CB: would create the photoimage on construction and store it as an
# attribute, except that it gives "RuntimeError: Too early to create
# image". Must be happening before tk starts, or something. So
# instead, return image on demand. Also, because of PIL bug (see
# topoconsole.py "got to keep references to the images") we store
# a reference to the image each time in _hack.
    def get_image(self):
        """
        Return an ImageTk.PhotoImage of the image at image_path
        (or None if image_path is None or an Image cannot be created).
        """
        image = None
        if self.image_path:
            image=ImageTk.PhotoImage(ImageOps.fit(
                Image.open(resolve_path(self.image_path)),self.size or (32,32)))
            self._hack.append(image)

        return image




# Note that TkParameterized extends TkParameterizedBase by adding
# widget-drawing abilities; documentation for using these classes
# begins at a more useful and simple level in TkParameterized (the
# documentation for TkParameterizedBase is more complex).
class TkParameterizedBase(Parameterized):
    """
    A Parameterized subclass that maintains Tkinter.Variable shadows
    (proxies) of its Parameters. The Tkinter Variable shadows are kept
    in sync with the Parameter values, and vice versa.

    Optionally performs the same for an *additional* shadowed
    Parameterized (extraPO). The Parameters of the extra
    shadowed PO are available via this object (via both the usual
    'dot' attribute access and dedicated parameter accessors
    declared in this class).

    The Tkinter.Variable shadows for this Parameterized and any
    extra shadowed one are available under their corresponding
    parameter names in the _tkvars dictionary.

    (See note 1 for complications arising from name clashes.)


    Parameters being represented by TkParameterizedBase also
    have a 'translators' dictionary, allowing mapping between string
    representations of the objects and the objects themselves (for use
    with e.g. a Tkinter.OptionMenu). More information about the
    translators is available from specific translator-related methods
    in this class.


    Notes
    =====

    (1) (a) There is an order of precedance for parameter lookup:
        this PO > shadowed PO.

        Therefore, if you create a Parameter for this PO
        with the same name as one of the shadowed PO's Parameters, only
        the Parameter on this PO will be shadowed.

        Example: 'name' is a common attribute. As a
        Parameterized, this object has a 'name' Parameter. Any
        shadowed PO will also have a 'name' Parameter. By default,
        this object's name will be shadowed at the expense of the name
        of the extra shadowed PO.

        The precedence order can be reversed by setting the attribute
        'self_first' on this object to False.


        (b) Along the same lines, an additional complication can arise
        relating specifically to 'dot' attribute lookup.  For
        instance, a sublass of TkParameterized might also
        inherit from Tkinter.Frame. Frame has many of its own
        attributes, including - for example - 'size'. If we shadow a
        Parameterized that has a 'size' Parameter, the
        Parameterized's size Parameter will not be available as
        .size because ('dot') attribute lookup begins on the local
        object and is not overridden by 'self_first'. Using the
        parameter accessors .get_parameter_object('size') or
        .get_parameter_value('size') (and the equivalent set versions)
        avoids this problem.



    (2) If a shadowed PO's Parameter value is modified elsewhere, the
        Tkinter Variable shadow is NOT updated until that Parameter value
        or shadow value is requested from this object. Thus requesting the
        value will always return an up-to-date result, but any GUI display
        of the Variable might display a stale value (until a GUI refresh
        takes place).
    """

    # CEBNOTE: avoid (as far as possible) defining Parameters for this
    # object because they might clash with Parameters of objects it
    # eventually represents.


    # CEBNOTE: Regarding note 1a above...it would be possible - with
    # some extra work - to shadow Parameters that are duplicated on
    # this PO and extraPO. Among other changes, things like the
    # _tkvars dict would need different (e.g. name on this PO and
    # name on extraPO)

    # CEBNOTE: Regarding note 1b...might be less confusing if we stop
    # parameters of shadowed POs being available as attributes (and
    # use only the parameter access methods instead). But having them
    # available as attributes is really convenient.

    # CEBNOTE: Regarding note 2 above...if it becomes a problem, we
    # could have some autorefresh of the vars or a callback of some
    # kind in the parameterized object itself. E.g a
    # refresh-the-widgets-on-focus-in method could make the gui in
    # sync with the actual object (so changes outside the gui could
    # show up when a frame takes focus). Or there could be timer
    # process.

    # CEB: because of note 1, attributes of this class should have
    # names that are unlikely to clash (or they should be private);
    # this will make it easier for creators and users of subclasses to
    # avoid name clashes.

    # must exist *before* an instance is init'd
    # (for __getattribute__)
    _extraPO = None

    # CEBALERT: Parameterized repr method leads to recursion error.
    def __repr__(self): return object.__repr__(self)


    def _setup_params(self,**params):
        """
        Parameters that are not in this object itself but are in the
        extraPO get set on extraPO. Then calls Parameterized's
        _setup_params().
        """
        ### a parameter might be passed in for one of the extra_pos;
        ### if a key in the params dict is not a *parameter* of this
        ### PO, then try it on the extra_pos
        for n,p in params.items():
            if n not in self.params():
                self.set_parameter_value(n,p)
                del params[n]

        Parameterized._setup_params(self,**params)

    # CEBALERT: rename extraPO...but to what?
    # Rename change_PO() and anything else related.
    def __init__(self,extraPO=None,self_first=True,live_update=True,**params):
        """


        Translation between displayed values and objects
        ------------------------------------------------

        A Parameter has a value, but that might need some processing
        to become a value suitable for display. For instance, the
        SheetMask() object <SheetMask SheetMask0001923> ...



        Important attributes
        --------------------

        * _extraPO


        * self_first
        Determines precedence order for Parameter lookup:
        if True, Parameters of this object take priority whenever
        there is a name clash; if False, Parameters of extraPO take
        priority.




        * obj2str_fn & str2obj_fn

        * translator_creators

        (Note that in the various dictionaries above, the entry for
        Parameter serves as a default, since keys are looked up by
        class, so any Parameter type not specifically listed will be
        covered by the Parameter entry.)



        * _tkvars


        """
        if not (extraPO is None or isinstance(extraPO,ParameterizedMetaclass) \
                or isinstance(extraPO,Parameterized)):
            raise TypeError("%s is not a Parameterized instance or class."%extraPO)

        # make self.first etc private

        self._live_update = live_update
        self.self_first = self_first

        ## Which Tkinter Variable to use for each Parameter type
        # (Note that, for instance, we don't include Number:DoubleVar.
        # This is because we use Number to control the type, so we
        # don't need restrictions from DoubleVar.)
        self._param_to_tkvar = {Boolean:T.BooleanVar,
                                Parameter:T.StringVar}

        # CEBALERT: Parameter is the base parameter class, but ...
        # at least need a test that will fail when a new param type added
        # Rename
        self.trans={Parameter:Eval_ReprTranslator,
                    Dynamic:Eval_ReprTranslator,
                    ObjectSelector:String_ObjectTranslator,
                    ClassSelector:CSPTranslator,
                    Number:Eval_ReprTranslator,
                    Boolean:BoolTranslator,
                    String:DoNothingTranslator,
                    List:ListTranslator,
                    HookList:ListTranslator} # CBALERT: sort out inherit.

        self.change_PO(extraPO)
        super(TkParameterizedBase,self).__init__(**params)


    def change_PO(self,extraPO):
        """
        Shadow the Parameters of extraPO.
        """
        self._extraPO = extraPO
        self._tkvars = {}
        self.translators = {}

        # (reverse list to respect precedence)
        for PO in self._source_POs()[::-1]:
            self._init_tkvars(PO)


    def _init_tkvars(self,PO):
        """
        Create Tkinter Variable shadows of all Parameters of PO.
        """
        for name,param in PO.params().items():
            self._create_tkvar(PO,name,param)


    def _create_tkvar(self,PO,name,param_obj):
        """
        Add _tkvars[name] to represent the parameter object with the specified name.

        The appropriate Variable is used for each Parameter type.

        Also adds tracing mechanism to keep the Variable and Parameter
        values in sync, and updates the translator dictionary to map
        string representations to the objects themselves.
        """
        # CEBALERT: should probably delete any existing tkvar for name
        self._create_translator(name,param_obj)

        tkvar = lookup_by_class(self._param_to_tkvar,type(param_obj))()
        self._tkvars[name] = tkvar

        # overwrite Variable's set() with one that will handle
        # transformations to string
        tkvar._original_set = tkvar.set
        tkvar.set = lambda v,x=name: self._tkvar_set(x,v)

        tkvar.set(self.get_parameter_value(name,PO))
        tkvar._last_good_val=tkvar.get() # for reverting
        tkvar.trace_variable('w',lambda a,b,c,p_name=name: self._handle_gui_set(p_name))
        # CB: Instead of a trace, could we override the Variable's
        # set() method i.e. trace it ourselves?  Or does too much
        # happen in tcl/tk for that to work?

        # Override the Variable's get() method to guarantee an
        # out-of-date value is never returned.  In cases where the
        # tkinter val is the most recently changed (i.e. when it's
        # edited in the gui, resulting in a trace_variable being
        # called), the _original_get() method is used.
        # CEBALERT: what about other users of the variable? Could they
        # be surprised by the result from get()?
        tkvar._original_get = tkvar.get
        tkvar.get = lambda x=name: self._tkvar_get(x)


################################################################################
#
################################################################################

    def _handle_gui_set(self,p_name):
        """
        * The callback to use for all GUI variable traces/binds *
        """
        if self._live_update:
            self._update_param_from_tkvar(p_name)


    def _tkvar_set(self,param_name,val):
        """
        Set the tk variable to (the possibly transformed-to-string) val.
        """
        self.debug("_tkvar_set(%s,%s)"%(param_name,val))
        val = self._object2string(param_name,val)
        tkvar = self._tkvars[param_name]
        tkvar._original_set(val) # trace not called because we're already in trace,
                                  # and tk disables trace activation during trace


    # CB: separate into update and get?
    def _tkvar_get(self,param_name):
        """
        Return the value of the tk variable representing param_name.

        (Before returning the variable's value, ensures it's up to date.)
        """
        tk_val = self._tkvars[param_name]._original_get()
        po_val = self.get_parameter_value(param_name)

        po_stringrep = self._object2string(param_name,po_val)

        if not self.translators[param_name].last_string2object_failed and not tk_val==po_stringrep:
            self._tkvars[param_name]._original_set(po_stringrep)
        return tk_val


    def _tkvar_changed(self,name):
        """
        Return True if the displayed value does not equal the object's
        value (and False otherwise).
        """
        self.debug("_tkvar_changed(%s)"%name)
        displayed_value = self._string2object(name,self._tkvars[name]._original_get())
        object_value = self.get_parameter_value(name) #getattr(self._extraPO,name)

        # use equality check then identity check because e.g.  val
        # starts at 0.5, type 0.8, then type 0.5, need that to be
        # changed is False, but some types cannot be equality compared
        # (can be identity compared).
        # CEBALERT: need to add a unit test to ensure this keeps working.
        # Plus, I need to think about this, because while the above is
        # true for floats, identity tests make more sense for many types
        # (i.e. you want to know the object is the same).
        try:
            if displayed_value != object_value:
                changed = True
            else:
                changed = False
        except:
            if displayed_value is not object_value:
                changed = True
            else:
                changed = False

        self.debug("..._v_c return %s"%changed)
        return changed


    def _update_param_from_tkvar(self,param_name):
        """
        Attempt to set the parameter represented by param_name to the
        value of its corresponding Tkinter Variable.

        If setting the parameter fails (e.g. an inappropriate value
        is set for that Parameter type), the Variable is reverted to
        its previous value.

        (Called by the Tkinter Variable's trace_variable() method.)
        """
        self.debug("TkPOb._update_param_from_tkvar(%s)"%param_name)

        parameter,sourcePO=self.get_parameter_object(param_name,with_source=True)

        ### can only edit constant parameters for class objects
        if parameter.constant is True and not isinstance(sourcePO,type):
            return  ### HIDDEN

        tkvar = self._tkvars[param_name]

        if self._tkvar_changed(param_name):
            # don't attempt to set if there was a string-to-object translation error
            if self.translators[param_name].last_string2object_failed:
                return   ### HIDDEN

            # (use _original_get() because we don't want the tkvar to be reset to
            # the parameter's current value!)
            val = self._string2object(param_name,tkvar._original_get())

            try:
                self._set_parameter(param_name,val)
            except: # everything
                tkvar.set(tkvar._last_good_val)
                raise # whatever the parameter-setting error was

            self.debug("set %s to %s"%(param_name,val))

            if hasattr(tkvar,'_on_modify'):
                tkvar._on_modify()

        ### call any function associated with GUI set()
        if hasattr(tkvar,'_on_set'):

            # CEBALERT: provide a way of allowing other gui components
            # to figure out where a callback error might have come
            # from. Callback instances (the Callback class is defined
            # in Tkinter.py) store a widget, but often it appears to
            # be the Tk instance - which is of no use in later
            # determining where an error might have originated.
            global _last_one_set
            if hasattr(self,'master'):
                _last_one_set = self.master

            tkvar._on_set()


################################################################################
#
################################################################################



    def _source_POs(self):
        """
        Return a list of Parameterizeds in which to find
        Parameters.

        The list is ordered by precedence, as defined by self_first.
        """
        if not self._extraPO:
            sources = [self]
        elif self.self_first:
            sources = [self,self._extraPO]
        else:
            sources = [self._extraPO,self]
        return sources




    def get_source_po(self,name):
        """
        Return the Parameterized which contains the parameter 'name'.
        """
        sources = self._source_POs()

        for po in sources:
            if name in po.params():
                return po


        raise AttributeError(self._attr_err_msg(name,sources))


    def get_parameter_object(self,name,parameterized_object=None,with_source=False):
        """
        Return the Parameter *object* (not value) specified by name,
        from the source_POs in this object (or the
        specified parameterized_object).

        If with_source=True, returns also the source parameterizedobject.
        """
        source = parameterized_object or self.get_source_po(name)
        parameter_object = source.params()[name]

        if not with_source:
            return parameter_object
        else:
            return parameter_object,source



######################################################################
# Attribute lookup

##### these guarantee only to get/set parameters #####
    def get_parameter_value(self,name,parameterized_object=None):
        """
        Return the value of the parameter specified by name.

        If a parameterized_object is specified, looks for the parameter there.
        Otherwise, looks in the source_POs of this object.
        """
        source = parameterized_object or self.get_source_po(name)
        return source.get_value_generator(name)

    # CEBALERT: shouldn't this use __set_parameter? Presumably doing
    # that kind of thing is part of the cleanup required in this file.
    def set_parameter_value(self,name,val,parameterized_object=None):
        """
        Set the value of the parameter specified by name to val.

        Updates the corresponding tkvar.
        """
        source = parameterized_object or self.get_source_po(name)
        object.__setattr__(source,name,val)

        # update the tkvar
        if name in self._tkvars:
            self._tkvars[name]._original_set(self._object2string(name,val))


######################################################

########## these lookup attributes in order ##########
# (i.e. you could get attribute of self rather than a parameter)
# (might remove these to save confusion: they are useful except when
#  someone would be surprised to get an attribute of e.g. a Frame (like 'size') when
#  they were expecting to get one of their parameters. Also, means you can't set
#  an attribute a on self if a exists on one of the shadowed objects)
# (also they (have to) ignore self_first)

    def __getattribute__(self,name):
        """
        If the attribute is found on this object, return it. Otherwise,
        return the attribute from the extraPO, if it exists.
        If there is no match, raise an attribute error.
        """
        try:
            return object.__getattribute__(self,name)
        except AttributeError:
            extraPO = object.__getattribute__(self,'_extraPO')

            if hasattr(extraPO,name):
                return getattr(extraPO,name) # HIDDEN!

            _attr_err_msg = object.__getattribute__(self,'_attr_err_msg')

            raise AttributeError(_attr_err_msg(name,[self,extraPO]))


    def __setattr__(self,name,val):
        """
        If the attribute already exists on this object, set it. If the attribute
        is found on the extraPO, set it there. Otherwise, set the
        attribute on this object (i.e. add a new attribute).
        """
        # use dir() not hasattr() because hasattr uses __getattribute__
        if name in dir(self):

            if name in self.params():
                self.set_parameter_value(name,val,self)
            else:
                object.__setattr__(self,name,val)

        elif name in dir(self._extraPO):

            if name in self._extraPO.params():
                self.set_parameter_value(name,val,self._extraPO)
            else:
                object.__setattr__(self._extraPO,name,val)

        else:

            # name not found, so set on this object
            object.__setattr__(self,name,val)
#######################################################


######################################################################





######################################################################
# Translation between GUI (strings) and true values

    def _create_translator(self,name,param):
        self.debug("_create_translator(%s,%s)"%(name,param))

        translator_type = lookup_by_class(self.trans,type(param))

        # Dynamic parameters only *might* contain a
        # dynamic value; if such a parameter really is dynamic, we
        # overwrite any more specific class found above
        # (e.g. a Number with a dynamic value will have a numeric
        # translator from above, so we replace that)
        if param_is_dynamically_generated(param,self.get_source_po(name)) or name in self.allow_dynamic:
            translator_type = self.trans[Dynamic]

        self.translators[name]=translator_type(param,initial_value=self.get_parameter_value(name))

        self.translators[name].msg_handler = self.msg_handler




    # CEBALERT: doc replace & rename to plastic or something
    def _object2string(self,param_name,obj,replace=True):
        """
        If val is one of the objects in param_name's translator,
        translate to the string.
        """
        self.debug("object2string(%s,%s)"%(param_name,obj))
        translator = self.translators[param_name]

        if not replace:
            translator=copy.copy(translator)

        return translator.object2string(obj)


    def _string2object(self,param_name,string):
        """
        Change the given string for the named parameter into an object.

        If there is a translator for param_name, translate the string
        to the object; otherwise, call convert_string2obj on the
        string.
        """
        self.debug("string2object(%s,%s)"%(param_name,string))
        translator = self.translators[param_name]
        o = translator.string2object(string)
        self.debug("...s2o return %s, type %s"%(o,type(o)))
        return o

######################################################################

    def _attr_err_msg(self,attr_name,objects):
        """
        Helper method: return the 'attr_name is not in objects' message.
        """
        if not hasattr(objects,'__len__'):objects=[objects]

        error_string = "'%s' not in %s"%(attr_name,objects.pop(0))

        for o in objects:
            error_string+=" or %s"%o

        return error_string


    def _set_parameter(self,param_name,val):
        """
        Helper method:
        """
        # use set_in_bounds if it exists
        # i.e. users of widgets get their values cropped
        # (no warnings/errors, so e.g. a string in a
        # tagged slider just goes to the default value)
        # CEBALERT: set_in_bounds not valid for POMetaclass?
        parameter,sourcePO=self.get_parameter_object(param_name,with_source=True)

        # CEBHACKALERT: GeneratorSheet.input_generator should be a
        # property? But it's a Parameter...
        # Setting input_generator rather than calling set_input_generator()
        # is probably a trap...
        if param_name=='input_generator' and hasattr(sourcePO,'set_input_generator'):
            sourcePO.set_input_generator(val)
        elif hasattr(parameter,'set_in_bounds') and isinstance(sourcePO,Parameterized):
            parameter.set_in_bounds(sourcePO,val)
        else:
            setattr(sourcePO,param_name,val)










class TkParameterized(TkParameterizedBase):
    """
    Provide widgets for Parameters of itself and up to one additional
    Parameterized instance or class.

    A subclass that defines a Parameter p can display it appropriately
    for manipulation by the user simply by calling
    pack_param('p'). The GUI display and the actual Parameter value
    are automatically synchronized (though see technical notes in
    TkParameterizedBase's documentation for more details).

    In general, pack_param() adds a Tkinter.Frame containing a label
    and a widget:

    ---------------------                     The Parameter's
    |                   |                     'representation'
    | [label]  [widget] |<----frame
    |                   |
    ---------------------

    In the same way, an instance of this class can be used to display
    the Parameters of an existing object. By passing in extraPO=x,
    where x is an existing Parameterized instance or class, a
    Parameter q of x can also be displayed in the GUI by calling
    pack_param('q').

    For representation in the GUI, Parameter values might need to be
    converted between their real values and strings used for display
    (e.g. for a ClassSelector, the options are really class objects,
    but the user is presented with a list of strings to choose
    from). Such translation is handled and documented in the
    TkParameterizedBase; the default behaviors can be overridden if
    required.

    (Note that this class simply adds widget drawing to
    TkParameterizedBase. More detail about the shadowing of
    Parameters is available in the documentation for
    TkParameterizedBase.)
    """

    # CEBNOTE: as for TkParameterizedBase, avoid declaring
    # Parameters here (to avoid name clashes with any additional
    # Parameters this might eventually be representing).

    pretty_parameters = Boolean(default=True,precedence=-1,
        doc="""Whether to format parameter names, or display the
        variable names instead.

        Example use:
          TkParameterized.pretty_parameters=False

        (This causes all Parameters throughout the GUI to be displayed
        with variable names.)
        """)


    def __init__(self,master,extraPO=None,self_first=True,
                 msg_handler=None,**params):
        """
        Initialize this object with the arguments and attributes
        described below:

        extraPO: optional Parameterized object for which to shadow
        Parameters (in addition to Parameters of this object; see
        superclass)

        self_first: if True, Parameters on this object take precedence
        over any ones with the same names in the extraPO (i.e. what
        to do if there are name clashes; see superclass)


        Important attributes
        ====================

        * param_immediately_apply_change

        Some types of Parameter are represented with widgets where
        a complete change can be instantaneous (e.g.  when
        selecting from an option list, the selection should be
        applied straightaway). Others are represented with widgets
        that do not finish their changes instantaneously
        (e.g. entry into a text box is not considered finished
        until Return is pressed).

        * widget_creators

        A dictionary of methods to create a widget for each Parameter
        type. For special widget options (specific to each particular
        parameter), see the corresponding method's docstring.

        * representations

        After pack_param() is called, a Parameter's representation
        consists of the tuple (frame,widget,label,pack_options) - the
        enclosing frame, the value-containing widget, the label
        holding the Parameter's name, and any options supplied for
        pack(). These can all be accessed through the representations
        dictionary, under the Parameter's name.
        """
        self.master = master
        self.msg_handler = msg_handler

        self.representations = {}

        # CEBALER: doc
        self.allow_dynamic = []
        self.allow_default = []

        self.param_immediately_apply_change = {Boolean:True,
                                               Selector:True,
                                               Number:False,
                                               Parameter:False,
                                               Filename:True}

        TkParameterizedBase.__init__(self,extraPO=extraPO,
                                     self_first=self_first,
                                     **params)

        self.balloon = Balloon(master)

        # CEBALERT: what about subclasses of Number (e.g. Integer,
        # which should get a slider that jumps between integers...
        # maybe that already happens)?
        self.widget_creators = {
            Boolean:self._create_boolean_widget,
            Dynamic:self._create_string_widget,
            Number:self._create_number_widget,
            Button:self._create_button_widget,
            String:self._create_string_widget,
            Selector:self._create_selector_widget,
            List:self._create_list_widget,
            HookList:self._create_list_widget,
            Filename:self._create_fileselector_widget,
            }

        self.representations = {}


        # CEBNOTE: it would be nice to sort out menus properly
        # (i.e. parameterize them)
        self.popup_menu = Menu(master, tearoff=0)
        self.dynamic_var = T.BooleanVar()
        self.default_var = T.BooleanVar()
        self.popup_menu.add("checkbutton",indexname="dynamic",
                            label="Enter dynamic value?",
                            state="disabled",command=self._switch_dynamic,
                            variable=self.dynamic_var)
        self.popup_menu.add("command",label="Reset to default",
                            command=self._default_param)


        ### Right-click menu for widgets
        master.option_add("*Menu.tearOff", "0")
        self.menu = Menu(master)
        self.menu.insert_command('end',label='Properties',
            command=lambda:self._edit_PO_in_currently_selected_widget())



    def _right_click(self, event, widget):
        """
        Popup the right-click menu.
        """
        self._currently_selected_widget = widget

        # need an actual mechanism for populating the menu, rather than this!!
        ### copied from edit_PO_in_currently...
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break
        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())
        ###

        if hasattr(PO_to_edit,'params'):
            self.menu.tk_popup(event.x_root, event.y_root)


    # CEBALERT: rename
    def _edit_PO_in_currently_selected_widget(self):
        """
        Open a new window containing a ParametersFrame (actually, a
        type(self)) for the PO in _currently_selected_widget.
        """
        # CEBALERT: simplify this lookup by value
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break

        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())

        parameter_window = AppWindow(self)
        parameter_window.title(PO_to_edit.name+' parameters')

        ### CEBALERT: confusing? ###
        title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")")
        title.pack(side = "top")
        self.balloon.bind(title,getdoc(self.get_parameter_object(param_name)))
        ############################

        # uh-oh
        if not isinstance(self,ParametersFrame):
            p_type = ParametersFrameWithApply
            parameter_frame = p_type(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler)

        else:
            p_type = type(self)
            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)

        parameter_frame.pack()



    def _update_dynamic_menu_entry(self,param_name):
        """Keep track of status of dynamic entry."""
        param,po = self.get_parameter_object(param_name,with_source=True)
        currently_dynamic = param_is_dynamically_generated(param,po)
        if hasattr(param,'_value_is_dynamic') and not currently_dynamic:
            self._right_click_param = param_name
            state = "normal"
        else:
            self._right_click_param = None
            state = "disabled"
        self.popup_menu.entryconfig("dynamic",state=state)
        self.dynamic_var.set(currently_dynamic or \
                             param_name in self.allow_dynamic)


    def _param_right_click(self,event,param_name):
        """Display a popup menu when user right clicks on a parameter."""
        self._update_dynamic_menu_entry(param_name)
        self.popup_menu.tk_popup(event.x_root,event.y_root)



################################################################################
#
################################################################################

    def _update_param_from_tkvar(self,param_name,force=False):
        """
        Prevents the superclass's _update_param_from_tkvar() method from being
        called unless:

        * param_name is a Parameter type that has changes immediately
          applied (see doc for param_immediately_apply_change
          dictionary);

        * force is True.

        (I.e. to update a parameter for which
        param_immediately_apply_change[type(parameter)]==False, call
        this method with force=True. E.g. when <Return> is pressed in
        a text box, this method is called with force=True.)
        """
        self.debug("TkPO._update_param_from_tkvar(%s)"%param_name)

        param_obj = self.get_parameter_object(param_name)

        if not lookup_by_class(self.param_immediately_apply_change,
                               type(param_obj)) and not force:
            return
        else:
            super(TkParameterized,self)._update_param_from_tkvar(param_name)


    def _handle_gui_set(self,p_name,force=False):
        """Override the superclass's method to X and allow status indications."""
        #logging.info("%s._handle_gui_set(%s,force=%s)"%(self,p_name,force))
        if self._live_update:
            self._update_param_from_tkvar(p_name,force)

        self._indicate_tkvar_status(p_name)



    #### Simulate GUI actions
    def gui_set_param(self,param_name,val):
        """Simulate setting the parameter in the GUI."""
        self._tkvar_set(param_name,val)  # ERROR: presumably calls trace stuff twice
        self._handle_gui_set(param_name,force=True)

    def gui_get_param(self,param_name):
        """Simulate getting the parameter in the GUI."""
        return self._tkvars[param_name].get()
    ####


################################################################################
# End
################################################################################





################################################################################
#
################################################################################

    # some refactoring required: should be a base method that's to do
    # with adding a representation for a parameter. then this stuff
    # would go in it.
    def _post_add_param(self,param_name):
        l = self.representations[param_name]['label']
        if l is not None:
            l.bind('<<right-click>>',lambda event: \
                   self._param_right_click(event,param_name))


    def pack_param(self,name,parent=None,widget_options={},
                   on_set=None,on_modify=None,**pack_options):
        """
        Create a widget for the Parameter name, configured according
        to widget_options, and pack()ed according to the pack_options.

        Pop-up help is automatically set from the Parameter's doc.

        The widget and label (if appropriate) are enlosed in a Frame
        so they can be manipulated as a single unit (see the class
        docstring). The representation
        (frame,widget,label,pack_options) is returned (as well as
        being stored in the representations dictionary).


        * parent is an existing widget that is to be the parent
        (master) of the widget created for this paramter. If no parent
        is specified, defaults to the originally supplied master
        (i.e. that set during __init__).

        * on_set is an optional function to call whenever the
        Parameter's corresponding Tkinter Variable's trace_variable
        indicates that it has been set (this does not necessarily mean
        that the widget's value has changed). When the widget is created,
        the on_set method will be called (because the creation of the
        widget triggers a set in Tkinter).

        * on_modify is an optional function to call whenever the
        corresponding Tkinter Variable is actually changed.


        widget_options specified here override anything that might have been
        set elsewhere (e.g. Button's size can be overridden here
        if required).



        Examples of use:
        pack_param(name)
        pack_param(name,side='left')
        pack_param(name,parent=frame3,on_set=some_func)
        pack_param(name,widget_options={'width':50},side='top',expand='yes',fill='y')
        """
        frame = T.Frame(parent or self.master)

        widget,label = self._create_widget(name,frame,widget_options,on_set,on_modify)

        # checkbuttons are 'widget label' rather than 'label widget'
        if widget.__class__ is T.Checkbutton:  # type(widget) doesn't seem to work
            widget_side='left'; label_side='right'
        else:
            label_side='left'; widget_side='right'

        if label: label.pack(side=label_side) # label can be None (e.g. for Button)
        widget.pack(side=widget_side,expand='yes',fill='x')

        representation = {"frame":frame,"widget":widget,
                          "label":label,"pack_options":pack_options,
                          "on_set":on_set,"on_modify":on_modify,
                          "widget_options":widget_options}
        self.representations[name] = representation

        # If there's a label, balloon's bound to it - otherwise, bound
        # to enclosing frame.
        # (E.g. when there's [label] [text_box], only want balloon for
        # label (because maybe more help will be present for what's in
        # the box) but when there's [button], want popup help over the
        # button.)
        param_obj = self.get_parameter_object(name)
        help_text = getdoc(param_obj)

        if param_obj.default is not None:
            # some params appear to have no docs!!!
            if help_text is not None:
                help_text+="\n\nDefault: %s"%self._object2string(name,param_obj.default,replace=False)

        self.balloon.bind(label or frame,help_text)

        frame.pack(pack_options)

        self._indicate_tkvar_status(name)

        self._post_add_param(name)
        return representation


    def hide_param(self,name):
        """Hide the representation of Parameter 'name'."""
        if name in self.representations:
            self.representations[name]['frame'].pack_forget()
        # CEBNOTE: forgetting label and widget rather than frame would
        # just hide while still occupying space (i.e. the empty frame
        # stays in place, and so widgets could later be inserted at
        # exact same place)
        #self.representations[name]['label'].pack_forget()
        #self.representations[name]['widget'].pack_forget()
        # unhide_param would need modifying too


    def unhide_param(self,name,new_pack_options={}):
        """
        Un-hide the representation of Parameter 'name'.

        Any new pack options supplied overwrite the originally
        supplied ones, but the parent of the widget remains the same.
        """
        # CEBNOTE: new_pack_options not really tested. Are they useful anyway?
        if name in self.representations:
            pack_options = self.representations[name]['pack_options']
            pack_options.update(new_pack_options)
            self.representations[name]['frame'].pack(pack_options)


    def unpack_param(self,name):
        """
        Destroy the representation of Parameter 'name'.

        (unpack and then pack a param if you want to put it in a different
        frame; otherwise simply use hide and unhide)
        """
        f = self.representations[name]['frame']
        w = self.representations[name]['widget']
        l = self.representations[name]['label']

        del self.representations[name]

        for x in [f,w,l]:
            x.destroy()


    def repack_param(self,name):

        f = self.representations[name]['frame']
        w = self.representations[name]['widget']
        l = self.representations[name]['label']
        o = self.representations[name]['pack_options']
        on_set = self.representations[name]['on_set']
        on_modify = self.representations[name]['on_modify']

        w.destroy(); l.destroy()

        param_obj,PO = self.get_parameter_object(name,with_source=True)
        self._create_tkvar(PO,name,param_obj)

        self.pack_param(name,f,on_set=on_set,on_modify=on_modify,**o)


    def _switch_dynamic(self,name=None,dynamic=False):

        param_name = name or self._right_click_param
        param,po = self.get_parameter_object(param_name,with_source=True)
        if not hasattr(param,'_value_is_dynamic'):
            return

        if param_name in self.allow_dynamic:
            self.allow_dynamic.remove(param_name)
        else:
            self.allow_dynamic.append(param_name)

        self.repack_param(param_name)

    def _default_param(self,name=None,default=False):

        parameter_name = name or self._right_click_param
        param,po = self.get_parameter_object(parameter_name,with_source=True)

        if parameter_name in self.allow_default:
            self.allow_default.remove(parameter_name)
        else:
            self.allow_default.append(parameter_name)

        assert isinstance(self._extraPO,Parameterized)
        defaults = self._extraPO.defaults()

        self.gui_set_param(parameter_name,defaults[parameter_name])
        w = self.representations[parameter_name]['widget']
        if hasattr(w,'tag_set'):w.tag_set()


################################################################################
#
################################################################################





################################################################################
# WIDGET CREATION
################################################################################

    def _create_widget(self,name,master,widget_options={},on_set=None,on_modify=None):
        """
        Return widget,label for parameter 'name', each having the master supplied

        The appropriate widget creation method is found from the
        widget_creators dictionary; see individual widget creation
        methods for details to each type of widget.
        """
        # select the appropriate widget-creation method;
        # default is self._create_string_widget...
        widget_creation_fn = self._create_string_widget

        param_obj,source_po = self.get_parameter_object(name,with_source=True)

        if not (param_is_dynamically_generated(param_obj,source_po) or name in self.allow_dynamic):
            # ...but overwrite that with a more specific one, if possible
            for c in classlist(type(param_obj))[::-1]:
                if self.widget_creators.has_key(c):
                    widget_creation_fn = self.widget_creators[c]
                    break
        elif name not in self.allow_dynamic:
            self.allow_dynamic.append(name)

        if on_set is not None:
            self._tkvars[name]._on_set=on_set

        if on_modify is not None:
            self._tkvars[name]._on_modify=on_modify

        widget=widget_creation_fn(master,name,widget_options)

        # Is widget a button (but not a checkbutton)? If so, no label wanted.
        # CEBALERT 'notNonelabel': change to have a label with no text
        if is_button(widget):
            label = None
        else:
            label = T.Label(master,text=self._pretty_print(name))

        # disable widgets for constant params
        if param_obj.constant and isinstance(source_po,Parameterized):
            # (need to be able to set on class, hence check it's PO not POMetaclass
            widget.config(state='disabled')

        widget.bind('<<right-click>>',lambda event: self._right_click(event, widget))

        return widget,label


    def _create_button_widget(self,frame,name,widget_options):
        """
        Return a FocusTakingButton to represent Parameter 'name'.

        Buttons require a command, which should have been specified as the
        'on_set' function passed to pack_param().

        After creating a button for a Parameter param, self.param() also
        executes the button's command.

        If the Button was declared with an image, the button will
        have that image (and no text); otherwise, the button will display
        the (possibly pretty_print()ed) name of the Parameter.
        """
        try:
            command = self._tkvars[name]._on_set
        except AttributeError:
            raise TypeError("No command given for '%s' button."%name)

        del self._tkvars[name]._on_set # because we use Button's command instead

        # Calling the parameter (e.g. self.Apply()) is like pressing the button:
        self.__dict__["_%s_param_value"%name]=command
        # like setattr(self,name,command) but without tracing etc

        # (...CEBNOTE: and so you can't edit a tkparameterizedobject
        # w/buttons with another tkparameterizedobject because the
        # button parameters all skip translation etc. Instead should
        # handle their translation. But we're not offering a GUI
        # builder so it doesn't matter.)

        button = FocusTakingButton(frame,command=command)

        button_param = self.get_parameter_object(name)

        image = button_param.get_image()
        if image:
            button['image']=image
            #button['relief']='flat'
        else:
            button['text']=self._pretty_print(name)


        # and set size from Button
        #if size_param.size:
        #    button['width']=size[0]
        #    button['height']=size[1]

        button.config(**widget_options) # widget_options override things from parameter
        return button


    def _create_fileselector_widget(self,frame,name,widget_options):
        widget = HackFileEntry(frame,self._tkvars[name])
        return widget


    def update_selector(self,name):

        if name in self.representations:
            widget_options = self.representations[name]["widget_options"]

            new_range,widget_options = self._X(name,widget_options)

            w = self.representations[name]['widget']
            # hACK: tuple to work around strange list parsing tkinter/tcl
            w.configure(values=tuple(new_range)) # what a mess
            #w.configure(state='readonly') # CEBALERT: why necessary?
            # does it get changed somewhere else by mistake? e.g. does
            # plotgrouppanel switch disabled to normal and normal to
            # disabled without checking that normal isn't readonly?


    def _X(self,name,widget_options):

        # CEBALERT: need to document how & why people should use 'sort_fn_args'
        # and 'new_default' when calling pack_param(). Also, simplify it if
        # possible.
        self.translators[name].update()

        new_range = self.translators[name].cache.keys()

        if 'sort_fn_args' not in widget_options:
            # no sort specified: defaults to sort()
            new_range.sort()
        else:
            sort_fn_args = widget_options['sort_fn_args']
            del widget_options['sort_fn_args']
            if sort_fn_args is not None:
                new_range = keys_sorted_by_value_unique_None_safe(self.translators[name].cache,**sort_fn_args)

        if len(new_range)<1:
            new_range=[None]

        tkvar = self._tkvars[name]


        if 'new_default' in widget_options:
            if widget_options['new_default']:
                current_value = new_range[0]
            del widget_options['new_default']
        else:
            current_value = self._object2string(name,self.get_parameter_value(name))
            if current_value not in new_range:
                current_value = new_range[0] # whatever was there is out of date now

        tkvar.set(current_value)

        return new_range,widget_options


    def _create_selector_widget(self,frame,name,widget_options):
        """
        Return a Tkinter.OptionMenu to represent Parameter 'name'.

        In addition to Tkinter.OptionMenu's usual options, the
        following additional ones may be included in widget_options:

        * sort_fn_args: if widget_options includes 'sort_fn_args',
          these are passed to the sort() method of the list of
          *objects* available for the parameter, and the names are
          displayed sorted in that order.  If 'sort_fn_args' is not
          present, the default is to sort the list of names using its
          sort() method.

        * new_default: if widget_options includes 'new_default':True,
          the currently selected value for the widget will be set
          to the first item in the (possibly sorted as above) range.
          Otherwise, the currently selected value will be left as the
          current value.
        """
        #param = self.get_parameter_object(name)
        #self._update_translator(name,param)

        ## sort the range for display
        # CEBALERT: extend OptionMenu so that it
        # (a) supports changing its option list (subject of a previous ALERT)
        # (b) supports sorting of its option list
        # (c) supports selecting a new default
        new_range,widget_options = self._X(name,widget_options)
        tkvar = self._tkvars[name]

        # Combobox looks bad with standard theme on my ubuntu
        # (and 'changed' marker - blue text - not visible).
        w = Combobox(frame,textvariable=tkvar,
                     values=new_range,state='readonly',
                     **widget_options)

        # Combobox (along with Checkbutton?) somehow sets its
        # associated textvariable without calling that textvariable's
        # set() method.  Therefore, to update the Selector's help text
        # when an item is selected, we bind to the
        # <<ComboboxSelected>> event.
        def _combobox_updated(event,name=name):
            w = self.representations[name]['widget']
            help_text =  getdoc(
                self._string2object(
                    name,
                    self._tkvars[name]._original_get()))

            self.balloon.bind(w,help_text)

        w.bind("<<ComboboxSelected>>",_combobox_updated)

        help_text = getdoc(self._string2object(name,tkvar._original_get()))
        self.balloon.bind(w,help_text)
        return w

    def _list_edit(self,param_name):
        val = self.get_parameter_value(param_name)

        param_obj = self.get_parameter_object(param_name)
        class_=None
        if hasattr(param_obj,'class_') and hasattr(param_obj.class_,'get_range'):
            # i.e. we can represent with ClassSelector
            class_ = param_obj.class_

        parameterized_instance = list_to_parameterized(val,class_)

        parameter_window = AppWindow(self)
        #parameter_window.title(PO_to_edit.name+' parameters')

        parameter_frame = EditingParametersFrameWithApply(parameter_window,parameterized_object=parameterized_instance,show_labels=False,on_close=lambda:self._handle_gui_set(param_name))

        # CEBALERT: more dynamic class changes to make it impossible
        # to follow what is happening...ParametersFrame needs some
        # reorganization...
        def _forgotten_why(itself):
            ### refresh()
            po_val = self.get_parameter_value(param_name)
            po_stringrep = self._object2string(param_name,po_val)
            self._tkvars[param_name]._original_set(po_stringrep)
            ###

        if _forgotten_why not in parameter_frame._apply_hooks:
            parameter_frame._apply_hooks.append(_forgotten_why)


        parameter_frame.pack()


    def _create_list_widget(self,frame,name,widget_options):
        #param = self.get_parameter_object(name)
        #value = self.get_parameter_value(name)

        X = lambda event=None,x=name: self._list_edit(x)

        w = ListWidget(frame,variable=self._tkvars[name],
                       cmd=X,**widget_options)
        #w = ListWidget(frame,variable=tkvar,**widget_options)


        #help_text = getdoc(self._string2object(name,tkvar._original_get()))
        #self.balloon.bind(w,help_text)
        return w



    def _create_number_widget(self,frame,name,widget_options):
        """
        Return a TaggedSlider to represent parameter 'name'.

        The slider's bounds are set to those of the Parameter.
        """
        w = TaggedSlider(frame,variable=self._tkvars[name],**widget_options)
        param = self.get_parameter_object(name)

        lower_bound,upper_bound = param.get_soft_bounds()

        if upper_bound is not None and lower_bound is not None:
            # TaggedSlider needs BOTH bounds (neither can be None)
            w.set_bounds(lower_bound,upper_bound,inclusive_bounds=param.inclusive_bounds)


        # have to do the lookup because subclass might override default
        if not lookup_by_class(self.param_immediately_apply_change,type(param)):
            w.bind('<<TagReturn>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            w.bind('<<TagFocusOut>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            w.bind('<<SliderSet>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))

        return w


    def _create_boolean_widget(self,frame,name,widget_options):
        """Return a Tkinter.Checkbutton to represent parameter 'name'."""
        # CB: might be necessary to pass actions to command option of Checkbutton;
        # could be cause of test pattern boolean not working?
        return T.Checkbutton(frame,variable=self._tkvars[name],**widget_options)


    def _create_string_widget(self,frame,name,widget_options):
        """Return a Tkinter.Entry to represent parameter 'name'."""
        widget = T.Entry(frame,textvariable=self._tkvars[name],**widget_options)
        param = self.get_parameter_object(name)
        if not lookup_by_class(self.param_immediately_apply_change,type(param)):
            widget.bind('<Return>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            widget.bind('<FocusOut>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
        return widget

################################################################################
# End WIDGET CREATION
################################################################################



    # CB: the colors etc for indication are only temporary
    def _indicate_tkvar_status(self,param_name,status=None):
        """
        Set the parameter's label to:
         - blue if the GUI value differs from that set on the object
         - red if the text doesn't translate to a correct value
         - black if the GUI and object have the same value
        """
        if self.translators[param_name].last_string2object_failed:
            status = 'error'

        self._set_widget_status(param_name,status)


    # this scheme is incompatible with tile
    # (tile having the right idea about how to do this kind of thing!)
    def _set_widget_status(self,param_name,status):

        if param_name in self.representations:
            if 'widget' in self.representations[param_name]:

                widget = self.representations[param_name]['widget']
                states = {'error'   : 'red',
                          'changed' : 'blue',
                          None      : 'black'}

                if is_button(widget):
                    # can't change state of button
                    return

                try:
                    widget.config(foreground=states[status])
                except T.TclError:  #CEBALERT uh-oh
                    pass

    def _pretty_print(self,s):
        """
        Convert a Parameter name s to a string suitable for display,
        if pretty_parameters is True.
        """
        if not self.pretty_parameters:
            return s
        else:
            n = s.replace("_"," ")
            n = n.capitalize()
            return n


# CEBALERT: is there no "file entry" widget for Tkinter?  I see there
# is one for Tix, but while that is listed as being in Python's
# standard library, it's rarely actually included...
import tkFileDialog
class HackFileEntry(T.Frame):
    def __init__(self,master,var,**config):
        T.Frame.__init__(self,master)
        self.var = var
        self.button = T.Button(self,textvariable=self.var,command=self.cmd)
        self.button.pack()

    def cmd(self):
        filename = tkFileDialog.askopenfilename()
        if filename!="":
            self.var.set(filename)


######################################################################
######################################################################

class Translator(object):
    """
    Abstract class that

    Translators handle translation between objects and their
    string representations in the GUI.

        last_string2object_failed is a flag that can be set to indicate that
        the last string-to-object translation failed.
        (TkParameterized checks this attribute for indicating errors to
        the user.)

    """
    last_string2object_failed = False # CEBALERT: why class attr?

    def __init__(self,param,initial_value=None):
        self.param = param
        self.msg_handler = None

    def string2object(self,string_):
        raise NotImplementedError

    def object2string(self,object_):
        raise NotImplementedError

    def _pass_out_msg(self,msg):
        if self.msg_handler:
            self.msg_handler.dynamicinfo(msg)

    def __copy__(self):
        """Copy only translator-specific state."""
        copy = self.__class__(self.param)
        copy.last_string2object_failed = self.last_string2object_failed
        copy.msg_handler = self.msg_handler
        return copy


class DoNothingTranslator(Translator):
    """
    Performs no translation. For use with Parameter types such as
    Boolean and String, where the representation
    of the object in the GUI is the object itself.
    """
    def string2object(self,string_):
        return string_

    def object2string(self,object_):
        return object_


class BoolTranslator(DoNothingTranslator):
    def string2object(self,string_):
        if string_=='None':
            return None
        else:
            return bool(string_)


# Error messages: need to change how they're reported
from param.parameterized import script_repr
class ListTranslator(Translator):

    def __init__(self,param,initial_value=None):
        super(ListTranslator,self).__init__(param)
        self.list_=initial_value

    #JABALERT: Shouldn't this be building a new list out of the string?
    def string2object(self,string_):
        return self.list_

    def object2string(self,object_):
        self.list_=object_
        return script_repr(self.list_,[],"",[]).replace("\n","")


class Eval_ReprTranslator(Translator):
    """
    Translates a string to an object by eval()ing the string in
    __main__.__dict__ (i.e. as if the string were typed at the
    commandline), and translates an object to a string by
    repr(object).
    """

    last_object = None
    last_string = None

    def __init__(self,param,initial_value=None):
        super(Eval_ReprTranslator,self).__init__(param,initial_value)
        self.last_string = self.object2string(initial_value)
        self.last_object = initial_value

    # the whole last_string deal is required because of execing in main
    def string2object(self,string_):
        if string_!=self.last_string:
            try:
                self.last_object = eval(string_,__main__.__dict__)
                self.last_string = string_
                self._pass_out_msg(" ")
                #print "OUT:"
            except Exception,inst:
                #print "OUT:", str(sys.exc_info()[0])[11::]+" ("+str(inst)+")"
                m = str(inst) # CEBALERT: clean up
                self._pass_out_msg(m)
                self.last_string2object_failed=True
                return string_ # HIDDEN

        self.last_string2object_failed=False
        return self.last_object


    def object2string(self,object_):
        if object_==self.last_object:
            return self.last_string
        else:
            repr=script_repr(object_,[],"",[])
            string_=repr.replace("\n","") if repr is not None else "None"
            self.last_object = object_
            self.last_string = string_
            return string_

    def __copy__(self):
        n = super(Eval_ReprTranslator,self).__copy__()
        n.last_string = self.last_string
        n.last_object = self.last_object
        return n


# CEBALERT: I hacked on support for non-hashable types like array (now
# have cache as {string: id(object)} and _cache_objects as
# {id(object): object}). The whole concept of translators needs
# revisiting anyway.
class String_ObjectTranslator(Translator):

    cache = property(lambda self: dict([(nam,self._cache_objects[objid])
                                        for nam,objid in self._cache.items()]))

    _cache = {}
    _cache_objects = {}

    def __init__(self,param,initial_value=None):
        super(String_ObjectTranslator,self).__init__(param,initial_value)
        self._cache = {}
        self._cache_objects = {}
        self.update()

    def string2object(self,string_):
        if string_ in self._cache:
            return self._cache_objects[self._cache[string_]]
        else:
            return string_

    def object2string(self,object_):
        inverse_cache = inverse(self._cache)
        if id(object_) in inverse_cache:
            return inverse_cache[id(object_)]
        else:
            return object_


    def update(self):
        for nam,obj in self.param.get_range().items():
            self._cache[nam] = id(obj)
            self._cache_objects[id(obj)] = obj


    def __copy__(self):
        n = super(String_ObjectTranslator,self).__copy__()
        n._cache = self._cache
        n._cache_objects = self._cache_objects
        return n



class CSPTranslator(String_ObjectTranslator):

    def string2object(self,string_):
        obj = super(CSPTranslator,self).string2object(string_)
        ## instantiate if it's just a class
        if isinstance(obj,type) and isinstance(string_,str):
            obj = obj()
            self._cache[string_]=id(obj)
            self._cache_objects[id(obj)]=obj

        return obj

    def object2string(self,object_):
        ## replace class if we already have object
        for name,objid in self._cache.items():
            obj = self._cache_objects[objid]
            if type(object_) is obj or type(object_) is type(obj):
                self._cache[name]=id(object_)
                self._cache_objects[id(object_)]= object_
        ##
        return super(CSPTranslator,self).object2string(object_)


    def __copy__(self):
        # because this one's object2string can modify cache
        n = Translator.__copy__(self)
        n._cache = copy.copy(self._cache)
        n._cache_objects = copy.copy(self._cache_objects)
        return n


def param_is_dynamically_generated(param,po):

    if not hasattr(param,'_value_is_dynamic'):
        return False

    if isinstance(po,Parameterized):
        return param._value_is_dynamic(po)
    elif isinstance(po,ParameterizedMetaclass):
        return param._value_is_dynamic(None)
    else:
        raise ValueError("po must be a Parameterized or ParameterizedMetaclass.")


class ParametersFrame(TkParameterized,T.Frame):
    """
    Displays and allows instantaneous editing of the Parameters
    of a supplied Parameterized.

    Changes made to Parameter representations on the GUI are
    immediately applied to the underlying object.
    """
    Defaults = Button(doc="""Return values to class defaults.""")

    Refresh = Button(doc="Return values to those currently set on the object (or, if editing a class, to those currently set on the class).")

    # CEBALERT: this is a Frame, so close isn't likely to make
    # sense. But fortunately the close button acts on master.
    # Just be sure not to use this button when you don't want
    # the master window to vanish (e.g. in the model editor).
    Close = Button(doc="Close the window. (If applicable, asks if unsaved changes should be saved).")

    display_threshold = Number(default=0,precedence=-10,doc="Parameters with precedence below this value are not displayed.")

    show_labels = Boolean(default=True)

    def __init__(self,master,parameterized_object=None,on_set=None,
                 on_modify=None,msg_handler=None,on_close=None,**params):
        """
        Create a ParametersFrame with the specifed master, and
        representing the Parameters of parameterized_object.

        on_set is an optional function to call whenever any of the
        GUI variables representing Parameter values is set() by the
        GUI (i.e. by the user). Since a variable's value is not
        necessarily changed by such a set(), on_modify is another
        optional function to call only when a GUI variable's value
        actually changes. (See TkParameterized for more detail.)
        """
        T.Frame.__init__(self,master,borderwidth=1,relief='raised')

        self.on_set = on_set
        self.on_modify = on_modify

        TkParameterized.__init__(self,master,
                                 extraPO=parameterized_object,
                                 self_first=False,
                                 msg_handler=msg_handler,
                                 **params)

        self.on_set = on_set
        self.on_modify = on_modify
        self.on_close = on_close

        ## Frame for the Parameters
        self._params_frame = T.Frame(self)
        self._params_frame.pack(side='top',expand='yes',fill='both')

        self.params_to_display = {}
        self.currently_displayed_params = {}

        if parameterized_object:
            self.set_PO(parameterized_object)

        self._create_button_panel()


        # CEBALERT: just because callers assume this pack()s itself.
        # Really it should be left to callers i.e. this should be removed.
        self.pack(expand='yes',fill='both')


    def hidden_param(self,name):
        """Return True if a parameter's precedence is below the display threshold."""
        # interpret a precedence of None as 0
        precedence = self.get_parameter_object(name).precedence or 0
        return precedence<self.display_threshold


    def _create_button_panel(self):
        """
        Add the buttons in their own panel (frame).
        """
        ## Buttons
        #
        # Our button order (when all buttons present):
        # [Defaults] [Refresh] [Apply] [Close]
        #
        # Our button - Windows
        # Close(yes) - OK
        # Close(no ) - Cancel
        # [X]        - Cancel
        # Apply      - Apply
        # Defaults   -
        # Refresh    - Reset
        #
        # I think Windows users will head for the window's [X]
        # when they want to close and cancel their changes,
        # because they won't know if [Close] saves their changes
        # or not (until they press it, and find that it asks).
        #
        #
        # Some links that discuss and address what order to use for buttons:
        #
        # http://java.sun.com/products/jlf/ed2/book/HIG.Dialogs2.html
        # http://developer.kde.org/documentation/books/kde-2.0-development/ch08lev1sec6.html
        # http://developer.kde.org/documentation/standards/kde/style/dialogs/index.html
        # http://doc.trolltech.com/qq/qq19-buttons.html


        # Catch click on the [X]: like clicking [Close]
        # CEBALERT: but what if this frame isn't in its own window!
        try:
            self.master.protocol("WM_DELETE_WINDOW",self._close_button)
        except AttributeError:
            pass

        buttons_frame = T.Frame(self,borderwidth=1,relief='sunken')
        self.buttons_frame = buttons_frame
        buttons_frame.pack(side="bottom",expand="no")

        self._buttons_frame_left = T.Frame(buttons_frame)
        self._buttons_frame_left.pack(side='left',expand='yes',fill='x')

        self._buttons_frame_right = T.Frame(buttons_frame)
        self._buttons_frame_right.pack(side='right',expand='yes',fill='x')

        self.pack_param('Defaults',parent=self._buttons_frame_left,
                        on_set=self._defaults_button,side='left')
        self.pack_param('Refresh',parent=self._buttons_frame_left,
                        on_set=self._refresh_button,side='left')
        self.pack_param('Close',parent=self._buttons_frame_right,
                        on_set=self._close_button,side='right')


    def _refresh_button(self):
        """See Refresh parameter."""
        for name in self.params_to_display.keys():
            self._tkvars[name].get()


    def _defaults_button(self):
        """See Defaults parameter."""
        assert isinstance(self._extraPO,Parameterized)
        defaults = self._extraPO.defaults()

        for param_name,val in defaults.items():
            if not self.hidden_param(param_name):
                self.gui_set_param(param_name,val)
                # tag_set() must be called in order to visually update the slider
                w = self.representations[param_name]['widget']
                if hasattr(w,'tag_set'):w.tag_set()


        # CEBALERT: why doesn't this first check that something has actually changed?
        if self.on_modify:
            self.on_modify()


        if self.on_set:
            self.on_set()

        self.update_idletasks()


    def _close_button(self):
        """See Close parameter."""
        if self.on_close:
            self.on_close()

        T.Frame.destroy(self) # hmm
        self.master.destroy()


    def set_PO(self,parameterized_object):
        self.change_PO(parameterized_object)

        title = "Parameters of "+ (parameterized_object.name or str(parameterized_object)) # (name for class is None)

        try:
            self.master.title(title)
        except AttributeError:
            # can only set window title on a window (model editor puts frame in another frame)
            pass

        self.__dict__['_name_param_value'] = title


        ### Pack all of the non-hidden Parameters
        self.params_to_display = {}
        for n,p in parameterized_object.params().items():
            if not self.hidden_param(n):
                self.params_to_display[n]=p

        self.pack_params_to_display()

        # hide Defaults button for classes
        if isinstance(parameterized_object,type):
            self.hide_param('Defaults')
        else:
            self.unhide_param('Defaults')


    def _wipe_currently_displayed_params(self):
        """Wipe old labels and widgets from screen."""
        for rep in self.currently_displayed_params.values():
            for w in rep:
                try:
                    rep[w].destroy()
                except: # e.g. buttons have None for label ('notNonelabel')
                    pass


    def _grid_param(self,parameter_name,row):
        widget = self.representations[parameter_name]['widget']
        label = self.representations[parameter_name]['label']

        # CB: (I know about the code duplication here & in tkpo)
        param_obj = self.get_parameter_object(parameter_name)
        help_text = getdoc(param_obj)

        if param_obj.default is not None:
            # some params appear to have no docs!!!
            if help_text is not None:
                help_text+="\n\nDefault: %s"%self._object2string(parameter_name,param_obj.default,replace=False)

        if self.show_labels:
            label.grid(row=row,column=0,
                       padx=2,pady=2,sticky=T.E)

            self.balloon.bind(label, help_text)

        # We want widgets to stretch to both sides...
        posn=T.E+T.W
        # ...except Checkbuttons, which should be left-aligned.
        if widget.__class__==T.Checkbutton:
            posn=T.W

        widget.grid(row=row,
                    column=1,
                    padx=2,
                    pady=2,
                    sticky=posn)

        # widgets expand to fill frame
        widget.master.grid_columnconfigure(1,weight=2)

        self._post_add_param(parameter_name)




    def _make_representation(self,name):
        if name in self.representations:
            for n,w in self.representations[name].items():
                try:
                    w.destroy()
                except: #e.g. buttons have None for label ('notNonelabel')
                    pass
                del self.representations[name][n]

        widget,label = self._create_widget(name,self._params_frame,
                                           on_set=self.on_set,
                                           on_modify=self.on_modify)

        label.bind("<Double-Button-1>",lambda event=None,x=name: self.switch_dynamic(x))

        self.representations[name]={'widget':widget,'label':label}
        self._indicate_tkvar_status(name)



    def pack_params_to_display(self):
        self._wipe_currently_displayed_params()

        ### sort Parameters by reverse precedence
        parameter_precedences = {}
        for name,parameter in self.params_to_display.items():
            parameter_precedences[name]=parameter.precedence
        sorted_parameter_names = keys_sorted_by_value(parameter_precedences)

        ### create the labels & widgets
        for name in self.params_to_display:
            self._make_representation(name)

        ### add widgets & labels to screen in a grid
        rows = range(len(sorted_parameter_names))
        for row,parameter_name in zip(rows,sorted_parameter_names):
            self._grid_param(parameter_name,row)

        self.currently_displayed_params = dict([(param_name,self.representations[param_name])
                                          for param_name in self.params_to_display])
        #self.event_generate("<<SizeRight>>")


    # CEBALERT: rename
    def _edit_PO_in_currently_selected_widget(self):
        """
        Open a new window containing a ParametersFrame (actually, a
        type(self)) for the PO in _currently_selected_widget.
        """
        # CEBALERT: simplify this lookup by value
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break

        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())

        parameter_window = AppWindow(self)
        parameter_window.title(PO_to_edit.name+' parameters')

        ### CEBALERT: confusing? ###
        title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")")
        title.pack(side = "top")
        self.balloon.bind(title,getdoc(self.get_parameter_object(param_name,self._extraPO)))
        ############################

        # CEBALERT: don't want EditingParametersFrameWithApply
        t = type(self)
        if t.__name__=='EditingParametersFrameWithApply':
            t = ParametersFrameWithApply

        parameter_frame = t(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler,on_set=self.on_set,on_modify=self.on_modify)
        parameter_frame.pack()

        # CEBALERT: need to get the list item to update in parent if editing via a right click
        # properties frame. need to get a new representation into translator.
        #parameter_frame._apply_hooks.append(lambda *args:self._refresh_value(param_name))


        # CEBALERT: need to sort out all this stuff in the tkpo/pf
        # hierarchy

##     def unpack_param(self,param_name):
##     def hide_param(self,param_name):
##     def unhide_param(self,param_name):

    def repack_param(self,param_name):

        self._refresh_value(param_name)

        r = self.representations[param_name]
        widget,label = r['widget'],r['label']
        row = int(widget.grid_info()['row'])

        widget.destroy()
        label.destroy()

        param = self.get_parameter_object(param_name)

        self._create_translator(param_name,param)
        self._make_representation(param_name)
        self._grid_param(param_name,row)

    def _refresh_value(self,param_name):
        pass



class ParametersFrameWithApply(ParametersFrame):
    """
    Displays and allows editing of the Parameters of a supplied
    Parameterized.

    Changes made to Parameter representations in the GUI are not
    applied to the underlying object until Apply is pressed.
    """

    # CEBENHANCEMENT: might be nice to make Apply button blue like the
    # unapplied changes, but can't currently set button color.
    Apply = Button(doc="""Set object's Parameters to displayed values.\n
                          When editing a class, sets the class defaults
                          (i.e. acts on the class object).""")

    def __init__(self,master,parameterized_object=None,
                 on_set=None,on_modify=None,**params):
        self._apply_hooks=[]
        super(ParametersFrameWithApply,self).__init__(master,
                                                      parameterized_object,
                                                      on_set,on_modify,
                                                      **params)
        self._live_update = False

        ### CEBALERT: describe why this apply is different from Apply
        for p in self.param_immediately_apply_change:
            self.param_immediately_apply_change[p]=True


        self.pack_param('Apply',parent=self._buttons_frame_right,
                        on_set=self._apply_button,side='left')

        assert self.has_unapplied_change() is False, "ParametersFrame altered a value on opening. If possible, please file a bug report on the website describing what you were doing when you received this error."

        # CEBALERT: should use existing code
        self.representations['Apply']['widget']['state']='disabled'


    def _create_string_widget(self,frame,name,widget_options):
        # don't want immediate updates, so unbind update events
        w= super(ParametersFrameWithApply,self)._create_string_widget(frame,name,widget_options)
        w.unbind('<Return>')
        w.unbind('<FocusOut>')
        return w


    def set_PO(self,parameterized_object):
        super(ParametersFrameWithApply,self).set_PO(parameterized_object)

        # (don't want to update parameters immediately)
        for v in self._tkvars.values():
            v._checking_get = v.get
            v.get = v._original_get


    def has_unapplied_change(self):
        """Return True if any one of the packed parameters' displayed values is different from
        that on the object."""
        for name in self.params_to_display.keys():
            if self._tkvar_changed(name):
                return True
        return False

    def _indicate_tkvar_status(self,param_name,status=None):
        if self._tkvar_changed(param_name):
            status = 'changed'

        super(ParametersFrameWithApply,self)._indicate_tkvar_status(param_name,status)


    def _handle_gui_set(self,p_name,force=False):
        #print "ParametersFrame._handle_gui_set",p_name
        TkParameterized._handle_gui_set(self,p_name,force)

        if 'Apply' in self.representations:
            w=self.representations['Apply']['widget']
            if self.has_unapplied_change():
                state='normal'
            else:
                state='disable'

            w.config(state=state)

    def _close_button(self):
        # CEBERRORALERT: dialog box should include a cancel button
        # Also, changes are *not* applied if one of the boxes is in
        # error.
        if self.has_unapplied_change() \
               and askyesno("Close","Apply changes before closing?"):
            self._apply_button()
        super(ParametersFrameWithApply,self)._close_button()


    def update_parameters(self):
        if isinstance(self._extraPO,type):
            for name in self.params_to_display.keys():
                #if self._tkvar_changed(name):
                self._update_param_from_tkvar(name)
        else:
            for name,param in self.params_to_display.items():
                if not param.constant:  #and self._tkvar_changed(name):
                    self._update_param_from_tkvar(name)


    def _apply_button(self):
        self.update_parameters()
        self._refresh_button(overwrite_error=False)
        # CEBALERT: because overriding a method that\s already been
        # installed as a callback does nothing
        for h in self._apply_hooks:
            h(self)

        # CEBALERT: how does apply button's status change now? Doesn't
        # work properly for list editing; stays grey until clicked twice.

    def _refresh_value(self,param_name):
        po_val = self.get_parameter_value(param_name)
        po_stringrep = self._object2string(param_name,po_val)
        self._tkvars[param_name]._original_set(po_stringrep)


    def _refresh_button(self,overwrite_error=True):
        for name in self.params_to_display.keys():
            if self.translators[name].last_string2object_failed and not overwrite_error:
                pass
            else:
                self._refresh_value(name)
                #print self._tkvars[name]._checking_get()
                # CEBALERT: taggedsliders need to have tag_set() called to update slider
                w = self.representations[name]['widget']
                if hasattr(w,'tag_set'):w.tag_set()

# CB: can override tracefn so that Apply/Refresh buttons are enabled/disabled as appropriate

def edit_parameters(parameterized,with_apply=True,**params):
    """
    Edit the Parameters of a supplied parameterized instance or class.

    Specify with_apply=False for a ParametersFrame (which immediately
    updates the object - no need to press the Apply button).

    Extra params are passed to the ParametersFrame constructor.
    """
    if not with_apply:
        pf_class = ParametersFrame
    else:
        pf_class = ParametersFrameWithApply

    return pf_class(T.Toplevel(),parameterized,**params)










# Barely wrapped tooltip from tklib.
# CB: this isn't the right way to do it, and it breaks menubar
# tips for some reason, but user code didn't have to change.

class Balloon(T.Widget):

    _tkname = '::tooltip::tooltip'

    def __init__(self,master,cnf={},**kw):
        master.tk.call("package","require","tooltip")
        T.Widget.__init__(self, master,self._tkname, cnf, kw)

    def bind(self,*args):
        """
        e.g. for a Button b and a Menu m with item 'Quit' in a T.Toplevel t

        balloon = Balloon(t)
        balloon.bind(b,'some guidance')
        balloon.bind(m,'Quit','more guidance')
        """
        if len(args)>2:
            self.tk.call(self._tkname,args[0]._w,'-index',args[1],args[2])
        else:
            self.tk.call(self._tkname,*args)

##     def tagbind(self,*args,**kw):
##         print "### Balloon.tagbind(): not yet implemented error ###"



# CEBALERT: should be renamed to something like IndexMenu, but then
# I am not sure if some of our tk hacks would work (e.g. in topoconsole,
# we set something for Menu on linux so that the popup behavior is
# reasonable).
class Menu(T.Menu):
    """
    Tkinter Menu, but with a way to access entries by name.

    Entries can be accessed via the label supplied when the entry is
    add()ed or insert()ed. For an entry whose label could change, you
    can supply 'indexname', which can then be used to access the entry
    no matter what the label might have become.
    """
    ## (Original Menu class is in lib/python2.4/lib-tk/Tkinter.py)

    def get_tkinter_index(self,index):
        """
        Return the Tkinter index, whether given an indexname or index
        (where index could be an int or Tkinter position e.g. 'end').
        """
        if isinstance(index,str):
            if index in self.indexname2index:
                i=self.indexname2index[index]
            else:
                # pass through tkinter to get 'end' etc converted to index
                i=self.index(index)
        else:
            i=index
        return i


    def get_indexname(self,index):
        """
        Return the indexname, whether given an indexname or index
        (where index could be an int or Tkinter position e.g. 'end').

        The returned value will be None if index refers to an unnamed
        entry.
        """
        if index in self.indexname2index:
            return index
        else:
            for name,i in self.indexname2index.items():
                if self.index(index)==i:
                    return name
        return None


    def __init__(self, master=None, cnf={}, **kw):
        # Creates two internal indexes to track names and commands
        self.indexname2index = {}  # rename to indexnames2index or similar
        self.named_commands = {}
        T.Menu.__init__(self,master,cnf,**kw)


    def __extract_indexname(self,cnf,kw):
        # indexname will be as specified by 'indexname' in cnf or kw,
        # or else as specified by 'label' in cnf or kw.
        #
        # indexname will be None if neither indexname nor label is
        # specified in cnf or kw.
        indexname=cnf.pop('indexname',kw.pop('indexname',None))
        if indexname is None:
            indexname = cnf.get('label',kw.get('label',None))
        return indexname

    def _update_indices(self,index,indexname,cnf,kw):
        if indexname is not None:
            self.indexname2index[indexname] = index
            # this pain is to keep the actual item, if it's a menu or a command, available to access
            self.named_commands[indexname] = cnf.get('menu',kw.get('menu',cnf.get('command',kw.get('command',None))))


    def add(self, itemType, cnf={}, **kw):
        """
        See Tkinter.Menu.add(), but 'indexname' can also be supplied.

        If supplied, indexname must be unique. If label is supplied without
        indexname, then label must be unique.
        """
        indexname = self.__extract_indexname(cnf,kw)
        assert indexname not in self.indexname2index
        T.Menu.add(self,itemType,cnf,**kw)
        self._update_indices(self.index("last") or 0,indexname,cnf,kw)


    def insert(self, index, itemType, cnf={}, **kw):
        """
        See Tkinter.Menu.insert(), but index can also be specified as text.
        """
        indexname = self.__extract_indexname(cnf,kw)
        assert indexname not in self.indexname2index

        # increase index of any item after insertion point
        for name,i in self.indexname2index.items():
            if i>=index:
                self.indexname2index[name]+=1

        T.Menu.insert(self,index,itemType,cnf,**kw)
        self._update_indices(index,indexname,cnf,kw)


    def __delete(self,index1):
        i1 = self.get_tkinter_index(index1)
        indexname1 = self.get_indexname(index1)

        assert (indexname1 in self.indexname2index or indexname1 is None)

        # decrease index of any item after deletion point
        for name,i in self.indexname2index.items():
            if i>i1:
                self.indexname2index[name]-=1

        self.named_commands.pop(indexname1,None)
        self.indexname2index.pop(indexname1,None)
        T.Menu.delete(self,i1,None)


    def delete(self, index1, index2=None):
        """
        If index2 is not specified, deletes the menu item at index1
        (an indexname or tk integer index).

        If index2 is specified, deletes menu items in
        range(index1,index2+1).
        """
        if index2 is not None:
            start = self.index(index1)
            end = self.index(index2)
            if start is not None and end is not None:
                for _ in range(start,end+1):
                    # __delete shifts the remaining items one position back,
                    # so repeatedly deleting the first item in the range will
                    # delete all of them
                    self.__delete(start)
        else:
            self.__delete(index1)



    ########## METHODS OVERRIDDEN FOR CONVENIENCE
    def entryconfigure(self, index, cnf=None, **kw):
        """Configure a menu item at INDEX."""
        i = self.get_tkinter_index(index)
        return T.Menu.entryconfigure(self,i,cnf,**kw)

    entryconfig = entryconfigure

    def invoke(self, index):
        """Invoke a menu item identified by INDEX and execute
        the associated command."""
        return T.Menu.invoke(self,self.get_tkinter_index(index))

    # other methods can be overriden if they're needed



class TaggedSlider(T.Frame):
    """
    Widget for manipulating a numeric value using either a slider or a
    text-entry box, keeping the two values in sync.

    Generates a number of Events:

    <<TagFocusOut>>  - tag loses focus
    <<TagReturn>>    - Return pressed in tag
    <<SliderSet>>    - slider is clicked/dragged
    """
    def __init__(self,master,variable,bounds=(0,1),inclusive_bounds=(True,True),
                 slider_length=100,tag_width=10,
                 tag_extra_config={},slider_extra_config={}):
        """
        On clicking or dragging the slider, the tag value is set
        to the slider's value.

        On pressing Return in or moving focus from the tag, the slider
        value is set, but also:

        * the range of the slider is adjusted (e.g. to fit a larger
          max value)

        * the resolution of the slider is adjusted based on the
          the value in the tag (e.g. 0.01 in the tag gives a
          resolution of 0.01), also taking into account the precision
          of the value in the tag (e.g. 0.0100 gives a resolution
          of 0.0001).
        """
        # CEBALERT: ...although respecting the precision isn't always
        # helpful because the slider can still have a limited
        # resolution anyway (from its length and the range, given the
        # size of a pixel...)
        T.Frame.__init__(self,master)

        self.variable= variable

        # CEBALERT: shouldn't this be calling set_slider_bounds()?
        self.bounds = bounds
        self.inclusive_bounds = inclusive_bounds

        self.tag = T.Entry(self,textvariable=self.variable,
                                 width=tag_width,**tag_extra_config)
        self.tag.pack(side='left')
        self.tag.bind('<Return>', self._tag_press_return)
        self.tag.bind('<FocusOut>', self._tag_focus_out)
        self.tag.bind('<Leave>', self._tag_focus_out)

        # no variable: we control the slider ourselves
        self.slider = T.Scale(self,variable=None,
                    from_=self.bounds[0],to=self.bounds[1],
                    orient='horizontal',length=slider_length,
                    showvalue=0,**slider_extra_config)

        self.slider.pack(side='right',expand="yes",fill='x')
        self.slider.bind('<ButtonRelease-1>', self._slider_used)
        self.slider.bind('<B1-Motion>', self._slider_used)

        self.tag_set()


    def tag_set(self):
        """
        After entering a value into the tag, this method should be
        called to set the slider correctly.

        (Called automatically for tag's <Return> and <FocusOut>
        events.)
        """
        # Set slider resolution. This is important because
        # we want the slider to be positioned at the exact
        # tag value.
        self._try_to_set_slider_resolution()
        self._try_to_set_slider()


    def set_slider_bounds(self,lower,upper,inclusive_bounds=None):
        """
        Set new lower and upper bounds for the slider.

        If specified, inclusive_bounds should be a sequence of length
        2 specifying True or False for the lower and upper bounds.
        """
        self.bounds = (lower,upper)

        if inclusive_bounds is not None:
            self.inclusive_bounds = inclusive_bounds

        epsilon = max(self.slider['resolution'],0.00000000001)

        if self.inclusive_bounds[0] is False:
            lower+=epsilon
        if self.inclusive_bounds[1] is False:
            upper-=epsilon
        self.slider.config(from_=lower,to=upper)

    set_bounds = set_slider_bounds


    # CB: why isn't this used for [] access? What should this
    # be called? Is it configure?
    def config(self,**options):
        """
        TaggedSlider is a compound widget. In most cases, config
        options should be passed to one of the component widgets
        (i.e. the tag or the slider). For some options, however,
        we need to handle them being set on the widget as a whole;
        further, some of these should be applied to both component
        widgets, but some should just be applied to one.

        Options handled:
        * state (applied to both)
        * background, foreground (applied to tag only)
        """
        if 'state' in options:
            self.tag['state']=options['state']
            self.slider['state']=options['state']
            del options['state']
        if 'background' in options:
            self.tag['background']=options['background']
            del options['background']
        if 'foreground' in options:
            self.tag['foreground']=options['foreground']
            del options['foreground']
        if len(options)>0:
            raise NotImplementedError(
                """Only state, background, and foreground options are
                currently supported for this widget; set options on
                either the component tag or slider instead.""")

        return {} # CEBALERT: need to return same object as Tkinter would.


    def get(self):
        """
        Calls the tag's get() method.

        Helps to match behavior of other Tkinter Widgets.
        """
        return self.tag.get()


    # CEBALERT: three different events for max. flexibility...but
    # often a user will just want to know if the value was set. Could
    # also generate a "<<TaggedSliderSet>>" event each time, which a
    # user could just look for. Or could these events all be children
    # of a <<TaggedSliderSet>>?
    def _slider_used(self,event=None):
        self.variable.set(self.slider.get())
        self.event_generate("<<SliderSet>>")


    def _tag_press_return(self,event=None):
        self.event_generate("<<TagReturn>>")
        self.tag_set()


    def _tag_focus_out(self,event=None):
        self.event_generate("<<TagFocusOut>>")
        self.tag_set()


    def _set_slider_resolution(self,value):
        # CEBALERT: how to find n. dp simply?
        p = decimal.Decimal(str(value)).as_tuple()[2]
        self.slider['resolution']=10**p


    def _try_to_set_slider_resolution(self):
        """
        Use the actual number in the box to set the slider's resolution,
        so that user-entered resolutions are respected (e.g. 0.1 vs 0.1000).

        If that's not possible (e.g. there's text in the box), use the
        value contained in the variable (because whatever owns the
        variable could have performed a conversion of the text -
        TkParameterized does this, for instance).

        Leaves the resolution as it was if no number is available.
        """
        try:
            # 1st choice is to get the actual number in the box:
            # allows us to respect user-entered resolution
            # (e.g. 0.010000)
            self._set_slider_resolution(self.tag.get())
            return True
        except: # probably tclerror
            try:
                # ...but that might have been text.  2nd choice is to
                # get the value from the variable (e.g. 0.01)
                self._set_slider_resolution(self.variable.get())
                return True
            except: # probably tclerror
                return False # can't set a new resolution


    def _try_to_set_slider(self):
        """
        If the value in the box can't be converted to a float, the slider doesn't get set.
        """
        tagvar_val = self.variable.get()
        try:
            val = float(tagvar_val)
            self.set_slider_bounds(min(self.bounds[0],val),
                                   max(self.bounds[1],val))
            self.slider.set(val)
        except ValueError:
            pass


class FocusTakingButton(T.Button):
    """
    A Tkinter Button that takes the focus when the mouse <Enter>s.

    (Tkinter.Button doesn't get focus even when it's clicked,
    and only <Enter> and <Leave> events work for buttons.)
    """
    def __init__(self, master=None, cnf={}, **kw):
        T.Button.__init__(self,master=master,cnf=cnf,**kw)
        self.bind("<Enter>", lambda e=None,x=self: x.focus_set())
        #self['highlightthickness']=0


# CB: tklib has status bar and scrolled window (among other widgets);
# should try them out (although they're tk only...) because we might
# not need the scrodget pacakge.

# Might wonder why we need <<SizeRight>> event, and don't just use the
# <Configure> event for calling sizeright: Can't distinguish manual
# resize from autoresizing.
class ScrolledFrame(T.Frame):
    """
    XXXX

    Content to be scrolled should go in the 'content' frame.
    """

    def _getstatus(self):
        return self._status
    def _setstatus(self,s):
        self._status = s
    status = property(_getstatus,_setstatus)

    def __init__(self,parent,**config):
        assert 'status' not in config
        T.Frame.__init__(self,parent,**config)


        abovetopframe = T.Frame(self)
        topframe = T.Frame(self)
        middleframe = T.Frame(self)
        botframe = T.Frame(self)

        botframe.pack(side="bottom",expand="yes",fill="x")
        abovetopframe.pack(side="top",expand="yes",fill="both")
        topframe.pack(side="top")#,expand="no",fill="both")
        middleframe.pack(side="top",expand='yes',fill='x')

        self.noscroll = abovetopframe
        self.noscroll_bottom = middleframe

        self.canvas = T.Canvas(topframe)
        self.canvas.pack()
        self.canvas.configure(width=0,height=0)

        self.sc = Scrodget(topframe,autohide=1)
        self.sc.associate(self.canvas)
        self.sc.pack(expand=1,fill="both")

        self.content = T.Frame(self.canvas)
        self.content.title = lambda x: self.title(x)
        self.content.container = self

        self.canvas.create_window(0,0,window=self.content,anchor='nw')

        self.bind("<<SizeRight>>",self.sizeright)

        #self.status = StatusBar(botframe)
        #if status:
        #    self.status.pack(side="bottom",fill="both",expand="yes")


    def sizeright(self,event=None):
        self.content.update_idletasks()
        W = self.content.winfo_width()
        H = self.content.winfo_height()
        self.canvas.configure(scrollregion=(0, 0, W, H))
        self.canvas.configure(width=W,height=H)



class ScrolledWindow(T.Toplevel):

    def __init__(self,parent,**config):
        T.Toplevel.__init__(self,parent,**config)
        self.maxsize(self.winfo_screenwidth(),self.winfo_screenheight())

        self.topframe = T.Frame(self)
        # CEBALERT: specifying 20 necessary because otherwise nothing
        # requests any height for the frame. I feel there should be
        # way to have the (status) label request the right height but
        # make no request about the width.
        self.botframe = T.Frame(self,height=20)

        self.botframe.pack(side='bottom',expand='no',fill='x')
        self.topframe.pack(side='top',expand='yes',fill='both')

        self._scrolledframe = ScrolledFrame(self.topframe)
        self._scrolledframe.pack(expand=1,fill='both')
        self.content = self._scrolledframe.content
        self.noscroll = self._scrolledframe.noscroll
        self.noscroll_bottom = self._scrolledframe.noscroll_bottom

    # required? presumably should be deleted
    def sizeright(self,event=None):
        self._scrolledframe.sizeright()





class ProgressController(object):
    def __init__(self,timer=None,sim=None,progress_var=None,status=None):

        self.status=status
        self.sim=sim
        self.timer = timer #or topo.sim.timer
        self.timer.receive_info.append(self.timing_info)

        self.progress_var = progress_var or T.DoubleVar()
        # trace the variable so that at 100 we can destroy the window
        self.progress_trace_name = self.progress_var.trace_variable(
            'w',lambda name,index,mode: self._close_if_complete())


    def _close_if_complete(self):
        """
        Close the specified progress window if the value of progress_var has reached 100.
        """
        if self.progress_var.get()>=100:
            # delete the variable trace (necessary?)
            #self.progress_var.trace_vdelete('w',self.progress_trace_name)

            self._close(final_message="Time %s: Finished %s"%(self.sim.timestr(),
                                                             self.timer.func.__name__))


    def _close(self,final_message=None):
        self.timer.receive_info.remove(self.timing_info)
        if final_message:
            self.status.response(final_message)

    def timing_info(self,time,percent,name,duration,remaining):
        self.progress_var.set(percent)

    def set_stop(self):
        """Declare that running should be interrupted."""
        self.timer.stop=True
        final_message = "Time %s: Interrupted %s"%(self.sim.timestr(),
                                                   self.timer.func.__name__)
        self._close(final_message)


class ProgressWindow(ProgressController,T.Frame):
    """
    Graphically displays progress information for a SomeTimer object.

    ** Currently expects a 0-100 (percent) value ***
    """
    def __init__(self,parent,sim=None,timer=None,progress_var=None,
                 title=None,status=None,unpack_master_when_done=True):
        ProgressController.__init__(self,timer=timer,sim=sim,
                                    progress_var=progress_var,status=status)
        T.Frame.__init__(self,parent)

        self.balloon = Balloon(self)

        self._unpack_master=unpack_master_when_done


        progress_bar = Progressbar(self,variable=self.progress_var)
        progress_bar.pack(side='left')

        # CEBALERT: would like time to be displayed inside the bar,
        # but I don;t know how to do that.
        self.timeleft=T.Label(self)
        self.timeleft.pack(side='left')

        stop_button = FocusTakingButton(self,text="X",command=self.set_stop)
        stop_button.pack(side='right')#side="bottom")
        self.balloon.bind(stop_button,"""Interrupt a procedure.""")
#        Stop a running simulation.
#
#        The simulation can be interrupted only on round integer
#        simulation times, e.g. at 3.0 or 4.0 but not 3.15.  This
#        ensures that stopping and restarting are safe for any
#        model set up to be in a consistent state at integer
#        boundaries, as the example Topographica models are.""")


    def _close(self,final_message=None):
        ProgressController._close(self,final_message)
        # such a hack
        if self._unpack_master:
            self.master.pack_forget()
            self.master.master['width']=1
        T.Frame.destroy(self)

    def timing_info(self,time,percent,name,duration,remaining):
        try:
            ProgressController.timing_info(self,time,percent,name,duration,remaining)

            Z = "%02d:%02d"%(int(remaining/60),int(remaining%60))
            self.timeleft['text']=Z

            if self.status is not None:
                self.status.response('Time: %s  Duration: %s'%(time,duration))
                self.update()
                self.update_idletasks()
        except T.TclError:
            pass




# CEB: slightly modifed code from http://code.activestate.com/recipes/576688/

# CEBALERT: this seems better than the tooltip provided by
# tklib. Should probably use this instead of 'Balloon' everywhere. I
# need to use Tooltip rather than Balloon for the status bar, because
# Tooltip allows a function to be passed for generating the text. I
# wish tk included a useful tooltip widget by default.
from time import time
class ToolTip( T.Toplevel ):
    """
    Provides a ToolTip widget for Tkinter.
    To apply a ToolTip to any Tkinter widget, simply pass the widget to the
    ToolTip constructor
    """
    def __init__( self, wdgt, msg=None, msgFunc=None, delay=1, follow=True ):
        """
        Initialize the ToolTip

        Arguments:
          wdgt: The widget this ToolTip is assigned to
          msg:  A static string message assigned to the ToolTip
          msgFunc: A function that retrieves a string to use as the ToolTip text
          delay:   The delay in seconds before the ToolTip appears(may be float)
          follow:  If True, the ToolTip follows motion, otherwise hides
        """
        self.wdgt = wdgt
        self.parent = self.wdgt.master                                          # The parent of the ToolTip is the parent of the ToolTips widget
        T.Toplevel.__init__( self, self.parent, bg='black', padx=1, pady=1 )      # Initalise the Toplevel
        self.withdraw()                                                         # Hide initially
        self.overrideredirect( True )                                           # The ToolTip Toplevel should have no frame or title bar

        self.msgVar = T.StringVar()                                               # The msgVar will contain the text displayed by the ToolTip
        if msg == None:
            pass#self.msgVar.set( 'No message provided' )
        else:
            self.msgVar.set( msg )
        self.msgFunc = msgFunc
        self.delay = delay
        self.follow = follow
        self.visible = 0
        self.lastMotion = 0
        T.Message( self, textvariable=self.msgVar, bg='#FFFFDD',
                 aspect=1000 ).grid()                                           # The test of the ToolTip is displayed in a Message widget
        self.wdgt.bind( '<Enter>', self.spawn, '+' )                            # Add bindings to the widget.  This will NOT override bindings that the widget already has
        self.wdgt.bind( '<Leave>', self.hide, '+' )
        self.wdgt.bind( '<Motion>', self.move, '+' )

    def spawn( self, event=None ):
        """
        Spawn the ToolTip.  This simply makes the ToolTip eligible for display.
        Usually this is caused by entering the widget

        Arguments:
          event: The event that called this funciton
        """
        self.visible = 1
        self.after( int( self.delay * 1000 ), self.show )                       # The after function takes a time argument in miliseconds

    def show( self ):
        """
        Displays the ToolTip if the time delay has been long enough
        """
        if self.visible == 1 and time() - self.lastMotion > self.delay:
            self.visible = 2
        if self.visible == 2 and self.msgVar.get()!='':
            self.deiconify()

    def move( self, event ):
        """
        Processes motion within the widget.

        Arguments:
          event: The event that called this function
        """
        self.lastMotion = time()
        if self.follow == False:                                                # If the follow flag is not set, motion within the widget will make the ToolTip dissapear
            self.withdraw()
            self.visible = 1
        self.geometry( '+%i+%i' % ( event.x_root+10, event.y_root+10 ) )        # Offset the ToolTip 10x10 pixes southwest of the pointer
        try:
            self.msgVar.set( self.msgFunc() )                                   # Try to call the message function.  Will not change the message if the message function is None or the message function fails
        except:
            pass
        self.after( int( self.delay * 1000 ), self.show )

    def hide( self, event=None ):
        """
        Hides the ToolTip.  Usually this is caused by leaving the widget

        Arguments:
          event: The event that called this function
        """
        self.visible = 0
        self.withdraw()


def bind_tooltip(widget,msg):
    kw = dict(delay=1,follow=False)
    if callable(msg):
        kw['msgFunc']=msg
    else:
        kw['msg']=msg
    ToolTip(widget,**kw)


class StatusBar(T.Frame):

    messageframe_pack_options = dict(side='left',expand='yes',fill='x')
    dynamicinfoframe_pack_options = dict(side='left',expand='yes',fill='x')
    progressframe_pack_options = dict(side='right')


    #####
    # CEBALERT: hacks until we're using styles.
    #
    # This won't look good across platforms. Should get defaults.
    label_background = '#d9d9d9'
    label_foreground = '#000000'
    def _wrn_fmt(self):
        self.messagelabel.config(background=self.label_background)
        self.messagelabel.config(foreground='red')

    def _err_fmt(self):
        self.messagelabel.config(background='red')
        self.messagelabel.config(foreground='white')

    def _nrm_fmt(self):
        self.messagelabel.config(background=self.label_background)
        self.messagelabel.config(foreground=self.label_foreground)
    #####


    def __init__(self,master):
        T.Frame.__init__(self, master)

        self.mstack = []
        self.msformat = []
        self.mpoint = -1
        self.pf0 = T.Frame(self)
        self.pf0.pack(side='right')
        self.progressframe=T.Frame(self.pf0)

        self.dynamicinfoframe=T.Frame(self)
        self.dynamicinfolabel=T.Label(self.dynamicinfoframe,anchor='w')
        self.dynamicinfolabel.pack(side='left')
        bind_tooltip(self.dynamicinfolabel,self._get_dynamicinfo)

        self.messageframe=T.Frame(self)
        self.messageclear=T.Button(self.messageframe,text="X",
                                   command=lambda *args: self.clear_message())

        self.messagefwd=T.Button(self.messageframe,text=">",
                                 command=lambda *args:self.next_message())

        self.messagebck=T.Button(self.messageframe,text="<",
                                 command=lambda *args:self.previous_message())

        bind_tooltip(self.messageclear,"Dismiss current message(s)")
        bind_tooltip(self.messagefwd,"Next message")
        bind_tooltip(self.messagebck,"Previous message")

        self.messageclear.pack(side='right')
        self.messagefwd.pack(side='right')
        self.messagebck.pack(side='right')

        self.messagelabel=T.Label(self.messageframe,anchor='w')
        self.messagelabel.pack(side='left')
        bind_tooltip(self.messagelabel,self._get_message)


        self._show_dynamicinfoframe()


    def open_progress_window(self,timer=None,sim=None):
        p = ProgressWindow(self.progressframe,sim=sim,timer=timer,status=self)
        p.pack()
        self.progressframe.pack(self.progressframe_pack_options)
        return p


    def _show_messageframe(self):
        self.dynamicinfoframe.pack_forget()
        self.messageframe.pack(**self.messageframe_pack_options)


    def _show_dynamicinfoframe(self):
        self.messageframe.pack_forget()
        self.dynamicinfoframe.pack(**self.dynamicinfoframe_pack_options)
        self.update_idletasks()


    def dynamicinfo(self,text):
        self.dynamicinfolabel.config(text=text)


    def response(self,text):
        self.dynamicinfo(text)

        # remove previous after()
        try:
            self.after_cancel(self.__last_after_id)
        except:
            pass

        self.__last_after_id = self.after(10000,lambda *args: self.clear_dynamicinfo())

    def _display_from_history(self):
        displayindex=self.mpoint+1
        self.msformat[self.mpoint]()
        if displayindex<0:
            self._display_message("%s: %s"%(displayindex,self.mstack[self.mpoint]))
        else:
            self._display_message(self.mstack[self.mpoint])


    def previous_message(self):
        self.mpoint=max(-len(self.mstack),self.mpoint-1)
        self._display_from_history()

    def next_message(self):
        self.mpoint=min(-1,self.mpoint+1)
        self._display_from_history()

    def message(self,text):
        self._nrm_fmt()
        self.mstack.append(text)
        self.msformat.append(self._nrm_fmt)
        self._display_message(text)


    def _display_message(self,text):
        if text!='':
            if len(self.mstack)>1:
                pass#self.mnavframe.pack(side='right')
            else:
                pass#self.mnavframe.pack_forget()
            self.messagelabel.config(text=text)
            self._show_messageframe()


    def warn(self,text):
        self._wrn_fmt()
        self.mstack.append(text)
        self.msformat.append(self._wrn_fmt)
        self._display_message(text)


    def error(self,text):
        self._err_fmt()
        self.mstack.append(text)
        self.msformat.append(self._err_fmt)
        self._display_message(text)
        self.bell()
#        # CEBALERT: would like to lock only the window with the error,
#        # not the whole app, but I don't know how to do that.
#        try:
#            self.grab_set()
#        except: # get tclerror if another app has grab
#            pass

    def clear_message(self):
        self.mpoint=-1
        self.mstack=[]
        self.messagelabel.config(text="")
        self._nrm_fmt()
        self._show_dynamicinfoframe()
#        try:
#            self.grab_release()
#        except:
#            pass

    def clear_dynamicinfo(self):
        self.dynamicinfolabel.config(text="")

    def _get_message(self):
        return self.messagelabel.config()['text'][4]
    def _get_dynamicinfo(self):
        return self.dynamicinfolabel.config()['text'][4]



# CEBALERT: rename
class AppWindow(ScrolledWindow):
    """
    A ScrolledWindow with extra features intended to be common to all
    windows of an application.

    Currently this only includes a window icon, but we intend to
    add a right-click menu and possibly more.
    """
    window_icon_path = None

    def __init__(self,parent,status=False,**config):
        ScrolledWindow.__init__(self,parent)
        self.content.title = self.title
        self.renew()
        ### Universal right-click menu
        # CB: not currently used by anything but the plotgrouppanels
        # self.context_menu = Tkinter.Menu(self, tearoff=0)
        # self.bind("<<right-click>>",self.display_context_menu)

        # status bar is currenlty inside scrolled area (a feature
        # request is to move it outside ie replace self.content with
        # just self)
        self.status = StatusBar(self.botframe)
        if status:
            # place doesn't interfere with parent's geometry
            # (don't want status bar to cause horizontal resizing,
            # but DO want it to cause vertical resizing - the vertical
            # part doesn't work, see earlier HACKALERT by botframe)
            self.status.place(relx=0,rely=0,relwidth=1.0,height='')

    def renew(self):
        # CEBALERT: doesn't work on OS X, and is a strange color on
        # Windows.  To get a proper icon on OS X, we probably have to
        # bundle into an application package or something like that.
        # On OS X, could possibly alter the small titlebar icon with something like:
        # self.attributes("-titlepath","/Users/x/topographica/AppIcon.icns")
        self.iconbitmap('@'+self.window_icon_path)

        self.protocol("WM_DELETE_WINDOW",self.mydestroy)

    def mydestroy(self):
        ScrolledWindow.destroy(self)



class ListWidget(T.Frame):

    # CEBALERT: as with several of my compound widgets, need to deal
    # with **config passed to __init__, by putting through config()
    # (and so also need to add config() methods where currently
    # missing).
    def __init__(self, master, variable,cmd,**widget_options):
        T.Frame.__init__(self, master)

        if 'state' in widget_options:
            self.disabled=widget_options['state']
        else:
            self.disabled=False

        self.entry=T.Entry(self,textvariable=variable,state='disabled',disabledforeground='black') #CEBALERT: presumably disabledforeground won't work work with styles...
        self.entry.pack(fill='both',expand=1)

        ### Right-click menu for widgets
        # CEBALERT: I can't work out how to make the right-click event
        # bound to the widget already (by the ParametersFrame) ever
        # activate!  It must be overwritten or something by tk. So I
        # have to duplicate the right-click menu code here.
        self.entry.bind("<<right-click>>",self._right_click)
        master.option_add("*Menu.tearOff", "0")
        self.menu = Menu(master)
        self.menu.insert_command('end',label='Properties',
            command=cmd)

    def _right_click(self, event):
        """
        Popup the right-click menu.
        """
        if self.disabled is False:
            self.menu.tk_popup(event.x_root, event.y_root)

    def config(self,**options):
        # all options are passed to the entry
        # state=disabled->no right click menu
        if 'state' in options:
            if options['state']=='disabled':
                self.disabled=True
            elif options['state']=='normal':
                self.disabled=False
        return self.entry.config(**options)



import new



# CEBALERT: dict stuff is just half implemented! does enough
# to support list subclasses
def dict_to_parameterized(class_name,parameter_values):
    # each item is just a Parameter (i.e. not specific)
    parameter_objs = dict([(name,Parameter(default=value))
                           for name,value in parameter_values.items()])

    # got to be a new class to get independent sets of parameters
    new_class = new.classobj(class_name,(Representer,),parameter_objs)
    return  new_class(parameter_values)


def list_to_parameterized(list_,class_=None):

    parameter_values = OrderedDict([('a'*i,item) for item,i in zip(list_,range(1,len(list_)+1))])

    if class_ is None:
        parameter_objs = dict([(name,Parameter(default=value))
                               for name,value in parameter_values.items()])
    else:
        parameter_objs = dict([(name,ClassSelector(class_=class_,default=value))
                               for name,value in parameter_values.items()])


    new_class = new.classobj('List',(ListRepresenter,),parameter_objs)
    #new_class.params('name').hidden=True

    inst = new_class(parameter_values,class_=class_)
    inst.list_ = list_ # that's a hack; need to get classes right
    return inst





############################################################

def standard_parameterized_params():
    return Parameterized.params().keys()


class Representer(Parameterized):

    name = Parameter(precedence=-1) # CEBALERT

    def __init__(self,represented,class_):
        ## CEBALERT: when we come to represent dicts in the GUI,
        ## will have to deal with name clashes (e.g. for name parameter)
        for name in standard_parameterized_params():
            assert name not in represented
        ##
        self.class_ = class_
        self.represented = represented
        super(Representer,self).__init__(**represented)
        self.initialized=False # allow all modifications


    def my_params(self):
        param_names = self.params().keys()

        # remove any of the standard params (e.g. name) that haven't
        # been added to represented
        for name in standard_parameterized_params():
            if name not in self.represented:
                param_names.pop(param_names.index(name))

        return param_names


    def update(self):
        for name in self.my_params():
            self.represented[name]=getattr(self,name)

    def add(self,name):
        if self.class_ is None:
            p = Parameter(default=None)
        else:
            p = ClassSelector(class_=self.class_,default=None)

        self._add_parameter(name,p)
        self.represented[name]=p.default
        return name

    def remove(self,name):
        # CB: don't move any of this method up to parameterized
        # without considering the consequences!

        del self.represented[name]

        cls=type(self)
        delattr(cls,name)
        try:
            delattr(cls,'_%s__params'%cls.__name__)
        except AttributeError:
            pass


class ListRepresenter(Representer):

    def update(self):
        super(ListRepresenter,self).update()

        ## grow or shrink original list if necessary
        l = len(self.list_)-len(self.represented)

        if l>0:
            for i in range(l):
                self.list_.pop()
        elif l<0:
            for i in range(-l):
                self.list_.append(None)

        if len(self.represented)==0:
            self.list_[:]=[]
            return

        for val,i in zip(self.represented.values(),range(len(self.list_))):
            self.list_[i]=val

    def add(self,name=None):
        try:
            longest = max([len(n) for n in self.my_params()])
        except ValueError:
            longest = 0

        if name is None:
            name = 'a'*(longest+1)
            #name = 'a'*(len(self.my_params())+1)
        assert name not in self.my_params()
        return super(ListRepresenter,self).add(name)


# Problems:
# * + button gets hidden as more rows added (unless window made
#   bigger)
#
# * window doesn't open at right width to display contents
#   list item contents
#
# * I suspect there's a bug where you can get to a situation in
#   which editing one box will result in changes to another,
#   thanks to the ridiculously and unnecessarily complex code
#   for tracking items. But I can't currently demonstrate such a
#   a bug...
class EditingParametersFrameWithApply(ParametersFrameWithApply):

    def __init__(self,master,parameterized_object=None,
                 on_set=None,on_modify=None,**params):
        super(EditingParametersFrameWithApply,self).__init__(
            master,parameterized_object,on_set,on_modify,**params)

        self.hide_param('Defaults')

        ## hack to find last row
        last_row = -1
        for n,d in self.currently_displayed_params.items():
            row = int(d['widget'].grid_info()['row'])
            last_row=max(row,last_row)

        ad=T.Button(self,text="+",command=self._add_param)
        ad.pack(side='right')

        ### CB: should use button parameter and pack_param
        self._hack=[]
        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/edit_add.png')),(20,20)))
        ad['image']=image
        self._hack.append(image)
        ###

        self._apply_hooks.append(lambda *args: self.xupdate())
        self._apply_hooks.append(lambda *args: self._update_apply_status())


# CEBALERT: name this, and there's duplication with represented etc.
    def xupdate(self):
        cls = type(self._extraPO)

        old = {}
        for name in self.params_to_display:
            old[name]=(self._tkvars[name],
                       self.representations[name],
                       self.params_to_display[name],
                       self.currently_displayed_params[name],
                       self._extraPO.params(name))

        new_pos = {}
        for name in self.params_to_display.keys():
            new_pos[int(self.representations[name]['widget'].grid_info()['row'])]=name

        self._extraPO.represented = OrderedDict()

        for name,pos in zip(sorted(self.params_to_display),range(len(self.params_to_display))):
            self._tkvars[name]=old[new_pos[pos]][0]

            # getting ridiculous. make sure old widgets are wiped from
            # the earth. who knows how many other times I should have
            # been doing this in the gui?
            for w in self.representations[name].values():
                try:
                    w.grid_remove()
                    w.destroy()
                except:
                    pass

            self.representations[name]=old[new_pos[pos]][1]
            self.params_to_display[name]=old[new_pos[pos]][2]
            self.currently_displayed_params[name]=old[new_pos[pos]][3]
            type.__setattr__(cls,name,old[new_pos[pos]][4])

            # (see alert at start of method)
            self._extraPO.represented[name]=getattr(self._extraPO,name)

        ## delete cached params()
        try:
            delattr(cls,'_%s__params'%cls.__name__)
        except AttributeError:
            pass

        self._extraPO.update()
        self.set_PO(self._extraPO) # HACK! At least extract relevant parts


    def _grid_param(self,parameter_name,row):
        super(EditingParametersFrameWithApply,self)._grid_param(
            parameter_name,row)
        lc = self.representations[parameter_name]['list_ctrl']
        lc.grid(column=3,row=row)


    def _make_representation(self,name):
        super(EditingParametersFrameWithApply,self)._make_representation(name)

        self.representations[name]['list_ctrl']=ListItemCtrlWidget(
            self._params_frame,
            lambda:self._up_param(name),
            lambda:self._down_param(name),
            lambda:self._del_param(name))

    def _add_param(self):
        # should refactor pack_params to get out adding of single param

        po = self._extraPO
        name = po.add()

        self._create_tkvar(po,name,po.params(name))

        self._make_representation(name)
        row = len(self.params_to_display)
        self._grid_param(name,row)

        self.params_to_display[name]=po.params(name)
        self.currently_displayed_params[name]=self.representations[name]

        self._update_apply_status()


    def _del_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])
        po = self._extraPO
        for w in self.representations[name]:
            try:
                self.representations[name][w].grid_forget()
                self.representations[name][w].destroy()
            except:
                pass

        del self._tkvars[name]
        del self.representations[name]
        del self.params_to_display[name]
        del self.currently_displayed_params[name]
        del self.translators[name]

        po.remove(name)

        # move ones below up one
        for n in self.params_to_display:
            for n2,w in self.representations[n].items():
                if hasattr(w,'grid_info'):
                    w_gi = w.grid_info()
                    if 'row' in w_gi:
                        r1 = int(w_gi['row'])
                        if r1>current_pos:
                            w_gi['row']='%s'%(r1-1)
                            w.grid(**w_gi)

        self._update_apply_status()



    def _down_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])

        if current_pos==len(self.params_to_display)-1:
            return

        new_pos = current_pos+1
        current_name = name

        self.y(current_name,current_pos,new_pos)


    def y(self,current_name,current_pos,new_pos):
        #### HACK TO FIND NEW NAME BY MATCHING ROW
        new_name=None
        for n in self.representations:
            if 'row' in self.representations[n]['widget'].grid_info():
                row = int(self.representations[n]['widget'].grid_info()['row'])
                if row==new_pos:
                    new_name=n
                    break
        assert new_name is not None
        ####

        for n,w in self.representations[current_name].items():
            if hasattr(w,'grid_info'):
                g = w.grid_info()
                if 'row' in g:
                    #print "regrid",current_name,current_pos
                    g['row']='%s'%new_pos
                    w.grid(**g)

        for n,w in self.representations[new_name].items():
            if hasattr(w,'grid_info'):
                g = w.grid_info()
                if 'row' in g:
                    #print "regrid",new_name,new_pos
                    g['row']='%s'%current_pos
                    w.grid(**g)

        self._update_apply_status()


    def _up_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])

        if current_pos==0:
            return

        new_pos = current_pos-1
        current_name = name

        self.y(current_name,current_pos,new_pos)

    # CEBALERT: why not in super?
    def _update_apply_status(self):
        w=self.representations['Apply']['widget']
        if self.has_unapplied_change():
            w.config(state='normal')
        else:
            w.config(state='disabled')

    def set_PO(self,parameterized_object):
        super(EditingParametersFrameWithApply,self).set_PO(parameterized_object)
        self.hide_param('Defaults') # CEBALERT: should just have been unpacked earlier

    def has_unapplied_change(self):
        # detect length change
        if len(self._extraPO.list_)!=len(self.params_to_display):
            return True

        # detect order change
        names = sorted(self.params_to_display.keys())
        for i,name in zip(range(len(names)),names):
            if 'widget' not in self.representations[name] or int(self.representations[name]['widget'].grid_info()['row'])!=i:
                return True

        return super(EditingParametersFrameWithApply,self).has_unapplied_change()


    def _refresh_button(self,overwrite_error=True):
        self.set_PO(self._extraPO)
        self._update_apply_status()

    # CEBALERT: because
    def _apply_button(self):
        self.update_parameters()

        for h in self._apply_hooks:
            h(self)


class ListItemCtrlWidget(T.Frame):
    def __init__(self,master,up_cmd,down_cmd,remove_cmd):
        T.Frame.__init__(self, master)

        up = T.Button(self,text="u",command=up_cmd)
        up.pack(side='left')

        down = T.Button(self,text="d",command=down_cmd)
        down.pack(side='left')

        remove = T.Button(self,text='-',command=remove_cmd)
        remove.pack(side='left')


        ### CEBALERT: should use button parameter & pack_param
        self._hack = []
        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/arrow-up.png')),(20,20)))
        up['image']=image
        self._hack.append(image)

        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/arrow-down-2.0.png')),(20,20)))
        down['image']=image
        self._hack.append(image)

        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/edit_remove.png')),(20,20)))
        remove['image']=image
        self._hack.append(image)
        ###



<filename>paramtk/__init__.py
"""
Classes linking Parameters to Tkinter.

TkParameterized allows flexible graphical representation and
manipulation of the parameters of a Parameterized instance or class.

ParametersFrame and ParametersFrameWithApply extend TkParameterized,
displaying all the Parameters together in a list. Both allow these
Parameters to be edited; ParametersFrame applies changes immediately
as they are made, whereas ParametersFrameWithApply makes no changes
until a confirmation is given (by pressing the 'Apply' button).

Using these classes to display parameters has several benefits,
including that, automatically:

- changes to the Parameters in the GUI are reflected in the objects
  themselves without any additional code;

- each Parameter is represented by an appropriate widget (e.g. slider
  for a Number);

- type and range checking is handled (by virtue of using parameters);

- doc strings are displayed as pop-up help for each parameter;

- parameter names are formatted nicely for display;

- changes to parameter values in the GUI can be associated with
  function calls.


Examples
========

(1) Display the parameters of an object inside an existing window

You want to display all parameters of a parameterized instance
inside an existing container (e.g. a window or a frame):

  # existing Parameterized instance g
  from topo import pattern
  g = pattern.Gaussian()

  # existing window t
  import Tkinter
  t = Tkinter.Toplevel()

  # display all the parameters of g in t
  from topo.param.tk import ParametersFrame
  ParametersFrame(t,g)
  #should be ParametersFrame(t,g).pack(); see ALERT in ParametersFrame


(2) Display the parameters of an object in a standalone window

You want a new window displaying only the parameters of your object:

  # existing Parameterized instance g
  from topo import pattern
  g = pattern.Gaussian()

  # display all the parameters of g in a new window
  from topo.param.tk import edit_parameters
  edit_parameters(g)


(3) Flexible GUI layout using TkParameterized

You want to display only some of the parameters of one or more
Parameterized instances:

 ## Existing, non-GUI code
 from topo import param

 class Object1(param.Parameterized):
     duration = param.Number(2.0,bounds=(0,None),doc='Duration of measurement')
     displacement = param.Number(0.0,bounds=(-1,1),doc='Displacement from point A')

 class Object2(param.Parameterized):
     active_today = param.Boolean(True,doc='Whether or not to count today')
     operator_name = param.String('<NAME>',doc='Operator today')

 o1 = Object1()
 o2 = Object2()

 ## Existing window
 import Tkinter
 t = Tkinter.Toplevel()

 ## Flexible GUI representation: display o1.duration, o1.displacement,
 ## and o2.active_today inside t, ignoring o2.operator_name
 from topo.param.tk import TkParameterized

 t1 = TkParameterized(t,o1)
 t2 = TkParameterized(t,o2)

 t1.pack_param('duration',side='left')
 t1.pack_param('displacement',side='bottom')
 t2.pack_param('active_today',side='right')


(4) ?

TkParameterized is itself a subclass of Parameterized, so a
TkParameterized class can have its own parameters (in addition to
representing those of an external parameterized instance or class).

  ## Existing class
  from topo import params

  class X(param.Parameterized):
      one = param.Boolean(True)
      two = param.Boolean(True)


  ## Panel to represent an instance of X
  from Tkinter import Frame
  from topo.param.tk import TkParameterized

  class XPanel(TkParameterized,Frame):

      dock = param.Boolean(False,doc='Whether to attach this Panel')

      def __init__(self,master,x):
          self.pack_param('dock',side='top',on_set=self.handle_dock)
          self.pack_param('one',side='left')
          self.pack_param('two',side='right')

      def handle_dock(self):
          if self.dock:
              # dock the window
          else:
              # undock the window


  ## Running the code
  t = Tkinter.Toplevel()
  x = X()
  XPanel(t,x)
"""

# CEBALERT: moving todo
# (1) move icons from topo/tkgui.
# (2) update documentation


# CB: This file is too long because the param/gui interface code has
# become too long, and needs cleaning up.  I'm still working on it
# (still have to attend to simple ALERTs and do a one-pass cleanup)

# It's quite likely that the way TkParameterizedBase is implemented
# could be simplified. Right now, it still contains leftovers of
# various attempts to get everything working. But it does seem to
# work!


## import logging
## import string,time
## log_name= 'guilog_%s'%string.join([str(i) for i in list(time.gmtime())],"")

## logging.basicConfig(level=logging.DEBUG,
##                     format='%(asctime)s %(levelname)s %(message)s',
##                     filename='topo/tkgui/%s'%log_name,
##                     filemode='w')
## from os import popen
## version = popen('svnversion -n').read()
## logging.info("tkgui logging started for %s"%version)


import __main__
import copy
import decimal
import os.path

import Tkinter as T

try:
    import Image, ImageOps, ImageTk
except ImportError:
    from PIL import Image, ImageOps, ImageTk # pyflakes:ignore (try/except import)


from inspect import getdoc

from tkMessageBox import _show,QUESTION,YESNO

from param.parameterized import Parameterized,ParameterizedMetaclass,\
     classlist

# CEBALERT: try to import from python 2.7's ttk first
from tilewrapper import Combobox,Progressbar

# CEBALERT: try to import python 2.7's ordered dictionary first
from odict import OrderedDict

from param import Boolean,String,Number,Selector,ClassSelector,\
     ObjectSelector,Callable,Dynamic,Parameter,List,HookList,\
     Filename,resolve_path


# (part of an existing ALERT - search this file)
_last_one_set = None


# CEBALERT: Tkinter.BooleanVar.get() doesn't support None. This is a
# hack because there's no way in the GUI to visually distinguish a
# "None" from a "False".
def _BooleanVar_get(instance):
    try:
        return instance._tk.getboolean(instance._tk.globalgetvar(instance._name)) # i.e. BooleanVar.get()
    except T.TclError:
        return instance._tk.globalgetvar(instance._name) # i.e. Variable.get()


root = None
def initialize():
    """
    Add extension Tcl/Tk code to the Tk instance at
    Tkinter._default_root (creating the Tk instance first if
    necessary).
    """
    global root

    if root is not None:
        print "param.tk already initialized; ignoring call to param.tk.initialize()"
        return

    if T._default_root is not None:
        root = T._default_root
    else:
        root = T.Tk()
        root.withdraw()

    # Until tklib, tcllib, and scrodget become more commonly
    # available, we include them in tkgui.
    externaltcl_path = os.path.join(os.path.split(__file__)[0],"tcl")
    root.tk.call("lappend","auto_path",externaltcl_path)

    T.BooleanVar.get = _BooleanVar_get



def inverse(dict_):
    """
    Return the inverse of dictionary dict_.

    (I.e. return a dictionary with keys that are the values of dict_,
    and values that are the corresponding keys from dict_.)

    The values of dict_ must be unique.
    """
    idict = dict([(value,key) for key,value in dict_.iteritems()])
    if len(idict)!=len(dict_):
        raise ValueError("Dictionary has no inverse (values not unique).")
    return idict


def lookup_by_class(dict_,class_):
    """
    Look for class_ or its superclasses in the keys of dict_; on
    finding a match, return the value (return None if no match found).

    Searches from the most immediate class to the most distant
    (i.e. from class_ to the final superclass of class_).
    """
    v = None
    for c in classlist(class_)[::-1]:
        if c in dict_:
            v = dict_[c]
            break
    return v


def keys_sorted_by_value(d):
    """
    Return the keys of dictionary d sorted by value.
    """
    # By <NAME>, 2004/01/23
    # http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306
    items=d.items()
    backitems=[ [v[1],v[0]] for v in items]
    backitems.sort()
    return [ backitems[i][1] for i in range(0,len(backitems))]


def keys_sorted_by_value_unique(d, **sort_kwargs):
    """
    Return the keys of d, sorted by value.

    The values of d must be unique (see inverse)
    """
    values = d.values()
    values.sort(**sort_kwargs)
    i = inverse(d)
    return [i[val] for val in values]


def keys_sorted_by_value_unique_None_safe(d, **sort_kwargs):
    """
    None is handled separately: if present, it always comes out at the
    end of the list. Allows callers to use any sort function without
    worrying that None will not match the other objects (e.g. for
    using an attribute present on all the other objects, such as
    precedence of sheets).
    """
    if 'None' in d:
        assert d['None'] is None
        None_in_d = True
    else:
        None_in_d = False

    if None_in_d:
        del d['None']

    sorted_keys = keys_sorted_by_value_unique(d,**sort_kwargs)

    if None_in_d:
        d['None']=None
        sorted_keys.append('None')

    return sorted_keys


def is_button(widget):
    """
    Simple detection of Button-like widgets that are not Checkbuttons
    (i.e. widgets that do not require a separate label).
    """
    # CEBALERT: document why try/except is needed
    try:
        button = 'command' in widget.config() and not hasattr(widget,'toggle')
    except T.TclError:
        button = False
    return button


#################################################################
# Very basic wrapper for scrodget
class ScrodgetWidget:
    def _require(self, master):
        master.tk.call("package", "require", "scrodget")

    def __init__(self, master, cnf={}, **kw):
        self._require(master)
        T.Widget.__init__(self, master, 'scrodget', cnf, kw)


class Scrodget(ScrodgetWidget, T.Widget):
    def associate(self,widget=None,*args):
        return self.tk.call(self._w,"associate",widget)
#################################################################



# CEB: workaround for tkinter lagging behind tk (tk must have changed
# the type of a returned value).  This is copied almost exactly from
# tkMessageBox. If there are other things like this, we could have the
# gui load some 'dynamic patches' to tkinter on startup, which could
# then be removed when tkinter is updated (they'd all be in one place,
# and no tkgui code would have to change).
def askyesno(title=None, message=None, **options):
    "Ask a question; return true if the answer is yes"
    s = _show(title, message, QUESTION, YESNO, **options)
    return str(s) == "yes"



# Buttons are not naturally represented by parameters?
#
# Maybe we should have a parent class that implements the
# non-Parameter specific stuff, then one that bolts on the
# Parameter-specific stuff, and then instead of Button we'd
# have TopoButton, or something like that...
class Button(Callable):
    """
    A GUI-specific parameter to display a button.

    Can be associated with an image by specifying an image_path
    (i.e. location of an image suitable for PIL, e.g. a PNG, TIFF, or
    JPEG image) and optionally a size (width,height) tuple.

    Note that the button size can also be set when there is no image,
    but instead of being presumed to be in pixels, it is presumed to
    be in text units (a Tkinter feature: see
    e.g. http://effbot.org/tkinterbook/button.htm). Therefore, to
    place two identically sized buttons next to each other, with one
    displaying text and the other an image, you first have to convert
    one of the sizes to the other's units.
    """
    # CEBALERT: we should probably solve the above for users,
    # but what a pain!
    __slots__ = ['image_path','size','_hack']

    def __init__(self,default=None,image_path=None,size=None,**params):
        Callable.__init__(self,default=default,**params)
        self.image_path = image_path
        self.size = size
        self._hack = []

# CB: would create the photoimage on construction and store it as an
# attribute, except that it gives "RuntimeError: Too early to create
# image". Must be happening before tk starts, or something. So
# instead, return image on demand. Also, because of PIL bug (see
# topoconsole.py "got to keep references to the images") we store
# a reference to the image each time in _hack.
    def get_image(self):
        """
        Return an ImageTk.PhotoImage of the image at image_path
        (or None if image_path is None or an Image cannot be created).
        """
        image = None
        if self.image_path:
            image=ImageTk.PhotoImage(ImageOps.fit(
                Image.open(resolve_path(self.image_path)),self.size or (32,32)))
            self._hack.append(image)

        return image




# Note that TkParameterized extends TkParameterizedBase by adding
# widget-drawing abilities; documentation for using these classes
# begins at a more useful and simple level in TkParameterized (the
# documentation for TkParameterizedBase is more complex).
class TkParameterizedBase(Parameterized):
    """
    A Parameterized subclass that maintains Tkinter.Variable shadows
    (proxies) of its Parameters. The Tkinter Variable shadows are kept
    in sync with the Parameter values, and vice versa.

    Optionally performs the same for an *additional* shadowed
    Parameterized (extraPO). The Parameters of the extra
    shadowed PO are available via this object (via both the usual
    'dot' attribute access and dedicated parameter accessors
    declared in this class).

    The Tkinter.Variable shadows for this Parameterized and any
    extra shadowed one are available under their corresponding
    parameter names in the _tkvars dictionary.

    (See note 1 for complications arising from name clashes.)


    Parameters being represented by TkParameterizedBase also
    have a 'translators' dictionary, allowing mapping between string
    representations of the objects and the objects themselves (for use
    with e.g. a Tkinter.OptionMenu). More information about the
    translators is available from specific translator-related methods
    in this class.


    Notes
    =====

    (1) (a) There is an order of precedance for parameter lookup:
        this PO > shadowed PO.

        Therefore, if you create a Parameter for this PO
        with the same name as one of the shadowed PO's Parameters, only
        the Parameter on this PO will be shadowed.

        Example: 'name' is a common attribute. As a
        Parameterized, this object has a 'name' Parameter. Any
        shadowed PO will also have a 'name' Parameter. By default,
        this object's name will be shadowed at the expense of the name
        of the extra shadowed PO.

        The precedence order can be reversed by setting the attribute
        'self_first' on this object to False.


        (b) Along the same lines, an additional complication can arise
        relating specifically to 'dot' attribute lookup.  For
        instance, a sublass of TkParameterized might also
        inherit from Tkinter.Frame. Frame has many of its own
        attributes, including - for example - 'size'. If we shadow a
        Parameterized that has a 'size' Parameter, the
        Parameterized's size Parameter will not be available as
        .size because ('dot') attribute lookup begins on the local
        object and is not overridden by 'self_first'. Using the
        parameter accessors .get_parameter_object('size') or
        .get_parameter_value('size') (and the equivalent set versions)
        avoids this problem.



    (2) If a shadowed PO's Parameter value is modified elsewhere, the
        Tkinter Variable shadow is NOT updated until that Parameter value
        or shadow value is requested from this object. Thus requesting the
        value will always return an up-to-date result, but any GUI display
        of the Variable might display a stale value (until a GUI refresh
        takes place).
    """

    # CEBNOTE: avoid (as far as possible) defining Parameters for this
    # object because they might clash with Parameters of objects it
    # eventually represents.


    # CEBNOTE: Regarding note 1a above...it would be possible - with
    # some extra work - to shadow Parameters that are duplicated on
    # this PO and extraPO. Among other changes, things like the
    # _tkvars dict would need different (e.g. name on this PO and
    # name on extraPO)

    # CEBNOTE: Regarding note 1b...might be less confusing if we stop
    # parameters of shadowed POs being available as attributes (and
    # use only the parameter access methods instead). But having them
    # available as attributes is really convenient.

    # CEBNOTE: Regarding note 2 above...if it becomes a problem, we
    # could have some autorefresh of the vars or a callback of some
    # kind in the parameterized object itself. E.g a
    # refresh-the-widgets-on-focus-in method could make the gui in
    # sync with the actual object (so changes outside the gui could
    # show up when a frame takes focus). Or there could be timer
    # process.

    # CEB: because of note 1, attributes of this class should have
    # names that are unlikely to clash (or they should be private);
    # this will make it easier for creators and users of subclasses to
    # avoid name clashes.

    # must exist *before* an instance is init'd
    # (for __getattribute__)
    _extraPO = None

    # CEBALERT: Parameterized repr method leads to recursion error.
    def __repr__(self): return object.__repr__(self)


    def _setup_params(self,**params):
        """
        Parameters that are not in this object itself but are in the
        extraPO get set on extraPO. Then calls Parameterized's
        _setup_params().
        """
        ### a parameter might be passed in for one of the extra_pos;
        ### if a key in the params dict is not a *parameter* of this
        ### PO, then try it on the extra_pos
        for n,p in params.items():
            if n not in self.params():
                self.set_parameter_value(n,p)
                del params[n]

        Parameterized._setup_params(self,**params)

    # CEBALERT: rename extraPO...but to what?
    # Rename change_PO() and anything else related.
    def __init__(self,extraPO=None,self_first=True,live_update=True,**params):
        """


        Translation between displayed values and objects
        ------------------------------------------------

        A Parameter has a value, but that might need some processing
        to become a value suitable for display. For instance, the
        SheetMask() object <SheetMask SheetMask0001923> ...



        Important attributes
        --------------------

        * _extraPO


        * self_first
        Determines precedence order for Parameter lookup:
        if True, Parameters of this object take priority whenever
        there is a name clash; if False, Parameters of extraPO take
        priority.




        * obj2str_fn & str2obj_fn

        * translator_creators

        (Note that in the various dictionaries above, the entry for
        Parameter serves as a default, since keys are looked up by
        class, so any Parameter type not specifically listed will be
        covered by the Parameter entry.)



        * _tkvars


        """
        if not (extraPO is None or isinstance(extraPO,ParameterizedMetaclass) \
                or isinstance(extraPO,Parameterized)):
            raise TypeError("%s is not a Parameterized instance or class."%extraPO)

        # make self.first etc private

        self._live_update = live_update
        self.self_first = self_first

        ## Which Tkinter Variable to use for each Parameter type
        # (Note that, for instance, we don't include Number:DoubleVar.
        # This is because we use Number to control the type, so we
        # don't need restrictions from DoubleVar.)
        self._param_to_tkvar = {Boolean:T.BooleanVar,
                                Parameter:T.StringVar}

        # CEBALERT: Parameter is the base parameter class, but ...
        # at least need a test that will fail when a new param type added
        # Rename
        self.trans={Parameter:Eval_ReprTranslator,
                    Dynamic:Eval_ReprTranslator,
                    ObjectSelector:String_ObjectTranslator,
                    ClassSelector:CSPTranslator,
                    Number:Eval_ReprTranslator,
                    Boolean:BoolTranslator,
                    String:DoNothingTranslator,
                    List:ListTranslator,
                    HookList:ListTranslator} # CBALERT: sort out inherit.

        self.change_PO(extraPO)
        super(TkParameterizedBase,self).__init__(**params)


    def change_PO(self,extraPO):
        """
        Shadow the Parameters of extraPO.
        """
        self._extraPO = extraPO
        self._tkvars = {}
        self.translators = {}

        # (reverse list to respect precedence)
        for PO in self._source_POs()[::-1]:
            self._init_tkvars(PO)


    def _init_tkvars(self,PO):
        """
        Create Tkinter Variable shadows of all Parameters of PO.
        """
        for name,param in PO.params().items():
            self._create_tkvar(PO,name,param)


    def _create_tkvar(self,PO,name,param_obj):
        """
        Add _tkvars[name] to represent the parameter object with the specified name.

        The appropriate Variable is used for each Parameter type.

        Also adds tracing mechanism to keep the Variable and Parameter
        values in sync, and updates the translator dictionary to map
        string representations to the objects themselves.
        """
        # CEBALERT: should probably delete any existing tkvar for name
        self._create_translator(name,param_obj)

        tkvar = lookup_by_class(self._param_to_tkvar,type(param_obj))()
        self._tkvars[name] = tkvar

        # overwrite Variable's set() with one that will handle
        # transformations to string
        tkvar._original_set = tkvar.set
        tkvar.set = lambda v,x=name: self._tkvar_set(x,v)

        tkvar.set(self.get_parameter_value(name,PO))
        tkvar._last_good_val=tkvar.get() # for reverting
        tkvar.trace_variable('w',lambda a,b,c,p_name=name: self._handle_gui_set(p_name))
        # CB: Instead of a trace, could we override the Variable's
        # set() method i.e. trace it ourselves?  Or does too much
        # happen in tcl/tk for that to work?

        # Override the Variable's get() method to guarantee an
        # out-of-date value is never returned.  In cases where the
        # tkinter val is the most recently changed (i.e. when it's
        # edited in the gui, resulting in a trace_variable being
        # called), the _original_get() method is used.
        # CEBALERT: what about other users of the variable? Could they
        # be surprised by the result from get()?
        tkvar._original_get = tkvar.get
        tkvar.get = lambda x=name: self._tkvar_get(x)


################################################################################
#
################################################################################

    def _handle_gui_set(self,p_name):
        """
        * The callback to use for all GUI variable traces/binds *
        """
        if self._live_update:
            self._update_param_from_tkvar(p_name)


    def _tkvar_set(self,param_name,val):
        """
        Set the tk variable to (the possibly transformed-to-string) val.
        """
        self.debug("_tkvar_set(%s,%s)"%(param_name,val))
        val = self._object2string(param_name,val)
        tkvar = self._tkvars[param_name]
        tkvar._original_set(val) # trace not called because we're already in trace,
                                  # and tk disables trace activation during trace


    # CB: separate into update and get?
    def _tkvar_get(self,param_name):
        """
        Return the value of the tk variable representing param_name.

        (Before returning the variable's value, ensures it's up to date.)
        """
        tk_val = self._tkvars[param_name]._original_get()
        po_val = self.get_parameter_value(param_name)

        po_stringrep = self._object2string(param_name,po_val)

        if not self.translators[param_name].last_string2object_failed and not tk_val==po_stringrep:
            self._tkvars[param_name]._original_set(po_stringrep)
        return tk_val


    def _tkvar_changed(self,name):
        """
        Return True if the displayed value does not equal the object's
        value (and False otherwise).
        """
        self.debug("_tkvar_changed(%s)"%name)
        displayed_value = self._string2object(name,self._tkvars[name]._original_get())
        object_value = self.get_parameter_value(name) #getattr(self._extraPO,name)

        # use equality check then identity check because e.g.  val
        # starts at 0.5, type 0.8, then type 0.5, need that to be
        # changed is False, but some types cannot be equality compared
        # (can be identity compared).
        # CEBALERT: need to add a unit test to ensure this keeps working.
        # Plus, I need to think about this, because while the above is
        # true for floats, identity tests make more sense for many types
        # (i.e. you want to know the object is the same).
        try:
            if displayed_value != object_value:
                changed = True
            else:
                changed = False
        except:
            if displayed_value is not object_value:
                changed = True
            else:
                changed = False

        self.debug("..._v_c return %s"%changed)
        return changed


    def _update_param_from_tkvar(self,param_name):
        """
        Attempt to set the parameter represented by param_name to the
        value of its corresponding Tkinter Variable.

        If setting the parameter fails (e.g. an inappropriate value
        is set for that Parameter type), the Variable is reverted to
        its previous value.

        (Called by the Tkinter Variable's trace_variable() method.)
        """
        self.debug("TkPOb._update_param_from_tkvar(%s)"%param_name)

        parameter,sourcePO=self.get_parameter_object(param_name,with_source=True)

        ### can only edit constant parameters for class objects
        if parameter.constant is True and not isinstance(sourcePO,type):
            return  ### HIDDEN

        tkvar = self._tkvars[param_name]

        if self._tkvar_changed(param_name):
            # don't attempt to set if there was a string-to-object translation error
            if self.translators[param_name].last_string2object_failed:
                return   ### HIDDEN

            # (use _original_get() because we don't want the tkvar to be reset to
            # the parameter's current value!)
            val = self._string2object(param_name,tkvar._original_get())

            try:
                self._set_parameter(param_name,val)
            except: # everything
                tkvar.set(tkvar._last_good_val)
                raise # whatever the parameter-setting error was

            self.debug("set %s to %s"%(param_name,val))

            if hasattr(tkvar,'_on_modify'):
                tkvar._on_modify()

        ### call any function associated with GUI set()
        if hasattr(tkvar,'_on_set'):

            # CEBALERT: provide a way of allowing other gui components
            # to figure out where a callback error might have come
            # from. Callback instances (the Callback class is defined
            # in Tkinter.py) store a widget, but often it appears to
            # be the Tk instance - which is of no use in later
            # determining where an error might have originated.
            global _last_one_set
            if hasattr(self,'master'):
                _last_one_set = self.master

            tkvar._on_set()


################################################################################
#
################################################################################



    def _source_POs(self):
        """
        Return a list of Parameterizeds in which to find
        Parameters.

        The list is ordered by precedence, as defined by self_first.
        """
        if not self._extraPO:
            sources = [self]
        elif self.self_first:
            sources = [self,self._extraPO]
        else:
            sources = [self._extraPO,self]
        return sources




    def get_source_po(self,name):
        """
        Return the Parameterized which contains the parameter 'name'.
        """
        sources = self._source_POs()

        for po in sources:
            if name in po.params():
                return po


        raise AttributeError(self._attr_err_msg(name,sources))


    def get_parameter_object(self,name,parameterized_object=None,with_source=False):
        """
        Return the Parameter *object* (not value) specified by name,
        from the source_POs in this object (or the
        specified parameterized_object).

        If with_source=True, returns also the source parameterizedobject.
        """
        source = parameterized_object or self.get_source_po(name)
        parameter_object = source.params()[name]

        if not with_source:
            return parameter_object
        else:
            return parameter_object,source



######################################################################
# Attribute lookup

##### these guarantee only to get/set parameters #####
    def get_parameter_value(self,name,parameterized_object=None):
        """
        Return the value of the parameter specified by name.

        If a parameterized_object is specified, looks for the parameter there.
        Otherwise, looks in the source_POs of this object.
        """
        source = parameterized_object or self.get_source_po(name)
        return source.get_value_generator(name)

    # CEBALERT: shouldn't this use __set_parameter? Presumably doing
    # that kind of thing is part of the cleanup required in this file.
    def set_parameter_value(self,name,val,parameterized_object=None):
        """
        Set the value of the parameter specified by name to val.

        Updates the corresponding tkvar.
        """
        source = parameterized_object or self.get_source_po(name)
        object.__setattr__(source,name,val)

        # update the tkvar
        if name in self._tkvars:
            self._tkvars[name]._original_set(self._object2string(name,val))


######################################################

########## these lookup attributes in order ##########
# (i.e. you could get attribute of self rather than a parameter)
# (might remove these to save confusion: they are useful except when
#  someone would be surprised to get an attribute of e.g. a Frame (like 'size') when
#  they were expecting to get one of their parameters. Also, means you can't set
#  an attribute a on self if a exists on one of the shadowed objects)
# (also they (have to) ignore self_first)

    def __getattribute__(self,name):
        """
        If the attribute is found on this object, return it. Otherwise,
        return the attribute from the extraPO, if it exists.
        If there is no match, raise an attribute error.
        """
        try:
            return object.__getattribute__(self,name)
        except AttributeError:
            extraPO = object.__getattribute__(self,'_extraPO')

            if hasattr(extraPO,name):
                return getattr(extraPO,name) # HIDDEN!

            _attr_err_msg = object.__getattribute__(self,'_attr_err_msg')

            raise AttributeError(_attr_err_msg(name,[self,extraPO]))


    def __setattr__(self,name,val):
        """
        If the attribute already exists on this object, set it. If the attribute
        is found on the extraPO, set it there. Otherwise, set the
        attribute on this object (i.e. add a new attribute).
        """
        # use dir() not hasattr() because hasattr uses __getattribute__
        if name in dir(self):

            if name in self.params():
                self.set_parameter_value(name,val,self)
            else:
                object.__setattr__(self,name,val)

        elif name in dir(self._extraPO):

            if name in self._extraPO.params():
                self.set_parameter_value(name,val,self._extraPO)
            else:
                object.__setattr__(self._extraPO,name,val)

        else:

            # name not found, so set on this object
            object.__setattr__(self,name,val)
#######################################################


######################################################################





######################################################################
# Translation between GUI (strings) and true values

    def _create_translator(self,name,param):
        self.debug("_create_translator(%s,%s)"%(name,param))

        translator_type = lookup_by_class(self.trans,type(param))

        # Dynamic parameters only *might* contain a
        # dynamic value; if such a parameter really is dynamic, we
        # overwrite any more specific class found above
        # (e.g. a Number with a dynamic value will have a numeric
        # translator from above, so we replace that)
        if param_is_dynamically_generated(param,self.get_source_po(name)) or name in self.allow_dynamic:
            translator_type = self.trans[Dynamic]

        self.translators[name]=translator_type(param,initial_value=self.get_parameter_value(name))

        self.translators[name].msg_handler = self.msg_handler




    # CEBALERT: doc replace & rename to plastic or something
    def _object2string(self,param_name,obj,replace=True):
        """
        If val is one of the objects in param_name's translator,
        translate to the string.
        """
        self.debug("object2string(%s,%s)"%(param_name,obj))
        translator = self.translators[param_name]

        if not replace:
            translator=copy.copy(translator)

        return translator.object2string(obj)


    def _string2object(self,param_name,string):
        """
        Change the given string for the named parameter into an object.

        If there is a translator for param_name, translate the string
        to the object; otherwise, call convert_string2obj on the
        string.
        """
        self.debug("string2object(%s,%s)"%(param_name,string))
        translator = self.translators[param_name]
        o = translator.string2object(string)
        self.debug("...s2o return %s, type %s"%(o,type(o)))
        return o

######################################################################

    def _attr_err_msg(self,attr_name,objects):
        """
        Helper method: return the 'attr_name is not in objects' message.
        """
        if not hasattr(objects,'__len__'):objects=[objects]

        error_string = "'%s' not in %s"%(attr_name,objects.pop(0))

        for o in objects:
            error_string+=" or %s"%o

        return error_string


    def _set_parameter(self,param_name,val):
        """
        Helper method:
        """
        # use set_in_bounds if it exists
        # i.e. users of widgets get their values cropped
        # (no warnings/errors, so e.g. a string in a
        # tagged slider just goes to the default value)
        # CEBALERT: set_in_bounds not valid for POMetaclass?
        parameter,sourcePO=self.get_parameter_object(param_name,with_source=True)

        # CEBHACKALERT: GeneratorSheet.input_generator should be a
        # property? But it's a Parameter...
        # Setting input_generator rather than calling set_input_generator()
        # is probably a trap...
        if param_name=='input_generator' and hasattr(sourcePO,'set_input_generator'):
            sourcePO.set_input_generator(val)
        elif hasattr(parameter,'set_in_bounds') and isinstance(sourcePO,Parameterized):
            parameter.set_in_bounds(sourcePO,val)
        else:
            setattr(sourcePO,param_name,val)










class TkParameterized(TkParameterizedBase):
    """
    Provide widgets for Parameters of itself and up to one additional
    Parameterized instance or class.

    A subclass that defines a Parameter p can display it appropriately
    for manipulation by the user simply by calling
    pack_param('p'). The GUI display and the actual Parameter value
    are automatically synchronized (though see technical notes in
    TkParameterizedBase's documentation for more details).

    In general, pack_param() adds a Tkinter.Frame containing a label
    and a widget:

    ---------------------                     The Parameter's
    |                   |                     'representation'
    | [label]  [widget] |<----frame
    |                   |
    ---------------------

    In the same way, an instance of this class can be used to display
    the Parameters of an existing object. By passing in extraPO=x,
    where x is an existing Parameterized instance or class, a
    Parameter q of x can also be displayed in the GUI by calling
    pack_param('q').

    For representation in the GUI, Parameter values might need to be
    converted between their real values and strings used for display
    (e.g. for a ClassSelector, the options are really class objects,
    but the user is presented with a list of strings to choose
    from). Such translation is handled and documented in the
    TkParameterizedBase; the default behaviors can be overridden if
    required.

    (Note that this class simply adds widget drawing to
    TkParameterizedBase. More detail about the shadowing of
    Parameters is available in the documentation for
    TkParameterizedBase.)
    """

    # CEBNOTE: as for TkParameterizedBase, avoid declaring
    # Parameters here (to avoid name clashes with any additional
    # Parameters this might eventually be representing).

    pretty_parameters = Boolean(default=True,precedence=-1,
        doc="""Whether to format parameter names, or display the
        variable names instead.

        Example use:
          TkParameterized.pretty_parameters=False

        (This causes all Parameters throughout the GUI to be displayed
        with variable names.)
        """)


    def __init__(self,master,extraPO=None,self_first=True,
                 msg_handler=None,**params):
        """
        Initialize this object with the arguments and attributes
        described below:

        extraPO: optional Parameterized object for which to shadow
        Parameters (in addition to Parameters of this object; see
        superclass)

        self_first: if True, Parameters on this object take precedence
        over any ones with the same names in the extraPO (i.e. what
        to do if there are name clashes; see superclass)


        Important attributes
        ====================

        * param_immediately_apply_change

        Some types of Parameter are represented with widgets where
        a complete change can be instantaneous (e.g.  when
        selecting from an option list, the selection should be
        applied straightaway). Others are represented with widgets
        that do not finish their changes instantaneously
        (e.g. entry into a text box is not considered finished
        until Return is pressed).

        * widget_creators

        A dictionary of methods to create a widget for each Parameter
        type. For special widget options (specific to each particular
        parameter), see the corresponding method's docstring.

        * representations

        After pack_param() is called, a Parameter's representation
        consists of the tuple (frame,widget,label,pack_options) - the
        enclosing frame, the value-containing widget, the label
        holding the Parameter's name, and any options supplied for
        pack(). These can all be accessed through the representations
        dictionary, under the Parameter's name.
        """
        self.master = master
        self.msg_handler = msg_handler

        self.representations = {}

        # CEBALER: doc
        self.allow_dynamic = []
        self.allow_default = []

        self.param_immediately_apply_change = {Boolean:True,
                                               Selector:True,
                                               Number:False,
                                               Parameter:False,
                                               Filename:True}

        TkParameterizedBase.__init__(self,extraPO=extraPO,
                                     self_first=self_first,
                                     **params)

        self.balloon = Balloon(master)

        # CEBALERT: what about subclasses of Number (e.g. Integer,
        # which should get a slider that jumps between integers...
        # maybe that already happens)?
        self.widget_creators = {
            Boolean:self._create_boolean_widget,
            Dynamic:self._create_string_widget,
            Number:self._create_number_widget,
            Button:self._create_button_widget,
            String:self._create_string_widget,
            Selector:self._create_selector_widget,
            List:self._create_list_widget,
            HookList:self._create_list_widget,
            Filename:self._create_fileselector_widget,
            }

        self.representations = {}


        # CEBNOTE: it would be nice to sort out menus properly
        # (i.e. parameterize them)
        self.popup_menu = Menu(master, tearoff=0)
        self.dynamic_var = T.BooleanVar()
        self.default_var = T.BooleanVar()
        self.popup_menu.add("checkbutton",indexname="dynamic",
                            label="Enter dynamic value?",
                            state="disabled",command=self._switch_dynamic,
                            variable=self.dynamic_var)
        self.popup_menu.add("command",label="Reset to default",
                            command=self._default_param)


        ### Right-click menu for widgets
        master.option_add("*Menu.tearOff", "0")
        self.menu = Menu(master)
        self.menu.insert_command('end',label='Properties',
            command=lambda:self._edit_PO_in_currently_selected_widget())



    def _right_click(self, event, widget):
        """
        Popup the right-click menu.
        """
        self._currently_selected_widget = widget

        # need an actual mechanism for populating the menu, rather than this!!
        ### copied from edit_PO_in_currently...
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break
        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())
        ###

        if hasattr(PO_to_edit,'params'):
            self.menu.tk_popup(event.x_root, event.y_root)


    # CEBALERT: rename
    def _edit_PO_in_currently_selected_widget(self):
        """
        Open a new window containing a ParametersFrame (actually, a
        type(self)) for the PO in _currently_selected_widget.
        """
        # CEBALERT: simplify this lookup by value
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break

        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())

        parameter_window = AppWindow(self)
        parameter_window.title(PO_to_edit.name+' parameters')

        ### CEBALERT: confusing? ###
        title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")")
        title.pack(side = "top")
        self.balloon.bind(title,getdoc(self.get_parameter_object(param_name)))
        ############################

        # uh-oh
        if not isinstance(self,ParametersFrame):
            p_type = ParametersFrameWithApply
            parameter_frame = p_type(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler)

        else:
            p_type = type(self)
            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)

        parameter_frame.pack()



    def _update_dynamic_menu_entry(self,param_name):
        """Keep track of status of dynamic entry."""
        param,po = self.get_parameter_object(param_name,with_source=True)
        currently_dynamic = param_is_dynamically_generated(param,po)
        if hasattr(param,'_value_is_dynamic') and not currently_dynamic:
            self._right_click_param = param_name
            state = "normal"
        else:
            self._right_click_param = None
            state = "disabled"
        self.popup_menu.entryconfig("dynamic",state=state)
        self.dynamic_var.set(currently_dynamic or \
                             param_name in self.allow_dynamic)


    def _param_right_click(self,event,param_name):
        """Display a popup menu when user right clicks on a parameter."""
        self._update_dynamic_menu_entry(param_name)
        self.popup_menu.tk_popup(event.x_root,event.y_root)



################################################################################
#
################################################################################

    def _update_param_from_tkvar(self,param_name,force=False):
        """
        Prevents the superclass's _update_param_from_tkvar() method from being
        called unless:

        * param_name is a Parameter type that has changes immediately
          applied (see doc for param_immediately_apply_change
          dictionary);

        * force is True.

        (I.e. to update a parameter for which
        param_immediately_apply_change[type(parameter)]==False, call
        this method with force=True. E.g. when <Return> is pressed in
        a text box, this method is called with force=True.)
        """
        self.debug("TkPO._update_param_from_tkvar(%s)"%param_name)

        param_obj = self.get_parameter_object(param_name)

        if not lookup_by_class(self.param_immediately_apply_change,
                               type(param_obj)) and not force:
            return
        else:
            super(TkParameterized,self)._update_param_from_tkvar(param_name)


    def _handle_gui_set(self,p_name,force=False):
        """Override the superclass's method to X and allow status indications."""
        #logging.info("%s._handle_gui_set(%s,force=%s)"%(self,p_name,force))
        if self._live_update:
            self._update_param_from_tkvar(p_name,force)

        self._indicate_tkvar_status(p_name)



    #### Simulate GUI actions
    def gui_set_param(self,param_name,val):
        """Simulate setting the parameter in the GUI."""
        self._tkvar_set(param_name,val)  # ERROR: presumably calls trace stuff twice
        self._handle_gui_set(param_name,force=True)

    def gui_get_param(self,param_name):
        """Simulate getting the parameter in the GUI."""
        return self._tkvars[param_name].get()
    ####


################################################################################
# End
################################################################################





################################################################################
#
################################################################################

    # some refactoring required: should be a base method that's to do
    # with adding a representation for a parameter. then this stuff
    # would go in it.
    def _post_add_param(self,param_name):
        l = self.representations[param_name]['label']
        if l is not None:
            l.bind('<<right-click>>',lambda event: \
                   self._param_right_click(event,param_name))


    def pack_param(self,name,parent=None,widget_options={},
                   on_set=None,on_modify=None,**pack_options):
        """
        Create a widget for the Parameter name, configured according
        to widget_options, and pack()ed according to the pack_options.

        Pop-up help is automatically set from the Parameter's doc.

        The widget and label (if appropriate) are enlosed in a Frame
        so they can be manipulated as a single unit (see the class
        docstring). The representation
        (frame,widget,label,pack_options) is returned (as well as
        being stored in the representations dictionary).


        * parent is an existing widget that is to be the parent
        (master) of the widget created for this paramter. If no parent
        is specified, defaults to the originally supplied master
        (i.e. that set during __init__).

        * on_set is an optional function to call whenever the
        Parameter's corresponding Tkinter Variable's trace_variable
        indicates that it has been set (this does not necessarily mean
        that the widget's value has changed). When the widget is created,
        the on_set method will be called (because the creation of the
        widget triggers a set in Tkinter).

        * on_modify is an optional function to call whenever the
        corresponding Tkinter Variable is actually changed.


        widget_options specified here override anything that might have been
        set elsewhere (e.g. Button's size can be overridden here
        if required).



        Examples of use:
        pack_param(name)
        pack_param(name,side='left')
        pack_param(name,parent=frame3,on_set=some_func)
        pack_param(name,widget_options={'width':50},side='top',expand='yes',fill='y')
        """
        frame = T.Frame(parent or self.master)

        widget,label = self._create_widget(name,frame,widget_options,on_set,on_modify)

        # checkbuttons are 'widget label' rather than 'label widget'
        if widget.__class__ is T.Checkbutton:  # type(widget) doesn't seem to work
            widget_side='left'; label_side='right'
        else:
            label_side='left'; widget_side='right'

        if label: label.pack(side=label_side) # label can be None (e.g. for Button)
        widget.pack(side=widget_side,expand='yes',fill='x')

        representation = {"frame":frame,"widget":widget,
                          "label":label,"pack_options":pack_options,
                          "on_set":on_set,"on_modify":on_modify,
                          "widget_options":widget_options}
        self.representations[name] = representation

        # If there's a label, balloon's bound to it - otherwise, bound
        # to enclosing frame.
        # (E.g. when there's [label] [text_box], only want balloon for
        # label (because maybe more help will be present for what's in
        # the box) but when there's [button], want popup help over the
        # button.)
        param_obj = self.get_parameter_object(name)
        help_text = getdoc(param_obj)

        if param_obj.default is not None:
            # some params appear to have no docs!!!
            if help_text is not None:
                help_text+="\n\nDefault: %s"%self._object2string(name,param_obj.default,replace=False)

        self.balloon.bind(label or frame,help_text)

        frame.pack(pack_options)

        self._indicate_tkvar_status(name)

        self._post_add_param(name)
        return representation


    def hide_param(self,name):
        """Hide the representation of Parameter 'name'."""
        if name in self.representations:
            self.representations[name]['frame'].pack_forget()
        # CEBNOTE: forgetting label and widget rather than frame would
        # just hide while still occupying space (i.e. the empty frame
        # stays in place, and so widgets could later be inserted at
        # exact same place)
        #self.representations[name]['label'].pack_forget()
        #self.representations[name]['widget'].pack_forget()
        # unhide_param would need modifying too


    def unhide_param(self,name,new_pack_options={}):
        """
        Un-hide the representation of Parameter 'name'.

        Any new pack options supplied overwrite the originally
        supplied ones, but the parent of the widget remains the same.
        """
        # CEBNOTE: new_pack_options not really tested. Are they useful anyway?
        if name in self.representations:
            pack_options = self.representations[name]['pack_options']
            pack_options.update(new_pack_options)
            self.representations[name]['frame'].pack(pack_options)


    def unpack_param(self,name):
        """
        Destroy the representation of Parameter 'name'.

        (unpack and then pack a param if you want to put it in a different
        frame; otherwise simply use hide and unhide)
        """
        f = self.representations[name]['frame']
        w = self.representations[name]['widget']
        l = self.representations[name]['label']

        del self.representations[name]

        for x in [f,w,l]:
            x.destroy()


    def repack_param(self,name):

        f = self.representations[name]['frame']
        w = self.representations[name]['widget']
        l = self.representations[name]['label']
        o = self.representations[name]['pack_options']
        on_set = self.representations[name]['on_set']
        on_modify = self.representations[name]['on_modify']

        w.destroy(); l.destroy()

        param_obj,PO = self.get_parameter_object(name,with_source=True)
        self._create_tkvar(PO,name,param_obj)

        self.pack_param(name,f,on_set=on_set,on_modify=on_modify,**o)


    def _switch_dynamic(self,name=None,dynamic=False):

        param_name = name or self._right_click_param
        param,po = self.get_parameter_object(param_name,with_source=True)
        if not hasattr(param,'_value_is_dynamic'):
            return

        if param_name in self.allow_dynamic:
            self.allow_dynamic.remove(param_name)
        else:
            self.allow_dynamic.append(param_name)

        self.repack_param(param_name)

    def _default_param(self,name=None,default=False):

        parameter_name = name or self._right_click_param
        param,po = self.get_parameter_object(parameter_name,with_source=True)

        if parameter_name in self.allow_default:
            self.allow_default.remove(parameter_name)
        else:
            self.allow_default.append(parameter_name)

        assert isinstance(self._extraPO,Parameterized)
        defaults = self._extraPO.defaults()

        self.gui_set_param(parameter_name,defaults[parameter_name])
        w = self.representations[parameter_name]['widget']
        if hasattr(w,'tag_set'):w.tag_set()


################################################################################
#
################################################################################





################################################################################
# WIDGET CREATION
################################################################################

    def _create_widget(self,name,master,widget_options={},on_set=None,on_modify=None):
        """
        Return widget,label for parameter 'name', each having the master supplied

        The appropriate widget creation method is found from the
        widget_creators dictionary; see individual widget creation
        methods for details to each type of widget.
        """
        # select the appropriate widget-creation method;
        # default is self._create_string_widget...
        widget_creation_fn = self._create_string_widget

        param_obj,source_po = self.get_parameter_object(name,with_source=True)

        if not (param_is_dynamically_generated(param_obj,source_po) or name in self.allow_dynamic):
            # ...but overwrite that with a more specific one, if possible
            for c in classlist(type(param_obj))[::-1]:
                if self.widget_creators.has_key(c):
                    widget_creation_fn = self.widget_creators[c]
                    break
        elif name not in self.allow_dynamic:
            self.allow_dynamic.append(name)

        if on_set is not None:
            self._tkvars[name]._on_set=on_set

        if on_modify is not None:
            self._tkvars[name]._on_modify=on_modify

        widget=widget_creation_fn(master,name,widget_options)

        # Is widget a button (but not a checkbutton)? If so, no label wanted.
        # CEBALERT 'notNonelabel': change to have a label with no text
        if is_button(widget):
            label = None
        else:
            label = T.Label(master,text=self._pretty_print(name))

        # disable widgets for constant params
        if param_obj.constant and isinstance(source_po,Parameterized):
            # (need to be able to set on class, hence check it's PO not POMetaclass
            widget.config(state='disabled')

        widget.bind('<<right-click>>',lambda event: self._right_click(event, widget))

        return widget,label


    def _create_button_widget(self,frame,name,widget_options):
        """
        Return a FocusTakingButton to represent Parameter 'name'.

        Buttons require a command, which should have been specified as the
        'on_set' function passed to pack_param().

        After creating a button for a Parameter param, self.param() also
        executes the button's command.

        If the Button was declared with an image, the button will
        have that image (and no text); otherwise, the button will display
        the (possibly pretty_print()ed) name of the Parameter.
        """
        try:
            command = self._tkvars[name]._on_set
        except AttributeError:
            raise TypeError("No command given for '%s' button."%name)

        del self._tkvars[name]._on_set # because we use Button's command instead

        # Calling the parameter (e.g. self.Apply()) is like pressing the button:
        self.__dict__["_%s_param_value"%name]=command
        # like setattr(self,name,command) but without tracing etc

        # (...CEBNOTE: and so you can't edit a tkparameterizedobject
        # w/buttons with another tkparameterizedobject because the
        # button parameters all skip translation etc. Instead should
        # handle their translation. But we're not offering a GUI
        # builder so it doesn't matter.)

        button = FocusTakingButton(frame,command=command)

        button_param = self.get_parameter_object(name)

        image = button_param.get_image()
        if image:
            button['image']=image
            #button['relief']='flat'
        else:
            button['text']=self._pretty_print(name)


        # and set size from Button
        #if size_param.size:
        #    button['width']=size[0]
        #    button['height']=size[1]

        button.config(**widget_options) # widget_options override things from parameter
        return button


    def _create_fileselector_widget(self,frame,name,widget_options):
        widget = HackFileEntry(frame,self._tkvars[name])
        return widget


    def update_selector(self,name):

        if name in self.representations:
            widget_options = self.representations[name]["widget_options"]

            new_range,widget_options = self._X(name,widget_options)

            w = self.representations[name]['widget']
            # hACK: tuple to work around strange list parsing tkinter/tcl
            w.configure(values=tuple(new_range)) # what a mess
            #w.configure(state='readonly') # CEBALERT: why necessary?
            # does it get changed somewhere else by mistake? e.g. does
            # plotgrouppanel switch disabled to normal and normal to
            # disabled without checking that normal isn't readonly?


    def _X(self,name,widget_options):

        # CEBALERT: need to document how & why people should use 'sort_fn_args'
        # and 'new_default' when calling pack_param(). Also, simplify it if
        # possible.
        self.translators[name].update()

        new_range = self.translators[name].cache.keys()

        if 'sort_fn_args' not in widget_options:
            # no sort specified: defaults to sort()
            new_range.sort()
        else:
            sort_fn_args = widget_options['sort_fn_args']
            del widget_options['sort_fn_args']
            if sort_fn_args is not None:
                new_range = keys_sorted_by_value_unique_None_safe(self.translators[name].cache,**sort_fn_args)

        if len(new_range)<1:
            new_range=[None]

        tkvar = self._tkvars[name]


        if 'new_default' in widget_options:
            if widget_options['new_default']:
                current_value = new_range[0]
            del widget_options['new_default']
        else:
            current_value = self._object2string(name,self.get_parameter_value(name))
            if current_value not in new_range:
                current_value = new_range[0] # whatever was there is out of date now

        tkvar.set(current_value)

        return new_range,widget_options


    def _create_selector_widget(self,frame,name,widget_options):
        """
        Return a Tkinter.OptionMenu to represent Parameter 'name'.

        In addition to Tkinter.OptionMenu's usual options, the
        following additional ones may be included in widget_options:

        * sort_fn_args: if widget_options includes 'sort_fn_args',
          these are passed to the sort() method of the list of
          *objects* available for the parameter, and the names are
          displayed sorted in that order.  If 'sort_fn_args' is not
          present, the default is to sort the list of names using its
          sort() method.

        * new_default: if widget_options includes 'new_default':True,
          the currently selected value for the widget will be set
          to the first item in the (possibly sorted as above) range.
          Otherwise, the currently selected value will be left as the
          current value.
        """
        #param = self.get_parameter_object(name)
        #self._update_translator(name,param)

        ## sort the range for display
        # CEBALERT: extend OptionMenu so that it
        # (a) supports changing its option list (subject of a previous ALERT)
        # (b) supports sorting of its option list
        # (c) supports selecting a new default
        new_range,widget_options = self._X(name,widget_options)
        tkvar = self._tkvars[name]

        # Combobox looks bad with standard theme on my ubuntu
        # (and 'changed' marker - blue text - not visible).
        w = Combobox(frame,textvariable=tkvar,
                     values=new_range,state='readonly',
                     **widget_options)

        # Combobox (along with Checkbutton?) somehow sets its
        # associated textvariable without calling that textvariable's
        # set() method.  Therefore, to update the Selector's help text
        # when an item is selected, we bind to the
        # <<ComboboxSelected>> event.
        def _combobox_updated(event,name=name):
            w = self.representations[name]['widget']
            help_text =  getdoc(
                self._string2object(
                    name,
                    self._tkvars[name]._original_get()))

            self.balloon.bind(w,help_text)

        w.bind("<<ComboboxSelected>>",_combobox_updated)

        help_text = getdoc(self._string2object(name,tkvar._original_get()))
        self.balloon.bind(w,help_text)
        return w

    def _list_edit(self,param_name):
        val = self.get_parameter_value(param_name)

        param_obj = self.get_parameter_object(param_name)
        class_=None
        if hasattr(param_obj,'class_') and hasattr(param_obj.class_,'get_range'):
            # i.e. we can represent with ClassSelector
            class_ = param_obj.class_

        parameterized_instance = list_to_parameterized(val,class_)

        parameter_window = AppWindow(self)
        #parameter_window.title(PO_to_edit.name+' parameters')

        parameter_frame = EditingParametersFrameWithApply(parameter_window,parameterized_object=parameterized_instance,show_labels=False,on_close=lambda:self._handle_gui_set(param_name))

        # CEBALERT: more dynamic class changes to make it impossible
        # to follow what is happening...ParametersFrame needs some
        # reorganization...
        def _forgotten_why(itself):
            ### refresh()
            po_val = self.get_parameter_value(param_name)
            po_stringrep = self._object2string(param_name,po_val)
            self._tkvars[param_name]._original_set(po_stringrep)
            ###

        if _forgotten_why not in parameter_frame._apply_hooks:
            parameter_frame._apply_hooks.append(_forgotten_why)


        parameter_frame.pack()


    def _create_list_widget(self,frame,name,widget_options):
        #param = self.get_parameter_object(name)
        #value = self.get_parameter_value(name)

        X = lambda event=None,x=name: self._list_edit(x)

        w = ListWidget(frame,variable=self._tkvars[name],
                       cmd=X,**widget_options)
        #w = ListWidget(frame,variable=tkvar,**widget_options)


        #help_text = getdoc(self._string2object(name,tkvar._original_get()))
        #self.balloon.bind(w,help_text)
        return w



    def _create_number_widget(self,frame,name,widget_options):
        """
        Return a TaggedSlider to represent parameter 'name'.

        The slider's bounds are set to those of the Parameter.
        """
        w = TaggedSlider(frame,variable=self._tkvars[name],**widget_options)
        param = self.get_parameter_object(name)

        lower_bound,upper_bound = param.get_soft_bounds()

        if upper_bound is not None and lower_bound is not None:
            # TaggedSlider needs BOTH bounds (neither can be None)
            w.set_bounds(lower_bound,upper_bound,inclusive_bounds=param.inclusive_bounds)


        # have to do the lookup because subclass might override default
        if not lookup_by_class(self.param_immediately_apply_change,type(param)):
            w.bind('<<TagReturn>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            w.bind('<<TagFocusOut>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            w.bind('<<SliderSet>>', lambda e=None,x=name: self._handle_gui_set(x,force=True))

        return w


    def _create_boolean_widget(self,frame,name,widget_options):
        """Return a Tkinter.Checkbutton to represent parameter 'name'."""
        # CB: might be necessary to pass actions to command option of Checkbutton;
        # could be cause of test pattern boolean not working?
        return T.Checkbutton(frame,variable=self._tkvars[name],**widget_options)


    def _create_string_widget(self,frame,name,widget_options):
        """Return a Tkinter.Entry to represent parameter 'name'."""
        widget = T.Entry(frame,textvariable=self._tkvars[name],**widget_options)
        param = self.get_parameter_object(name)
        if not lookup_by_class(self.param_immediately_apply_change,type(param)):
            widget.bind('<Return>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
            widget.bind('<FocusOut>', lambda e=None,x=name: self._handle_gui_set(x,force=True))
        return widget

################################################################################
# End WIDGET CREATION
################################################################################



    # CB: the colors etc for indication are only temporary
    def _indicate_tkvar_status(self,param_name,status=None):
        """
        Set the parameter's label to:
         - blue if the GUI value differs from that set on the object
         - red if the text doesn't translate to a correct value
         - black if the GUI and object have the same value
        """
        if self.translators[param_name].last_string2object_failed:
            status = 'error'

        self._set_widget_status(param_name,status)


    # this scheme is incompatible with tile
    # (tile having the right idea about how to do this kind of thing!)
    def _set_widget_status(self,param_name,status):

        if param_name in self.representations:
            if 'widget' in self.representations[param_name]:

                widget = self.representations[param_name]['widget']
                states = {'error'   : 'red',
                          'changed' : 'blue',
                          None      : 'black'}

                if is_button(widget):
                    # can't change state of button
                    return

                try:
                    widget.config(foreground=states[status])
                except T.TclError:  #CEBALERT uh-oh
                    pass

    def _pretty_print(self,s):
        """
        Convert a Parameter name s to a string suitable for display,
        if pretty_parameters is True.
        """
        if not self.pretty_parameters:
            return s
        else:
            n = s.replace("_"," ")
            n = n.capitalize()
            return n


# CEBALERT: is there no "file entry" widget for Tkinter?  I see there
# is one for Tix, but while that is listed as being in Python's
# standard library, it's rarely actually included...
import tkFileDialog
class HackFileEntry(T.Frame):
    def __init__(self,master,var,**config):
        T.Frame.__init__(self,master)
        self.var = var
        self.button = T.Button(self,textvariable=self.var,command=self.cmd)
        self.button.pack()

    def cmd(self):
        filename = tkFileDialog.askopenfilename()
        if filename!="":
            self.var.set(filename)


######################################################################
######################################################################

class Translator(object):
    """
    Abstract class that

    Translators handle translation between objects and their
    string representations in the GUI.

        last_string2object_failed is a flag that can be set to indicate that
        the last string-to-object translation failed.
        (TkParameterized checks this attribute for indicating errors to
        the user.)

    """
    last_string2object_failed = False # CEBALERT: why class attr?

    def __init__(self,param,initial_value=None):
        self.param = param
        self.msg_handler = None

    def string2object(self,string_):
        raise NotImplementedError

    def object2string(self,object_):
        raise NotImplementedError

    def _pass_out_msg(self,msg):
        if self.msg_handler:
            self.msg_handler.dynamicinfo(msg)

    def __copy__(self):
        """Copy only translator-specific state."""
        copy = self.__class__(self.param)
        copy.last_string2object_failed = self.last_string2object_failed
        copy.msg_handler = self.msg_handler
        return copy


class DoNothingTranslator(Translator):
    """
    Performs no translation. For use with Parameter types such as
    Boolean and String, where the representation
    of the object in the GUI is the object itself.
    """
    def string2object(self,string_):
        return string_

    def object2string(self,object_):
        return object_


class BoolTranslator(DoNothingTranslator):
    def string2object(self,string_):
        if string_=='None':
            return None
        else:
            return bool(string_)


# Error messages: need to change how they're reported
from param.parameterized import script_repr
class ListTranslator(Translator):

    def __init__(self,param,initial_value=None):
        super(ListTranslator,self).__init__(param)
        self.list_=initial_value

    #JABALERT: Shouldn't this be building a new list out of the string?
    def string2object(self,string_):
        return self.list_

    def object2string(self,object_):
        self.list_=object_
        return script_repr(self.list_,[],"",[]).replace("\n","")


class Eval_ReprTranslator(Translator):
    """
    Translates a string to an object by eval()ing the string in
    __main__.__dict__ (i.e. as if the string were typed at the
    commandline), and translates an object to a string by
    repr(object).
    """

    last_object = None
    last_string = None

    def __init__(self,param,initial_value=None):
        super(Eval_ReprTranslator,self).__init__(param,initial_value)
        self.last_string = self.object2string(initial_value)
        self.last_object = initial_value

    # the whole last_string deal is required because of execing in main
    def string2object(self,string_):
        if string_!=self.last_string:
            try:
                self.last_object = eval(string_,__main__.__dict__)
                self.last_string = string_
                self._pass_out_msg(" ")
                #print "OUT:"
            except Exception,inst:
                #print "OUT:", str(sys.exc_info()[0])[11::]+" ("+str(inst)+")"
                m = str(inst) # CEBALERT: clean up
                self._pass_out_msg(m)
                self.last_string2object_failed=True
                return string_ # HIDDEN

        self.last_string2object_failed=False
        return self.last_object


    def object2string(self,object_):
        if object_==self.last_object:
            return self.last_string
        else:
            repr=script_repr(object_,[],"",[])
            string_=repr.replace("\n","") if repr is not None else "None"
            self.last_object = object_
            self.last_string = string_
            return string_

    def __copy__(self):
        n = super(Eval_ReprTranslator,self).__copy__()
        n.last_string = self.last_string
        n.last_object = self.last_object
        return n


# CEBALERT: I hacked on support for non-hashable types like array (now
# have cache as {string: id(object)} and _cache_objects as
# {id(object): object}). The whole concept of translators needs
# revisiting anyway.
class String_ObjectTranslator(Translator):

    cache = property(lambda self: dict([(nam,self._cache_objects[objid])
                                        for nam,objid in self._cache.items()]))

    _cache = {}
    _cache_objects = {}

    def __init__(self,param,initial_value=None):
        super(String_ObjectTranslator,self).__init__(param,initial_value)
        self._cache = {}
        self._cache_objects = {}
        self.update()

    def string2object(self,string_):
        if string_ in self._cache:
            return self._cache_objects[self._cache[string_]]
        else:
            return string_

    def object2string(self,object_):
        inverse_cache = inverse(self._cache)
        if id(object_) in inverse_cache:
            return inverse_cache[id(object_)]
        else:
            return object_


    def update(self):
        for nam,obj in self.param.get_range().items():
            self._cache[nam] = id(obj)
            self._cache_objects[id(obj)] = obj


    def __copy__(self):
        n = super(String_ObjectTranslator,self).__copy__()
        n._cache = self._cache
        n._cache_objects = self._cache_objects
        return n



class CSPTranslator(String_ObjectTranslator):

    def string2object(self,string_):
        obj = super(CSPTranslator,self).string2object(string_)
        ## instantiate if it's just a class
        if isinstance(obj,type) and isinstance(string_,str):
            obj = obj()
            self._cache[string_]=id(obj)
            self._cache_objects[id(obj)]=obj

        return obj

    def object2string(self,object_):
        ## replace class if we already have object
        for name,objid in self._cache.items():
            obj = self._cache_objects[objid]
            if type(object_) is obj or type(object_) is type(obj):
                self._cache[name]=id(object_)
                self._cache_objects[id(object_)]= object_
        ##
        return super(CSPTranslator,self).object2string(object_)


    def __copy__(self):
        # because this one's object2string can modify cache
        n = Translator.__copy__(self)
        n._cache = copy.copy(self._cache)
        n._cache_objects = copy.copy(self._cache_objects)
        return n


def param_is_dynamically_generated(param,po):

    if not hasattr(param,'_value_is_dynamic'):
        return False

    if isinstance(po,Parameterized):
        return param._value_is_dynamic(po)
    elif isinstance(po,ParameterizedMetaclass):
        return param._value_is_dynamic(None)
    else:
        raise ValueError("po must be a Parameterized or ParameterizedMetaclass.")


class ParametersFrame(TkParameterized,T.Frame):
    """
    Displays and allows instantaneous editing of the Parameters
    of a supplied Parameterized.

    Changes made to Parameter representations on the GUI are
    immediately applied to the underlying object.
    """
    Defaults = Button(doc="""Return values to class defaults.""")

    Refresh = Button(doc="Return values to those currently set on the object (or, if editing a class, to those currently set on the class).")

    # CEBALERT: this is a Frame, so close isn't likely to make
    # sense. But fortunately the close button acts on master.
    # Just be sure not to use this button when you don't want
    # the master window to vanish (e.g. in the model editor).
    Close = Button(doc="Close the window. (If applicable, asks if unsaved changes should be saved).")

    display_threshold = Number(default=0,precedence=-10,doc="Parameters with precedence below this value are not displayed.")

    show_labels = Boolean(default=True)

    def __init__(self,master,parameterized_object=None,on_set=None,
                 on_modify=None,msg_handler=None,on_close=None,**params):
        """
        Create a ParametersFrame with the specifed master, and
        representing the Parameters of parameterized_object.

        on_set is an optional function to call whenever any of the
        GUI variables representing Parameter values is set() by the
        GUI (i.e. by the user). Since a variable's value is not
        necessarily changed by such a set(), on_modify is another
        optional function to call only when a GUI variable's value
        actually changes. (See TkParameterized for more detail.)
        """
        T.Frame.__init__(self,master,borderwidth=1,relief='raised')

        self.on_set = on_set
        self.on_modify = on_modify

        TkParameterized.__init__(self,master,
                                 extraPO=parameterized_object,
                                 self_first=False,
                                 msg_handler=msg_handler,
                                 **params)

        self.on_set = on_set
        self.on_modify = on_modify
        self.on_close = on_close

        ## Frame for the Parameters
        self._params_frame = T.Frame(self)
        self._params_frame.pack(side='top',expand='yes',fill='both')

        self.params_to_display = {}
        self.currently_displayed_params = {}

        if parameterized_object:
            self.set_PO(parameterized_object)

        self._create_button_panel()


        # CEBALERT: just because callers assume this pack()s itself.
        # Really it should be left to callers i.e. this should be removed.
        self.pack(expand='yes',fill='both')


    def hidden_param(self,name):
        """Return True if a parameter's precedence is below the display threshold."""
        # interpret a precedence of None as 0
        precedence = self.get_parameter_object(name).precedence or 0
        return precedence<self.display_threshold


    def _create_button_panel(self):
        """
        Add the buttons in their own panel (frame).
        """
        ## Buttons
        #
        # Our button order (when all buttons present):
        # [Defaults] [Refresh] [Apply] [Close]
        #
        # Our button - Windows
        # Close(yes) - OK
        # Close(no ) - Cancel
        # [X]        - Cancel
        # Apply      - Apply
        # Defaults   -
        # Refresh    - Reset
        #
        # I think Windows users will head for the window's [X]
        # when they want to close and cancel their changes,
        # because they won't know if [Close] saves their changes
        # or not (until they press it, and find that it asks).
        #
        #
        # Some links that discuss and address what order to use for buttons:
        #
        # http://java.sun.com/products/jlf/ed2/book/HIG.Dialogs2.html
        # http://developer.kde.org/documentation/books/kde-2.0-development/ch08lev1sec6.html
        # http://developer.kde.org/documentation/standards/kde/style/dialogs/index.html
        # http://doc.trolltech.com/qq/qq19-buttons.html


        # Catch click on the [X]: like clicking [Close]
        # CEBALERT: but what if this frame isn't in its own window!
        try:
            self.master.protocol("WM_DELETE_WINDOW",self._close_button)
        except AttributeError:
            pass

        buttons_frame = T.Frame(self,borderwidth=1,relief='sunken')
        self.buttons_frame = buttons_frame
        buttons_frame.pack(side="bottom",expand="no")

        self._buttons_frame_left = T.Frame(buttons_frame)
        self._buttons_frame_left.pack(side='left',expand='yes',fill='x')

        self._buttons_frame_right = T.Frame(buttons_frame)
        self._buttons_frame_right.pack(side='right',expand='yes',fill='x')

        self.pack_param('Defaults',parent=self._buttons_frame_left,
                        on_set=self._defaults_button,side='left')
        self.pack_param('Refresh',parent=self._buttons_frame_left,
                        on_set=self._refresh_button,side='left')
        self.pack_param('Close',parent=self._buttons_frame_right,
                        on_set=self._close_button,side='right')


    def _refresh_button(self):
        """See Refresh parameter."""
        for name in self.params_to_display.keys():
            self._tkvars[name].get()


    def _defaults_button(self):
        """See Defaults parameter."""
        assert isinstance(self._extraPO,Parameterized)
        defaults = self._extraPO.defaults()

        for param_name,val in defaults.items():
            if not self.hidden_param(param_name):
                self.gui_set_param(param_name,val)
                # tag_set() must be called in order to visually update the slider
                w = self.representations[param_name]['widget']
                if hasattr(w,'tag_set'):w.tag_set()


        # CEBALERT: why doesn't this first check that something has actually changed?
        if self.on_modify:
            self.on_modify()


        if self.on_set:
            self.on_set()

        self.update_idletasks()


    def _close_button(self):
        """See Close parameter."""
        if self.on_close:
            self.on_close()

        T.Frame.destroy(self) # hmm
        self.master.destroy()


    def set_PO(self,parameterized_object):
        self.change_PO(parameterized_object)

        title = "Parameters of "+ (parameterized_object.name or str(parameterized_object)) # (name for class is None)

        try:
            self.master.title(title)
        except AttributeError:
            # can only set window title on a window (model editor puts frame in another frame)
            pass

        self.__dict__['_name_param_value'] = title


        ### Pack all of the non-hidden Parameters
        self.params_to_display = {}
        for n,p in parameterized_object.params().items():
            if not self.hidden_param(n):
                self.params_to_display[n]=p

        self.pack_params_to_display()

        # hide Defaults button for classes
        if isinstance(parameterized_object,type):
            self.hide_param('Defaults')
        else:
            self.unhide_param('Defaults')


    def _wipe_currently_displayed_params(self):
        """Wipe old labels and widgets from screen."""
        for rep in self.currently_displayed_params.values():
            for w in rep:
                try:
                    rep[w].destroy()
                except: # e.g. buttons have None for label ('notNonelabel')
                    pass


    def _grid_param(self,parameter_name,row):
        widget = self.representations[parameter_name]['widget']
        label = self.representations[parameter_name]['label']

        # CB: (I know about the code duplication here & in tkpo)
        param_obj = self.get_parameter_object(parameter_name)
        help_text = getdoc(param_obj)

        if param_obj.default is not None:
            # some params appear to have no docs!!!
            if help_text is not None:
                help_text+="\n\nDefault: %s"%self._object2string(parameter_name,param_obj.default,replace=False)

        if self.show_labels:
            label.grid(row=row,column=0,
                       padx=2,pady=2,sticky=T.E)

            self.balloon.bind(label, help_text)

        # We want widgets to stretch to both sides...
        posn=T.E+T.W
        # ...except Checkbuttons, which should be left-aligned.
        if widget.__class__==T.Checkbutton:
            posn=T.W

        widget.grid(row=row,
                    column=1,
                    padx=2,
                    pady=2,
                    sticky=posn)

        # widgets expand to fill frame
        widget.master.grid_columnconfigure(1,weight=2)

        self._post_add_param(parameter_name)




    def _make_representation(self,name):
        if name in self.representations:
            for n,w in self.representations[name].items():
                try:
                    w.destroy()
                except: #e.g. buttons have None for label ('notNonelabel')
                    pass
                del self.representations[name][n]

        widget,label = self._create_widget(name,self._params_frame,
                                           on_set=self.on_set,
                                           on_modify=self.on_modify)

        label.bind("<Double-Button-1>",lambda event=None,x=name: self.switch_dynamic(x))

        self.representations[name]={'widget':widget,'label':label}
        self._indicate_tkvar_status(name)



    def pack_params_to_display(self):
        self._wipe_currently_displayed_params()

        ### sort Parameters by reverse precedence
        parameter_precedences = {}
        for name,parameter in self.params_to_display.items():
            parameter_precedences[name]=parameter.precedence
        sorted_parameter_names = keys_sorted_by_value(parameter_precedences)

        ### create the labels & widgets
        for name in self.params_to_display:
            self._make_representation(name)

        ### add widgets & labels to screen in a grid
        rows = range(len(sorted_parameter_names))
        for row,parameter_name in zip(rows,sorted_parameter_names):
            self._grid_param(parameter_name,row)

        self.currently_displayed_params = dict([(param_name,self.representations[param_name])
                                          for param_name in self.params_to_display])
        #self.event_generate("<<SizeRight>>")


    # CEBALERT: rename
    def _edit_PO_in_currently_selected_widget(self):
        """
        Open a new window containing a ParametersFrame (actually, a
        type(self)) for the PO in _currently_selected_widget.
        """
        # CEBALERT: simplify this lookup by value
        param_name = None
        for name,representation in self.representations.items():
            if self._currently_selected_widget is representation['widget']:
                param_name=name
                break

        # CEBALERT: should have used get_parameter_value(param_name)?
        PO_to_edit = self._string2object(param_name,self._tkvars[param_name].get())

        parameter_window = AppWindow(self)
        parameter_window.title(PO_to_edit.name+' parameters')

        ### CEBALERT: confusing? ###
        title=T.Label(parameter_window, text="("+param_name + " of " + (self._extraPO.name or 'class '+self._extraPO.__name__) + ")")
        title.pack(side = "top")
        self.balloon.bind(title,getdoc(self.get_parameter_object(param_name,self._extraPO)))
        ############################

        # CEBALERT: don't want EditingParametersFrameWithApply
        t = type(self)
        if t.__name__=='EditingParametersFrameWithApply':
            t = ParametersFrameWithApply

        parameter_frame = t(parameter_window,parameterized_object=PO_to_edit,msg_handler=self.msg_handler,on_set=self.on_set,on_modify=self.on_modify)
        parameter_frame.pack()

        # CEBALERT: need to get the list item to update in parent if editing via a right click
        # properties frame. need to get a new representation into translator.
        #parameter_frame._apply_hooks.append(lambda *args:self._refresh_value(param_name))


        # CEBALERT: need to sort out all this stuff in the tkpo/pf
        # hierarchy

##     def unpack_param(self,param_name):
##     def hide_param(self,param_name):
##     def unhide_param(self,param_name):

    def repack_param(self,param_name):

        self._refresh_value(param_name)

        r = self.representations[param_name]
        widget,label = r['widget'],r['label']
        row = int(widget.grid_info()['row'])

        widget.destroy()
        label.destroy()

        param = self.get_parameter_object(param_name)

        self._create_translator(param_name,param)
        self._make_representation(param_name)
        self._grid_param(param_name,row)

    def _refresh_value(self,param_name):
        pass



class ParametersFrameWithApply(ParametersFrame):
    """
    Displays and allows editing of the Parameters of a supplied
    Parameterized.

    Changes made to Parameter representations in the GUI are not
    applied to the underlying object until Apply is pressed.
    """

    # CEBENHANCEMENT: might be nice to make Apply button blue like the
    # unapplied changes, but can't currently set button color.
    Apply = Button(doc="""Set object's Parameters to displayed values.\n
                          When editing a class, sets the class defaults
                          (i.e. acts on the class object).""")

    def __init__(self,master,parameterized_object=None,
                 on_set=None,on_modify=None,**params):
        self._apply_hooks=[]
        super(ParametersFrameWithApply,self).__init__(master,
                                                      parameterized_object,
                                                      on_set,on_modify,
                                                      **params)
        self._live_update = False

        ### CEBALERT: describe why this apply is different from Apply
        for p in self.param_immediately_apply_change:
            self.param_immediately_apply_change[p]=True


        self.pack_param('Apply',parent=self._buttons_frame_right,
                        on_set=self._apply_button,side='left')

        assert self.has_unapplied_change() is False, "ParametersFrame altered a value on opening. If possible, please file a bug report on the website describing what you were doing when you received this error."

        # CEBALERT: should use existing code
        self.representations['Apply']['widget']['state']='disabled'


    def _create_string_widget(self,frame,name,widget_options):
        # don't want immediate updates, so unbind update events
        w= super(ParametersFrameWithApply,self)._create_string_widget(frame,name,widget_options)
        w.unbind('<Return>')
        w.unbind('<FocusOut>')
        return w


    def set_PO(self,parameterized_object):
        super(ParametersFrameWithApply,self).set_PO(parameterized_object)

        # (don't want to update parameters immediately)
        for v in self._tkvars.values():
            v._checking_get = v.get
            v.get = v._original_get


    def has_unapplied_change(self):
        """Return True if any one of the packed parameters' displayed values is different from
        that on the object."""
        for name in self.params_to_display.keys():
            if self._tkvar_changed(name):
                return True
        return False

    def _indicate_tkvar_status(self,param_name,status=None):
        if self._tkvar_changed(param_name):
            status = 'changed'

        super(ParametersFrameWithApply,self)._indicate_tkvar_status(param_name,status)


    def _handle_gui_set(self,p_name,force=False):
        #print "ParametersFrame._handle_gui_set",p_name
        TkParameterized._handle_gui_set(self,p_name,force)

        if 'Apply' in self.representations:
            w=self.representations['Apply']['widget']
            if self.has_unapplied_change():
                state='normal'
            else:
                state='disable'

            w.config(state=state)

    def _close_button(self):
        # CEBERRORALERT: dialog box should include a cancel button
        # Also, changes are *not* applied if one of the boxes is in
        # error.
        if self.has_unapplied_change() \
               and askyesno("Close","Apply changes before closing?"):
            self._apply_button()
        super(ParametersFrameWithApply,self)._close_button()


    def update_parameters(self):
        if isinstance(self._extraPO,type):
            for name in self.params_to_display.keys():
                #if self._tkvar_changed(name):
                self._update_param_from_tkvar(name)
        else:
            for name,param in self.params_to_display.items():
                if not param.constant:  #and self._tkvar_changed(name):
                    self._update_param_from_tkvar(name)


    def _apply_button(self):
        self.update_parameters()
        self._refresh_button(overwrite_error=False)
        # CEBALERT: because overriding a method that\s already been
        # installed as a callback does nothing
        for h in self._apply_hooks:
            h(self)

        # CEBALERT: how does apply button's status change now? Doesn't
        # work properly for list editing; stays grey until clicked twice.

    def _refresh_value(self,param_name):
        po_val = self.get_parameter_value(param_name)
        po_stringrep = self._object2string(param_name,po_val)
        self._tkvars[param_name]._original_set(po_stringrep)


    def _refresh_button(self,overwrite_error=True):
        for name in self.params_to_display.keys():
            if self.translators[name].last_string2object_failed and not overwrite_error:
                pass
            else:
                self._refresh_value(name)
                #print self._tkvars[name]._checking_get()
                # CEBALERT: taggedsliders need to have tag_set() called to update slider
                w = self.representations[name]['widget']
                if hasattr(w,'tag_set'):w.tag_set()

# CB: can override tracefn so that Apply/Refresh buttons are enabled/disabled as appropriate

def edit_parameters(parameterized,with_apply=True,**params):
    """
    Edit the Parameters of a supplied parameterized instance or class.

    Specify with_apply=False for a ParametersFrame (which immediately
    updates the object - no need to press the Apply button).

    Extra params are passed to the ParametersFrame constructor.
    """
    if not with_apply:
        pf_class = ParametersFrame
    else:
        pf_class = ParametersFrameWithApply

    return pf_class(T.Toplevel(),parameterized,**params)










# Barely wrapped tooltip from tklib.
# CB: this isn't the right way to do it, and it breaks menubar
# tips for some reason, but user code didn't have to change.

class Balloon(T.Widget):

    _tkname = '::tooltip::tooltip'

    def __init__(self,master,cnf={},**kw):
        master.tk.call("package","require","tooltip")
        T.Widget.__init__(self, master,self._tkname, cnf, kw)

    def bind(self,*args):
        """
        e.g. for a Button b and a Menu m with item 'Quit' in a T.Toplevel t

        balloon = Balloon(t)
        balloon.bind(b,'some guidance')
        balloon.bind(m,'Quit','more guidance')
        """
        if len(args)>2:
            self.tk.call(self._tkname,args[0]._w,'-index',args[1],args[2])
        else:
            self.tk.call(self._tkname,*args)

##     def tagbind(self,*args,**kw):
##         print "### Balloon.tagbind(): not yet implemented error ###"



# CEBALERT: should be renamed to something like IndexMenu, but then
# I am not sure if some of our tk hacks would work (e.g. in topoconsole,
# we set something for Menu on linux so that the popup behavior is
# reasonable).
class Menu(T.Menu):
    """
    Tkinter Menu, but with a way to access entries by name.

    Entries can be accessed via the label supplied when the entry is
    add()ed or insert()ed. For an entry whose label could change, you
    can supply 'indexname', which can then be used to access the entry
    no matter what the label might have become.
    """
    ## (Original Menu class is in lib/python2.4/lib-tk/Tkinter.py)

    def get_tkinter_index(self,index):
        """
        Return the Tkinter index, whether given an indexname or index
        (where index could be an int or Tkinter position e.g. 'end').
        """
        if isinstance(index,str):
            if index in self.indexname2index:
                i=self.indexname2index[index]
            else:
                # pass through tkinter to get 'end' etc converted to index
                i=self.index(index)
        else:
            i=index
        return i


    def get_indexname(self,index):
        """
        Return the indexname, whether given an indexname or index
        (where index could be an int or Tkinter position e.g. 'end').

        The returned value will be None if index refers to an unnamed
        entry.
        """
        if index in self.indexname2index:
            return index
        else:
            for name,i in self.indexname2index.items():
                if self.index(index)==i:
                    return name
        return None


    def __init__(self, master=None, cnf={}, **kw):
        # Creates two internal indexes to track names and commands
        self.indexname2index = {}  # rename to indexnames2index or similar
        self.named_commands = {}
        T.Menu.__init__(self,master,cnf,**kw)


    def __extract_indexname(self,cnf,kw):
        # indexname will be as specified by 'indexname' in cnf or kw,
        # or else as specified by 'label' in cnf or kw.
        #
        # indexname will be None if neither indexname nor label is
        # specified in cnf or kw.
        indexname=cnf.pop('indexname',kw.pop('indexname',None))
        if indexname is None:
            indexname = cnf.get('label',kw.get('label',None))
        return indexname

    def _update_indices(self,index,indexname,cnf,kw):
        if indexname is not None:
            self.indexname2index[indexname] = index
            # this pain is to keep the actual item, if it's a menu or a command, available to access
            self.named_commands[indexname] = cnf.get('menu',kw.get('menu',cnf.get('command',kw.get('command',None))))


    def add(self, itemType, cnf={}, **kw):
        """
        See Tkinter.Menu.add(), but 'indexname' can also be supplied.

        If supplied, indexname must be unique. If label is supplied without
        indexname, then label must be unique.
        """
        indexname = self.__extract_indexname(cnf,kw)
        assert indexname not in self.indexname2index
        T.Menu.add(self,itemType,cnf,**kw)
        self._update_indices(self.index("last") or 0,indexname,cnf,kw)


    def insert(self, index, itemType, cnf={}, **kw):
        """
        See Tkinter.Menu.insert(), but index can also be specified as text.
        """
        indexname = self.__extract_indexname(cnf,kw)
        assert indexname not in self.indexname2index

        # increase index of any item after insertion point
        for name,i in self.indexname2index.items():
            if i>=index:
                self.indexname2index[name]+=1

        T.Menu.insert(self,index,itemType,cnf,**kw)
        self._update_indices(index,indexname,cnf,kw)


    def __delete(self,index1):
        i1 = self.get_tkinter_index(index1)
        indexname1 = self.get_indexname(index1)

        assert (indexname1 in self.indexname2index or indexname1 is None)

        # decrease index of any item after deletion point
        for name,i in self.indexname2index.items():
            if i>i1:
                self.indexname2index[name]-=1

        self.named_commands.pop(indexname1,None)
        self.indexname2index.pop(indexname1,None)
        T.Menu.delete(self,i1,None)


    def delete(self, index1, index2=None):
        """
        If index2 is not specified, deletes the menu item at index1
        (an indexname or tk integer index).

        If index2 is specified, deletes menu items in
        range(index1,index2+1).
        """
        if index2 is not None:
            start = self.index(index1)
            end = self.index(index2)
            if start is not None and end is not None:
                for _ in range(start,end+1):
                    # __delete shifts the remaining items one position back,
                    # so repeatedly deleting the first item in the range will
                    # delete all of them
                    self.__delete(start)
        else:
            self.__delete(index1)



    ########## METHODS OVERRIDDEN FOR CONVENIENCE
    def entryconfigure(self, index, cnf=None, **kw):
        """Configure a menu item at INDEX."""
        i = self.get_tkinter_index(index)
        return T.Menu.entryconfigure(self,i,cnf,**kw)

    entryconfig = entryconfigure

    def invoke(self, index):
        """Invoke a menu item identified by INDEX and execute
        the associated command."""
        return T.Menu.invoke(self,self.get_tkinter_index(index))

    # other methods can be overriden if they're needed



class TaggedSlider(T.Frame):
    """
    Widget for manipulating a numeric value using either a slider or a
    text-entry box, keeping the two values in sync.

    Generates a number of Events:

    <<TagFocusOut>>  - tag loses focus
    <<TagReturn>>    - Return pressed in tag
    <<SliderSet>>    - slider is clicked/dragged
    """
    def __init__(self,master,variable,bounds=(0,1),inclusive_bounds=(True,True),
                 slider_length=100,tag_width=10,
                 tag_extra_config={},slider_extra_config={}):
        """
        On clicking or dragging the slider, the tag value is set
        to the slider's value.

        On pressing Return in or moving focus from the tag, the slider
        value is set, but also:

        * the range of the slider is adjusted (e.g. to fit a larger
          max value)

        * the resolution of the slider is adjusted based on the
          the value in the tag (e.g. 0.01 in the tag gives a
          resolution of 0.01), also taking into account the precision
          of the value in the tag (e.g. 0.0100 gives a resolution
          of 0.0001).
        """
        # CEBALERT: ...although respecting the precision isn't always
        # helpful because the slider can still have a limited
        # resolution anyway (from its length and the range, given the
        # size of a pixel...)
        T.Frame.__init__(self,master)

        self.variable= variable

        # CEBALERT: shouldn't this be calling set_slider_bounds()?
        self.bounds = bounds
        self.inclusive_bounds = inclusive_bounds

        self.tag = T.Entry(self,textvariable=self.variable,
                                 width=tag_width,**tag_extra_config)
        self.tag.pack(side='left')
        self.tag.bind('<Return>', self._tag_press_return)
        self.tag.bind('<FocusOut>', self._tag_focus_out)
        self.tag.bind('<Leave>', self._tag_focus_out)

        # no variable: we control the slider ourselves
        self.slider = T.Scale(self,variable=None,
                    from_=self.bounds[0],to=self.bounds[1],
                    orient='horizontal',length=slider_length,
                    showvalue=0,**slider_extra_config)

        self.slider.pack(side='right',expand="yes",fill='x')
        self.slider.bind('<ButtonRelease-1>', self._slider_used)
        self.slider.bind('<B1-Motion>', self._slider_used)

        self.tag_set()


    def tag_set(self):
        """
        After entering a value into the tag, this method should be
        called to set the slider correctly.

        (Called automatically for tag's <Return> and <FocusOut>
        events.)
        """
        # Set slider resolution. This is important because
        # we want the slider to be positioned at the exact
        # tag value.
        self._try_to_set_slider_resolution()
        self._try_to_set_slider()


    def set_slider_bounds(self,lower,upper,inclusive_bounds=None):
        """
        Set new lower and upper bounds for the slider.

        If specified, inclusive_bounds should be a sequence of length
        2 specifying True or False for the lower and upper bounds.
        """
        self.bounds = (lower,upper)

        if inclusive_bounds is not None:
            self.inclusive_bounds = inclusive_bounds

        epsilon = max(self.slider['resolution'],0.00000000001)

        if self.inclusive_bounds[0] is False:
            lower+=epsilon
        if self.inclusive_bounds[1] is False:
            upper-=epsilon
        self.slider.config(from_=lower,to=upper)

    set_bounds = set_slider_bounds


    # CB: why isn't this used for [] access? What should this
    # be called? Is it configure?
    def config(self,**options):
        """
        TaggedSlider is a compound widget. In most cases, config
        options should be passed to one of the component widgets
        (i.e. the tag or the slider). For some options, however,
        we need to handle them being set on the widget as a whole;
        further, some of these should be applied to both component
        widgets, but some should just be applied to one.

        Options handled:
        * state (applied to both)
        * background, foreground (applied to tag only)
        """
        if 'state' in options:
            self.tag['state']=options['state']
            self.slider['state']=options['state']
            del options['state']
        if 'background' in options:
            self.tag['background']=options['background']
            del options['background']
        if 'foreground' in options:
            self.tag['foreground']=options['foreground']
            del options['foreground']
        if len(options)>0:
            raise NotImplementedError(
                """Only state, background, and foreground options are
                currently supported for this widget; set options on
                either the component tag or slider instead.""")

        return {} # CEBALERT: need to return same object as Tkinter would.


    def get(self):
        """
        Calls the tag's get() method.

        Helps to match behavior of other Tkinter Widgets.
        """
        return self.tag.get()


    # CEBALERT: three different events for max. flexibility...but
    # often a user will just want to know if the value was set. Could
    # also generate a "<<TaggedSliderSet>>" event each time, which a
    # user could just look for. Or could these events all be children
    # of a <<TaggedSliderSet>>?
    def _slider_used(self,event=None):
        self.variable.set(self.slider.get())
        self.event_generate("<<SliderSet>>")


    def _tag_press_return(self,event=None):
        self.event_generate("<<TagReturn>>")
        self.tag_set()


    def _tag_focus_out(self,event=None):
        self.event_generate("<<TagFocusOut>>")
        self.tag_set()


    def _set_slider_resolution(self,value):
        # CEBALERT: how to find n. dp simply?
        p = decimal.Decimal(str(value)).as_tuple()[2]
        self.slider['resolution']=10**p


    def _try_to_set_slider_resolution(self):
        """
        Use the actual number in the box to set the slider's resolution,
        so that user-entered resolutions are respected (e.g. 0.1 vs 0.1000).

        If that's not possible (e.g. there's text in the box), use the
        value contained in the variable (because whatever owns the
        variable could have performed a conversion of the text -
        TkParameterized does this, for instance).

        Leaves the resolution as it was if no number is available.
        """
        try:
            # 1st choice is to get the actual number in the box:
            # allows us to respect user-entered resolution
            # (e.g. 0.010000)
            self._set_slider_resolution(self.tag.get())
            return True
        except: # probably tclerror
            try:
                # ...but that might have been text.  2nd choice is to
                # get the value from the variable (e.g. 0.01)
                self._set_slider_resolution(self.variable.get())
                return True
            except: # probably tclerror
                return False # can't set a new resolution


    def _try_to_set_slider(self):
        """
        If the value in the box can't be converted to a float, the slider doesn't get set.
        """
        tagvar_val = self.variable.get()
        try:
            val = float(tagvar_val)
            self.set_slider_bounds(min(self.bounds[0],val),
                                   max(self.bounds[1],val))
            self.slider.set(val)
        except ValueError:
            pass


class FocusTakingButton(T.Button):
    """
    A Tkinter Button that takes the focus when the mouse <Enter>s.

    (Tkinter.Button doesn't get focus even when it's clicked,
    and only <Enter> and <Leave> events work for buttons.)
    """
    def __init__(self, master=None, cnf={}, **kw):
        T.Button.__init__(self,master=master,cnf=cnf,**kw)
        self.bind("<Enter>", lambda e=None,x=self: x.focus_set())
        #self['highlightthickness']=0


# CB: tklib has status bar and scrolled window (among other widgets);
# should try them out (although they're tk only...) because we might
# not need the scrodget pacakge.

# Might wonder why we need <<SizeRight>> event, and don't just use the
# <Configure> event for calling sizeright: Can't distinguish manual
# resize from autoresizing.
class ScrolledFrame(T.Frame):
    """
    XXXX

    Content to be scrolled should go in the 'content' frame.
    """

    def _getstatus(self):
        return self._status
    def _setstatus(self,s):
        self._status = s
    status = property(_getstatus,_setstatus)

    def __init__(self,parent,**config):
        assert 'status' not in config
        T.Frame.__init__(self,parent,**config)


        abovetopframe = T.Frame(self)
        topframe = T.Frame(self)
        middleframe = T.Frame(self)
        botframe = T.Frame(self)

        botframe.pack(side="bottom",expand="yes",fill="x")
        abovetopframe.pack(side="top",expand="yes",fill="both")
        topframe.pack(side="top")#,expand="no",fill="both")
        middleframe.pack(side="top",expand='yes',fill='x')

        self.noscroll = abovetopframe
        self.noscroll_bottom = middleframe

        self.canvas = T.Canvas(topframe)
        self.canvas.pack()
        self.canvas.configure(width=0,height=0)

        self.sc = Scrodget(topframe,autohide=1)
        self.sc.associate(self.canvas)
        self.sc.pack(expand=1,fill="both")

        self.content = T.Frame(self.canvas)
        self.content.title = lambda x: self.title(x)
        self.content.container = self

        self.canvas.create_window(0,0,window=self.content,anchor='nw')

        self.bind("<<SizeRight>>",self.sizeright)

        #self.status = StatusBar(botframe)
        #if status:
        #    self.status.pack(side="bottom",fill="both",expand="yes")


    def sizeright(self,event=None):
        self.content.update_idletasks()
        W = self.content.winfo_width()
        H = self.content.winfo_height()
        self.canvas.configure(scrollregion=(0, 0, W, H))
        self.canvas.configure(width=W,height=H)



class ScrolledWindow(T.Toplevel):

    def __init__(self,parent,**config):
        T.Toplevel.__init__(self,parent,**config)
        self.maxsize(self.winfo_screenwidth(),self.winfo_screenheight())

        self.topframe = T.Frame(self)
        # CEBALERT: specifying 20 necessary because otherwise nothing
        # requests any height for the frame. I feel there should be
        # way to have the (status) label request the right height but
        # make no request about the width.
        self.botframe = T.Frame(self,height=20)

        self.botframe.pack(side='bottom',expand='no',fill='x')
        self.topframe.pack(side='top',expand='yes',fill='both')

        self._scrolledframe = ScrolledFrame(self.topframe)
        self._scrolledframe.pack(expand=1,fill='both')
        self.content = self._scrolledframe.content
        self.noscroll = self._scrolledframe.noscroll
        self.noscroll_bottom = self._scrolledframe.noscroll_bottom

    # required? presumably should be deleted
    def sizeright(self,event=None):
        self._scrolledframe.sizeright()





class ProgressController(object):
    def __init__(self,timer=None,sim=None,progress_var=None,status=None):

        self.status=status
        self.sim=sim
        self.timer = timer #or topo.sim.timer
        self.timer.receive_info.append(self.timing_info)

        self.progress_var = progress_var or T.DoubleVar()
        # trace the variable so that at 100 we can destroy the window
        self.progress_trace_name = self.progress_var.trace_variable(
            'w',lambda name,index,mode: self._close_if_complete())


    def _close_if_complete(self):
        """
        Close the specified progress window if the value of progress_var has reached 100.
        """
        if self.progress_var.get()>=100:
            # delete the variable trace (necessary?)
            #self.progress_var.trace_vdelete('w',self.progress_trace_name)

            self._close(final_message="Time %s: Finished %s"%(self.sim.timestr(),
                                                             self.timer.func.__name__))


    def _close(self,final_message=None):
        self.timer.receive_info.remove(self.timing_info)
        if final_message:
            self.status.response(final_message)

    def timing_info(self,time,percent,name,duration,remaining):
        self.progress_var.set(percent)

    def set_stop(self):
        """Declare that running should be interrupted."""
        self.timer.stop=True
        final_message = "Time %s: Interrupted %s"%(self.sim.timestr(),
                                                   self.timer.func.__name__)
        self._close(final_message)


class ProgressWindow(ProgressController,T.Frame):
    """
    Graphically displays progress information for a SomeTimer object.

    ** Currently expects a 0-100 (percent) value ***
    """
    def __init__(self,parent,sim=None,timer=None,progress_var=None,
                 title=None,status=None,unpack_master_when_done=True):
        ProgressController.__init__(self,timer=timer,sim=sim,
                                    progress_var=progress_var,status=status)
        T.Frame.__init__(self,parent)

        self.balloon = Balloon(self)

        self._unpack_master=unpack_master_when_done


        progress_bar = Progressbar(self,variable=self.progress_var)
        progress_bar.pack(side='left')

        # CEBALERT: would like time to be displayed inside the bar,
        # but I don;t know how to do that.
        self.timeleft=T.Label(self)
        self.timeleft.pack(side='left')

        stop_button = FocusTakingButton(self,text="X",command=self.set_stop)
        stop_button.pack(side='right')#side="bottom")
        self.balloon.bind(stop_button,"""Interrupt a procedure.""")
#        Stop a running simulation.
#
#        The simulation can be interrupted only on round integer
#        simulation times, e.g. at 3.0 or 4.0 but not 3.15.  This
#        ensures that stopping and restarting are safe for any
#        model set up to be in a consistent state at integer
#        boundaries, as the example Topographica models are.""")


    def _close(self,final_message=None):
        ProgressController._close(self,final_message)
        # such a hack
        if self._unpack_master:
            self.master.pack_forget()
            self.master.master['width']=1
        T.Frame.destroy(self)

    def timing_info(self,time,percent,name,duration,remaining):
        try:
            ProgressController.timing_info(self,time,percent,name,duration,remaining)

            Z = "%02d:%02d"%(int(remaining/60),int(remaining%60))
            self.timeleft['text']=Z

            if self.status is not None:
                self.status.response('Time: %s  Duration: %s'%(time,duration))
                self.update()
                self.update_idletasks()
        except T.TclError:
            pass




# CEB: slightly modifed code from http://code.activestate.com/recipes/576688/

# CEBALERT: this seems better than the tooltip provided by
# tklib. Should probably use this instead of 'Balloon' everywhere. I
# need to use Tooltip rather than Balloon for the status bar, because
# Tooltip allows a function to be passed for generating the text. I
# wish tk included a useful tooltip widget by default.
from time import time
class ToolTip( T.Toplevel ):
    """
    Provides a ToolTip widget for Tkinter.
    To apply a ToolTip to any Tkinter widget, simply pass the widget to the
    ToolTip constructor
    """
    def __init__( self, wdgt, msg=None, msgFunc=None, delay=1, follow=True ):
        """
        Initialize the ToolTip

        Arguments:
          wdgt: The widget this ToolTip is assigned to
          msg:  A static string message assigned to the ToolTip
          msgFunc: A function that retrieves a string to use as the ToolTip text
          delay:   The delay in seconds before the ToolTip appears(may be float)
          follow:  If True, the ToolTip follows motion, otherwise hides
        """
        self.wdgt = wdgt
        self.parent = self.wdgt.master                                          # The parent of the ToolTip is the parent of the ToolTips widget
        T.Toplevel.__init__( self, self.parent, bg='black', padx=1, pady=1 )      # Initalise the Toplevel
        self.withdraw()                                                         # Hide initially
        self.overrideredirect( True )                                           # The ToolTip Toplevel should have no frame or title bar

        self.msgVar = T.StringVar()                                               # The msgVar will contain the text displayed by the ToolTip
        if msg == None:
            pass#self.msgVar.set( 'No message provided' )
        else:
            self.msgVar.set( msg )
        self.msgFunc = msgFunc
        self.delay = delay
        self.follow = follow
        self.visible = 0
        self.lastMotion = 0
        T.Message( self, textvariable=self.msgVar, bg='#FFFFDD',
                 aspect=1000 ).grid()                                           # The test of the ToolTip is displayed in a Message widget
        self.wdgt.bind( '<Enter>', self.spawn, '+' )                            # Add bindings to the widget.  This will NOT override bindings that the widget already has
        self.wdgt.bind( '<Leave>', self.hide, '+' )
        self.wdgt.bind( '<Motion>', self.move, '+' )

    def spawn( self, event=None ):
        """
        Spawn the ToolTip.  This simply makes the ToolTip eligible for display.
        Usually this is caused by entering the widget

        Arguments:
          event: The event that called this funciton
        """
        self.visible = 1
        self.after( int( self.delay * 1000 ), self.show )                       # The after function takes a time argument in miliseconds

    def show( self ):
        """
        Displays the ToolTip if the time delay has been long enough
        """
        if self.visible == 1 and time() - self.lastMotion > self.delay:
            self.visible = 2
        if self.visible == 2 and self.msgVar.get()!='':
            self.deiconify()

    def move( self, event ):
        """
        Processes motion within the widget.

        Arguments:
          event: The event that called this function
        """
        self.lastMotion = time()
        if self.follow == False:                                                # If the follow flag is not set, motion within the widget will make the ToolTip dissapear
            self.withdraw()
            self.visible = 1
        self.geometry( '+%i+%i' % ( event.x_root+10, event.y_root+10 ) )        # Offset the ToolTip 10x10 pixes southwest of the pointer
        try:
            self.msgVar.set( self.msgFunc() )                                   # Try to call the message function.  Will not change the message if the message function is None or the message function fails
        except:
            pass
        self.after( int( self.delay * 1000 ), self.show )

    def hide( self, event=None ):
        """
        Hides the ToolTip.  Usually this is caused by leaving the widget

        Arguments:
          event: The event that called this function
        """
        self.visible = 0
        self.withdraw()


def bind_tooltip(widget,msg):
    kw = dict(delay=1,follow=False)
    if callable(msg):
        kw['msgFunc']=msg
    else:
        kw['msg']=msg
    ToolTip(widget,**kw)


class StatusBar(T.Frame):

    messageframe_pack_options = dict(side='left',expand='yes',fill='x')
    dynamicinfoframe_pack_options = dict(side='left',expand='yes',fill='x')
    progressframe_pack_options = dict(side='right')


    #####
    # CEBALERT: hacks until we're using styles.
    #
    # This won't look good across platforms. Should get defaults.
    label_background = '#d9d9d9'
    label_foreground = '#000000'
    def _wrn_fmt(self):
        self.messagelabel.config(background=self.label_background)
        self.messagelabel.config(foreground='red')

    def _err_fmt(self):
        self.messagelabel.config(background='red')
        self.messagelabel.config(foreground='white')

    def _nrm_fmt(self):
        self.messagelabel.config(background=self.label_background)
        self.messagelabel.config(foreground=self.label_foreground)
    #####


    def __init__(self,master):
        T.Frame.__init__(self, master)

        self.mstack = []
        self.msformat = []
        self.mpoint = -1
        self.pf0 = T.Frame(self)
        self.pf0.pack(side='right')
        self.progressframe=T.Frame(self.pf0)

        self.dynamicinfoframe=T.Frame(self)
        self.dynamicinfolabel=T.Label(self.dynamicinfoframe,anchor='w')
        self.dynamicinfolabel.pack(side='left')
        bind_tooltip(self.dynamicinfolabel,self._get_dynamicinfo)

        self.messageframe=T.Frame(self)
        self.messageclear=T.Button(self.messageframe,text="X",
                                   command=lambda *args: self.clear_message())

        self.messagefwd=T.Button(self.messageframe,text=">",
                                 command=lambda *args:self.next_message())

        self.messagebck=T.Button(self.messageframe,text="<",
                                 command=lambda *args:self.previous_message())

        bind_tooltip(self.messageclear,"Dismiss current message(s)")
        bind_tooltip(self.messagefwd,"Next message")
        bind_tooltip(self.messagebck,"Previous message")

        self.messageclear.pack(side='right')
        self.messagefwd.pack(side='right')
        self.messagebck.pack(side='right')

        self.messagelabel=T.Label(self.messageframe,anchor='w')
        self.messagelabel.pack(side='left')
        bind_tooltip(self.messagelabel,self._get_message)


        self._show_dynamicinfoframe()


    def open_progress_window(self,timer=None,sim=None):
        p = ProgressWindow(self.progressframe,sim=sim,timer=timer,status=self)
        p.pack()
        self.progressframe.pack(self.progressframe_pack_options)
        return p


    def _show_messageframe(self):
        self.dynamicinfoframe.pack_forget()
        self.messageframe.pack(**self.messageframe_pack_options)


    def _show_dynamicinfoframe(self):
        self.messageframe.pack_forget()
        self.dynamicinfoframe.pack(**self.dynamicinfoframe_pack_options)
        self.update_idletasks()


    def dynamicinfo(self,text):
        self.dynamicinfolabel.config(text=text)


    def response(self,text):
        self.dynamicinfo(text)

        # remove previous after()
        try:
            self.after_cancel(self.__last_after_id)
        except:
            pass

        self.__last_after_id = self.after(10000,lambda *args: self.clear_dynamicinfo())

    def _display_from_history(self):
        displayindex=self.mpoint+1
        self.msformat[self.mpoint]()
        if displayindex<0:
            self._display_message("%s: %s"%(displayindex,self.mstack[self.mpoint]))
        else:
            self._display_message(self.mstack[self.mpoint])


    def previous_message(self):
        self.mpoint=max(-len(self.mstack),self.mpoint-1)
        self._display_from_history()

    def next_message(self):
        self.mpoint=min(-1,self.mpoint+1)
        self._display_from_history()

    def message(self,text):
        self._nrm_fmt()
        self.mstack.append(text)
        self.msformat.append(self._nrm_fmt)
        self._display_message(text)


    def _display_message(self,text):
        if text!='':
            if len(self.mstack)>1:
                pass#self.mnavframe.pack(side='right')
            else:
                pass#self.mnavframe.pack_forget()
            self.messagelabel.config(text=text)
            self._show_messageframe()


    def warn(self,text):
        self._wrn_fmt()
        self.mstack.append(text)
        self.msformat.append(self._wrn_fmt)
        self._display_message(text)


    def error(self,text):
        self._err_fmt()
        self.mstack.append(text)
        self.msformat.append(self._err_fmt)
        self._display_message(text)
        self.bell()
#        # CEBALERT: would like to lock only the window with the error,
#        # not the whole app, but I don't know how to do that.
#        try:
#            self.grab_set()
#        except: # get tclerror if another app has grab
#            pass

    def clear_message(self):
        self.mpoint=-1
        self.mstack=[]
        self.messagelabel.config(text="")
        self._nrm_fmt()
        self._show_dynamicinfoframe()
#        try:
#            self.grab_release()
#        except:
#            pass

    def clear_dynamicinfo(self):
        self.dynamicinfolabel.config(text="")

    def _get_message(self):
        return self.messagelabel.config()['text'][4]
    def _get_dynamicinfo(self):
        return self.dynamicinfolabel.config()['text'][4]



# CEBALERT: rename
class AppWindow(ScrolledWindow):
    """
    A ScrolledWindow with extra features intended to be common to all
    windows of an application.

    Currently this only includes a window icon, but we intend to
    add a right-click menu and possibly more.
    """
    window_icon_path = None

    def __init__(self,parent,status=False,**config):
        ScrolledWindow.__init__(self,parent)
        self.content.title = self.title
        self.renew()
        ### Universal right-click menu
        # CB: not currently used by anything but the plotgrouppanels
        # self.context_menu = Tkinter.Menu(self, tearoff=0)
        # self.bind("<<right-click>>",self.display_context_menu)

        # status bar is currenlty inside scrolled area (a feature
        # request is to move it outside ie replace self.content with
        # just self)
        self.status = StatusBar(self.botframe)
        if status:
            # place doesn't interfere with parent's geometry
            # (don't want status bar to cause horizontal resizing,
            # but DO want it to cause vertical resizing - the vertical
            # part doesn't work, see earlier HACKALERT by botframe)
            self.status.place(relx=0,rely=0,relwidth=1.0,height='')

    def renew(self):
        # CEBALERT: doesn't work on OS X, and is a strange color on
        # Windows.  To get a proper icon on OS X, we probably have to
        # bundle into an application package or something like that.
        # On OS X, could possibly alter the small titlebar icon with something like:
        # self.attributes("-titlepath","/Users/x/topographica/AppIcon.icns")
        self.iconbitmap('@'+self.window_icon_path)

        self.protocol("WM_DELETE_WINDOW",self.mydestroy)

    def mydestroy(self):
        ScrolledWindow.destroy(self)



class ListWidget(T.Frame):

    # CEBALERT: as with several of my compound widgets, need to deal
    # with **config passed to __init__, by putting through config()
    # (and so also need to add config() methods where currently
    # missing).
    def __init__(self, master, variable,cmd,**widget_options):
        T.Frame.__init__(self, master)

        if 'state' in widget_options:
            self.disabled=widget_options['state']
        else:
            self.disabled=False

        self.entry=T.Entry(self,textvariable=variable,state='disabled',disabledforeground='black') #CEBALERT: presumably disabledforeground won't work work with styles...
        self.entry.pack(fill='both',expand=1)

        ### Right-click menu for widgets
        # CEBALERT: I can't work out how to make the right-click event
        # bound to the widget already (by the ParametersFrame) ever
        # activate!  It must be overwritten or something by tk. So I
        # have to duplicate the right-click menu code here.
        self.entry.bind("<<right-click>>",self._right_click)
        master.option_add("*Menu.tearOff", "0")
        self.menu = Menu(master)
        self.menu.insert_command('end',label='Properties',
            command=cmd)

    def _right_click(self, event):
        """
        Popup the right-click menu.
        """
        if self.disabled is False:
            self.menu.tk_popup(event.x_root, event.y_root)

    def config(self,**options):
        # all options are passed to the entry
        # state=disabled->no right click menu
        if 'state' in options:
            if options['state']=='disabled':
                self.disabled=True
            elif options['state']=='normal':
                self.disabled=False
        return self.entry.config(**options)



import new



# CEBALERT: dict stuff is just half implemented! does enough
# to support list subclasses
def dict_to_parameterized(class_name,parameter_values):
    # each item is just a Parameter (i.e. not specific)
    parameter_objs = dict([(name,Parameter(default=value))
                           for name,value in parameter_values.items()])

    # got to be a new class to get independent sets of parameters
    new_class = new.classobj(class_name,(Representer,),parameter_objs)
    return  new_class(parameter_values)


def list_to_parameterized(list_,class_=None):

    parameter_values = OrderedDict([('a'*i,item) for item,i in zip(list_,range(1,len(list_)+1))])

    if class_ is None:
        parameter_objs = dict([(name,Parameter(default=value))
                               for name,value in parameter_values.items()])
    else:
        parameter_objs = dict([(name,ClassSelector(class_=class_,default=value))
                               for name,value in parameter_values.items()])


    new_class = new.classobj('List',(ListRepresenter,),parameter_objs)
    #new_class.params('name').hidden=True

    inst = new_class(parameter_values,class_=class_)
    inst.list_ = list_ # that's a hack; need to get classes right
    return inst





############################################################

def standard_parameterized_params():
    return Parameterized.params().keys()


class Representer(Parameterized):

    name = Parameter(precedence=-1) # CEBALERT

    def __init__(self,represented,class_):
        ## CEBALERT: when we come to represent dicts in the GUI,
        ## will have to deal with name clashes (e.g. for name parameter)
        for name in standard_parameterized_params():
            assert name not in represented
        ##
        self.class_ = class_
        self.represented = represented
        super(Representer,self).__init__(**represented)
        self.initialized=False # allow all modifications


    def my_params(self):
        param_names = self.params().keys()

        # remove any of the standard params (e.g. name) that haven't
        # been added to represented
        for name in standard_parameterized_params():
            if name not in self.represented:
                param_names.pop(param_names.index(name))

        return param_names


    def update(self):
        for name in self.my_params():
            self.represented[name]=getattr(self,name)

    def add(self,name):
        if self.class_ is None:
            p = Parameter(default=None)
        else:
            p = ClassSelector(class_=self.class_,default=None)

        self._add_parameter(name,p)
        self.represented[name]=p.default
        return name

    def remove(self,name):
        # CB: don't move any of this method up to parameterized
        # without considering the consequences!

        del self.represented[name]

        cls=type(self)
        delattr(cls,name)
        try:
            delattr(cls,'_%s__params'%cls.__name__)
        except AttributeError:
            pass


class ListRepresenter(Representer):

    def update(self):
        super(ListRepresenter,self).update()

        ## grow or shrink original list if necessary
        l = len(self.list_)-len(self.represented)

        if l>0:
            for i in range(l):
                self.list_.pop()
        elif l<0:
            for i in range(-l):
                self.list_.append(None)

        if len(self.represented)==0:
            self.list_[:]=[]
            return

        for val,i in zip(self.represented.values(),range(len(self.list_))):
            self.list_[i]=val

    def add(self,name=None):
        try:
            longest = max([len(n) for n in self.my_params()])
        except ValueError:
            longest = 0

        if name is None:
            name = 'a'*(longest+1)
            #name = 'a'*(len(self.my_params())+1)
        assert name not in self.my_params()
        return super(ListRepresenter,self).add(name)


# Problems:
# * + button gets hidden as more rows added (unless window made
#   bigger)
#
# * window doesn't open at right width to display contents
#   list item contents
#
# * I suspect there's a bug where you can get to a situation in
#   which editing one box will result in changes to another,
#   thanks to the ridiculously and unnecessarily complex code
#   for tracking items. But I can't currently demonstrate such a
#   a bug...
class EditingParametersFrameWithApply(ParametersFrameWithApply):

    def __init__(self,master,parameterized_object=None,
                 on_set=None,on_modify=None,**params):
        super(EditingParametersFrameWithApply,self).__init__(
            master,parameterized_object,on_set,on_modify,**params)

        self.hide_param('Defaults')

        ## hack to find last row
        last_row = -1
        for n,d in self.currently_displayed_params.items():
            row = int(d['widget'].grid_info()['row'])
            last_row=max(row,last_row)

        ad=T.Button(self,text="+",command=self._add_param)
        ad.pack(side='right')

        ### CB: should use button parameter and pack_param
        self._hack=[]
        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/edit_add.png')),(20,20)))
        ad['image']=image
        self._hack.append(image)
        ###

        self._apply_hooks.append(lambda *args: self.xupdate())
        self._apply_hooks.append(lambda *args: self._update_apply_status())


# CEBALERT: name this, and there's duplication with represented etc.
    def xupdate(self):
        cls = type(self._extraPO)

        old = {}
        for name in self.params_to_display:
            old[name]=(self._tkvars[name],
                       self.representations[name],
                       self.params_to_display[name],
                       self.currently_displayed_params[name],
                       self._extraPO.params(name))

        new_pos = {}
        for name in self.params_to_display.keys():
            new_pos[int(self.representations[name]['widget'].grid_info()['row'])]=name

        self._extraPO.represented = OrderedDict()

        for name,pos in zip(sorted(self.params_to_display),range(len(self.params_to_display))):
            self._tkvars[name]=old[new_pos[pos]][0]

            # getting ridiculous. make sure old widgets are wiped from
            # the earth. who knows how many other times I should have
            # been doing this in the gui?
            for w in self.representations[name].values():
                try:
                    w.grid_remove()
                    w.destroy()
                except:
                    pass

            self.representations[name]=old[new_pos[pos]][1]
            self.params_to_display[name]=old[new_pos[pos]][2]
            self.currently_displayed_params[name]=old[new_pos[pos]][3]
            type.__setattr__(cls,name,old[new_pos[pos]][4])

            # (see alert at start of method)
            self._extraPO.represented[name]=getattr(self._extraPO,name)

        ## delete cached params()
        try:
            delattr(cls,'_%s__params'%cls.__name__)
        except AttributeError:
            pass

        self._extraPO.update()
        self.set_PO(self._extraPO) # HACK! At least extract relevant parts


    def _grid_param(self,parameter_name,row):
        super(EditingParametersFrameWithApply,self)._grid_param(
            parameter_name,row)
        lc = self.representations[parameter_name]['list_ctrl']
        lc.grid(column=3,row=row)


    def _make_representation(self,name):
        super(EditingParametersFrameWithApply,self)._make_representation(name)

        self.representations[name]['list_ctrl']=ListItemCtrlWidget(
            self._params_frame,
            lambda:self._up_param(name),
            lambda:self._down_param(name),
            lambda:self._del_param(name))

    def _add_param(self):
        # should refactor pack_params to get out adding of single param

        po = self._extraPO
        name = po.add()

        self._create_tkvar(po,name,po.params(name))

        self._make_representation(name)
        row = len(self.params_to_display)
        self._grid_param(name,row)

        self.params_to_display[name]=po.params(name)
        self.currently_displayed_params[name]=self.representations[name]

        self._update_apply_status()


    def _del_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])
        po = self._extraPO
        for w in self.representations[name]:
            try:
                self.representations[name][w].grid_forget()
                self.representations[name][w].destroy()
            except:
                pass

        del self._tkvars[name]
        del self.representations[name]
        del self.params_to_display[name]
        del self.currently_displayed_params[name]
        del self.translators[name]

        po.remove(name)

        # move ones below up one
        for n in self.params_to_display:
            for n2,w in self.representations[n].items():
                if hasattr(w,'grid_info'):
                    w_gi = w.grid_info()
                    if 'row' in w_gi:
                        r1 = int(w_gi['row'])
                        if r1>current_pos:
                            w_gi['row']='%s'%(r1-1)
                            w.grid(**w_gi)

        self._update_apply_status()



    def _down_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])

        if current_pos==len(self.params_to_display)-1:
            return

        new_pos = current_pos+1
        current_name = name

        self.y(current_name,current_pos,new_pos)


    def y(self,current_name,current_pos,new_pos):
        #### HACK TO FIND NEW NAME BY MATCHING ROW
        new_name=None
        for n in self.representations:
            if 'row' in self.representations[n]['widget'].grid_info():
                row = int(self.representations[n]['widget'].grid_info()['row'])
                if row==new_pos:
                    new_name=n
                    break
        assert new_name is not None
        ####

        for n,w in self.representations[current_name].items():
            if hasattr(w,'grid_info'):
                g = w.grid_info()
                if 'row' in g:
                    #print "regrid",current_name,current_pos
                    g['row']='%s'%new_pos
                    w.grid(**g)

        for n,w in self.representations[new_name].items():
            if hasattr(w,'grid_info'):
                g = w.grid_info()
                if 'row' in g:
                    #print "regrid",new_name,new_pos
                    g['row']='%s'%current_pos
                    w.grid(**g)

        self._update_apply_status()


    def _up_param(self,name):
        current_pos = int(self.representations[name]['widget'].grid_info()['row'])

        if current_pos==0:
            return

        new_pos = current_pos-1
        current_name = name

        self.y(current_name,current_pos,new_pos)

    # CEBALERT: why not in super?
    def _update_apply_status(self):
        w=self.representations['Apply']['widget']
        if self.has_unapplied_change():
            w.config(state='normal')
        else:
            w.config(state='disabled')

    def set_PO(self,parameterized_object):
        super(EditingParametersFrameWithApply,self).set_PO(parameterized_object)
        self.hide_param('Defaults') # CEBALERT: should just have been unpacked earlier

    def has_unapplied_change(self):
        # detect length change
        if len(self._extraPO.list_)!=len(self.params_to_display):
            return True

        # detect order change
        names = sorted(self.params_to_display.keys())
        for i,name in zip(range(len(names)),names):
            if 'widget' not in self.representations[name] or int(self.representations[name]['widget'].grid_info()['row'])!=i:
                return True

        return super(EditingParametersFrameWithApply,self).has_unapplied_change()


    def _refresh_button(self,overwrite_error=True):
        self.set_PO(self._extraPO)
        self._update_apply_status()

    # CEBALERT: because
    def _apply_button(self):
        self.update_parameters()

        for h in self._apply_hooks:
            h(self)


class ListItemCtrlWidget(T.Frame):
    def __init__(self,master,up_cmd,down_cmd,remove_cmd):
        T.Frame.__init__(self, master)

        up = T.Button(self,text="u",command=up_cmd)
        up.pack(side='left')

        down = T.Button(self,text="d",command=down_cmd)
        down.pack(side='left')

        remove = T.Button(self,text='-',command=remove_cmd)
        remove.pack(side='left')


        ### CEBALERT: should use button parameter & pack_param
        self._hack = []
        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/arrow-up.png')),(20,20)))
        up['image']=image
        self._hack.append(image)

        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/arrow-down-2.0.png')),(20,20)))
        down['image']=image
        self._hack.append(image)

        image=ImageTk.PhotoImage(ImageOps.fit(
            Image.open(resolve_path('tkgui/icons/edit_remove.png')),(20,20)))
        remove['image']=image
        self._hack.append(image)
        ###



Classes linking Parameters to Tkinter.  TkParameterized allows flexible graphical representation and manipulation of the parameters of a Parameterized instance or class.  ParametersFrame and ParametersFrameWithApply extend TkParameterized, displaying all the Parameters together in a list. Both allow these Parameters to be edited; ParametersFrame applies changes immediately as they are made, whereas ParametersFrameWithApply makes no changes until a confirmation is given (by pressing the 'Apply' button).  Using these classes to display parameters has several benefits, including that, automatically:  - changes to the Parameters in the GUI are reflected in the objects   themselves without any additional code;  - each Parameter is represented by an appropriate widget (e.g. slider   for a Number);  - type and range checking is handled (by virtue of using parameters);  - doc strings are displayed as pop-up help for each parameter;  - parameter names are formatted nicely for display;  - changes to parameter values in the GUI can be associated with   function calls.   Examples ========  (1) Display the parameters of an object inside an existing window  You want to display all parameters of a parameterized instance inside an existing container (e.g. a window or a frame):    # existing Parameterized instance g   from topo import pattern   g = pattern.Gaussian()    # existing window t   import Tkinter   t = Tkinter.Toplevel()    # display all the parameters of g in t   from topo.param.tk import ParametersFrame   ParametersFrame(t,g)   #should be ParametersFrame(t,g).pack(); see ALERT in ParametersFrame   (2) Display the parameters of an object in a standalone window  You want a new window displaying only the parameters of your object:    # existing Parameterized instance g   from topo import pattern   g = pattern.Gaussian()    # display all the parameters of g in a new window   from topo.param.tk import edit_parameters   edit_parameters(g)   (3) Flexible GUI layout using TkParameterized  You want to display only some of the parameters of one or more Parameterized instances:   ## Existing, non-GUI code  from topo import param   class Object1(param.Parameterized):      duration = param.Number(2.0,bounds=(0,None),doc='Duration of measurement')      displacement = param.Number(0.0,bounds=(-1,1),doc='Displacement from point A')   class Object2(param.Parameterized):      active_today = param.Boolean(True,doc='Whether or not to count today')      operator_name = param.String('<NAME>',doc='Operator today')   o1 = Object1()  o2 = Object2()   ## Existing window  import Tkinter  t = Tkinter.Toplevel()   ## Flexible GUI representation: display o1.duration, o1.displacement,  ## and o2.active_today inside t, ignoring o2.operator_name  from topo.param.tk import TkParameterized   t1 = TkParameterized(t,o1)  t2 = TkParameterized(t,o2)   t1.pack_param('duration',side='left')  t1.pack_param('displacement',side='bottom')  t2.pack_param('active_today',side='right')   (4) ?  TkParameterized is itself a subclass of Parameterized, so a TkParameterized class can have its own parameters (in addition to representing those of an external parameterized instance or class).    ## Existing class   from topo import params    class X(param.Parameterized):       one = param.Boolean(True)       two = param.Boolean(True)     ## Panel to represent an instance of X   from Tkinter import Frame   from topo.param.tk import TkParameterized    class XPanel(TkParameterized,Frame):        dock = param.Boolean(False,doc='Whether to attach this Panel')        def __init__(self,master,x):           self.pack_param('dock',side='top',on_set=self.handle_dock)           self.pack_param('one',side='left')           self.pack_param('two',side='right')        def handle_dock(self):           if self.dock:               # dock the window           else:               # undock the window     ## Running the code   t = Tkinter.Toplevel()   x = X()   XPanel(t,x) # CEBALERT: moving todo # (1) move icons from topo/tkgui. # (2) update documentation # CB: This file is too long because the param/gui interface code has # become too long, and needs cleaning up.  I'm still working on it # (still have to attend to simple ALERTs and do a one-pass cleanup) # It's quite likely that the way TkParameterizedBase is implemented # could be simplified. Right now, it still contains leftovers of # various attempts to get everything working. But it does seem to # work! ## import logging ## import string,time ## log_name= 'guilog_%s'%string.join([str(i) for i in list(time.gmtime())],"") ## logging.basicConfig(level=logging.DEBUG, ##                     format='%(asctime)s %(levelname)s %(message)s', ##                     filename='topo/tkgui/%s'%log_name, ##                     filemode='w') ## from os import popen ## version = popen('svnversion -n').read() ## logging.info("tkgui logging started for %s"%version) # pyflakes:ignore (try/except import) # CEBALERT: try to import from python 2.7's ttk first # CEBALERT: try to import python 2.7's ordered dictionary first # (part of an existing ALERT - search this file) # CEBALERT: Tkinter.BooleanVar.get() doesn't support None. This is a # hack because there's no way in the GUI to visually distinguish a # "None" from a "False". # i.e. BooleanVar.get() # i.e. Variable.get() Add extension Tcl/Tk code to the Tk instance at     Tkinter._default_root (creating the Tk instance first if     necessary). # Until tklib, tcllib, and scrodget become more commonly # available, we include them in tkgui. Return the inverse of dictionary dict_.      (I.e. return a dictionary with keys that are the values of dict_,     and values that are the corresponding keys from dict_.)      The values of dict_ must be unique. Look for class_ or its superclasses in the keys of dict_; on     finding a match, return the value (return None if no match found).      Searches from the most immediate class to the most distant     (i.e. from class_ to the final superclass of class_). Return the keys of dictionary d sorted by value. # By <NAME>, 2004/01/23 # http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306 Return the keys of d, sorted by value.      The values of d must be unique (see inverse) None is handled separately: if present, it always comes out at the     end of the list. Allows callers to use any sort function without     worrying that None will not match the other objects (e.g. for     using an attribute present on all the other objects, such as     precedence of sheets). Simple detection of Button-like widgets that are not Checkbuttons     (i.e. widgets that do not require a separate label). # CEBALERT: document why try/except is needed ################################################################# # Very basic wrapper for scrodget ################################################################# # CEB: workaround for tkinter lagging behind tk (tk must have changed # the type of a returned value).  This is copied almost exactly from # tkMessageBox. If there are other things like this, we could have the # gui load some 'dynamic patches' to tkinter on startup, which could # then be removed when tkinter is updated (they'd all be in one place, # and no tkgui code would have to change). # Buttons are not naturally represented by parameters? # # Maybe we should have a parent class that implements the # non-Parameter specific stuff, then one that bolts on the # Parameter-specific stuff, and then instead of Button we'd # have TopoButton, or something like that... A GUI-specific parameter to display a button.      Can be associated with an image by specifying an image_path     (i.e. location of an image suitable for PIL, e.g. a PNG, TIFF, or     JPEG image) and optionally a size (width,height) tuple.      Note that the button size can also be set when there is no image,     but instead of being presumed to be in pixels, it is presumed to     be in text units (a Tkinter feature: see     e.g. http://effbot.org/tkinterbook/button.htm). Therefore, to     place two identically sized buttons next to each other, with one     displaying text and the other an image, you first have to convert     one of the sizes to the other's units. # CEBALERT: we should probably solve the above for users, # but what a pain! # CB: would create the photoimage on construction and store it as an # attribute, except that it gives "RuntimeError: Too early to create # image". Must be happening before tk starts, or something. So # instead, return image on demand. Also, because of PIL bug (see # topoconsole.py "got to keep references to the images") we store # a reference to the image each time in _hack. Return an ImageTk.PhotoImage of the image at image_path         (or None if image_path is None or an Image cannot be created). # Note that TkParameterized extends TkParameterizedBase by adding # widget-drawing abilities; documentation for using these classes # begins at a more useful and simple level in TkParameterized (the # documentation for TkParameterizedBase is more complex). A Parameterized subclass that maintains Tkinter.Variable shadows     (proxies) of its Parameters. The Tkinter Variable shadows are kept     in sync with the Parameter values, and vice versa.      Optionally performs the same for an *additional* shadowed     Parameterized (extraPO). The Parameters of the extra     shadowed PO are available via this object (via both the usual     'dot' attribute access and dedicated parameter accessors     declared in this class).      The Tkinter.Variable shadows for this Parameterized and any     extra shadowed one are available under their corresponding     parameter names in the _tkvars dictionary.      (See note 1 for complications arising from name clashes.)       Parameters being represented by TkParameterizedBase also     have a 'translators' dictionary, allowing mapping between string     representations of the objects and the objects themselves (for use     with e.g. a Tkinter.OptionMenu). More information about the     translators is available from specific translator-related methods     in this class.       Notes     =====      (1) (a) There is an order of precedance for parameter lookup:         this PO > shadowed PO.          Therefore, if you create a Parameter for this PO         with the same name as one of the shadowed PO's Parameters, only         the Parameter on this PO will be shadowed.          Example: 'name' is a common attribute. As a         Parameterized, this object has a 'name' Parameter. Any         shadowed PO will also have a 'name' Parameter. By default,         this object's name will be shadowed at the expense of the name         of the extra shadowed PO.          The precedence order can be reversed by setting the attribute         'self_first' on this object to False.           (b) Along the same lines, an additional complication can arise         relating specifically to 'dot' attribute lookup.  For         instance, a sublass of TkParameterized might also         inherit from Tkinter.Frame. Frame has many of its own         attributes, including - for example - 'size'. If we shadow a         Parameterized that has a 'size' Parameter, the         Parameterized's size Parameter will not be available as         .size because ('dot') attribute lookup begins on the local         object and is not overridden by 'self_first'. Using the         parameter accessors .get_parameter_object('size') or         .get_parameter_value('size') (and the equivalent set versions)         avoids this problem.        (2) If a shadowed PO's Parameter value is modified elsewhere, the         Tkinter Variable shadow is NOT updated until that Parameter value         or shadow value is requested from this object. Thus requesting the         value will always return an up-to-date result, but any GUI display         of the Variable might display a stale value (until a GUI refresh         takes place). # CEBNOTE: avoid (as far as possible) defining Parameters for this # object because they might clash with Parameters of objects it # eventually represents. # CEBNOTE: Regarding note 1a above...it would be possible - with # some extra work - to shadow Parameters that are duplicated on # this PO and extraPO. Among other changes, things like the # _tkvars dict would need different (e.g. name on this PO and # name on extraPO) # CEBNOTE: Regarding note 1b...might be less confusing if we stop # parameters of shadowed POs being available as attributes (and # use only the parameter access methods instead). But having them # available as attributes is really convenient. # CEBNOTE: Regarding note 2 above...if it becomes a problem, we # could have some autorefresh of the vars or a callback of some # kind in the parameterized object itself. E.g a # refresh-the-widgets-on-focus-in method could make the gui in # sync with the actual object (so changes outside the gui could # show up when a frame takes focus). Or there could be timer # process. # CEB: because of note 1, attributes of this class should have # names that are unlikely to clash (or they should be private); # this will make it easier for creators and users of subclasses to # avoid name clashes. # must exist *before* an instance is init'd # (for __getattribute__) # CEBALERT: Parameterized repr method leads to recursion error. Parameters that are not in this object itself but are in the         extraPO get set on extraPO. Then calls Parameterized's         _setup_params(). ### a parameter might be passed in for one of the extra_pos; ### if a key in the params dict is not a *parameter* of this ### PO, then try it on the extra_pos # CEBALERT: rename extraPO...but to what? # Rename change_PO() and anything else related. Translation between displayed values and objects         ------------------------------------------------          A Parameter has a value, but that might need some processing         to become a value suitable for display. For instance, the         SheetMask() object <SheetMask SheetMask0001923> ...            Important attributes         --------------------          * _extraPO           * self_first         Determines precedence order for Parameter lookup:         if True, Parameters of this object take priority whenever         there is a name clash; if False, Parameters of extraPO take         priority.             * obj2str_fn & str2obj_fn          * translator_creators          (Note that in the various dictionaries above, the entry for         Parameter serves as a default, since keys are looked up by         class, so any Parameter type not specifically listed will be         covered by the Parameter entry.)            * _tkvars # make self.first etc private ## Which Tkinter Variable to use for each Parameter type # (Note that, for instance, we don't include Number:DoubleVar. # This is because we use Number to control the type, so we # don't need restrictions from DoubleVar.) # CEBALERT: Parameter is the base parameter class, but ... # at least need a test that will fail when a new param type added # Rename # CBALERT: sort out inherit. Shadow the Parameters of extraPO. # (reverse list to respect precedence) Create Tkinter Variable shadows of all Parameters of PO. Add _tkvars[name] to represent the parameter object with the specified name.          The appropriate Variable is used for each Parameter type.          Also adds tracing mechanism to keep the Variable and Parameter         values in sync, and updates the translator dictionary to map         string representations to the objects themselves. # CEBALERT: should probably delete any existing tkvar for name # overwrite Variable's set() with one that will handle # transformations to string # for reverting # CB: Instead of a trace, could we override the Variable's # set() method i.e. trace it ourselves?  Or does too much # happen in tcl/tk for that to work? # Override the Variable's get() method to guarantee an # out-of-date value is never returned.  In cases where the # tkinter val is the most recently changed (i.e. when it's # edited in the gui, resulting in a trace_variable being # called), the _original_get() method is used. # CEBALERT: what about other users of the variable? Could they # be surprised by the result from get()? ################################################################################ # ################################################################################ * The callback to use for all GUI variable traces/binds * Set the tk variable to (the possibly transformed-to-string) val. # trace not called because we're already in trace, # and tk disables trace activation during trace # CB: separate into update and get? Return the value of the tk variable representing param_name.          (Before returning the variable's value, ensures it's up to date.) Return True if the displayed value does not equal the object's         value (and False otherwise). #getattr(self._extraPO,name) # use equality check then identity check because e.g.  val # starts at 0.5, type 0.8, then type 0.5, need that to be # changed is False, but some types cannot be equality compared # (can be identity compared). # CEBALERT: need to add a unit test to ensure this keeps working. # Plus, I need to think about this, because while the above is # true for floats, identity tests make more sense for many types # (i.e. you want to know the object is the same). Attempt to set the parameter represented by param_name to the         value of its corresponding Tkinter Variable.          If setting the parameter fails (e.g. an inappropriate value         is set for that Parameter type), the Variable is reverted to         its previous value.          (Called by the Tkinter Variable's trace_variable() method.) ### can only edit constant parameters for class objects ### HIDDEN # don't attempt to set if there was a string-to-object translation error ### HIDDEN # (use _original_get() because we don't want the tkvar to be reset to # the parameter's current value!) # everything # whatever the parameter-setting error was ### call any function associated with GUI set() # CEBALERT: provide a way of allowing other gui components # to figure out where a callback error might have come # from. Callback instances (the Callback class is defined # in Tkinter.py) store a widget, but often it appears to # be the Tk instance - which is of no use in later # determining where an error might have originated. ################################################################################ # ################################################################################ Return a list of Parameterizeds in which to find         Parameters.          The list is ordered by precedence, as defined by self_first. Return the Parameterized which contains the parameter 'name'. Return the Parameter *object* (not value) specified by name,         from the source_POs in this object (or the         specified parameterized_object).          If with_source=True, returns also the source parameterizedobject. ###################################################################### # Attribute lookup ##### these guarantee only to get/set parameters ##### Return the value of the parameter specified by name.          If a parameterized_object is specified, looks for the parameter there.         Otherwise, looks in the source_POs of this object. # CEBALERT: shouldn't this use __set_parameter? Presumably doing # that kind of thing is part of the cleanup required in this file. Set the value of the parameter specified by name to val.          Updates the corresponding tkvar. # update the tkvar ###################################################### ########## these lookup attributes in order ########## # (i.e. you could get attribute of self rather than a parameter) # (might remove these to save confusion: they are useful except when #  someone would be surprised to get an attribute of e.g. a Frame (like 'size') when #  they were expecting to get one of their parameters. Also, means you can't set #  an attribute a on self if a exists on one of the shadowed objects) # (also they (have to) ignore self_first) If the attribute is found on this object, return it. Otherwise,         return the attribute from the extraPO, if it exists.         If there is no match, raise an attribute error. # HIDDEN! If the attribute already exists on this object, set it. If the attribute         is found on the extraPO, set it there. Otherwise, set the         attribute on this object (i.e. add a new attribute). # use dir() not hasattr() because hasattr uses __getattribute__ # name not found, so set on this object ####################################################### ###################################################################### ###################################################################### # Translation between GUI (strings) and true values # Dynamic parameters only *might* contain a # dynamic value; if such a parameter really is dynamic, we # overwrite any more specific class found above # (e.g. a Number with a dynamic value will have a numeric # translator from above, so we replace that) # CEBALERT: doc replace & rename to plastic or something If val is one of the objects in param_name's translator,         translate to the string. Change the given string for the named parameter into an object.          If there is a translator for param_name, translate the string         to the object; otherwise, call convert_string2obj on the         string. ###################################################################### Helper method: return the 'attr_name is not in objects' message. Helper method: # use set_in_bounds if it exists # i.e. users of widgets get their values cropped # (no warnings/errors, so e.g. a string in a # tagged slider just goes to the default value) # CEBALERT: set_in_bounds not valid for POMetaclass? # CEBHACKALERT: GeneratorSheet.input_generator should be a # property? But it's a Parameter... # Setting input_generator rather than calling set_input_generator() # is probably a trap... Provide widgets for Parameters of itself and up to one additional     Parameterized instance or class.      A subclass that defines a Parameter p can display it appropriately     for manipulation by the user simply by calling     pack_param('p'). The GUI display and the actual Parameter value     are automatically synchronized (though see technical notes in     TkParameterizedBase's documentation for more details).      In general, pack_param() adds a Tkinter.Frame containing a label     and a widget:      ---------------------                     The Parameter's     |                   |                     'representation'     | [label]  [widget] |<----frame     |                   |     ---------------------      In the same way, an instance of this class can be used to display     the Parameters of an existing object. By passing in extraPO=x,     where x is an existing Parameterized instance or class, a     Parameter q of x can also be displayed in the GUI by calling     pack_param('q').      For representation in the GUI, Parameter values might need to be     converted between their real values and strings used for display     (e.g. for a ClassSelector, the options are really class objects,     but the user is presented with a list of strings to choose     from). Such translation is handled and documented in the     TkParameterizedBase; the default behaviors can be overridden if     required.      (Note that this class simply adds widget drawing to     TkParameterizedBase. More detail about the shadowing of     Parameters is available in the documentation for     TkParameterizedBase.) # CEBNOTE: as for TkParameterizedBase, avoid declaring # Parameters here (to avoid name clashes with any additional # Parameters this might eventually be representing). Whether to format parameter names, or display the         variable names instead.          Example use:           TkParameterized.pretty_parameters=False          (This causes all Parameters throughout the GUI to be displayed         with variable names.) Initialize this object with the arguments and attributes         described below:          extraPO: optional Parameterized object for which to shadow         Parameters (in addition to Parameters of this object; see         superclass)          self_first: if True, Parameters on this object take precedence         over any ones with the same names in the extraPO (i.e. what         to do if there are name clashes; see superclass)           Important attributes         ====================          * param_immediately_apply_change          Some types of Parameter are represented with widgets where         a complete change can be instantaneous (e.g.  when         selecting from an option list, the selection should be         applied straightaway). Others are represented with widgets         that do not finish their changes instantaneously         (e.g. entry into a text box is not considered finished         until Return is pressed).          * widget_creators          A dictionary of methods to create a widget for each Parameter         type. For special widget options (specific to each particular         parameter), see the corresponding method's docstring.          * representations          After pack_param() is called, a Parameter's representation         consists of the tuple (frame,widget,label,pack_options) - the         enclosing frame, the value-containing widget, the label         holding the Parameter's name, and any options supplied for         pack(). These can all be accessed through the representations         dictionary, under the Parameter's name. # CEBALER: doc # CEBALERT: what about subclasses of Number (e.g. Integer, # which should get a slider that jumps between integers... # maybe that already happens)? # CEBNOTE: it would be nice to sort out menus properly # (i.e. parameterize them) ### Right-click menu for widgets Popup the right-click menu. # need an actual mechanism for populating the menu, rather than this!! ### copied from edit_PO_in_currently... # CEBALERT: should have used get_parameter_value(param_name)? ### # CEBALERT: rename Open a new window containing a ParametersFrame (actually, a         type(self)) for the PO in _currently_selected_widget. # CEBALERT: simplify this lookup by value # CEBALERT: should have used get_parameter_value(param_name)? ### CEBALERT: confusing? ### ############################ # uh-oh Keep track of status of dynamic entry. Display a popup menu when user right clicks on a parameter. ################################################################################ # ################################################################################ Prevents the superclass's _update_param_from_tkvar() method from being         called unless:          * param_name is a Parameter type that has changes immediately           applied (see doc for param_immediately_apply_change           dictionary);          * force is True.          (I.e. to update a parameter for which         param_immediately_apply_change[type(parameter)]==False, call         this method with force=True. E.g. when <Return> is pressed in         a text box, this method is called with force=True.) Override the superclass's method to X and allow status indications. #logging.info("%s._handle_gui_set(%s,force=%s)"%(self,p_name,force)) #### Simulate GUI actions Simulate setting the parameter in the GUI. # ERROR: presumably calls trace stuff twice Simulate getting the parameter in the GUI. #### ################################################################################ # End ################################################################################ ################################################################################ # ################################################################################ # some refactoring required: should be a base method that's to do # with adding a representation for a parameter. then this stuff # would go in it. Create a widget for the Parameter name, configured according         to widget_options, and pack()ed according to the pack_options.          Pop-up help is automatically set from the Parameter's doc.          The widget and label (if appropriate) are enlosed in a Frame         so they can be manipulated as a single unit (see the class         docstring). The representation         (frame,widget,label,pack_options) is returned (as well as         being stored in the representations dictionary).           * parent is an existing widget that is to be the parent         (master) of the widget created for this paramter. If no parent         is specified, defaults to the originally supplied master         (i.e. that set during __init__).          * on_set is an optional function to call whenever the         Parameter's corresponding Tkinter Variable's trace_variable         indicates that it has been set (this does not necessarily mean         that the widget's value has changed). When the widget is created,         the on_set method will be called (because the creation of the         widget triggers a set in Tkinter).          * on_modify is an optional function to call whenever the         corresponding Tkinter Variable is actually changed.           widget_options specified here override anything that might have been         set elsewhere (e.g. Button's size can be overridden here         if required).            Examples of use:         pack_param(name)         pack_param(name,side='left')         pack_param(name,parent=frame3,on_set=some_func)         pack_param(name,widget_options={'width':50},side='top',expand='yes',fill='y') # checkbuttons are 'widget label' rather than 'label widget' # type(widget) doesn't seem to work # label can be None (e.g. for Button) # If there's a label, balloon's bound to it - otherwise, bound # to enclosing frame. # (E.g. when there's [label] [text_box], only want balloon for # label (because maybe more help will be present for what's in # the box) but when there's [button], want popup help over the # button.) # some params appear to have no docs!!! Hide the representation of Parameter 'name'. # CEBNOTE: forgetting label and widget rather than frame would # just hide while still occupying space (i.e. the empty frame # stays in place, and so widgets could later be inserted at # exact same place) #self.representations[name]['label'].pack_forget() #self.representations[name]['widget'].pack_forget() # unhide_param would need modifying too Un-hide the representation of Parameter 'name'.          Any new pack options supplied overwrite the originally         supplied ones, but the parent of the widget remains the same. # CEBNOTE: new_pack_options not really tested. Are they useful anyway? Destroy the representation of Parameter 'name'.          (unpack and then pack a param if you want to put it in a different         frame; otherwise simply use hide and unhide) ################################################################################ # ################################################################################ ################################################################################ # WIDGET CREATION ################################################################################ Return widget,label for parameter 'name', each having the master supplied          The appropriate widget creation method is found from the         widget_creators dictionary; see individual widget creation         methods for details to each type of widget. # select the appropriate widget-creation method; # default is self._create_string_widget... # ...but overwrite that with a more specific one, if possible # Is widget a button (but not a checkbutton)? If so, no label wanted. # CEBALERT 'notNonelabel': change to have a label with no text # disable widgets for constant params # (need to be able to set on class, hence check it's PO not POMetaclass Return a FocusTakingButton to represent Parameter 'name'.          Buttons require a command, which should have been specified as the         'on_set' function passed to pack_param().          After creating a button for a Parameter param, self.param() also         executes the button's command.          If the Button was declared with an image, the button will         have that image (and no text); otherwise, the button will display         the (possibly pretty_print()ed) name of the Parameter. # because we use Button's command instead # Calling the parameter (e.g. self.Apply()) is like pressing the button: # like setattr(self,name,command) but without tracing etc # (...CEBNOTE: and so you can't edit a tkparameterizedobject # w/buttons with another tkparameterizedobject because the # button parameters all skip translation etc. Instead should # handle their translation. But we're not offering a GUI # builder so it doesn't matter.) #button['relief']='flat' # and set size from Button #if size_param.size: #    button['width']=size[0] #    button['height']=size[1] # widget_options override things from parameter # hACK: tuple to work around strange list parsing tkinter/tcl # what a mess #w.configure(state='readonly') # CEBALERT: why necessary? # does it get changed somewhere else by mistake? e.g. does # plotgrouppanel switch disabled to normal and normal to # disabled without checking that normal isn't readonly? # CEBALERT: need to document how & why people should use 'sort_fn_args' # and 'new_default' when calling pack_param(). Also, simplify it if # possible. # no sort specified: defaults to sort() # whatever was there is out of date now Return a Tkinter.OptionMenu to represent Parameter 'name'.          In addition to Tkinter.OptionMenu's usual options, the         following additional ones may be included in widget_options:          * sort_fn_args: if widget_options includes 'sort_fn_args',           these are passed to the sort() method of the list of           *objects* available for the parameter, and the names are           displayed sorted in that order.  If 'sort_fn_args' is not           present, the default is to sort the list of names using its           sort() method.          * new_default: if widget_options includes 'new_default':True,           the currently selected value for the widget will be set           to the first item in the (possibly sorted as above) range.           Otherwise, the currently selected value will be left as the           current value. #param = self.get_parameter_object(name) #self._update_translator(name,param) ## sort the range for display # CEBALERT: extend OptionMenu so that it # (a) supports changing its option list (subject of a previous ALERT) # (b) supports sorting of its option list # (c) supports selecting a new default # Combobox looks bad with standard theme on my ubuntu # (and 'changed' marker - blue text - not visible). # Combobox (along with Checkbutton?) somehow sets its # associated textvariable without calling that textvariable's # set() method.  Therefore, to update the Selector's help text # when an item is selected, we bind to the # <<ComboboxSelected>> event. # i.e. we can represent with ClassSelector #parameter_window.title(PO_to_edit.name+' parameters') # CEBALERT: more dynamic class changes to make it impossible # to follow what is happening...ParametersFrame needs some # reorganization... ### refresh() ### #param = self.get_parameter_object(name) #value = self.get_parameter_value(name) #w = ListWidget(frame,variable=tkvar,**widget_options) #help_text = getdoc(self._string2object(name,tkvar._original_get())) #self.balloon.bind(w,help_text) Return a TaggedSlider to represent parameter 'name'.          The slider's bounds are set to those of the Parameter. # TaggedSlider needs BOTH bounds (neither can be None) # have to do the lookup because subclass might override default Return a Tkinter.Checkbutton to represent parameter 'name'. # CB: might be necessary to pass actions to command option of Checkbutton; # could be cause of test pattern boolean not working? Return a Tkinter.Entry to represent parameter 'name'. ################################################################################ # End WIDGET CREATION ################################################################################ # CB: the colors etc for indication are only temporary Set the parameter's label to:          - blue if the GUI value differs from that set on the object          - red if the text doesn't translate to a correct value          - black if the GUI and object have the same value # this scheme is incompatible with tile # (tile having the right idea about how to do this kind of thing!) # can't change state of button #CEBALERT uh-oh Convert a Parameter name s to a string suitable for display,         if pretty_parameters is True. # CEBALERT: is there no "file entry" widget for Tkinter?  I see there # is one for Tix, but while that is listed as being in Python's # standard library, it's rarely actually included... ###################################################################### ###################################################################### Abstract class that      Translators handle translation between objects and their     string representations in the GUI.          last_string2object_failed is a flag that can be set to indicate that         the last string-to-object translation failed.         (TkParameterized checks this attribute for indicating errors to         the user.) # CEBALERT: why class attr? Copy only translator-specific state. Performs no translation. For use with Parameter types such as     Boolean and String, where the representation     of the object in the GUI is the object itself. # Error messages: need to change how they're reported #JABALERT: Shouldn't this be building a new list out of the string? Translates a string to an object by eval()ing the string in     __main__.__dict__ (i.e. as if the string were typed at the     commandline), and translates an object to a string by     repr(object). # the whole last_string deal is required because of execing in main #print "OUT:" #print "OUT:", str(sys.exc_info()[0])[11::]+" ("+str(inst)+")" # CEBALERT: clean up # HIDDEN # CEBALERT: I hacked on support for non-hashable types like array (now # have cache as {string: id(object)} and _cache_objects as # {id(object): object}). The whole concept of translators needs # revisiting anyway. ## instantiate if it's just a class ## replace class if we already have object ## # because this one's object2string can modify cache Displays and allows instantaneous editing of the Parameters     of a supplied Parameterized.      Changes made to Parameter representations on the GUI are     immediately applied to the underlying object. Return values to class defaults. # CEBALERT: this is a Frame, so close isn't likely to make # sense. But fortunately the close button acts on master. # Just be sure not to use this button when you don't want # the master window to vanish (e.g. in the model editor). Create a ParametersFrame with the specifed master, and         representing the Parameters of parameterized_object.          on_set is an optional function to call whenever any of the         GUI variables representing Parameter values is set() by the         GUI (i.e. by the user). Since a variable's value is not         necessarily changed by such a set(), on_modify is another         optional function to call only when a GUI variable's value         actually changes. (See TkParameterized for more detail.) ## Frame for the Parameters # CEBALERT: just because callers assume this pack()s itself. # Really it should be left to callers i.e. this should be removed. Return True if a parameter's precedence is below the display threshold. # interpret a precedence of None as 0 Add the buttons in their own panel (frame). ## Buttons # # Our button order (when all buttons present): # [Defaults] [Refresh] [Apply] [Close] # # Our button - Windows # Close(yes) - OK # Close(no ) - Cancel # [X]        - Cancel # Apply      - Apply # Defaults   - # Refresh    - Reset # # I think Windows users will head for the window's [X] # when they want to close and cancel their changes, # because they won't know if [Close] saves their changes # or not (until they press it, and find that it asks). # # # Some links that discuss and address what order to use for buttons: # # http://java.sun.com/products/jlf/ed2/book/HIG.Dialogs2.html # http://developer.kde.org/documentation/books/kde-2.0-development/ch08lev1sec6.html # http://developer.kde.org/documentation/standards/kde/style/dialogs/index.html # http://doc.trolltech.com/qq/qq19-buttons.html # Catch click on the [X]: like clicking [Close] # CEBALERT: but what if this frame isn't in its own window! See Refresh parameter. See Defaults parameter. # tag_set() must be called in order to visually update the slider # CEBALERT: why doesn't this first check that something has actually changed? See Close parameter. # hmm # (name for class is None) # can only set window title on a window (model editor puts frame in another frame) ### Pack all of the non-hidden Parameters # hide Defaults button for classes Wipe old labels and widgets from screen. # e.g. buttons have None for label ('notNonelabel') # CB: (I know about the code duplication here & in tkpo) # some params appear to have no docs!!! # We want widgets to stretch to both sides... # ...except Checkbuttons, which should be left-aligned. # widgets expand to fill frame #e.g. buttons have None for label ('notNonelabel') ### sort Parameters by reverse precedence ### create the labels & widgets ### add widgets & labels to screen in a grid #self.event_generate("<<SizeRight>>") # CEBALERT: rename Open a new window containing a ParametersFrame (actually, a         type(self)) for the PO in _currently_selected_widget. # CEBALERT: simplify this lookup by value # CEBALERT: should have used get_parameter_value(param_name)? ### CEBALERT: confusing? ### ############################ # CEBALERT: don't want EditingParametersFrameWithApply # CEBALERT: need to get the list item to update in parent if editing via a right click # properties frame. need to get a new representation into translator. #parameter_frame._apply_hooks.append(lambda *args:self._refresh_value(param_name)) # CEBALERT: need to sort out all this stuff in the tkpo/pf # hierarchy ##     def unpack_param(self,param_name): ##     def hide_param(self,param_name): ##     def unhide_param(self,param_name): Displays and allows editing of the Parameters of a supplied     Parameterized.      Changes made to Parameter representations in the GUI are not     applied to the underlying object until Apply is pressed. # CEBENHANCEMENT: might be nice to make Apply button blue like the # unapplied changes, but can't currently set button color. Set object's Parameters to displayed values.\n                           When editing a class, sets the class defaults                           (i.e. acts on the class object). ### CEBALERT: describe why this apply is different from Apply # CEBALERT: should use existing code # don't want immediate updates, so unbind update events # (don't want to update parameters immediately) Return True if any one of the packed parameters' displayed values is different from         that on the object. #print "ParametersFrame._handle_gui_set",p_name # CEBERRORALERT: dialog box should include a cancel button # Also, changes are *not* applied if one of the boxes is in # error. #if self._tkvar_changed(name): #and self._tkvar_changed(name): # CEBALERT: because overriding a method that\s already been # installed as a callback does nothing # CEBALERT: how does apply button's status change now? Doesn't # work properly for list editing; stays grey until clicked twice. #print self._tkvars[name]._checking_get() # CEBALERT: taggedsliders need to have tag_set() called to update slider # CB: can override tracefn so that Apply/Refresh buttons are enabled/disabled as appropriate Edit the Parameters of a supplied parameterized instance or class.      Specify with_apply=False for a ParametersFrame (which immediately     updates the object - no need to press the Apply button).      Extra params are passed to the ParametersFrame constructor. # Barely wrapped tooltip from tklib. # CB: this isn't the right way to do it, and it breaks menubar # tips for some reason, but user code didn't have to change. e.g. for a Button b and a Menu m with item 'Quit' in a T.Toplevel t          balloon = Balloon(t)         balloon.bind(b,'some guidance')         balloon.bind(m,'Quit','more guidance') ##     def tagbind(self,*args,**kw): ##         print "### Balloon.tagbind(): not yet implemented error ###" # CEBALERT: should be renamed to something like IndexMenu, but then # I am not sure if some of our tk hacks would work (e.g. in topoconsole, # we set something for Menu on linux so that the popup behavior is # reasonable). Tkinter Menu, but with a way to access entries by name.      Entries can be accessed via the label supplied when the entry is     add()ed or insert()ed. For an entry whose label could change, you     can supply 'indexname', which can then be used to access the entry     no matter what the label might have become. ## (Original Menu class is in lib/python2.4/lib-tk/Tkinter.py) Return the Tkinter index, whether given an indexname or index         (where index could be an int or Tkinter position e.g. 'end'). # pass through tkinter to get 'end' etc converted to index Return the indexname, whether given an indexname or index         (where index could be an int or Tkinter position e.g. 'end').          The returned value will be None if index refers to an unnamed         entry. # Creates two internal indexes to track names and commands # rename to indexnames2index or similar # indexname will be as specified by 'indexname' in cnf or kw, # or else as specified by 'label' in cnf or kw. # # indexname will be None if neither indexname nor label is # specified in cnf or kw. # this pain is to keep the actual item, if it's a menu or a command, available to access See Tkinter.Menu.add(), but 'indexname' can also be supplied.          If supplied, indexname must be unique. If label is supplied without         indexname, then label must be unique. See Tkinter.Menu.insert(), but index can also be specified as text. # increase index of any item after insertion point # decrease index of any item after deletion point If index2 is not specified, deletes the menu item at index1         (an indexname or tk integer index).          If index2 is specified, deletes menu items in         range(index1,index2+1). # __delete shifts the remaining items one position back, # so repeatedly deleting the first item in the range will # delete all of them ########## METHODS OVERRIDDEN FOR CONVENIENCE Configure a menu item at INDEX. Invoke a menu item identified by INDEX and execute         the associated command. # other methods can be overriden if they're needed Widget for manipulating a numeric value using either a slider or a     text-entry box, keeping the two values in sync.      Generates a number of Events:      <<TagFocusOut>>  - tag loses focus     <<TagReturn>>    - Return pressed in tag     <<SliderSet>>    - slider is clicked/dragged On clicking or dragging the slider, the tag value is set         to the slider's value.          On pressing Return in or moving focus from the tag, the slider         value is set, but also:          * the range of the slider is adjusted (e.g. to fit a larger           max value)          * the resolution of the slider is adjusted based on the           the value in the tag (e.g. 0.01 in the tag gives a           resolution of 0.01), also taking into account the precision           of the value in the tag (e.g. 0.0100 gives a resolution           of 0.0001). # CEBALERT: ...although respecting the precision isn't always # helpful because the slider can still have a limited # resolution anyway (from its length and the range, given the # size of a pixel...) # CEBALERT: shouldn't this be calling set_slider_bounds()? # no variable: we control the slider ourselves After entering a value into the tag, this method should be         called to set the slider correctly.          (Called automatically for tag's <Return> and <FocusOut>         events.) # Set slider resolution. This is important because # we want the slider to be positioned at the exact # tag value. Set new lower and upper bounds for the slider.          If specified, inclusive_bounds should be a sequence of length         2 specifying True or False for the lower and upper bounds. # CB: why isn't this used for [] access? What should this # be called? Is it configure? TaggedSlider is a compound widget. In most cases, config         options should be passed to one of the component widgets         (i.e. the tag or the slider). For some options, however,         we need to handle them being set on the widget as a whole;         further, some of these should be applied to both component         widgets, but some should just be applied to one.          Options handled:         * state (applied to both)         * background, foreground (applied to tag only) Only state, background, and foreground options are                 currently supported for this widget; set options on                 either the component tag or slider instead. # CEBALERT: need to return same object as Tkinter would. Calls the tag's get() method.          Helps to match behavior of other Tkinter Widgets. # CEBALERT: three different events for max. flexibility...but # often a user will just want to know if the value was set. Could # also generate a "<<TaggedSliderSet>>" event each time, which a # user could just look for. Or could these events all be children # of a <<TaggedSliderSet>>? # CEBALERT: how to find n. dp simply? Use the actual number in the box to set the slider's resolution,         so that user-entered resolutions are respected (e.g. 0.1 vs 0.1000).          If that's not possible (e.g. there's text in the box), use the         value contained in the variable (because whatever owns the         variable could have performed a conversion of the text -         TkParameterized does this, for instance).          Leaves the resolution as it was if no number is available. # 1st choice is to get the actual number in the box: # allows us to respect user-entered resolution # (e.g. 0.010000) # probably tclerror # ...but that might have been text.  2nd choice is to # get the value from the variable (e.g. 0.01) # probably tclerror # can't set a new resolution If the value in the box can't be converted to a float, the slider doesn't get set. A Tkinter Button that takes the focus when the mouse <Enter>s.      (Tkinter.Button doesn't get focus even when it's clicked,     and only <Enter> and <Leave> events work for buttons.) #self['highlightthickness']=0 # CB: tklib has status bar and scrolled window (among other widgets); # should try them out (although they're tk only...) because we might # not need the scrodget pacakge. # Might wonder why we need <<SizeRight>> event, and don't just use the # <Configure> event for calling sizeright: Can't distinguish manual # resize from autoresizing. XXXX      Content to be scrolled should go in the 'content' frame. #,expand="no",fill="both") #self.status = StatusBar(botframe) #if status: #    self.status.pack(side="bottom",fill="both",expand="yes") # CEBALERT: specifying 20 necessary because otherwise nothing # requests any height for the frame. I feel there should be # way to have the (status) label request the right height but # make no request about the width. # required? presumably should be deleted #or topo.sim.timer # trace the variable so that at 100 we can destroy the window Close the specified progress window if the value of progress_var has reached 100. # delete the variable trace (necessary?) #self.progress_var.trace_vdelete('w',self.progress_trace_name) Declare that running should be interrupted. Graphically displays progress information for a SomeTimer object.      ** Currently expects a 0-100 (percent) value *** # CEBALERT: would like time to be displayed inside the bar, # but I don;t know how to do that. #side="bottom") Interrupt a procedure. #        Stop a running simulation. # #        The simulation can be interrupted only on round integer #        simulation times, e.g. at 3.0 or 4.0 but not 3.15.  This #        ensures that stopping and restarting are safe for any #        model set up to be in a consistent state at integer #        boundaries, as the example Topographica models are.""") # such a hack # CEB: slightly modifed code from http://code.activestate.com/recipes/576688/ # CEBALERT: this seems better than the tooltip provided by # tklib. Should probably use this instead of 'Balloon' everywhere. I # need to use Tooltip rather than Balloon for the status bar, because # Tooltip allows a function to be passed for generating the text. I # wish tk included a useful tooltip widget by default. Provides a ToolTip widget for Tkinter.     To apply a ToolTip to any Tkinter widget, simply pass the widget to the     ToolTip constructor Initialize the ToolTip          Arguments:           wdgt: The widget this ToolTip is assigned to           msg:  A static string message assigned to the ToolTip           msgFunc: A function that retrieves a string to use as the ToolTip text           delay:   The delay in seconds before the ToolTip appears(may be float)           follow:  If True, the ToolTip follows motion, otherwise hides # The parent of the ToolTip is the parent of the ToolTips widget # Initalise the Toplevel # Hide initially # The ToolTip Toplevel should have no frame or title bar # The msgVar will contain the text displayed by the ToolTip #self.msgVar.set( 'No message provided' ) # The test of the ToolTip is displayed in a Message widget # Add bindings to the widget.  This will NOT override bindings that the widget already has Spawn the ToolTip.  This simply makes the ToolTip eligible for display.         Usually this is caused by entering the widget          Arguments:           event: The event that called this funciton # The after function takes a time argument in miliseconds Displays the ToolTip if the time delay has been long enough Processes motion within the widget.          Arguments:           event: The event that called this function # If the follow flag is not set, motion within the widget will make the ToolTip dissapear # Offset the ToolTip 10x10 pixes southwest of the pointer # Try to call the message function.  Will not change the message if the message function is None or the message function fails Hides the ToolTip.  Usually this is caused by leaving the widget          Arguments:           event: The event that called this function ##### # CEBALERT: hacks until we're using styles. # # This won't look good across platforms. Should get defaults. ##### # remove previous after() #self.mnavframe.pack(side='right') #self.mnavframe.pack_forget() #        # CEBALERT: would like to lock only the window with the error, #        # not the whole app, but I don't know how to do that. #        try: #            self.grab_set() #        except: # get tclerror if another app has grab #            pass #        try: #            self.grab_release() #        except: #            pass # CEBALERT: rename A ScrolledWindow with extra features intended to be common to all     windows of an application.      Currently this only includes a window icon, but we intend to     add a right-click menu and possibly more. ### Universal right-click menu # CB: not currently used by anything but the plotgrouppanels # self.context_menu = Tkinter.Menu(self, tearoff=0) # self.bind("<<right-click>>",self.display_context_menu) # status bar is currenlty inside scrolled area (a feature # request is to move it outside ie replace self.content with # just self) # place doesn't interfere with parent's geometry # (don't want status bar to cause horizontal resizing, # but DO want it to cause vertical resizing - the vertical # part doesn't work, see earlier HACKALERT by botframe) # CEBALERT: doesn't work on OS X, and is a strange color on # Windows.  To get a proper icon on OS X, we probably have to # bundle into an application package or something like that. # On OS X, could possibly alter the small titlebar icon with something like: # self.attributes("-titlepath","/Users/x/topographica/AppIcon.icns") # CEBALERT: as with several of my compound widgets, need to deal # with **config passed to __init__, by putting through config() # (and so also need to add config() methods where currently # missing). #CEBALERT: presumably disabledforeground won't work work with styles... ### Right-click menu for widgets # CEBALERT: I can't work out how to make the right-click event # bound to the widget already (by the ParametersFrame) ever # activate!  It must be overwritten or something by tk. So I # have to duplicate the right-click menu code here. Popup the right-click menu. # all options are passed to the entry # state=disabled->no right click menu # CEBALERT: dict stuff is just half implemented! does enough # to support list subclasses # each item is just a Parameter (i.e. not specific) # got to be a new class to get independent sets of parameters #new_class.params('name').hidden=True # that's a hack; need to get classes right ############################################################ # CEBALERT ## CEBALERT: when we come to represent dicts in the GUI, ## will have to deal with name clashes (e.g. for name parameter) ## # allow all modifications # remove any of the standard params (e.g. name) that haven't # been added to represented # CB: don't move any of this method up to parameterized # without considering the consequences! ## grow or shrink original list if necessary #name = 'a'*(len(self.my_params())+1) # Problems: # * + button gets hidden as more rows added (unless window made #   bigger) # # * window doesn't open at right width to display contents #   list item contents # # * I suspect there's a bug where you can get to a situation in #   which editing one box will result in changes to another, #   thanks to the ridiculously and unnecessarily complex code #   for tracking items. But I can't currently demonstrate such a #   a bug... ## hack to find last row ### CB: should use button parameter and pack_param ### # CEBALERT: name this, and there's duplication with represented etc. # getting ridiculous. make sure old widgets are wiped from # the earth. who knows how many other times I should have # been doing this in the gui? # (see alert at start of method) ## delete cached params() # HACK! At least extract relevant parts # should refactor pack_params to get out adding of single param # move ones below up one #### HACK TO FIND NEW NAME BY MATCHING ROW #### #print "regrid",current_name,current_pos #print "regrid",new_name,new_pos # CEBALERT: why not in super? # CEBALERT: should just have been unpacked earlier # detect length change # detect order change # CEBALERT: because ### CEBALERT: should use button parameter & pack_param ###