Changeset 937


Ignore:
Timestamp:
May 30, 2013 4:01:11 PM (10 years ago)
Author:
toby
Message:

More improvements to ValidatedTxtCtrl?

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/GSASIIgrid.py

    r936 r937  
    146146    when invalid input is supplied. As valid values are typed,
    147147    they are placed into the dict or list where the initial value
    148     came from. The type of the initial value must be int, float or str;
    149     this type is preserved.
     148    came from. The type of the initial value must be int,
     149    float or str or None (see :obj:`key` and :obj:`typeHint`);
     150    this type (or the one in :obj:`typeHint`) is preserved.
    150151
    151152    Float values can be entered in the TextCtrl as numbers or also
     
    155156
    156157    :param wx.Panel parent: name of panel or frame that will be
    157       the parent to the TextCtrl
     158      the parent to the TextCtrl. Can be None.
    158159
    159160    :param dict/list loc: the dict or list with the initial value to be
    160       placed in the TextCtrl
    161 
    162     :param int/str key: the dict key or the list index for the value   
    163 
    164     :param bool notBlank: if True (default) blank values are not allowed
     161      placed in the TextCtrl.
     162
     163    :param int/str key: the dict key or the list index for the value to be
     164      edited by the TextCtrl. The ``loc[key]`` element must exist, but may
     165      have value None. If None, the type for the element is taken from
     166      :obj:`typeHint` and the value for the control is set initially
     167      blank (and thus invalid.) This is a way to specify a field without a
     168      default value: a user must set a valid value.
     169      If the value is not None, it must have a base
     170      type of int, float, str or unicode; the TextCrtl will be initialized
     171      from this value.
     172
     173    :param bool notBlank: if True (default) blank values are invalid
    165174      for str inputs.
    166175     
    167     :param number min: Minimum allowed value. If None (default) the
    168       lower limit is unbounded
    169 
    170     :param number max: Maximum allowed value. If None (default) the
     176    :param number min: minimum allowed valid value. If None (default) the
     177      lower limit is unbounded.
     178
     179    :param number max: maximum allowed valid value. If None (default) the
    171180      upper limit is unbounded
    172181
     
    176185
    177186    :param function OKcontrol: specifies a function or method that will be
    178       called when the input is validated. It is supplied with one argument
    179       which is False if the TextCtrl contains an invalid value and True if
    180       the value is valid. Note that this function should check all values
    181       in the dialog when True, since other entries might be invalid.
     187      called when the input is validated. The called function is supplied
     188      with one argument which is False if the TextCtrl contains an invalid
     189      value and True if the value is valid.
     190      Note that this function should check all values
     191      in the dialog when True, since other entries might be invalid.
     192      The default for this is None, which indicates no function should
     193      be called.
     194
     195    :param function OnLeave: specifies a function or method that will be
     196      called when the focus for the control is lost.
     197      The called function is supplied with (at present) three keyword arguments:
     198
     199        :invalid: (*bool*) True if the value for the TextCtrl is invalid
     200        :value:   (*int/float/str*)  the value contained in the TextCtrl
     201        :tc:      (*wx.TextCtrl*)  the TextCtrl name
     202
     203      The number of keyword arguments may be increased in the future, if needs arise,
     204      so it is best to code these functions with a \*\*kwargs argument so they will
     205      continue to run without errors
     206
     207      The default for OnLeave is None, which indicates no function should
     208      be called.
     209
     210    :param type typeHint: the value of typeHint is used if the initial value
     211      for the dict/list element ``loc[key]`` is None. In this case typeHint
     212      must be int or float, which specifies the type for input to the TextCtrl.
     213      Defaults as None.
    182214
    183215    '''
    184216    def __init__(self,parent,loc,key,notBlank=True,min=None,max=None,
    185                  size=None,OKcontrol=None):
     217                 size=None,OKcontrol=None,OnLeave=None,typeHint=None):
     218        # save passed values needed outside __init__
    186219        self.result = loc
    187220        self.key = key
    188221        self.OKcontrol=OKcontrol
    189         self.invalid = None
     222        self.OnLeave = OnLeave
     223        # initialization
     224        self.invalid = False   # indicates if the control has invalid contents
     225        self.evaluated = False # set to True when the validator recognizes an expression
    190226        val = loc[key]
    191         if isinstance(val,int):
     227        if isinstance(val,int) or (val is None and typeHint is int):
    192228            wx.TextCtrl.__init__(
    193                 self,parent,wx.ID_ANY,str(val),
     229                self,parent,wx.ID_ANY,
    194230                validator=NumberValidator(int,result=loc,key=key,min=min,max=max,
    195231                                          OKcontrol=OKcontrol)
    196232                )
    197             self.invalid = not self.Validate()
    198         elif isinstance(val,int) or isinstance(val,float):
     233            if val is not None:
     234                self.SetValue(str(val))
     235            else:
     236                self.ShowStringValidity()
     237        elif isinstance(val,float) or (val is None and typeHint is float):
    199238            wx.TextCtrl.__init__(
    200                 self,parent,wx.ID_ANY,str(val),
     239                self,parent,wx.ID_ANY,
    201240                validator=NumberValidator(float,result=loc,key=key,min=min,max=max,
    202241                                          OKcontrol=OKcontrol)
    203242                )
    204             self.invalid = not self.Validate()
    205         elif isinstance(val,str):
    206             wx.TextCtrl.__init__(self,parent,wx.ID_ANY,str(val))
     243            if val is not None:
     244                self.SetValue(str(val))
     245            else:
     246                self.ShowStringValidity()
     247        elif isinstance(val,str) or isinstance(val,unicode):
     248            wx.TextCtrl.__init__(self,parent,wx.ID_ANY,val)
    207249            if notBlank:
    208250                self.Bind(wx.EVT_CHAR,self._onStringKey)
     
    210252            else:
    211253                self.invalid = False
     254        elif val is None:
     255            raise Exception,("ValidatedTxtCtrl error: value of "+str(key)+
     256                             " element is None and typeHint not defined as int or float")
    212257        else:
    213             raise Exception,"ValidatedTxtCtrl Unknown type "+str(type(val))
     258            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
     259                             ") type: "+str(type(val)))
    214260        if size: self.SetSize(size)
     261        # When the mouse is moved away or the widget loses focus
     262        # display the last saved value, if an expression
     263        self.Bind(wx.EVT_LEAVE_WINDOW, self._onLoseFocus)
     264        self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
    215265
    216266    def _onStringKey(self,event):
     
    246296            self.SetForegroundColour("black")
    247297            self.Refresh()
    248             self.result[self.key] = val
    249298            if self.OKcontrol and previousInvalid:
    250299                self.OKcontrol(True)
     300        self.result[self.key] = val # always store the result
     301
     302    def _onLoseFocus(self,event):
     303        if self.evaluated: self.EvaluateExpression()
     304        if self.OnLeave: self.OnLeave(invalid=self.invalid,
     305                                      value=self.result[self.key],
     306                                      tc=self)
     307           
     308    def EvaluateExpression(self):
     309        '''Show the computed value when an expression is entered to the TextCtrl
     310        Make sure that the number fits by truncating decimal places and switching
     311        to scientific notation, as needed.
     312        Called on loss of focus.
     313        '''
     314        if self.invalid: return # don't substitute for an invalid expression
     315        if not self.evaluated: return # true when an expression is evaluated
     316        if self.result is not None: # retrieve the stored result
     317            val = self.result[self.key]
     318            self.SetValue(G2py3.FormatValue(val))
     319        self.evaluated = False # expression has been recast as value, reset flag
    251320       
    252321class NumberValidator(wx.PyValidator):
     
    259328      If the number is valid, it is saved in result[key]
    260329
    261     :param type typ: data type, int or float
    262 
    263     :param bool positiveonly: used for typ=int. If True, only positive
    264       integers are allowed (default False)
     330    :param type typ: the base data type. Must be int or float.
     331
     332    :param bool positiveonly: If True, negative integers are not allowed
     333      (default False). This prevents the + or - keys from being pressed.
     334      Used with typ=int; ignored for typ=float.
    265335
    266336    :param number min: Minimum allowed value. If None (default) the
     
    282352        'Create the validator'
    283353        wx.PyValidator.__init__(self)
     354        # save passed parameters
    284355        self.typ = typ
    285356        self.positiveonly = positiveonly
     
    289360        self.key = key
    290361        self.OKcontrol = OKcontrol
    291         self.evaluated = False
    292         # When the mouse is moved away or the widget loses focus
    293         # display the last saved value
    294         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
    295         self.Bind(wx.EVT_KILL_FOCUS, self.OnLeave)
     362        # set allowed keys by data type
    296363        if self.typ == int and self.positiveonly:
    297364            self.validchars = '0123456789'
    298             self.Bind(wx.EVT_CHAR, self.OnChar)
    299365        elif self.typ == int:
    300366            self.validchars = '0123456789+-'
    301             self.Bind(wx.EVT_CHAR, self.OnChar)
    302367        elif self.typ == float:
    303368            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
    304369            # also addition, subtraction, division, multiplication, exponentiation
    305370            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
    306             self.Bind(wx.EVT_CHAR, self.OnChar)
    307371        else:
    308372            self.validchars = None
     373        self.Bind(wx.EVT_CHAR, self.OnChar)
    309374    def Clone(self):
    310375        'Create a copy of the validator, a strange, but required component'
     
    330395        If the value is valid, save it in the dict/list where
    331396        the initial value was stored, if appropriate.
     397
     398        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
     399          is associated with.
    332400        '''
    333401        tc.invalid = False # assume invalid
     
    335403            val = self.typ(tc.GetValue())
    336404        except (ValueError, SyntaxError) as e:
    337             if self.typ is float: # for float values, see if an expression can be
    338                 # evaluated
     405            if self.typ is float: # for float values, see if an expression can be evaluated
    339406                val = G2py3.FormulaEval(tc.GetValue())
    340407                if val is None:
     
    342409                    return
    343410                else:
    344                     self.evaluated = True
     411                    tc.evaluated = True
    345412            else:
    346413                tc.invalid = True
     
    356423
    357424    def ShowValidity(self,tc):
    358         'Set the control colors to show invalid input'
     425        '''Set the control colors to show invalid input
     426
     427        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
     428          is associated with.
     429
     430        '''
    359431        if tc.invalid:
    360432            tc.SetForegroundColour("red")
     
    370442            return True
    371443
    372     def OnLeave(self, event):
    373         '''Show the computed value when an expression is entered to the TextCtrl
    374         Make sure that the number fits by truncating decimal places and switching
    375         to scientific notation, as needed.
    376         Called on loss of focus.
    377         '''
    378         tc = self.GetWindow()
    379         if tc.invalid: return # don't substitute for an invalid expression
    380         if not self.evaluated: return # true when an expression is evaluated
    381         self.evaluated = False
    382         if self.result is not None: # retrieve the stored result
    383             val = self.result[self.key]
    384             tc.SetValue(G2py3.FormatValue(val))
    385 
    386444    def CheckInput(self,previousInvalid):
    387445        '''called to test every change to the TextCtrl for validity and
     
    392450        If valid, check for any other invalid entries only when
    393451        changing from invalid to valid, since that is slower.
     452
     453        :param bool previousInvalid: True if the TextCtrl contents were
     454          invalid prior to the current change.
    394455        '''
    395456        tc = self.GetWindow()
     
    435496
    436497################################################################################
     498def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[],
     499                 title='Edit items',header='',size=(300,250)):
     500    '''Shell routine to call a ScrolledMultiEditor dialog. See
     501    :class:`ScrolledMultiEditor` for parameter definitions.
     502
     503    :returns: True if the OK button is pressed; False if the window is closed
     504      with the system menu or the Close button.
     505
     506    '''
     507    dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl,
     508                              title,header,size)
     509    if dlg.ShowModal() == wx.ID_OK:
     510        dlg.Destroy()
     511        return True
     512    else:
     513        dlg.Destroy()
     514        return False
     515
    437516class ScrolledMultiEditor(wx.Dialog):
    438517    '''Define a window for editing a potentially large number of dict- or
     
    472551      (300,250).
    473552
    474     :returns: the wx.Dialog created here. Use .ShowModal() to
     553    :returns: the wx.Dialog created here. Use method .ShowModal() to display it.
    475554   
    476     ''Example for use of ScrolledMultiEditor:''
     555    *Example for use of ScrolledMultiEditor:*
    477556
    478557    ::
     
    484563                 print d[k]
    485564
    486     ''Example definitions for dictlst and elemlst:''
     565    *Example definitions for dictlst and elemlst:*
    487566
    488567    ::
     
    12301309################################################################################
    12311310class AddHelp(wx.Menu):
    1232     '''This class a single entry for the help menu (used on the Mac only):
     1311    '''For the Mac: creates an entry to the help menu of type
    12331312    'Help on <helpType>': where helpType is a reference to an HTML page to
    1234     be opened
    1235 
    1236     NOTE: the title when appending this menu should be '&Help' so the wx handles
    1237     it correctly.
     1313    be opened.
     1314
     1315    NOTE: when appending this menu (menu.Append) be sure to set the title to
     1316    '&Help' so that wx handles it correctly.
    12381317    '''
    12391318    def __init__(self,frame,helpType,helpLbl=None,title=''):
     
    12551334################################################################################
    12561335class MyHtmlPanel(wx.Panel):
    1257     '''Defines a panel to display Help information'''
     1336    '''Defines a panel to display HTML help information, as an alternative to
     1337    displaying help information in a web browser.
     1338    '''
    12581339    def __init__(self, frame, id):
    12591340        self.frame = frame
     
    13131394################################################################################
    13141395class DataFrame(wx.Frame):
    1315     '''Create the dataframe window and all the entries in menus.
    1316     The binding is for the menus is not done here, but rather is done
    1317     where the functions can be accessed (in various GSASII*GUI modules).
     1396    '''Create the data item window and all the entries in menus used in
     1397    that window. For Linux and windows, the menu entries are created for the
     1398    current data item window, but in the Mac the menu is accessed from all
     1399    windows. This means that a different menu is posted depending on which
     1400    data item is posted. On the Mac, all the menus contain the data tree menu
     1401    items, but additional menus are added specific to the data item.
     1402
     1403    Note that while the menus are created here,
     1404    the binding for the menus is done later in various GSASII*GUI modules,
     1405    where the functions to be called are defined.
    13181406    '''
    13191407    def Bind(self,*args,**kwargs):
     
    29443032    frm.Show(True)
    29453033    Data1 = {
    2946         'Order':0,
     3034        'Order':1,
    29473035        'omega':'string',
    29483036        'chi':2.0,
     
    29623050    header="""This is a longer\nmultiline and perhaps silly header"""
    29633051    dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
    2964                                     header=header)
     3052                              header=header)
    29653053    print Data1
    29663054    if dlg.ShowModal() == wx.ID_OK:
    29673055        for d,k in zip(dictlst,elemlst):
    29683056            print k,d[k]
    2969     #app.MainLoop()
     3057    dlg.Destroy()
     3058    if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
     3059                               header=header):
     3060        for d,k in zip(dictlst,elemlst):
     3061            print k,d[k]
     3062
     3063#app.MainLoop()
  • trunk/GSASIIpy3.py

    r933 r937  
    2323    be evaluated.
    2424
    25     :param str string: Character string c
     25    :param str string: Character string containing a Python expression
     26      to be evaluated.
     27
     28    :returns: the value for the expression as a float or None if the expression does not
     29      evaluate to a valid number.
     30   
    2631    '''
    2732    try:
     
    3338
    3439def FormatValue(val,maxdigits=10):
    35     '''Format a float to fit in maxdigits spaces, showing as much
    36     precision as possible, more or less
     40    '''Format a float to fit in ``maxdigits`` spaces, showing as much
     41    precision as possible, more or less.
     42
     43    :param float val: number to be formatted.
     44
     45    :param int maxdigits: the number of digits to be used for display of the
     46      number (defaults to 10).
     47
     48    :returns: a string with <= maxdigits characters (I hope). 
    3749    '''
    3850    # does the standard str() conversion fit?
Note: See TracChangeset for help on using the changeset viewer.