source: trunk/GSASIIctrlGUI.py @ 3056

Last change on this file since 3056 was 3056, checked in by toby, 4 years ago

expand error reporting with constraints (no fixes yet to prevent them)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 204.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIctrlGUI - Custom GSAS-II GUI controls
3########### SVN repository information ###################
4# $Date: 2017-09-08 19:17:29 +0000 (Fri, 08 Sep 2017) $
5# $Author: toby $
6# $Revision: 3056 $
7# $URL: trunk/GSASIIctrlGUI.py $
8# $Id: GSASIIctrlGUI.py 3056 2017-09-08 19:17:29Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIctrlGUI: Custom GUI controls*
12---------------------------------------------
13
14A library of GUI controls for reuse throughout GSAS-II
15
16'''
17import os
18import sys
19try:
20    import wx
21    import wx.grid as wg
22    # import wx.wizard as wz
23    import wx.aui
24    import wx.lib.scrolledpanel as wxscroll
25    import wx.html        # could postpone this for quicker startup
26    import matplotlib as mpl
27except ImportError:
28    raise
29    # Dumm 'wx' so this file can be imported
30    vals = ('TreeCtrl TextCtrl PyValidator Button ComboBox Choice CheckBox'.split() +
31            'Dialog ID_ANY OPEN'.split())
32    class Placeholder(object):
33        def __init__(self, vals):
34            for val in vals:
35                setattr(self, val, object)
36        def __getattr__(self, value):
37            if value[0].isupper():
38                return object
39            return Placeholder([])
40    wx = Placeholder(vals)
41    wxscroll = Placeholder(['ScrolledPanel'])
42    wg = Placeholder('Grid PyGridTableBase PyGridCellEditor'.split())
43import time
44import copy
45import webbrowser     # could postpone this for quicker startup
46
47import GSASIIpath
48GSASIIpath.SetVersionNumber("$Revision: 3056 $")
49import GSASIIdataGUI as G2gd
50import GSASIIpwdGUI as G2pdG
51import GSASIIpy3 as G2py3
52import GSASIIlog as log
53import GSASIIobj as G2obj
54
55# Define a short names for convenience
56WHITE = (255,255,255)
57DULL_YELLOW = (230,230,190)
58# Don't depend on wx, for scriptable
59try:
60    VERY_LIGHT_GREY = wx.Colour(235,235,235)
61    WACV = wx.ALIGN_CENTER_VERTICAL
62except:
63    # Don't depend on GUI
64    pass
65
66################################################################################
67#### Fixed definitions for wx Ids
68################################################################################
69def Define_wxId(*args):
70    '''routine to create unique global wx Id symbols in this module.
71    '''
72    for arg in args:
73        if GSASIIpath.GetConfigValue('debug') and not arg.startswith('wxID_'):
74            print 'Problem in name',arg
75        if arg in globals():
76            if GSASIIpath.GetConfigValue('debug'): print arg,'already defined'
77            continue
78        exec('global '+arg+';'+arg+' = wx.NewId()')
79
80################################################################################
81#### Tree Control
82################################################################################
83class G2TreeCtrl(wx.TreeCtrl):
84    '''Create a wrapper around the standard TreeCtrl so we can "wrap"
85    various events.
86   
87    This logs when a tree item is selected (in :meth:`onSelectionChanged`)
88
89    This also wraps lists and dicts pulled out of the tree to track where
90    they were retrieved from.
91    '''
92    #def SelectItem(self,event):
93    #    print 'Select Item'
94    #    import GSASIIobj as G2obj
95    #    G2obj.HowDidIgetHere()
96    #    wx.TreeCtrl.SelectItem(self,event)
97       
98    def __init__(self,parent=None,*args,**kwargs):
99        super(self.__class__,self).__init__(parent=parent,*args,**kwargs)
100        self.G2frame = parent.GetTopLevelParent()
101        self.root = self.AddRoot('Loaded Data: ')
102        self.SelectionChanged = None
103        self.textlist = None
104        log.LogInfo['Tree'] = self
105
106    def _getTreeItemsList(self,item):
107        '''Get the full tree hierarchy from a reference to a tree item.
108        Note that this effectively hard-codes phase and histogram names in the
109        returned list. We may want to make these names relative in the future.
110        '''
111        textlist = [self.GetItemText(item)]
112        parent = self.GetItemParent(item)
113        while parent:
114            if parent == self.root: break
115            textlist.insert(0,self.GetItemText(parent))
116            parent = self.GetItemParent(parent)
117        return textlist
118
119    def onSelectionChanged(self,event):
120        '''Log each press on a tree item here.
121        '''
122        if not self.G2frame.treePanel:
123            return
124        if self.SelectionChanged:
125            textlist = self._getTreeItemsList(event.GetItem())
126            if log.LogInfo['Logging'] and event.GetItem() != self.root:
127                textlist[0] = self.GetRelativeHistNum(textlist[0])
128                if textlist[0] == "Phases" and len(textlist) > 1:
129                    textlist[1] = self.GetRelativePhaseNum(textlist[1])
130                log.MakeTreeLog(textlist)
131            if textlist == self.textlist:
132                return      #same as last time - don't get it again
133            self.textlist = textlist
134            self.SelectionChanged(event)
135
136    def Bind(self,eventtype,handler,*args,**kwargs):
137        '''Override the Bind() function so that page change events can be trapped
138        '''
139        if eventtype == wx.EVT_TREE_SEL_CHANGED:
140            self.SelectionChanged = handler
141            wx.TreeCtrl.Bind(self,eventtype,self.onSelectionChanged)
142            return
143        wx.TreeCtrl.Bind(self,eventtype,handler,*args,**kwargs)
144
145    # commented out, disables Logging
146    # def GetItemPyData(self,*args,**kwargs):
147    #    '''Override the standard method to wrap the contents
148    #    so that the source can be logged when changed
149    #    '''
150    #    data = super(self.__class__,self).GetItemPyData(*args,**kwargs)
151    #    textlist = self._getTreeItemsList(args[0])
152    #    if type(data) is dict:
153    #        return log.dictLogged(data,textlist)
154    #    if type(data) is list:
155    #        return log.listLogged(data,textlist)
156    #    if type(data) is tuple: #N.B. tuples get converted to lists
157    #        return log.listLogged(list(data),textlist)
158    #    return data
159
160    def GetRelativeHistNum(self,histname):
161        '''Returns list with a histogram type and a relative number for that
162        histogram, or the original string if not a histogram
163        '''
164        histtype = histname.split()[0]
165        if histtype != histtype.upper(): # histograms (only) have a keyword all in caps
166            return histname
167        item, cookie = self.GetFirstChild(self.root)
168        i = 0
169        while item:
170            itemtext = self.GetItemText(item)
171            if itemtext == histname:
172                return histtype,i
173            elif itemtext.split()[0] == histtype:
174                i += 1
175            item, cookie = self.GetNextChild(self.root, cookie)
176        else:
177            raise Exception("Histogram not found: "+histname)
178
179    def ConvertRelativeHistNum(self,histtype,histnum):
180        '''Converts a histogram type and relative histogram number to a
181        histogram name in the current project
182        '''
183        item, cookie = self.GetFirstChild(self.root)
184        i = 0
185        while item:
186            itemtext = self.GetItemText(item)
187            if itemtext.split()[0] == histtype:
188                if i == histnum: return itemtext
189                i += 1
190            item, cookie = self.GetNextChild(self.root, cookie)
191        else:
192            raise Exception("Histogram #'+str(histnum)+' of type "+histtype+' not found')
193       
194    def GetRelativePhaseNum(self,phasename):
195        '''Returns a phase number if the string matches a phase name
196        or else returns the original string
197        '''
198        item, cookie = self.GetFirstChild(self.root)
199        while item:
200            itemtext = self.GetItemText(item)
201            if itemtext == "Phases":
202                parent = item
203                item, cookie = self.GetFirstChild(parent)
204                i = 0
205                while item:
206                    itemtext = self.GetItemText(item)
207                    if itemtext == phasename:
208                        return i
209                    item, cookie = self.GetNextChild(parent, cookie)
210                    i += 1
211                else:
212                    return phasename # not a phase name
213            item, cookie = self.GetNextChild(self.root, cookie)
214        else:
215            raise Exception("No phases found ")
216
217    def ConvertRelativePhaseNum(self,phasenum):
218        '''Converts relative phase number to a phase name in
219        the current project
220        '''
221        item, cookie = self.GetFirstChild(self.root)
222        while item:
223            itemtext = self.GetItemText(item)
224            if itemtext == "Phases":
225                parent = item
226                item, cookie = self.GetFirstChild(parent)
227                i = 0
228                while item:
229                    if i == phasenum:
230                        return self.GetItemText(item)
231                    item, cookie = self.GetNextChild(parent, cookie)
232                    i += 1
233                else:
234                    raise Exception("Phase "+str(phasenum)+" not found")
235            item, cookie = self.GetNextChild(self.root, cookie)
236        else:
237            raise Exception("No phases found ")
238
239    def GetImageLoc(self,TreeId):
240        '''Get Image data from the Tree. Handles cases where the
241        image name is specified, as well as where the image file name is
242        a tuple containing the image file and an image number
243        '''
244       
245        size,imagefile = self.GetItemPyData(TreeId)
246        if type(imagefile) is tuple or type(imagefile) is list:
247            return size,imagefile[0],imagefile[1]
248        else:
249            return size,imagefile,None
250
251    def UpdateImageLoc(self,TreeId,imagefile):
252        '''Saves a new imagefile name in the Tree. Handles cases where the
253        image name is specified, as well as where the image file name is
254        a tuple containing the image file and an image number
255        '''
256       
257        idata = self.GetItemPyData(TreeId)
258        if type(idata[1]) is tuple or type(idata[1]) is list:
259            idata[1] = list(idata[1])
260            idata[1][0] = [imagefile,idata[1][1]]
261        else:
262            idata[1]  = imagefile
263       
264    def SaveExposedItems(self):
265        '''Traverse the top level tree items and save names of exposed (expanded) tree items.
266        Done before a refinement.
267        '''
268        self.ExposedItems = []
269        item, cookie = self.GetFirstChild(self.root)
270        while item:
271            name = self.GetItemText(item)
272            if self.IsExpanded(item): self.ExposedItems.append(name)
273            item, cookie = self.GetNextChild(self.root, cookie)
274#        print 'exposed:',self.ExposedItems
275
276    def RestoreExposedItems(self):
277        '''Traverse the top level tree items and restore exposed (expanded) tree items
278        back to their previous state (done after a reload of the tree after a refinement)
279        '''
280        item, cookie = self.GetFirstChild(self.root)
281        while item:
282            name = self.GetItemText(item)
283            if name in self.ExposedItems: self.Expand(item)
284            item, cookie = self.GetNextChild(self.root, cookie)
285
286################################################################################
287#### TextCtrl that stores input as entered with optional validation
288################################################################################
289class ValidatedTxtCtrl(wx.TextCtrl):
290    '''Create a TextCtrl widget that uses a validator to prevent the
291    entry of inappropriate characters and changes color to highlight
292    when invalid input is supplied. As valid values are typed,
293    they are placed into the dict or list where the initial value
294    came from. The type of the initial value must be int,
295    float or str or None (see :obj:`key` and :obj:`typeHint`);
296    this type (or the one in :obj:`typeHint`) is preserved.
297
298    Float values can be entered in the TextCtrl as numbers or also
299    as algebraic expressions using operators + - / \* () and \*\*,
300    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
301    as well as appreviations s, sin, c, cos, t, tan and sq.
302
303    :param wx.Panel parent: name of panel or frame that will be
304      the parent to the TextCtrl. Can be None.
305
306    :param dict/list loc: the dict or list with the initial value to be
307      placed in the TextCtrl.
308
309    :param int/str key: the dict key or the list index for the value to be
310      edited by the TextCtrl. The ``loc[key]`` element must exist, but may
311      have value None. If None, the type for the element is taken from
312      :obj:`typeHint` and the value for the control is set initially
313      blank (and thus invalid.) This is a way to specify a field without a
314      default value: a user must set a valid value.
315       
316      If the value is not None, it must have a base
317      type of int, float, str or unicode; the TextCrtl will be initialized
318      from this value.
319     
320    :param list nDig: number of digits, places and optionally the format
321       ([nDig,nPlc,fmt]) after decimal to use for display of float. The format
322       is either 'f' (default) or 'g'. Alternately, None can be specified which
323       causes numbers to be displayed with approximately 5 significant figures
324       (Default=None).
325
326    :param bool notBlank: if True (default) blank values are invalid
327      for str inputs.
328     
329    :param number min: minimum allowed valid value. If None (default) the
330      lower limit is unbounded.
331      NB: test in NumberValidator is val >= min not val > min
332
333    :param number max: maximum allowed valid value. If None (default) the
334      upper limit is unbounded
335      NB: test in NumberValidator is val <= max not val < max
336     
337    :param list exclLim: if True exclude min/max value ([exclMin,exclMax]);
338      (Default=[False,False])
339
340    :param function OKcontrol: specifies a function or method that will be
341      called when the input is validated. The called function is supplied
342      with one argument which is False if the TextCtrl contains an invalid
343      value and True if the value is valid.
344      Note that this function should check all values
345      in the dialog when True, since other entries might be invalid.
346      The default for this is None, which indicates no function should
347      be called.
348
349    :param function OnLeave: specifies a function or method that will be
350      called when the focus for the control is lost.
351      The called function is supplied with (at present) three keyword arguments:
352
353      * invalid: (*bool*) True if the value for the TextCtrl is invalid
354      * value:   (*int/float/str*)  the value contained in the TextCtrl
355      * tc:      (*wx.TextCtrl*)  the TextCtrl object
356
357      The number of keyword arguments may be increased in the future should needs arise,
358      so it is best to code these functions with a \*\*kwargs argument so they will
359      continue to run without errors
360
361      The default for OnLeave is None, which indicates no function should
362      be called.
363
364    :param type typeHint: the value of typeHint is overrides the initial value
365      for the dict/list element ``loc[key]``, if set to
366      int or float, which specifies the type for input to the TextCtrl.
367      Defaults as None, which is ignored.
368
369    :param bool CIFinput: for str input, indicates that only printable
370      ASCII characters may be entered into the TextCtrl. Forces output
371      to be ASCII rather than Unicode. For float and int input, allows
372      use of a single '?' or '.' character as valid input.
373
374    :param dict OnLeaveArgs: a dict with keyword args that are passed to
375      the :attr:`OnLeave` function. Defaults to ``{}``
376
377    :param bool ASCIIonly: if set as True will remove unicode characters from
378      strings
379
380    :param (other): other optional keyword parameters for the
381      wx.TextCtrl widget such as size or style may be specified.
382    '''
383    def __init__(self,parent,loc,key,nDig=None,notBlank=True,min=None,max=None,
384        OKcontrol=None,OnLeave=None,typeHint=None,CIFinput=False,exclLim=[False,False],
385        OnLeaveArgs={}, ASCIIonly=False, **kw):
386        # save passed values needed outside __init__
387        self.result = loc
388        self.key = key
389        self.nDig = nDig
390        self.OKcontrol=OKcontrol
391        self.OnLeave = OnLeave
392        self.OnLeaveArgs = OnLeaveArgs
393        self.CIFinput = CIFinput
394        self.notBlank = notBlank
395        self.ASCIIonly = ASCIIonly
396        self.type = str
397        # initialization
398        self.invalid = False   # indicates if the control has invalid contents
399        self.evaluated = False # set to True when the validator recognizes an expression
400        val = loc[key]
401        if 'style' in kw: # add a "Process Enter" to style
402            kw['style'] += kw['style'] | wx.TE_PROCESS_ENTER
403        else:
404            kw['style'] = wx.TE_PROCESS_ENTER
405        if isinstance(val,int) or typeHint is int:
406            self.type = int
407            wx.TextCtrl.__init__(self,parent,wx.ID_ANY,
408                validator=NumberValidator(int,result=loc,key=key,min=min,max=max,
409                    exclLim=exclLim,OKcontrol=OKcontrol,CIFinput=CIFinput),**kw)
410            if val is not None:
411                self._setValue(val)
412            else: # no default is invalid for a number
413                self.invalid = True
414                self._IndicateValidity()
415
416        elif isinstance(val,float) or typeHint is float:
417            self.type = float
418            wx.TextCtrl.__init__(self,parent,wx.ID_ANY,
419                validator=NumberValidator(float,result=loc,key=key,min=min,max=max,
420                    exclLim=exclLim,OKcontrol=OKcontrol,CIFinput=CIFinput),**kw)
421            if val is not None:
422                self._setValue(val)
423            else:
424                self.invalid = True
425                self._IndicateValidity()
426
427        elif isinstance(val,str) or isinstance(val,unicode) or typeHint is str:
428            if self.CIFinput:
429                wx.TextCtrl.__init__(
430                    self,parent,wx.ID_ANY,
431                    validator=ASCIIValidator(result=loc,key=key),
432                    **kw)
433            else:
434                wx.TextCtrl.__init__(self,parent,wx.ID_ANY,**kw)
435            if val is not None:
436                self.SetValue(val)
437            if notBlank:
438                self.Bind(wx.EVT_CHAR,self._onStringKey)
439                self.ShowStringValidity() # test if valid input
440            else:
441                self.invalid = False
442                self.Bind(wx.EVT_CHAR,self._GetStringValue)
443        elif val is None:
444            raise Exception,("ValidatedTxtCtrl error: value of "+str(key)+
445                             " element is None and typeHint not defined as int or float")
446        else:
447            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
448                             ") type: "+str(type(val)))
449        # When the mouse is moved away or the widget loses focus,
450        # display the last saved value, if an expression
451        self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
452        self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus)
453        self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
454        # patch for wx 2.9 on Mac
455        i,j= wx.__version__.split('.')[0:2]
456        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
457            self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
458
459    def SetValue(self,val):
460        if self.result is not None: # note that this bypasses formatting
461            self.result[self.key] = val
462            log.LogVarChange(self.result,self.key)
463        self._setValue(val)
464
465    def _setValue(self,val,show=True):
466        '''Check the validity of an int or float value and convert to a str.
467        Possibly format it. If show is True, display the formatted value in
468        the Text widget.
469        '''
470        self.invalid = False
471        if self.type is int:
472            try:
473                if int(val) != val:
474                    self.invalid = True
475                else:
476                    val = int(val)
477            except:
478                if self.CIFinput and (val == '?' or val == '.'):
479                    pass
480                else:
481                    self.invalid = True
482            if show: wx.TextCtrl.SetValue(self,str(val))
483        elif self.type is float:
484            try:
485                val = float(val) # convert strings, if needed
486            except:
487                if self.CIFinput and (val == '?' or val == '.'):
488                    pass
489                else:
490                    self.invalid = True
491            if self.nDig and show:
492                wx.TextCtrl.SetValue(self,str(G2py3.FormatValue(val,self.nDig)))
493            elif show:
494                wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)).rstrip('0'))
495        else:
496            if self.ASCIIonly:
497                s = ''
498                for c in val:
499                    if ord(c) < 128: s += c
500                if val != s:
501                    val = s
502                    show = True
503            if show:
504                try:
505                    wx.TextCtrl.SetValue(self,str(val))
506                except:
507                    wx.TextCtrl.SetValue(self,val)
508            self.ShowStringValidity() # test if valid input
509            return
510       
511        self._IndicateValidity()
512        if self.OKcontrol:
513            self.OKcontrol(not self.invalid)
514
515    def OnKeyDown(self,event):
516        'Special callback for wx 2.9+ on Mac where backspace is not processed by validator'
517        key = event.GetKeyCode()
518        if key in [wx.WXK_BACK, wx.WXK_DELETE]:
519            if self.Validator: wx.CallAfter(self.Validator.TestValid,self)
520        if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
521            self._onLoseFocus(None)
522        if event: event.Skip()
523                   
524    def _onStringKey(self,event):
525        if event: event.Skip()
526        if self.invalid: # check for validity after processing the keystroke
527            wx.CallAfter(self.ShowStringValidity,True) # was invalid
528        else:
529            wx.CallAfter(self.ShowStringValidity,False) # was valid
530
531    def _IndicateValidity(self):
532        'Set the control colors to show invalid input'
533        if self.invalid:
534            ins = self.GetInsertionPoint()
535            self.SetForegroundColour("red")
536            self.SetBackgroundColour("yellow")
537            self.SetFocus()
538            self.Refresh() # this selects text on some Linuxes
539            self.SetSelection(0,0)   # unselect
540            self.SetInsertionPoint(ins) # put insertion point back
541        else: # valid input
542            self.SetBackgroundColour(
543                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
544            self.SetForegroundColour("black")
545            self.Refresh()
546
547    def ShowStringValidity(self,previousInvalid=True):
548        '''Check if input is valid. Anytime the input is
549        invalid, call self.OKcontrol (if defined) because it is fast.
550        If valid, check for any other invalid entries only when
551        changing from invalid to valid, since that is slower.
552       
553        :param bool previousInvalid: True if the TextCtrl contents were
554          invalid prior to the current change.
555         
556        '''
557        val = self.GetValue().strip()
558        if self.notBlank:
559            self.invalid = not val
560        else:
561            self.invalid = False
562        self._IndicateValidity()
563        if self.invalid:
564            if self.OKcontrol:
565                self.OKcontrol(False)
566        elif self.OKcontrol and previousInvalid:
567            self.OKcontrol(True)
568        # always store the result
569        if self.CIFinput: # for CIF make results ASCII
570            self.result[self.key] = val.encode('ascii','replace') 
571        else:
572            self.result[self.key] = val
573        log.LogVarChange(self.result,self.key)
574
575    def _GetStringValue(self,event):
576        '''Get string input and store.
577        '''
578        if event: event.Skip() # process keystroke
579        wx.CallAfter(self._SaveStringValue)
580       
581    def _SaveStringValue(self):
582        val = self.GetValue().strip()
583        # always store the result
584        if self.CIFinput: # for CIF make results ASCII
585            self.result[self.key] = val.encode('ascii','replace') 
586        else:
587            self.result[self.key] = val
588        log.LogVarChange(self.result,self.key)
589
590    def _onLeaveWindow(self,event):
591        '''If the mouse leaves the text box, save the result, if valid,
592        but (unlike _onLoseFocus) don't update the textbox contents.
593        '''
594        if not self.IsModified():   #ignore mouse crusing
595            return
596        if self.evaluated and not self.invalid: # deal with computed expressions
597            self.evaluated = False # expression has been recast as value, reset flag
598        if self.invalid: # don't update an invalid expression
599            if event: event.Skip()
600            return
601        self._setValue(self.result[self.key],show=False) # save value quietly
602        if self.OnLeave:
603            self.event = event
604            self.OnLeave(invalid=self.invalid,value=self.result[self.key],
605                tc=self,**self.OnLeaveArgs)
606        if event: event.Skip()
607           
608    def _onLoseFocus(self,event):
609        '''Enter has been pressed or focus transferred to another control,
610        Evaluate and update the current control contents
611        '''
612        if event: event.Skip()
613        if not self.IsModified():   #ignore mouse crusing
614            return
615        if self.evaluated: # deal with computed expressions
616            if self.invalid: # don't substitute for an invalid expression
617                return 
618            self.evaluated = False # expression has been recast as value, reset flag
619            self._setValue(self.result[self.key])
620        elif self.result is not None: # show formatted result, as Bob wants
621            if not self.invalid: # don't update an invalid expression
622                self._setValue(self.result[self.key])
623        if self.OnLeave:
624            self.event = event
625            self.OnLeave(invalid=self.invalid,value=self.result[self.key],
626                tc=self,**self.OnLeaveArgs)
627
628################################################################################
629class NumberValidator(wx.PyValidator):
630    '''A validator to be used with a TextCtrl to prevent
631    entering characters other than digits, signs, and for float
632    input, a period and exponents.
633   
634    The value is checked for validity after every keystroke
635      If an invalid number is entered, the box is highlighted.
636      If the number is valid, it is saved in result[key]
637
638    :param type typ: the base data type. Must be int or float.
639
640    :param bool positiveonly: If True, negative integers are not allowed
641      (default False). This prevents the + or - keys from being pressed.
642      Used with typ=int; ignored for typ=float.
643
644    :param number min: Minimum allowed value. If None (default) the
645      lower limit is unbounded
646
647    :param number max: Maximum allowed value. If None (default) the
648      upper limit is unbounded
649     
650    :param list exclLim: if True exclude min/max value ([exclMin,exclMax]);
651     (Default=[False,False])
652
653    :param dict/list result: List or dict where value should be placed when valid
654
655    :param any key: key to use for result (int for list)
656
657    :param function OKcontrol: function or class method to control
658      an OK button for a window.
659      Ignored if None (default)
660
661    :param bool CIFinput: allows use of a single '?' or '.' character
662      as valid input.
663     
664    '''
665    def __init__(self, typ, positiveonly=False, min=None, max=None,exclLim=[False,False],
666        result=None, key=None, OKcontrol=None, CIFinput=False):
667        'Create the validator'
668        wx.PyValidator.__init__(self)
669        # save passed parameters
670        self.typ = typ
671        self.positiveonly = positiveonly
672        self.min = min
673        self.max = max
674        self.exclLim = exclLim
675        self.result = result
676        self.key = key
677        self.OKcontrol = OKcontrol
678        self.CIFinput = CIFinput
679        # set allowed keys by data type
680        self.Bind(wx.EVT_CHAR, self.OnChar)
681        if self.typ == int and self.positiveonly:
682            self.validchars = '0123456789'
683        elif self.typ == int:
684            self.validchars = '0123456789+-'
685        elif self.typ == float:
686            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
687            # also addition, subtraction, division, multiplication, exponentiation
688            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
689        else:
690            self.validchars = None
691            return
692        if self.CIFinput:
693            self.validchars += '?.'
694    def Clone(self):
695        'Create a copy of the validator, a strange, but required component'
696        return NumberValidator(typ=self.typ, 
697                               positiveonly=self.positiveonly,
698                               min=self.min, max=self.max,
699                               result=self.result, key=self.key,
700                               OKcontrol=self.OKcontrol,
701                               CIFinput=self.CIFinput)
702    def TransferToWindow(self):
703        'Needed by validator, strange, but required component'
704        return True # Prevent wxDialog from complaining.
705    def TransferFromWindow(self):
706        'Needed by validator, strange, but required component'
707        return True # Prevent wxDialog from complaining.
708    def TestValid(self,tc):
709        '''Check if the value is valid by casting the input string
710        into the current type.
711
712        Set the invalid variable in the TextCtrl object accordingly.
713
714        If the value is valid, save it in the dict/list where
715        the initial value was stored, if appropriate.
716
717        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
718          is associated with.
719        '''
720        tc.invalid = False # assume valid
721        if self.CIFinput:
722            val = tc.GetValue().strip()
723            if val == '?' or val == '.':
724                self.result[self.key] = val
725                log.LogVarChange(self.result,self.key)
726                return
727        try:
728            val = self.typ(tc.GetValue())
729        except (ValueError, SyntaxError):
730            if self.typ is float: # for float values, see if an expression can be evaluated
731                val = G2py3.FormulaEval(tc.GetValue())
732                if val is None:
733                    tc.invalid = True
734                    return
735                else:
736                    tc.evaluated = True
737            else: 
738                tc.invalid = True
739                return
740        if self.max != None:
741            if val >= self.max and self.exclLim[1]:
742                tc.invalid = True
743            elif val > self.max:
744                tc.invalid = True
745        if self.min != None:
746            if val <= self.min and self.exclLim[0]:
747                tc.invalid = True
748            elif val < self.min:
749                tc.invalid = True  # invalid
750        if self.key is not None and self.result is not None and not tc.invalid:
751            self.result[self.key] = val
752            log.LogVarChange(self.result,self.key)
753
754    def ShowValidity(self,tc):
755        '''Set the control colors to show invalid input
756
757        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
758          is associated with.
759
760        '''
761        if tc.invalid:
762            ins = tc.GetInsertionPoint()
763            tc.SetForegroundColour("red")
764            tc.SetBackgroundColour("yellow")
765            tc.SetFocus()
766            tc.Refresh() # this selects text on some Linuxes
767            tc.SetSelection(0,0)   # unselect
768            tc.SetInsertionPoint(ins) # put insertion point back
769            return False
770        else: # valid input
771            tc.SetBackgroundColour(
772                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
773            tc.SetForegroundColour("black")
774            tc.Refresh()
775            return True
776
777    def CheckInput(self,previousInvalid):
778        '''called to test every change to the TextCtrl for validity and
779        to change the appearance of the TextCtrl
780
781        Anytime the input is invalid, call self.OKcontrol
782        (if defined) because it is fast.
783        If valid, check for any other invalid entries only when
784        changing from invalid to valid, since that is slower.
785
786        :param bool previousInvalid: True if the TextCtrl contents were
787          invalid prior to the current change.
788        '''
789        tc = self.GetWindow()
790        self.TestValid(tc)
791        self.ShowValidity(tc)
792        # if invalid
793        if tc.invalid and self.OKcontrol:
794            self.OKcontrol(False)
795        if not tc.invalid and self.OKcontrol and previousInvalid:
796            self.OKcontrol(True)
797
798    def OnChar(self, event):
799        '''Called each type a key is pressed
800        ignores keys that are not allowed for int and float types
801        '''
802        key = event.GetKeyCode()
803        tc = self.GetWindow()
804        if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
805            if tc.invalid:
806                self.CheckInput(True) 
807            else:
808                self.CheckInput(False) 
809            if event: event.Skip()
810            return
811        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
812            if event: event.Skip()
813            if tc.invalid:
814                wx.CallAfter(self.CheckInput,True) 
815            else:
816                wx.CallAfter(self.CheckInput,False) 
817            return
818        elif chr(key) in self.validchars: # valid char pressed?
819            if event: event.Skip()
820            if tc.invalid:
821                wx.CallAfter(self.CheckInput,True) 
822            else:
823                wx.CallAfter(self.CheckInput,False) 
824            return
825        if not wx.Validator_IsSilent(): wx.Bell()
826        return  # Returning without calling event.Skip, which eats the keystroke
827
828################################################################################
829class ASCIIValidator(wx.PyValidator):
830    '''A validator to be used with a TextCtrl to prevent
831    entering characters other than ASCII characters.
832   
833    The value is checked for validity after every keystroke
834      If an invalid number is entered, the box is highlighted.
835      If the number is valid, it is saved in result[key]
836
837    :param dict/list result: List or dict where value should be placed when valid
838
839    :param any key: key to use for result (int for list)
840
841    '''
842    def __init__(self, result=None, key=None):
843        'Create the validator'
844        import string
845        wx.PyValidator.__init__(self)
846        # save passed parameters
847        self.result = result
848        self.key = key
849        self.validchars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
850        self.Bind(wx.EVT_CHAR, self.OnChar)
851    def Clone(self):
852        'Create a copy of the validator, a strange, but required component'
853        return ASCIIValidator(result=self.result, key=self.key)
854        tc = self.GetWindow()
855        tc.invalid = False # make sure the validity flag is defined in parent
856    def TransferToWindow(self):
857        'Needed by validator, strange, but required component'
858        return True # Prevent wxDialog from complaining.
859    def TransferFromWindow(self):
860        'Needed by validator, strange, but required component'
861        return True # Prevent wxDialog from complaining.
862    def TestValid(self,tc):
863        '''Check if the value is valid by casting the input string
864        into ASCII.
865
866        Save it in the dict/list where the initial value was stored
867
868        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
869          is associated with.
870        '''
871        self.result[self.key] = tc.GetValue().encode('ascii','replace')
872        log.LogVarChange(self.result,self.key)
873
874    def OnChar(self, event):
875        '''Called each type a key is pressed
876        ignores keys that are not allowed for int and float types
877        '''
878        key = event.GetKeyCode()
879        tc = self.GetWindow()
880        if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
881            self.TestValid(tc)
882            if event: event.Skip()
883            return
884        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
885            if event: event.Skip()
886            self.TestValid(tc)
887            return
888        elif chr(key) in self.validchars: # valid char pressed?
889            if event: event.Skip()
890            self.TestValid(tc)
891            return
892        if not wx.Validator_IsSilent():
893            wx.Bell()
894        return  # Returning without calling event.Skip, which eats the keystroke
895
896################################################################################
897def HorizontalLine(sizer,parent):
898    '''Draws a horizontal line as wide as the window.
899    This shows up on the Mac as a very thin line, no matter what I do
900    '''
901    line = wx.StaticLine(parent, size=(-1,3), style=wx.LI_HORIZONTAL)
902    sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 5)
903
904################################################################################
905class G2LoggedButton(wx.Button):
906    '''A version of wx.Button that creates logging events. Bindings are saved
907    in the object, and are looked up rather than directly set with a bind.
908    An index to these buttons is saved as log.ButtonBindingLookup
909    :param wx.Panel parent: parent widget
910    :param int id: Id for button
911    :param str label: label for button
912    :param str locationcode: a label used internally to uniquely indentify the button
913    :param function handler: a routine to call when the button is pressed
914    '''
915    def __init__(self,parent,id=wx.ID_ANY,label='',locationcode='',
916                 handler=None,*args,**kwargs):
917        super(self.__class__,self).__init__(parent,id,label,*args,**kwargs)
918        self.label = label
919        self.handler = handler
920        self.locationcode = locationcode
921        key = locationcode + '+' + label # hash code to find button
922        self.Bind(wx.EVT_BUTTON,self.onPress)
923        log.ButtonBindingLookup[key] = self
924    def onPress(self,event):
925        'create log event and call handler'
926        log.MakeButtonLog(self.locationcode,self.label)
927        self.handler(event)
928       
929################################################################################
930class EnumSelector(wx.ComboBox):
931    '''A customized :class:`wxpython.ComboBox` that selects items from a list
932    of choices, but sets a dict (list) entry to the corresponding
933    entry from the input list of values.
934
935    :param wx.Panel parent: the parent to the :class:`~wxpython.ComboBox` (usually a
936      frame or panel)
937    :param dict dct: a dict (or list) to contain the value set
938      for the :class:`~wxpython.ComboBox`.
939    :param item: the dict key (or list index) where ``dct[item]`` will
940      be set to the value selected in the :class:`~wxpython.ComboBox`. Also, dct[item]
941      contains the starting value shown in the widget. If the value
942      does not match an entry in :data:`values`, the first value
943      in :data:`choices` is used as the default, but ``dct[item]`` is
944      not changed.   
945    :param list choices: a list of choices to be displayed to the
946      user such as
947      ::
948     
949      ["default","option 1","option 2",]
950
951      Note that these options will correspond to the entries in
952      :data:`values` (if specified) item by item.
953    :param list values: a list of values that correspond to
954      the options in :data:`choices`, such as
955      ::
956     
957      [0,1,2]
958     
959      The default for :data:`values` is to use the same list as
960      specified for :data:`choices`.
961    :param (other): additional keyword arguments accepted by
962      :class:`~wxpython.ComboBox` can be specified.
963    '''
964    def __init__(self,parent,dct,item,choices,values=None,**kw):
965        if values is None:
966            values = choices
967        if dct[item] in values:
968            i = values.index(dct[item])
969        else:
970            i = 0
971        startval = choices[i]
972        wx.ComboBox.__init__(self,parent,wx.ID_ANY,startval,
973                             choices = choices,
974                             style=wx.CB_DROPDOWN|wx.CB_READONLY,
975                             **kw)
976        self.choices = choices
977        self.values = values
978        self.dct = dct
979        self.item = item
980        self.Bind(wx.EVT_COMBOBOX, self.onSelection)
981    def onSelection(self,event):
982        # respond to a selection by setting the enum value in the CIF dictionary
983        if self.GetValue() in self.choices: # should always be true!
984            self.dct[self.item] = self.values[self.choices.index(self.GetValue())]
985        else:
986            self.dct[self.item] = self.values[0] # unknown
987
988################################################################################
989class G2ChoiceButton(wx.Choice):
990    '''A customized version of a wx.Choice that automatically initializes
991    the control to match a supplied value and saves the choice directly
992    into an array or list. Optionally a function can be called each time a
993    choice is selected. The widget can be used with an array item that is set to
994    to the choice by number (``indLoc[indKey]``) or by string value
995    (``strLoc[strKey]``) or both. The initial value is taken from ``indLoc[indKey]``
996    if not None or ``strLoc[strKey]`` if not None.
997
998    :param wx.Panel parent: name of panel or frame that will be
999      the parent to the widget. Can be None.
1000    :param list choiceList: a list or tuple of choices to offer the user.
1001    :param dict/list indLoc: a dict or list with the initial value to be
1002      placed in the Choice button. If this is None, this is ignored.
1003    :param int/str indKey: the dict key or the list index for the value to be
1004      edited by the Choice button. If indLoc is not None then this
1005      must be specified and the ``indLoc[indKey]`` will be set. If the value
1006      for ``indLoc[indKey]`` is not None, it should be an integer in
1007      range(len(choiceList)). The Choice button will be initialized to the
1008      choice corresponding to the value in this element if not None.
1009    :param dict/list strLoc: a dict or list with the string value corresponding to
1010      indLoc/indKey. Default (None) means that this is not used.
1011    :param int/str strKey: the dict key or the list index for the string value
1012      The ``strLoc[strKey]`` element must exist or strLoc must be None (default).
1013    :param function onChoice: name of a function to call when the choice is made.
1014    '''
1015    def __init__(self,parent,choiceList,indLoc=None,indKey=None,strLoc=None,strKey=None,
1016                 onChoice=None,**kwargs):
1017        wx.Choice.__init__(self,parent,choices=choiceList,id=wx.ID_ANY,**kwargs)
1018        self.choiceList = choiceList
1019        self.indLoc = indLoc
1020        self.indKey = indKey
1021        self.strLoc = strLoc
1022        self.strKey = strKey
1023        self.onChoice = None
1024        self.SetSelection(wx.NOT_FOUND)
1025        if self.indLoc is not None and self.indLoc.get(self.indKey) is not None:
1026            self.SetSelection(self.indLoc[self.indKey])
1027            if self.strLoc is not None:
1028                self.strLoc[self.strKey] = self.GetStringSelection()
1029                log.LogVarChange(self.strLoc,self.strKey)
1030        elif self.strLoc is not None and self.strLoc.get(self.strKey) is not None:
1031            try:
1032                self.SetSelection(choiceList.index(self.strLoc[self.strKey]))
1033                if self.indLoc is not None:
1034                    self.indLoc[self.indKey] = self.GetSelection()
1035                    log.LogVarChange(self.indLoc,self.indKey)
1036            except ValueError:
1037                pass
1038        self.Bind(wx.EVT_CHOICE, self._OnChoice)
1039        #if self.strLoc is not None: # make sure strLoc gets initialized
1040        #    self._OnChoice(None) # note that onChoice will not be called
1041        self.onChoice = onChoice
1042    def _OnChoice(self,event):
1043        if self.indLoc is not None:
1044            self.indLoc[self.indKey] = self.GetSelection()
1045            log.LogVarChange(self.indLoc,self.indKey)
1046        if self.strLoc is not None:
1047            self.strLoc[self.strKey] = self.GetStringSelection()
1048            log.LogVarChange(self.strLoc,self.strKey)
1049        if self.onChoice:
1050            self.onChoice()
1051
1052############################################################### Custom checkbox that saves values into dict/list as used
1053class G2CheckBox(wx.CheckBox):
1054    '''A customized version of a CheckBox that automatically initializes
1055    the control to a supplied list or dict entry and updates that
1056    entry as the widget is used.
1057
1058    :param wx.Panel parent: name of panel or frame that will be
1059      the parent to the widget. Can be None.
1060    :param str label: text to put on check button
1061    :param dict/list loc: the dict or list with the initial value to be
1062      placed in the CheckBox.
1063    :param int/str key: the dict key or the list index for the value to be
1064      edited by the CheckBox. The ``loc[key]`` element must exist.
1065      The CheckBox will be initialized from this value.
1066      If the value is anything other that True (or 1), it will be taken as
1067      False.
1068    :param function OnChange: specifies a function or method that will be
1069      called when the CheckBox is changed (Default is None).
1070      The called function is supplied with one argument, the calling event.
1071    '''
1072    def __init__(self,parent,label,loc,key,OnChange=None):
1073        wx.CheckBox.__init__(self,parent,id=wx.ID_ANY,label=label)
1074        self.loc = loc
1075        self.key = key
1076        self.OnChange = OnChange
1077        self.SetValue(self.loc[self.key]==True)
1078        self.Bind(wx.EVT_CHECKBOX, self._OnCheckBox)
1079    def _OnCheckBox(self,event):
1080        self.loc[self.key] = self.GetValue()
1081        log.LogVarChange(self.loc,self.key)
1082        if self.OnChange: self.OnChange(event)
1083                   
1084################################################################################
1085#### Commonly used dialogs
1086################################################################################
1087def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[],
1088                 title='Edit items',header='',size=(300,250),
1089                             CopyButton=False, ASCIIonly=False, **kw):
1090    '''Shell routine to call a ScrolledMultiEditor dialog. See
1091    :class:`ScrolledMultiEditor` for parameter definitions.
1092
1093    :returns: True if the OK button is pressed; False if the window is closed
1094      with the system menu or the Cancel button.
1095
1096    '''
1097    dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl,
1098                              title,header,size,
1099                              CopyButton, ASCIIonly, **kw)
1100    if dlg.ShowModal() == wx.ID_OK:
1101        dlg.Destroy()
1102        return True
1103    else:
1104        dlg.Destroy()
1105        return False
1106
1107################################################################################
1108class ScrolledMultiEditor(wx.Dialog):
1109    '''Define a window for editing a potentially large number of dict- or
1110    list-contained values with validation for each item. Edited values are
1111    automatically placed in their source location. If invalid entries
1112    are provided, the TextCtrl is turned yellow and the OK button is disabled.
1113
1114    The type for each TextCtrl validation is determined by the
1115    initial value of the entry (int, float or string).
1116    Float values can be entered in the TextCtrl as numbers or also
1117    as algebraic expressions using operators + - / \* () and \*\*,
1118    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
1119    as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq().
1120
1121    :param wx.Frame parent: name of parent window, or may be None
1122
1123    :param tuple dictlst: a list of dicts or lists containing values to edit
1124
1125    :param tuple elemlst: a list of keys for each item in a dictlst. Must have the
1126      same length as dictlst.
1127
1128    :param wx.Frame parent: name of parent window, or may be None
1129   
1130    :param tuple prelbl: a list of labels placed before the TextCtrl for each
1131      item (optional)
1132   
1133    :param tuple postlbl: a list of labels placed after the TextCtrl for each
1134      item (optional)
1135
1136    :param str title: a title to place in the frame of the dialog
1137
1138    :param str header: text to place at the top of the window. May contain
1139      new line characters.
1140
1141    :param wx.Size size: a size parameter that dictates the
1142      size for the scrolled region of the dialog. The default is
1143      (300,250).
1144
1145    :param bool CopyButton: if True adds a small button that copies the
1146      value for the current row to all fields below (default is False)
1147     
1148    :param bool ASCIIonly: if set as True will remove unicode characters from
1149      strings
1150     
1151    :param list minvals: optional list of minimum values for validation
1152      of float or int values. Ignored if value is None.
1153    :param list maxvals: optional list of maximum values for validation
1154      of float or int values. Ignored if value is None.
1155    :param list sizevals: optional list of wx.Size values for each input
1156      widget. Ignored if value is None.
1157     
1158    :param tuple checkdictlst: an optional list of dicts or lists containing bool
1159      values (similar to dictlst).
1160    :param tuple checkelemlst: an optional list of dicts or lists containing bool
1161      key values (similar to elemlst). Must be used with checkdictlst.
1162    :param string checklabel: a string to use for each checkbutton
1163     
1164    :returns: the wx.Dialog created here. Use method .ShowModal() to display it.
1165   
1166    *Example for use of ScrolledMultiEditor:*
1167
1168    ::
1169
1170        dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl,
1171                                        header=header)
1172        if dlg.ShowModal() == wx.ID_OK:
1173             for d,k in zip(dictlst,elemlst):
1174                 print d[k]
1175
1176    *Example definitions for dictlst and elemlst:*
1177
1178    ::
1179     
1180          dictlst = (dict1,list1,dict1,list1)
1181          elemlst = ('a', 1, 2, 3)
1182
1183      This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited.
1184   
1185    Note that these items must have int, float or str values assigned to
1186    them. The dialog will force these types to be retained. String values
1187    that are blank are marked as invalid.
1188    '''
1189   
1190    def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[],
1191                 title='Edit items',header='',size=(300,250),
1192                 CopyButton=False, ASCIIonly=False,
1193                 minvals=[],maxvals=[],sizevals=[],
1194                 checkdictlst=[], checkelemlst=[], checklabel=""):
1195        if len(dictlst) != len(elemlst):
1196            raise Exception,"ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))
1197        if len(checkdictlst) != len(checkelemlst):
1198            raise Exception,"ScrolledMultiEditor error: len(checkdictlst) != len(checkelemlst) "+str(len(checkdictlst))+" != "+str(len(checkelemlst))
1199        wx.Dialog.__init__( # create dialog & sizer
1200            self,parent,wx.ID_ANY,title,
1201            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1202        mainSizer = wx.BoxSizer(wx.VERTICAL)
1203        self.orig = []
1204        self.dictlst = dictlst
1205        self.elemlst = elemlst
1206        self.checkdictlst = checkdictlst
1207        self.checkelemlst = checkelemlst
1208        self.StartCheckValues = [checkdictlst[i][checkelemlst[i]] for i in range(len(checkdictlst))]
1209        self.ButtonIndex = {}
1210        for d,i in zip(dictlst,elemlst):
1211            self.orig.append(d[i])
1212        # add a header if supplied
1213        if header:
1214            subSizer = wx.BoxSizer(wx.HORIZONTAL)
1215            subSizer.Add((-1,-1),1,wx.EXPAND)
1216            subSizer.Add(wx.StaticText(self,wx.ID_ANY,header))
1217            subSizer.Add((-1,-1),1,wx.EXPAND)
1218            mainSizer.Add(subSizer,0,wx.EXPAND,0)
1219        # make OK button now, because we will need it for validation
1220        self.OKbtn = wx.Button(self, wx.ID_OK)
1221        self.OKbtn.SetDefault()
1222        # create scrolled panel and sizer
1223        panel = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=size,
1224            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
1225        cols = 4
1226        if CopyButton: cols += 1
1227        subSizer = wx.FlexGridSizer(cols=cols,hgap=2,vgap=2)
1228        self.ValidatedControlsList = [] # make list of TextCtrls
1229        self.CheckControlsList = [] # make list of CheckBoxes
1230        for i,(d,k) in enumerate(zip(dictlst,elemlst)):
1231            if i >= len(prelbl): # label before TextCtrl, or put in a blank
1232                subSizer.Add((-1,-1)) 
1233            else:
1234                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i])))
1235            kargs = {}
1236            if i < len(minvals):
1237                if minvals[i] is not None: kargs['min']=minvals[i]
1238            if i < len(maxvals):
1239                if maxvals[i] is not None: kargs['max']=maxvals[i]
1240            if i < len(sizevals):
1241                if sizevals[i]: kargs['size']=sizevals[i]
1242            if CopyButton:
1243                import wx.lib.colourselect as wscs  # is there a way to test?
1244                but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP
1245                    parent=panel,colour=(255,255,200),size=wx.Size(30,23),
1246                    style=wx.RAISED_BORDER)
1247                but.Bind(wx.EVT_BUTTON, self._OnCopyButton)
1248                but.SetToolTipString('Press to copy adjacent value to all rows below')
1249                self.ButtonIndex[but] = i
1250                subSizer.Add(but)
1251            # create the validated TextCrtl, store it and add it to the sizer
1252            ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,ASCIIonly=ASCIIonly,
1253                                    **kargs)
1254            self.ValidatedControlsList.append(ctrl)
1255            subSizer.Add(ctrl)
1256            if i < len(postlbl): # label after TextCtrl, or put in a blank
1257                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i])))
1258            else:
1259                subSizer.Add((-1,-1))
1260            if i < len(checkdictlst):
1261                ch = G2CheckBox(panel,checklabel,checkdictlst[i],checkelemlst[i])
1262                self.CheckControlsList.append(ch)
1263                subSizer.Add(ch)                   
1264            else:
1265                subSizer.Add((-1,-1))
1266        # finish up ScrolledPanel
1267        panel.SetSizer(subSizer)
1268        panel.SetAutoLayout(1)
1269        panel.SetupScrolling()
1270        # patch for wx 2.9 on Mac
1271        i,j= wx.__version__.split('.')[0:2]
1272        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
1273            panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1]))       
1274        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
1275
1276        # Sizer for OK/Close buttons. N.B. on Close changes are discarded
1277        # by restoring the initial values
1278        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
1279        btnsizer.Add(self.OKbtn)
1280        btn = wx.Button(self, wx.ID_CLOSE,"Cancel") 
1281        btn.Bind(wx.EVT_BUTTON,self._onClose)
1282        btnsizer.Add(btn)
1283        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1284        # size out the window. Set it to be enlarged but not made smaller
1285        self.SetSizer(mainSizer)
1286        mainSizer.Fit(self)
1287        self.SetMinSize(self.GetSize())
1288
1289    def _OnCopyButton(self,event):
1290        'Implements the copy down functionality'
1291        but = event.GetEventObject()
1292        n = self.ButtonIndex.get(but)
1293        if n is None: return
1294        for i,(d,k,ctrl) in enumerate(zip(self.dictlst,self.elemlst,self.ValidatedControlsList)):
1295            if i < n: continue
1296            if i == n:
1297                val = d[k]
1298                continue
1299            d[k] = val
1300            ctrl.SetValue(val)
1301        for i in range(len(self.checkdictlst)):
1302            if i < n: continue
1303            self.checkdictlst[i][self.checkelemlst[i]] = self.checkdictlst[n][self.checkelemlst[n]]
1304            self.CheckControlsList[i].SetValue(self.checkdictlst[i][self.checkelemlst[i]])
1305    def _onClose(self,event):
1306        'Used on Cancel: Restore original values & close the window'
1307        for d,i,v in zip(self.dictlst,self.elemlst,self.orig):
1308            d[i] = v
1309        for i in range(len(self.checkdictlst)):
1310            self.checkdictlst[i][self.checkelemlst[i]] = self.StartCheckValues[i]
1311        self.EndModal(wx.ID_CANCEL)
1312       
1313    def ControlOKButton(self,setvalue):
1314        '''Enable or Disable the OK button for the dialog. Note that this is
1315        passed into the ValidatedTxtCtrl for use by validators.
1316
1317        :param bool setvalue: if True, all entries in the dialog are
1318          checked for validity. if False then the OK button is disabled.
1319
1320        '''
1321        if setvalue: # turn button on, do only if all controls show as valid
1322            for ctrl in self.ValidatedControlsList:
1323                if ctrl.invalid:
1324                    self.OKbtn.Disable()
1325                    return
1326            else:
1327                self.OKbtn.Enable()
1328        else:
1329            self.OKbtn.Disable()
1330
1331###############################################  Multichoice Dialog with set all, toggle & filter options
1332class G2MultiChoiceDialog(wx.Dialog):
1333    '''A dialog similar to MultiChoiceDialog except that buttons are
1334    added to set all choices and to toggle all choices.
1335
1336    :param wx.Frame ParentFrame: reference to parent frame
1337    :param str title: heading above list of choices
1338    :param str header: Title to place on window frame
1339    :param list ChoiceList: a list of choices where one more will be selected
1340    :param bool toggle: If True (default) the toggle and select all buttons
1341      are displayed
1342    :param bool monoFont: If False (default), use a variable-spaced font;
1343      if True use a equally-spaced font.
1344    :param bool filterBox: If True (default) an input widget is placed on
1345      the window and only entries matching the entered text are shown.
1346    :param dict extraOpts: a dict containing a entries of form label_i and value_i with extra
1347      options to present to the user, where value_i is the default value.
1348      Options are listed ordered by the value_i values.
1349    :param kw: optional keyword parameters for the wx.Dialog may
1350      be included such as size [which defaults to `(320,310)`] and
1351      style (which defaults to `wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL`);
1352      note that `wx.OK` and `wx.CANCEL` style items control
1353      the presence of the eponymous buttons in the dialog.
1354    :returns: the name of the created dialog 
1355    '''
1356    def __init__(self,parent, title, header, ChoiceList, toggle=True,
1357                 monoFont=False, filterBox=True, extraOpts={}, **kw):
1358        # process keyword parameters, notably style
1359        options = {'size':(320,310), # default Frame keywords
1360                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
1361                   }
1362        options.update(kw)
1363        self.ChoiceList = ['%4d) %s'%(i,item) for i,item in enumerate(ChoiceList)] # numbered list of choices (list of str values)
1364        self.Selections = len(self.ChoiceList) * [False,] # selection status for each choice (list of bools)
1365        self.filterlist = range(len(self.ChoiceList)) # list of the choice numbers that have been filtered (list of int indices)
1366        self.Stride = 1
1367        if options['style'] & wx.OK:
1368            useOK = True
1369            options['style'] ^= wx.OK
1370        else:
1371            useOK = False
1372        if options['style'] & wx.CANCEL:
1373            useCANCEL = True
1374            options['style'] ^= wx.CANCEL
1375        else:
1376            useCANCEL = False       
1377        # create the dialog frame
1378        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
1379        # fill the dialog
1380        Sizer = wx.BoxSizer(wx.VERTICAL)
1381        topSizer = wx.BoxSizer(wx.HORIZONTAL)
1382        topSizer.Add(wx.StaticText(self,wx.ID_ANY,title,size=(-1,35)),
1383            1,wx.ALL|wx.EXPAND|WACV,1)
1384        if filterBox:
1385            self.timer = wx.Timer()
1386            self.timer.Bind(wx.EVT_TIMER,self.Filter)
1387            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Name \nFilter: '),0,wx.ALL|WACV,1)
1388            self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),style=wx.TE_PROCESS_ENTER)
1389            self.filterBox.Bind(wx.EVT_TEXT,self.onChar)
1390            self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
1391            topSizer.Add(self.filterBox,0,wx.ALL|WACV,0)
1392        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
1393        self.settingRange = False
1394        self.rangeFirst = None
1395        self.clb = wx.CheckListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, self.ChoiceList)
1396        self.clb.Bind(wx.EVT_CHECKLISTBOX,self.OnCheck)
1397        if monoFont:
1398            font1 = wx.Font(self.clb.GetFont().GetPointSize(),
1399                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
1400            self.clb.SetFont(font1)
1401        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
1402        Sizer.Add((-1,10))
1403        # set/toggle buttons
1404        if toggle:
1405            tSizer = wx.FlexGridSizer(cols=2,hgap=5,vgap=5)
1406            tSizer.Add(wx.StaticText(self,label=' Apply stride:'),0,WACV)
1407            numbs = [str(i+1) for i in range(9)]+[str(2*i+10) for i in range(6)]
1408            self.stride = wx.ComboBox(self,value='1',choices=numbs,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1409            self.stride.Bind(wx.EVT_COMBOBOX,self.OnStride)
1410            tSizer.Add(self.stride,0,WACV)
1411            setBut = wx.Button(self,wx.ID_ANY,'Set All')
1412            setBut.Bind(wx.EVT_BUTTON,self._SetAll)
1413            tSizer.Add(setBut)
1414            togBut = wx.Button(self,wx.ID_ANY,'Toggle All')
1415            togBut.Bind(wx.EVT_BUTTON,self._ToggleAll)
1416            tSizer.Add(togBut)
1417            self.rangeBut = wx.ToggleButton(self,wx.ID_ANY,'Set Range')
1418            self.rangeBut.Bind(wx.EVT_TOGGLEBUTTON,self.SetRange)
1419            tSizer.Add(self.rangeBut)
1420            self.rangeCapt = wx.StaticText(self,wx.ID_ANY,'')
1421            tSizer.Add(self.rangeCapt)
1422            Sizer.Add(tSizer,0,wx.LEFT,12)
1423        # Extra widgets
1424        Sizer.Add((-1,5),0,wx.LEFT,0)
1425        bSizer = wx.BoxSizer(wx.VERTICAL)
1426        for lbl in sorted(extraOpts.keys()):
1427            if not lbl.startswith('label'): continue
1428            key = lbl.replace('label','value')
1429            if key not in extraOpts: continue
1430            eSizer = wx.BoxSizer(wx.HORIZONTAL)
1431            if type(extraOpts[key]) is bool:
1432                eSizer.Add(G2CheckBox(self,extraOpts[lbl],extraOpts,key))
1433            else:
1434                eSizer.Add(wx.StaticText(self,wx.ID_ANY,extraOpts[lbl]))
1435                eSizer.Add(ValidatedTxtCtrl(self,extraOpts,key))
1436            bSizer.Add(eSizer,0,wx.LEFT,0)
1437        Sizer.Add(bSizer,0,wx.CENTER,0)
1438        Sizer.Add((-1,5),0,wx.LEFT,0)
1439        # OK/Cancel buttons
1440        btnsizer = wx.StdDialogButtonSizer()
1441        if useOK:
1442            self.OKbtn = wx.Button(self, wx.ID_OK)
1443            self.OKbtn.SetDefault()
1444            btnsizer.AddButton(self.OKbtn)
1445            self.OKbtn.Bind(wx.EVT_BUTTON,self.onOk)
1446        if useCANCEL:
1447            btn = wx.Button(self, wx.ID_CANCEL)
1448            btn.Bind(wx.EVT_BUTTON,self.onCancel)
1449            btnsizer.AddButton(btn)
1450        btnsizer.Realize()
1451        Sizer.Add((-1,5))
1452        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
1453        Sizer.Add((-1,20))
1454        # OK done, let's get outa here
1455        self.SetSizer(Sizer)
1456        Sizer.Fit(self)
1457        self.CenterOnParent()
1458       
1459    def onOk(self,event):
1460        parent = self.GetParent()
1461        parent.Raise()
1462        self.EndModal(wx.ID_OK)             
1463       
1464    def onCancel(self,event):
1465        parent = self.GetParent()
1466        parent.Raise()
1467        self.EndModal(wx.ID_CANCEL)
1468       
1469    def OnStride(self,event):
1470        self.Stride = int(self.stride.GetValue())
1471
1472    def SetRange(self,event):
1473        '''Respond to a press of the Set Range button. Set the range flag and
1474        the caption next to the button
1475        '''
1476        self.settingRange = self.rangeBut.GetValue()
1477        if self.settingRange:
1478            self.rangeCapt.SetLabel('Select range start')
1479        else:
1480            self.rangeCapt.SetLabel('')           
1481        self.rangeFirst = None
1482       
1483    def GetSelections(self):
1484        'Returns a list of the indices for the selected choices'
1485        # update self.Selections with settings for displayed items
1486        for i in range(len(self.filterlist)):
1487            self.Selections[self.filterlist[i]] = self.clb.IsChecked(i)
1488        # return all selections, shown or hidden
1489        return [i for i in range(len(self.Selections)) if self.Selections[i]]
1490       
1491    def SetSelections(self,selList):
1492        '''Sets the selection indices in selList as selected. Resets any previous
1493        selections for compatibility with wx.MultiChoiceDialog. Note that
1494        the state for only the filtered items is shown.
1495
1496        :param list selList: indices of items to be selected. These indices
1497          are referenced to the order in self.ChoiceList
1498        '''
1499        self.Selections = len(self.ChoiceList) * [False,] # reset selections
1500        for sel in selList:
1501            self.Selections[sel] = True
1502        self._ShowSelections()
1503
1504    def _ShowSelections(self):
1505        'Show the selection state for displayed items'
1506        self.clb.SetChecked(
1507            [i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
1508            ) # Note anything previously checked will be cleared.
1509           
1510    def _SetAll(self,event):
1511        'Set all viewed choices on'
1512        self.clb.SetChecked(range(0,len(self.filterlist),self.Stride))
1513        self.stride.SetValue('1')
1514        self.Stride = 1
1515       
1516    def _ToggleAll(self,event):
1517        'flip the state of all viewed choices'
1518        for i in range(len(self.filterlist)):
1519            self.clb.Check(i,not self.clb.IsChecked(i))
1520           
1521    def onChar(self,event):
1522        'Respond to keyboard events in the Filter box'
1523        self.OKbtn.Enable(False)
1524        if self.timer.IsRunning():
1525            self.timer.Stop()
1526        self.timer.Start(1000,oneShot=True)
1527        if event: event.Skip()
1528       
1529    def OnCheck(self,event):
1530        '''for CheckListBox events; if Set Range is in use, this sets/clears all
1531        entries in range between start and end according to the value in start.
1532        Repeated clicks on the start change the checkbox state, but do not trigger
1533        the range copy.
1534        The caption next to the button is updated on the first button press.
1535        '''
1536        if self.settingRange:
1537            id = event.GetInt()
1538            if self.rangeFirst is None:
1539                name = self.clb.GetString(id)
1540                self.rangeCapt.SetLabel(name+' to...')
1541                self.rangeFirst = id
1542            elif self.rangeFirst == id:
1543                pass
1544            else:
1545                for i in range(min(self.rangeFirst,id), max(self.rangeFirst,id)+1,self.Stride):
1546                    self.clb.Check(i,self.clb.IsChecked(self.rangeFirst))
1547                self.rangeBut.SetValue(False)
1548                self.rangeCapt.SetLabel('')
1549            return
1550       
1551    def Filter(self,event):
1552        '''Read text from filter control and select entries that match. Called by
1553        Timer after a delay with no input or if Enter is pressed.
1554        '''
1555        if self.timer.IsRunning():
1556            self.timer.Stop()
1557        self.GetSelections() # record current selections
1558        txt = self.filterBox.GetValue()
1559        self.clb.Clear()
1560       
1561        self.Update()
1562        self.filterlist = []
1563        if txt:
1564            txt = txt.lower()
1565            ChoiceList = []
1566            for i,item in enumerate(self.ChoiceList):
1567                if item.lower().find(txt) != -1:
1568                    ChoiceList.append(item)
1569                    self.filterlist.append(i)
1570        else:
1571            self.filterlist = range(len(self.ChoiceList))
1572            ChoiceList = self.ChoiceList
1573        self.clb.AppendItems(ChoiceList)
1574        self._ShowSelections()
1575        self.OKbtn.Enable(True)
1576
1577def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem):
1578    '''Select a variable from a list, then edit it and select histograms
1579    to copy it to.
1580
1581    :param wx.Frame G2frame: main GSAS-II frame
1582    :param dict array: the array (dict or list) where values to be edited are kept
1583    :param list labelLst: labels for each data item
1584    :param list elemKeysLst: a list of lists of keys needed to be applied (see below)
1585      to obtain the value of each parameter
1586    :param list dspLst: list list of digits to be displayed (10,4) is 10 digits
1587      with 4 decimal places. Can be None.
1588    :param list refFlgElem: a list of lists of keys needed to be applied (see below)
1589      to obtain the refine flag for each parameter or None if the parameter
1590      does not have refine flag.
1591
1592    Example::
1593      array = data
1594      labelLst = ['v1','v2']
1595      elemKeysLst = [['v1'], ['v2',0]]
1596      refFlgElem = [None, ['v2',1]]
1597
1598     * The value for v1 will be in data['v1'] and this cannot be refined while,
1599     * The value for v2 will be in data['v2'][0] and its refinement flag is data['v2'][1]
1600    '''
1601    def unkey(dct,keylist):
1602        '''dive into a nested set of dicts/lists applying keys in keylist
1603        consecutively
1604        '''
1605        d = dct
1606        for k in keylist:
1607            d = d[k]
1608        return d
1609
1610    def OnChoice(event):
1611        'Respond when a parameter is selected in the Choice box'
1612        valSizer.DeleteWindows()
1613        lbl = event.GetString()
1614        copyopts['currentsel'] = lbl
1615        i = labelLst.index(lbl)
1616        OKbtn.Enable(True)
1617        ch.SetLabel(lbl)
1618        args = {}
1619        if dspLst[i]:
1620            args = {'nDig':dspLst[i]}
1621        Val = ValidatedTxtCtrl(
1622            dlg,
1623            unkey(array,elemKeysLst[i][:-1]),
1624            elemKeysLst[i][-1],
1625            **args)
1626        copyopts['startvalue'] = unkey(array,elemKeysLst[i])
1627        #unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] =
1628        valSizer.Add(Val,0,wx.LEFT,5)
1629        dlg.SendSizeEvent()
1630       
1631    # SelectEdit1Var execution begins here
1632    saveArray = copy.deepcopy(array) # keep original values
1633    TreeItemType = G2frame.GPXtree.GetItemText(G2frame.PickId)
1634    copyopts = {'InTable':False,"startvalue":None,'currentsel':None}       
1635    hst = G2frame.GPXtree.GetItemText(G2frame.PatternId)
1636    histList = G2pdG.GetHistsLikeSelected(G2frame)
1637    if not histList:
1638        G2frame.ErrorDialog('No match','No histograms match '+hst,G2frame)
1639        return
1640    dlg = wx.Dialog(G2frame,wx.ID_ANY,'Set a parameter value',
1641        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1642    mainSizer = wx.BoxSizer(wx.VERTICAL)
1643    mainSizer.Add((5,5))
1644    subSizer = wx.BoxSizer(wx.HORIZONTAL)
1645    subSizer.Add((-1,-1),1,wx.EXPAND)
1646    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Select a parameter and set a new value'))
1647    subSizer.Add((-1,-1),1,wx.EXPAND)
1648    mainSizer.Add(subSizer,0,wx.EXPAND,0)
1649    mainSizer.Add((0,10))
1650
1651    subSizer = wx.FlexGridSizer(0,2,5,0)
1652    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Parameter: '))
1653    ch = wx.Choice(dlg, wx.ID_ANY, choices = sorted(labelLst))
1654    ch.SetSelection(-1)
1655    ch.Bind(wx.EVT_CHOICE, OnChoice)
1656    subSizer.Add(ch)
1657    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Value: '))
1658    valSizer = wx.BoxSizer(wx.HORIZONTAL)
1659    subSizer.Add(valSizer)
1660    mainSizer.Add(subSizer)
1661
1662    mainSizer.Add((-1,20))
1663    subSizer = wx.BoxSizer(wx.HORIZONTAL)
1664    subSizer.Add(G2CheckBox(dlg, 'Edit in table ', copyopts, 'InTable'))
1665    mainSizer.Add(subSizer)
1666
1667    btnsizer = wx.StdDialogButtonSizer()
1668    OKbtn = wx.Button(dlg, wx.ID_OK,'Continue')
1669    OKbtn.Enable(False)
1670    OKbtn.SetDefault()
1671    OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
1672    btnsizer.AddButton(OKbtn)
1673    btn = wx.Button(dlg, wx.ID_CANCEL)
1674    btnsizer.AddButton(btn)
1675    btnsizer.Realize()
1676    mainSizer.Add((-1,5),1,wx.EXPAND,1)
1677    mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
1678    mainSizer.Add((-1,10))
1679
1680    dlg.SetSizer(mainSizer)
1681    dlg.CenterOnParent()
1682    if dlg.ShowModal() != wx.ID_OK:
1683        array.update(saveArray)
1684        dlg.Destroy()
1685        return
1686    dlg.Destroy()
1687
1688    copyList = []
1689    lbl = copyopts['currentsel']
1690    dlg = G2MultiChoiceDialog(G2frame,'Copy parameter '+lbl+' from\n'+hst,
1691        'Copy parameters', histList)
1692    dlg.CenterOnParent()
1693    try:
1694        if dlg.ShowModal() == wx.ID_OK:
1695            for i in dlg.GetSelections(): 
1696                copyList.append(histList[i])
1697        else:
1698            # reset the parameter since cancel was pressed
1699            array.update(saveArray)
1700            return
1701    finally:
1702        dlg.Destroy()
1703
1704    prelbl = [hst]
1705    i = labelLst.index(lbl)
1706    keyLst = elemKeysLst[i]
1707    refkeys = refFlgElem[i]
1708    dictlst = [unkey(array,keyLst[:-1])]
1709    if refkeys is not None:
1710        refdictlst = [unkey(array,refkeys[:-1])]
1711    else:
1712        refdictlst = None
1713    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hst)
1714    hstData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Instrument Parameters'))[0]
1715    for h in copyList:
1716        Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,h)
1717        instData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Instrument Parameters'))[0]
1718        if len(hstData) != len(instData) or hstData['Type'][0] != instData['Type'][0]:  #don't mix data types or lam & lam1/lam2 parms!
1719            print h+' not copied - instrument parameters not commensurate'
1720            continue
1721        hData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,TreeItemType))
1722        if TreeItemType == 'Instrument Parameters':
1723            hData = hData[0]
1724        #copy the value if it is changed or we will not edit in a table
1725        valNow = unkey(array,keyLst)
1726        if copyopts['startvalue'] != valNow or not copyopts['InTable']:
1727            unkey(hData,keyLst[:-1])[keyLst[-1]] = valNow
1728        prelbl += [h]
1729        dictlst += [unkey(hData,keyLst[:-1])]
1730        if refdictlst is not None:
1731            refdictlst += [unkey(hData,refkeys[:-1])]
1732    if refdictlst is None:
1733        args = {}
1734    else:
1735        args = {'checkdictlst':refdictlst,
1736                'checkelemlst':len(dictlst)*[refkeys[-1]],
1737                'checklabel':'Refine?'}
1738    if copyopts['InTable']:
1739        dlg = ScrolledMultiEditor(
1740            G2frame,dictlst,
1741            len(dictlst)*[keyLst[-1]],prelbl,
1742            header='Editing parameter '+lbl,
1743            CopyButton=True,**args)
1744        dlg.CenterOnParent()
1745        if dlg.ShowModal() != wx.ID_OK:
1746            array.update(saveArray)
1747        dlg.Destroy()
1748
1749################################################################        Single choice Dialog with filter options
1750class G2SingleChoiceDialog(wx.Dialog):
1751    '''A dialog similar to wx.SingleChoiceDialog except that a filter can be
1752    added.
1753
1754    :param wx.Frame ParentFrame: reference to parent frame
1755    :param str title: heading above list of choices
1756    :param str header: Title to place on window frame
1757    :param list ChoiceList: a list of choices where one will be selected
1758    :param bool monoFont: If False (default), use a variable-spaced font;
1759      if True use a equally-spaced font.
1760    :param bool filterBox: If True (default) an input widget is placed on
1761      the window and only entries matching the entered text are shown.
1762    :param kw: optional keyword parameters for the wx.Dialog may
1763      be included such as size [which defaults to `(320,310)`] and
1764      style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
1765      note that ``wx.OK`` and ``wx.CANCEL`` controls
1766      the presence of the eponymous buttons in the dialog.
1767    :returns: the name of the created dialog
1768    '''
1769    def __init__(self,parent, title, header, ChoiceList, 
1770                 monoFont=False, filterBox=True, **kw):
1771        # process keyword parameters, notably style
1772        options = {'size':(320,310), # default Frame keywords
1773                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
1774                   }
1775        options.update(kw)
1776        self.ChoiceList = ChoiceList
1777        self.filterlist = range(len(self.ChoiceList))
1778        if options['style'] & wx.OK:
1779            useOK = True
1780            options['style'] ^= wx.OK
1781        else:
1782            useOK = False
1783        if options['style'] & wx.CANCEL:
1784            useCANCEL = True
1785            options['style'] ^= wx.CANCEL
1786        else:
1787            useCANCEL = False       
1788        # create the dialog frame
1789        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
1790        # fill the dialog
1791        Sizer = wx.BoxSizer(wx.VERTICAL)
1792        topSizer = wx.BoxSizer(wx.HORIZONTAL)
1793        topSizer.Add(
1794            wx.StaticText(self,wx.ID_ANY,title,size=(-1,35)),
1795            1,wx.ALL|wx.EXPAND|WACV,1)
1796        if filterBox:
1797            self.timer = wx.Timer()
1798            self.timer.Bind(wx.EVT_TIMER,self.Filter)
1799            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Filter: '),0,wx.ALL,1)
1800            self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),
1801                                         style=wx.TE_PROCESS_ENTER)
1802            self.filterBox.Bind(wx.EVT_CHAR,self.onChar)
1803            self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
1804        topSizer.Add(self.filterBox,0,wx.ALL,0)
1805        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
1806        self.clb = wx.ListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
1807        self.clb.Bind(wx.EVT_LEFT_DCLICK,self.onDoubleClick)
1808        if monoFont:
1809            font1 = wx.Font(self.clb.GetFont().GetPointSize(),
1810                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
1811            self.clb.SetFont(font1)
1812        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
1813        Sizer.Add((-1,10))
1814        # OK/Cancel buttons
1815        btnsizer = wx.StdDialogButtonSizer()
1816        if useOK:
1817            self.OKbtn = wx.Button(self, wx.ID_OK)
1818            self.OKbtn.SetDefault()
1819            btnsizer.AddButton(self.OKbtn)
1820        if useCANCEL:
1821            btn = wx.Button(self, wx.ID_CANCEL)
1822            btnsizer.AddButton(btn)
1823        btnsizer.Realize()
1824        Sizer.Add((-1,5))
1825        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
1826        Sizer.Add((-1,20))
1827        # OK done, let's get outa here
1828        self.SetSizer(Sizer)
1829    def GetSelection(self):
1830        'Returns the index of the selected choice'
1831        i = self.clb.GetSelection()
1832        if i < 0 or i >= len(self.filterlist):
1833            return wx.NOT_FOUND
1834        return self.filterlist[i]
1835    def onChar(self,event):
1836        self.OKbtn.Enable(False)
1837        if self.timer.IsRunning():
1838            self.timer.Stop()
1839        self.timer.Start(1000,oneShot=True)
1840        if event: event.Skip()
1841    def Filter(self,event):
1842        if self.timer.IsRunning():
1843            self.timer.Stop()
1844        txt = self.filterBox.GetValue()
1845        self.clb.Clear()
1846        self.Update()
1847        self.filterlist = []
1848        if txt:
1849            txt = txt.lower()
1850            ChoiceList = []
1851            for i,item in enumerate(self.ChoiceList):
1852                if item.lower().find(txt) != -1:
1853                    ChoiceList.append(item)
1854                    self.filterlist.append(i)
1855        else:
1856            self.filterlist = range(len(self.ChoiceList))
1857            ChoiceList = self.ChoiceList
1858        self.clb.AppendItems(ChoiceList)
1859        self.OKbtn.Enable(True)
1860    def onDoubleClick(self,event):
1861        self.EndModal(wx.ID_OK)
1862       
1863################################################################################
1864class FlagSetDialog(wx.Dialog):
1865    ''' Creates popup with table of variables to be checked for e.g. refinement flags
1866    '''
1867    def __init__(self,parent,title,colnames,rownames,flags):
1868        wx.Dialog.__init__(self,parent,-1,title,
1869            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1870        self.panel = None
1871        self.colnames = colnames
1872        self.rownames = rownames
1873        self.flags = flags
1874        self.newflags = copy.copy(flags)
1875        self.Draw()
1876       
1877    def Draw(self):
1878        Indx = {}
1879       
1880        def OnSelection(event):
1881            Obj = event.GetEventObject()
1882            [name,ia] = Indx[Obj.GetId()]
1883            self.newflags[name][ia] = Obj.GetValue()
1884           
1885        if self.panel:
1886            self.panel.DestroyChildren()  #safe: wx.Panel
1887            self.panel.Destroy()
1888        self.panel = wx.Panel(self)
1889        mainSizer = wx.BoxSizer(wx.VERTICAL)
1890        flagSizer = wx.FlexGridSizer(0,len(self.colnames),5,5)
1891        for item in self.colnames:
1892            flagSizer.Add(wx.StaticText(self.panel,label=item),0,WACV)
1893        for ia,atm in enumerate(self.rownames):
1894            flagSizer.Add(wx.StaticText(self.panel,label=atm),0,WACV)
1895            for name in self.colnames[1:]:
1896                if self.flags[name][ia]:
1897                    self.newflags[name][ia] = False     #default is off
1898                    flg = wx.CheckBox(self.panel,-1,label='')
1899                    flg.Bind(wx.EVT_CHECKBOX,OnSelection)
1900                    Indx[flg.GetId()] = [name,ia]
1901                    flagSizer.Add(flg,0,WACV)
1902                else:
1903                    flagSizer.Add(wx.StaticText(self.panel,label='na'),0,WACV)
1904           
1905        mainSizer.Add(flagSizer,0)
1906        OkBtn = wx.Button(self.panel,-1,"Ok")
1907        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1908        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1909        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1910        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1911        btnSizer.Add((20,20),1)
1912        btnSizer.Add(OkBtn)
1913        btnSizer.Add(CancelBtn)
1914        btnSizer.Add((20,20),1)
1915        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1916        self.panel.SetSizer(mainSizer)
1917        self.panel.Fit()
1918        self.Fit()
1919       
1920    def GetSelection(self):
1921        return self.newflags
1922
1923    def OnOk(self,event):
1924        parent = self.GetParent()
1925        parent.Raise()
1926        self.EndModal(wx.ID_OK)             
1927       
1928    def OnCancel(self,event):
1929        parent = self.GetParent()
1930        parent.Raise()
1931        self.EndModal(wx.ID_CANCEL)
1932
1933###################################################################,#############
1934def G2MessageBox(parent,msg,title='Error'):
1935    '''Simple code to display a error or warning message
1936    '''
1937    dlg = wx.MessageDialog(parent,StripIndents(msg), title, wx.OK)
1938    dlg.ShowModal()
1939    dlg.Destroy()
1940   
1941################################################################################
1942class PickTwoDialog(wx.Dialog):
1943    '''This does not seem to be in use
1944    '''
1945    def __init__(self,parent,title,prompt,names,choices):
1946        wx.Dialog.__init__(self,parent,-1,title, 
1947            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1948        self.panel = None
1949        self.prompt = prompt
1950        self.choices = choices
1951        self.names = names
1952        self.Draw()
1953
1954    def Draw(self):
1955        Indx = {}
1956       
1957        def OnSelection(event):
1958            Obj = event.GetEventObject()
1959            id = Indx[Obj.GetId()]
1960            self.choices[id] = Obj.GetValue().encode()  #to avoid Unicode versions
1961            self.Draw()
1962           
1963        if self.panel:
1964            self.panel.DestroyChildren()  #safe: wx.Panel
1965            self.panel.Destroy()
1966        self.panel = wx.Panel(self)
1967        mainSizer = wx.BoxSizer(wx.VERTICAL)
1968        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1969        for isel,name in enumerate(self.choices):
1970            lineSizer = wx.BoxSizer(wx.HORIZONTAL)
1971            lineSizer.Add(wx.StaticText(self.panel,-1,'Reference atom '+str(isel+1)),0,wx.ALIGN_CENTER)
1972            nameList = self.names[:]
1973            if isel:
1974                if self.choices[0] in nameList:
1975                    nameList.remove(self.choices[0])
1976            choice = wx.ComboBox(self.panel,-1,value=name,choices=nameList,
1977                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1978            Indx[choice.GetId()] = isel
1979            choice.Bind(wx.EVT_COMBOBOX, OnSelection)
1980            lineSizer.Add(choice,0,WACV)
1981            mainSizer.Add(lineSizer)
1982        OkBtn = wx.Button(self.panel,-1,"Ok")
1983        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1984        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1985        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1986        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1987        btnSizer.Add((20,20),1)
1988        btnSizer.Add(OkBtn)
1989        btnSizer.Add(CancelBtn)
1990        btnSizer.Add((20,20),1)
1991        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1992        self.panel.SetSizer(mainSizer)
1993        self.panel.Fit()
1994        self.Fit()
1995       
1996    def GetSelection(self):
1997        return self.choices
1998
1999    def OnOk(self,event):
2000        parent = self.GetParent()
2001        parent.Raise()
2002        self.EndModal(wx.ID_OK)             
2003       
2004    def OnCancel(self,event):
2005        parent = self.GetParent()
2006        parent.Raise()
2007        self.EndModal(wx.ID_CANCEL)
2008
2009################################################################################
2010class SingleFloatDialog(wx.Dialog):
2011    '''Dialog to obtain a single float value from user
2012
2013    Typical usage::
2014
2015            limits = (0,1)
2016            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for...',default,limits)
2017            if dlg.ShowModal() == wx.ID_OK:
2018                parm = dlg.GetValue()
2019            dlg.Destroy()   
2020
2021    '''
2022    def __init__(self,parent,title,prompt,value,limits=[0.,1.],format='%.5g'):
2023        wx.Dialog.__init__(self,parent,-1,title, 
2024            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
2025        self.panel = None
2026        self.limits = limits
2027        self.value = value
2028        self.prompt = prompt
2029        self.format = format
2030        self.Draw()
2031       
2032    def Draw(self):
2033       
2034        def OnValItem(event):
2035            if event: event.Skip()
2036            try:
2037                val = float(valItem.GetValue())
2038                if val < self.limits[0] or val > self.limits[1]:
2039                    raise ValueError
2040            except ValueError:
2041                val = self.value
2042            self.value = val
2043            valItem.SetValue(self.format%(self.value))
2044           
2045        if self.panel: self.panel.Destroy()
2046        self.panel = wx.Panel(self)
2047        mainSizer = wx.BoxSizer(wx.VERTICAL)
2048        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
2049        valItem = wx.TextCtrl(self.panel,-1,value=self.format%(self.value),style=wx.TE_PROCESS_ENTER)
2050        mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
2051        valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
2052        valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
2053        OkBtn = wx.Button(self.panel,-1,"Ok")
2054        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
2055        CancelBtn = wx.Button(self.panel,-1,'Cancel')
2056        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
2057        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
2058        btnSizer.Add((20,20),1)
2059        btnSizer.Add(OkBtn)
2060        btnSizer.Add(CancelBtn)
2061        btnSizer.Add((20,20),1)
2062        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
2063        self.panel.SetSizer(mainSizer)
2064        self.panel.Fit()
2065        self.Fit()
2066
2067    def GetValue(self):
2068        return self.value
2069       
2070    def OnOk(self,event):
2071        parent = self.GetParent()
2072        parent.Raise()
2073        self.EndModal(wx.ID_OK)             
2074       
2075    def OnCancel(self,event):
2076        parent = self.GetParent()
2077        parent.Raise()
2078        self.EndModal(wx.ID_CANCEL)
2079
2080################################################################################
2081class MultiFloatDialog(wx.Dialog):
2082    'Dialog to obtain a multi float value from user'
2083    def __init__(self,parent,title,prompts,values,limits=[[0.,1.],],formats=['%.5g',]):
2084        wx.Dialog.__init__(self,parent,-1,title, 
2085            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
2086        self.panel = None
2087        self.limits = limits
2088        self.values = values
2089        self.prompts = prompts
2090        self.formats = formats
2091        self.Draw()
2092       
2093    def Draw(self):
2094       
2095        def OnValItem(event):
2096            if event: event.Skip()
2097            Obj = event.GetEventObject()
2098            id,limits,format = Indx[Obj]
2099            try:
2100                val = float(Obj.GetValue())
2101                if val < limits[0] or val > limits[1]:
2102                    raise ValueError
2103            except ValueError:
2104                val = self.values[id]
2105            self.values[id] = val
2106            Obj.SetValue(format%(val))
2107           
2108        Indx = {}
2109        if self.panel: self.panel.Destroy()
2110        self.panel = wx.Panel(self)
2111        mainSizer = wx.BoxSizer(wx.VERTICAL)
2112        lineSizer = wx.FlexGridSizer(0,2,5,5)
2113        for id,[prompt,value,limits,format] in enumerate(zip(self.prompts,self.values,self.limits,self.formats)):
2114            lineSizer.Add(wx.StaticText(self.panel,label=prompt),0,wx.ALIGN_CENTER)
2115            valItem = wx.TextCtrl(self.panel,value=format%(value),style=wx.TE_PROCESS_ENTER)
2116            Indx[valItem] = [id,limits,format]
2117            lineSizer.Add(valItem,0,wx.ALIGN_CENTER)
2118            valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
2119            valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
2120        mainSizer.Add(lineSizer)
2121        OkBtn = wx.Button(self.panel,-1,"Ok")
2122        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
2123        CancelBtn = wx.Button(self.panel,-1,'Cancel')
2124        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
2125        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
2126        btnSizer.Add((20,20),1)
2127        btnSizer.Add(OkBtn)
2128        btnSizer.Add(CancelBtn)
2129        btnSizer.Add((20,20),1)
2130        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
2131        self.panel.SetSizer(mainSizer)
2132        self.panel.Fit()
2133        self.Fit()
2134
2135    def GetValues(self):
2136        return self.values
2137       
2138    def OnOk(self,event):
2139        parent = self.GetParent()
2140        parent.Raise()
2141        self.EndModal(wx.ID_OK)             
2142       
2143    def OnCancel(self,event):
2144        parent = self.GetParent()
2145        parent.Raise()
2146        self.EndModal(wx.ID_CANCEL)
2147
2148################################################################################
2149class SingleStringDialog(wx.Dialog):
2150    '''Dialog to obtain a single string value from user
2151   
2152    :param wx.Frame parent: name of parent frame
2153    :param str title: title string for dialog
2154    :param str prompt: string to tell use what they are inputting
2155    :param str value: default input value, if any
2156    '''
2157    def __init__(self,parent,title,prompt,value='',size=(200,-1),help=''):
2158        wx.Dialog.__init__(self,parent,wx.ID_ANY,title, 
2159                           pos=wx.DefaultPosition,
2160                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2161        self.value = value
2162        self.prompt = prompt
2163        self.CenterOnParent()
2164        self.panel = wx.Panel(self)
2165        mainSizer = wx.BoxSizer(wx.VERTICAL)
2166        if help:
2167            sizer1 = wx.BoxSizer(wx.HORIZONTAL)   
2168            sizer1.Add((-1,-1),1, wx.EXPAND, 0)
2169            sizer1.Add(HelpButton(self.panel,help),0,wx.ALIGN_RIGHT|wx.ALL)
2170            mainSizer.Add(sizer1,0,wx.ALIGN_RIGHT|wx.EXPAND)
2171        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
2172        self.valItem = wx.TextCtrl(self.panel,-1,value=self.value,size=size)
2173        mainSizer.Add(self.valItem,0,wx.ALIGN_CENTER)
2174        btnsizer = wx.StdDialogButtonSizer()
2175        OKbtn = wx.Button(self.panel, wx.ID_OK)
2176        OKbtn.SetDefault()
2177        btnsizer.AddButton(OKbtn)
2178        btn = wx.Button(self.panel, wx.ID_CANCEL)
2179        btnsizer.AddButton(btn)
2180        btnsizer.Realize()
2181        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
2182        self.panel.SetSizer(mainSizer)
2183        self.panel.Fit()
2184        self.Fit()
2185
2186    def Show(self):
2187        '''Use this method after creating the dialog to post it
2188        :returns: True if the user pressed OK; False if the User pressed Cancel
2189        '''
2190        if self.ShowModal() == wx.ID_OK:
2191            self.value = self.valItem.GetValue()
2192            return True
2193        else:
2194            return False
2195
2196    def GetValue(self):
2197        '''Use this method to get the value entered by the user
2198        :returns: string entered by user
2199        '''
2200        return self.value
2201
2202################################################################################
2203class MultiStringDialog(wx.Dialog):
2204    '''Dialog to obtain a multi string values from user
2205   
2206    :param wx.Frame parent: name of parent frame
2207    :param str title: title string for dialog
2208    :param str prompts: strings to tell use what they are inputting
2209    :param str values: default input values, if any
2210    '''
2211    def __init__(self,parent,title,prompts,values=[]):      #,size=(200,-1)?
2212       
2213        wx.Dialog.__init__(self,parent,wx.ID_ANY,title, 
2214                           pos=wx.DefaultPosition,
2215                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2216        self.values = values
2217        self.prompts = prompts
2218        self.CenterOnParent()
2219        self.panel = wx.Panel(self)
2220        mainSizer = wx.BoxSizer(wx.VERTICAL)
2221        promptSizer = wx.FlexGridSizer(0,2,5,5)
2222        self.Indx = {}
2223        for prompt,value in zip(prompts,values):
2224            promptSizer.Add(wx.StaticText(self.panel,-1,prompt),0,WACV)
2225            valItem = wx.TextCtrl(self.panel,-1,value=value,style=wx.TE_PROCESS_ENTER)
2226            self.Indx[valItem.GetId()] = prompt
2227            valItem.Bind(wx.EVT_TEXT,self.newValue)
2228            promptSizer.Add(valItem,0,WACV)
2229        mainSizer.Add(promptSizer,0)
2230        btnsizer = wx.StdDialogButtonSizer()
2231        OKbtn = wx.Button(self.panel, wx.ID_OK)
2232        OKbtn.SetDefault()
2233        btnsizer.AddButton(OKbtn)
2234        btn = wx.Button(self.panel, wx.ID_CANCEL)
2235        btnsizer.AddButton(btn)
2236        btnsizer.Realize()
2237        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
2238        self.panel.SetSizer(mainSizer)
2239        self.panel.Fit()
2240        self.Fit()
2241       
2242    def newValue(self,event):
2243        Obj = event.GetEventObject()
2244        item = self.Indx[Obj.GetId()]
2245        id = self.prompts.index(item)
2246        self.values[id] = Obj.GetValue()
2247
2248    def Show(self):
2249        '''Use this method after creating the dialog to post it
2250        :returns: True if the user pressed OK; False if the User pressed Cancel
2251        '''
2252        if self.ShowModal() == wx.ID_OK:
2253            return True
2254        else:
2255            return False
2256
2257    def GetValues(self):
2258        '''Use this method to get the value entered by the user
2259        :returns: string entered by user
2260        '''
2261        return self.values
2262
2263################################################################################
2264class G2ColumnIDDialog(wx.Dialog):
2265    '''A dialog for matching column data to desired items; some columns may be ignored.
2266   
2267    :param wx.Frame ParentFrame: reference to parent frame
2268    :param str title: heading above list of choices
2269    :param str header: Title to place on window frame
2270    :param list ChoiceList: a list of possible choices for the columns
2271    :param list ColumnData: lists of column data to be matched with ChoiceList
2272    :param bool monoFont: If False (default), use a variable-spaced font;
2273      if True use a equally-spaced font.
2274    :param kw: optional keyword parameters for the wx.Dialog may
2275      be included such as size [which defaults to `(320,310)`] and
2276      style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
2277      note that ``wx.OK`` and ``wx.CANCEL`` controls
2278      the presence of the eponymous buttons in the dialog.
2279    :returns: the name of the created dialog
2280   
2281    '''
2282
2283    def __init__(self,parent, title, header,Comments,ChoiceList, ColumnData,
2284                 monoFont=False, **kw):
2285
2286        def OnOk(sevent):
2287            OK = True
2288            selCols = []
2289            for col in self.sel:
2290                item = col.GetValue()
2291                if item != ' ' and item in selCols:
2292                    OK = False
2293                    break
2294                else:
2295                    selCols.append(item)
2296            parent = self.GetParent()
2297            if not OK:
2298                parent.ErrorDialog('Duplicate',item+' selected more than once')
2299                return
2300            parent.Raise()
2301            self.EndModal(wx.ID_OK)
2302           
2303        def OnModify(event):
2304            if event: event.Skip()
2305            Obj = event.GetEventObject()
2306            icol,colData = Indx[Obj.GetId()]
2307            modify = Obj.GetValue()
2308            if not modify:
2309                return
2310            #print 'Modify column',icol,' by', modify
2311            for i,item in enumerate(self.ColumnData[icol]):
2312                self.ColumnData[icol][i] = str(eval(item+modify))
2313            colData.SetValue('\n'.join(self.ColumnData[icol]))
2314            Obj.SetValue('')
2315           
2316        # process keyword parameters, notably style
2317        options = {'size':(600,310), # default Frame keywords
2318                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
2319                   }
2320        options.update(kw)
2321        self.Comments = ''.join(Comments)
2322        self.ChoiceList = ChoiceList
2323        self.ColumnData = ColumnData
2324        nCol = len(ColumnData)
2325        if options['style'] & wx.OK:
2326            useOK = True
2327            options['style'] ^= wx.OK
2328        else:
2329            useOK = False
2330        if options['style'] & wx.CANCEL:
2331            useCANCEL = True
2332            options['style'] ^= wx.CANCEL
2333        else:
2334            useCANCEL = False       
2335        # create the dialog frame
2336        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
2337        panel = wxscroll.ScrolledPanel(self)
2338        # fill the dialog
2339        Sizer = wx.BoxSizer(wx.VERTICAL)
2340        Sizer.Add((-1,5))
2341        Sizer.Add(wx.StaticText(panel,label=title),0,WACV)
2342        if self.Comments:
2343            Sizer.Add(wx.StaticText(panel,label=' Header lines:'),0,WACV)
2344            Sizer.Add(wx.TextCtrl(panel,value=self.Comments,size=(200,-1),
2345                style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP),0,wx.ALL|wx.EXPAND|WACV,8)
2346        columnsSizer = wx.FlexGridSizer(0,nCol,5,10)
2347        self.sel = []
2348        self.mod = []
2349        Indx = {}
2350        for icol,col in enumerate(self.ColumnData):
2351            colSizer = wx.BoxSizer(wx.VERTICAL)
2352            colSizer.Add(wx.StaticText(panel,label=' Column #%d Select:'%(icol)),0,WACV)
2353            self.sel.append(wx.ComboBox(panel,value=' ',choices=self.ChoiceList,style=wx.CB_READONLY|wx.CB_DROPDOWN))
2354            colSizer.Add(self.sel[-1])
2355            colData = wx.TextCtrl(panel,value='\n'.join(self.ColumnData[icol]),size=(120,-1),
2356                style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
2357            colSizer.Add(colData,0,WACV)
2358            colSizer.Add(wx.StaticText(panel,label=' Modify by:'),0,WACV)
2359            mod = wx.TextCtrl(panel,size=(120,-1),value='',style=wx.TE_PROCESS_ENTER)
2360            mod.Bind(wx.EVT_TEXT_ENTER,OnModify)
2361            mod.Bind(wx.EVT_KILL_FOCUS,OnModify)
2362            Indx[mod.GetId()] = [icol,colData]
2363            colSizer.Add(mod,0,WACV)
2364            columnsSizer.Add(colSizer)
2365        Sizer.Add(columnsSizer)
2366        Sizer.Add(wx.StaticText(panel,label=' For modify by, enter arithmetic string eg. "-12345.67". "+","-","*","/","**" all allowed'),0,WACV) 
2367        Sizer.Add((-1,10))
2368        # OK/Cancel buttons
2369        btnsizer = wx.StdDialogButtonSizer()
2370        if useOK:
2371            self.OKbtn = wx.Button(panel, wx.ID_OK)
2372            self.OKbtn.SetDefault()
2373            btnsizer.AddButton(self.OKbtn)
2374            self.OKbtn.Bind(wx.EVT_BUTTON, OnOk)
2375        if useCANCEL:
2376            btn = wx.Button(panel, wx.ID_CANCEL)
2377            btnsizer.AddButton(btn)
2378        btnsizer.Realize()
2379        Sizer.Add((-1,5))
2380        Sizer.Add(btnsizer,0,wx.ALIGN_LEFT,20)
2381        Sizer.Add((-1,5))
2382        # OK done, let's get outa here
2383        panel.SetSizer(Sizer)
2384        panel.SetAutoLayout(1)
2385        panel.SetupScrolling()
2386        Size = [450,375]
2387        panel.SetSize(Size)
2388        Size[0] += 25; Size[1]+= 25
2389        self.SetSize(Size)
2390       
2391    def GetSelection(self):
2392        'Returns the selected sample parm for each column'
2393        selCols = []
2394        for item in self.sel:
2395            selCols.append(item.GetValue())
2396        return selCols,self.ColumnData
2397   
2398################################################################################
2399class G2HistoDataDialog(wx.Dialog):
2400    '''A dialog for editing histogram data globally.
2401   
2402    :param wx.Frame ParentFrame: reference to parent frame
2403    :param str title: heading above list of choices
2404    :param str header: Title to place on window frame
2405    :param list ParmList: a list of names for the columns
2406    :param list ParmFmt: a list of formatting strings for the columns
2407    :param list: HistoList: a list of histogram names
2408    :param list ParmData: a list of lists of data matched to ParmList; one for each item in HistoList
2409    :param bool monoFont: If False (default), use a variable-spaced font;
2410      if True use a equally-spaced font.
2411    :param kw: optional keyword parameters for the wx.Dialog may
2412      be included such as size [which defaults to `(320,310)`] and
2413      style (which defaults to
2414      ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
2415      note that ``wx.OK`` and ``wx.CANCEL`` controls the presence of the eponymous buttons in the dialog.
2416    :returns: the modified ParmData
2417   
2418    '''
2419
2420    def __init__(self,parent, title, header,ParmList,ParmFmt,HistoList,ParmData,
2421                 monoFont=False, **kw):
2422
2423        def OnOk(sevent):
2424            parent.Raise()
2425            self.EndModal(wx.ID_OK)
2426           
2427        def OnModify(event):
2428            Obj = event.GetEventObject()
2429            irow,it = Indx[Obj.GetId()]
2430            try:
2431                val = float(Obj.GetValue())
2432            except ValueError:
2433                val = self.ParmData[irow][it]
2434            self.ParmData[irow][it] = val
2435            Obj.SetValue(self.ParmFmt[it]%val)
2436                       
2437        # process keyword parameters, notably style
2438        options = {'size':(600,310), # default Frame keywords
2439                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
2440                   }
2441        options.update(kw)
2442        self.ParmList = ParmList
2443        self.ParmFmt = ParmFmt
2444        self.HistoList = HistoList
2445        self.ParmData = ParmData
2446        nCol = len(ParmList)
2447        if options['style'] & wx.OK:
2448            useOK = True
2449            options['style'] ^= wx.OK
2450        else:
2451            useOK = False
2452        if options['style'] & wx.CANCEL:
2453            useCANCEL = True
2454            options['style'] ^= wx.CANCEL
2455        else:
2456            useCANCEL = False       
2457        # create the dialog frame
2458        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
2459        panel = wxscroll.ScrolledPanel(self)
2460        # fill the dialog
2461        Sizer = wx.BoxSizer(wx.VERTICAL)
2462        Sizer.Add((-1,5))
2463        Sizer.Add(wx.StaticText(panel,label=title),0,WACV)
2464        dataSizer = wx.FlexGridSizer(0,nCol+1,0,0)
2465        self.sel = []
2466        self.mod = []
2467        Indx = {}
2468        for item in ['Histogram',]+self.ParmList:
2469            dataSizer.Add(wx.StaticText(panel,-1,label=' %10s '%(item)),0,WACV)
2470        for irow,name in enumerate(self.HistoList):
2471            dataSizer.Add(wx.StaticText(panel,label=name),0,WACV|wx.LEFT|wx.RIGHT,10)
2472            for it,item in enumerate(self.ParmData[irow]):
2473                dat = wx.TextCtrl(panel,-1,value=self.ParmFmt[it]%(item),style=wx.TE_PROCESS_ENTER)
2474                dataSizer.Add(dat,0,WACV)
2475                dat.Bind(wx.EVT_TEXT_ENTER,OnModify)
2476                dat.Bind(wx.EVT_KILL_FOCUS,OnModify)
2477                Indx[dat.GetId()] = [irow,it]
2478        Sizer.Add(dataSizer)
2479        Sizer.Add((-1,10))
2480        # OK/Cancel buttons
2481        btnsizer = wx.StdDialogButtonSizer()
2482        if useOK:
2483            self.OKbtn = wx.Button(panel, wx.ID_OK)
2484            self.OKbtn.SetDefault()
2485            btnsizer.AddButton(self.OKbtn)
2486            self.OKbtn.Bind(wx.EVT_BUTTON, OnOk)
2487        if useCANCEL:
2488            btn = wx.Button(panel, wx.ID_CANCEL)
2489            btnsizer.AddButton(btn)
2490        btnsizer.Realize()
2491        Sizer.Add((-1,5))
2492        Sizer.Add(btnsizer,0,wx.ALIGN_LEFT,20)
2493        Sizer.Add((-1,5))
2494        # OK done, let's get outa here
2495        panel.SetSizer(Sizer)
2496        panel.SetAutoLayout(1)
2497        panel.SetupScrolling()
2498        Size = [450,375]
2499        panel.SetSize(Size)
2500        Size[0] += 25; Size[1]+= 25
2501        self.SetSize(Size)
2502       
2503    def GetData(self):
2504        'Returns the modified ParmData'
2505        return self.ParmData
2506   
2507################################################################################
2508def ItemSelector(ChoiceList, ParentFrame=None,
2509                 title='Select an item',
2510                 size=None, header='Item Selector',
2511                 useCancel=True,multiple=False):
2512    ''' Provide a wx dialog to select a single item or multiple items from list of choices
2513
2514    :param list ChoiceList: a list of choices where one will be selected
2515    :param wx.Frame ParentFrame: Name of parent frame (default None)
2516    :param str title: heading above list of choices (default 'Select an item')
2517    :param wx.Size size: Size for dialog to be created (default None -- size as needed)
2518    :param str header: Title to place on window frame (default 'Item Selector')
2519    :param bool useCancel: If True (default) both the OK and Cancel buttons are offered
2520    :param bool multiple: If True then multiple items can be selected (default False)
2521   
2522    :returns: the selection index or None or a selection list if multiple is true
2523
2524    Called by GSASIIdataGUI.OnReOrgSelSeq() Which is not fully implemented.
2525    '''
2526    if multiple:
2527        if useCancel:
2528            dlg = G2MultiChoiceDialog(
2529                ParentFrame,title, header, ChoiceList)
2530        else:
2531            dlg = G2MultiChoiceDialog(
2532                ParentFrame,title, header, ChoiceList,
2533                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
2534    else:
2535        if useCancel:
2536            dlg = wx.SingleChoiceDialog(
2537                ParentFrame,title, header, ChoiceList)
2538        else:
2539            dlg = wx.SingleChoiceDialog(
2540                ParentFrame,title, header,ChoiceList,
2541                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
2542    if size: dlg.SetSize(size)
2543    if dlg.ShowModal() == wx.ID_OK:
2544        if multiple:
2545            dlg.Destroy()
2546            return dlg.GetSelections()
2547        else:
2548            dlg.Destroy()
2549            return dlg.GetSelection()
2550    else:
2551        dlg.Destroy()
2552        return None
2553    dlg.Destroy()
2554
2555######################################################### Column-order selection dialog
2556def GetItemOrder(parent,keylist,vallookup,posdict):
2557    '''Creates a panel where items can be ordered into columns
2558   
2559    :param list keylist: is a list of keys for column assignments
2560    :param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
2561       Each inner dict contains variable names as keys and their associated values
2562    :param dict posdict: is a dict keyed by names in keylist where each item is a dict.
2563       Each inner dict contains column numbers as keys and their associated
2564       variable name as a value. This is used for both input and output.
2565       
2566    '''
2567    dlg = wx.Dialog(parent,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2568    sizer = wx.BoxSizer(wx.VERTICAL)
2569    spanel = OrderBox(dlg,keylist,vallookup,posdict)
2570    spanel.Fit()
2571    sizer.Add(spanel,1,wx.EXPAND)
2572    btnsizer = wx.StdDialogButtonSizer()
2573    btn = wx.Button(dlg, wx.ID_OK)
2574    btn.SetDefault()
2575    btnsizer.AddButton(btn)
2576    #btn = wx.Button(dlg, wx.ID_CANCEL)
2577    #btnsizer.AddButton(btn)
2578    btnsizer.Realize()
2579    sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.ALL, 5)
2580    dlg.SetSizer(sizer)
2581    sizer.Fit(dlg)
2582    dlg.ShowModal()
2583
2584################################################################################
2585class MultiIntegerDialog(wx.Dialog):
2586    '''Input a series of integers based on prompts
2587    '''
2588    def __init__(self,parent,title,prompts,values):
2589        wx.Dialog.__init__(self,parent,-1,title, 
2590            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
2591        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
2592        self.values = values
2593        self.prompts = prompts
2594        self.Draw()
2595       
2596    def Draw(self):
2597       
2598        def OnValItem(event):
2599            event.Skip()
2600            Obj = event.GetEventObject()
2601            ind = Indx[Obj.GetId()]
2602            try:
2603                val = int(Obj.GetValue())
2604                if val <= 0:
2605                    raise ValueError
2606            except ValueError:
2607                val = self.values[ind]
2608            self.values[ind] = val
2609            Obj.SetValue('%d'%(val))
2610           
2611        self.panel.Destroy()
2612        self.panel = wx.Panel(self)
2613        mainSizer = wx.BoxSizer(wx.VERTICAL)
2614        Indx = {}
2615        for ival,[prompt,value] in enumerate(zip(self.prompts,self.values)):
2616            mainSizer.Add(wx.StaticText(self.panel,-1,prompt),0,wx.ALIGN_CENTER)
2617            valItem = wx.TextCtrl(self.panel,-1,value='%d'%(value),style=wx.TE_PROCESS_ENTER)
2618            mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
2619            Indx[valItem.GetId()] = ival
2620            valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
2621            valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
2622        OkBtn = wx.Button(self.panel,-1,"Ok")
2623        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
2624        CancelBtn = wx.Button(self.panel,-1,'Cancel')
2625        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
2626        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
2627        btnSizer.Add((20,20),1)
2628        btnSizer.Add(OkBtn)
2629        btnSizer.Add(CancelBtn)
2630        btnSizer.Add((20,20),1)
2631        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
2632        self.panel.SetSizer(mainSizer)
2633        self.panel.Fit()
2634        self.Fit()
2635
2636    def GetValues(self):
2637        return self.values
2638       
2639    def OnOk(self,event):
2640        parent = self.GetParent()
2641        parent.Raise()
2642        self.EndModal(wx.ID_OK)             
2643       
2644    def OnCancel(self,event):
2645        parent = self.GetParent()
2646        parent.Raise()
2647        self.EndModal(wx.ID_CANCEL)
2648
2649################################################################################
2650class OrderBox(wxscroll.ScrolledPanel):
2651    '''Creates a panel with scrollbars where items can be ordered into columns
2652   
2653    :param list keylist: is a list of keys for column assignments
2654    :param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
2655      Each inner dict contains variable names as keys and their associated values
2656    :param dict posdict: is a dict keyed by names in keylist where each item is a dict.
2657      Each inner dict contains column numbers as keys and their associated
2658      variable name as a value. This is used for both input and output.
2659     
2660    '''
2661    def __init__(self,parent,keylist,vallookup,posdict,*arg,**kw):
2662        self.keylist = keylist
2663        self.vallookup = vallookup
2664        self.posdict = posdict
2665        self.maxcol = 0
2666        for nam in keylist:
2667            posdict = self.posdict[nam]
2668            if posdict.keys():
2669                self.maxcol = max(self.maxcol, max(posdict))
2670        wxscroll.ScrolledPanel.__init__(self,parent,wx.ID_ANY,*arg,**kw)
2671        self.GBsizer = wx.GridBagSizer(4,4)
2672        self.SetBackgroundColour(WHITE)
2673        self.SetSizer(self.GBsizer)
2674        colList = [str(i) for i in range(self.maxcol+2)]
2675        for i in range(self.maxcol+1):
2676            wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
2677            wid.SetBackgroundColour(DULL_YELLOW)
2678            wid.SetMinSize((50,-1))
2679            self.GBsizer.Add(wid,(0,i),flag=wx.ALIGN_CENTER|wx.EXPAND)
2680        self.chceDict = {}
2681        for row,nam in enumerate(self.keylist):
2682            posdict = self.posdict[nam]
2683            for col in posdict:
2684                lbl = posdict[col]
2685                pnl = wx.Panel(self,wx.ID_ANY)
2686                pnl.SetBackgroundColour(VERY_LIGHT_GREY)
2687                insize = wx.BoxSizer(wx.VERTICAL)
2688                wid = wx.Choice(pnl,wx.ID_ANY,choices=colList)
2689                insize.Add(wid,0,wx.EXPAND|wx.BOTTOM,3)
2690                wid.SetSelection(col)
2691                self.chceDict[wid] = (row,col)
2692                wid.Bind(wx.EVT_CHOICE,self.OnChoice)
2693                wid = wx.StaticText(pnl,wx.ID_ANY,lbl)
2694                insize.Add(wid,0,flag=wx.EXPAND)
2695                try:
2696                    val = G2py3.FormatSigFigs(self.vallookup[nam][lbl],maxdigits=8)
2697                except KeyError:
2698                    val = '?'
2699                wid = wx.StaticText(pnl,wx.ID_ANY,'('+val+')')
2700                insize.Add(wid,0,flag=wx.EXPAND)
2701                pnl.SetSizer(insize)
2702                self.GBsizer.Add(pnl,(row+1,col),flag=wx.EXPAND)
2703        self.SetAutoLayout(1)
2704        self.SetupScrolling()
2705        self.SetMinSize((
2706            min(700,self.GBsizer.GetSize()[0]),
2707            self.GBsizer.GetSize()[1]+20))
2708    def OnChoice(self,event):
2709        '''Called when a column is assigned to a variable
2710        '''
2711        row,col = self.chceDict[event.EventObject] # which variable was this?
2712        newcol = event.Selection # where will it be moved?
2713        if newcol == col:
2714            return # no change: nothing to do!
2715        prevmaxcol = self.maxcol # save current table size
2716        key = self.keylist[row] # get the key for the current row
2717        lbl = self.posdict[key][col] # selected variable name
2718        lbl1 = self.posdict[key].get(col+1,'') # next variable name, if any
2719        # if a posXXX variable is selected, and the next variable is posXXX, move them together
2720        repeat = 1
2721        if lbl[:3] == 'pos' and lbl1[:3] == 'int' and lbl[3:] == lbl1[3:]:
2722            repeat = 2
2723        for i in range(repeat): # process the posXXX and then the intXXX (or a single variable)
2724            col += i
2725            newcol += i
2726            if newcol in self.posdict[key]:
2727                # find first non-blank after newcol
2728                for mtcol in range(newcol+1,self.maxcol+2):
2729                    if mtcol not in self.posdict[key]: break
2730                l1 = range(mtcol,newcol,-1)+[newcol]
2731                l = range(mtcol-1,newcol-1,-1)+[col]
2732            else:
2733                l1 = [newcol]
2734                l = [col]
2735            # move all of the items, starting from the last column
2736            for newcol,col in zip(l1,l):
2737                #print 'moving',col,'to',newcol
2738                self.posdict[key][newcol] = self.posdict[key][col]
2739                del self.posdict[key][col]
2740                self.maxcol = max(self.maxcol,newcol)
2741                obj = self.GBsizer.FindItemAtPosition((row+1,col))
2742                self.GBsizer.SetItemPosition(obj.GetWindow(),(row+1,newcol))
2743                for wid in obj.GetWindow().Children:
2744                    if wid in self.chceDict:
2745                        self.chceDict[wid] = (row,newcol)
2746                        wid.SetSelection(self.chceDict[wid][1])
2747        # has the table gotten larger? If so we need new column heading(s)
2748        if prevmaxcol != self.maxcol:
2749            for i in range(prevmaxcol+1,self.maxcol+1):
2750                wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
2751                wid.SetBackgroundColour(DULL_YELLOW)
2752                wid.SetMinSize((50,-1))
2753                self.GBsizer.Add(wid,(0,i),flag=wx.ALIGN_CENTER|wx.EXPAND)
2754            colList = [str(i) for i in range(self.maxcol+2)]
2755            for wid in self.chceDict:
2756                wid.SetItems(colList)
2757                wid.SetSelection(self.chceDict[wid][1])
2758        self.GBsizer.Layout()
2759        self.FitInside()
2760
2761################################################################################
2762def GetImportFile(G2frame, message, defaultDir="", defaultFile="", style=wx.OPEN,
2763                  *args, **kwargs):
2764    '''Uses a customized dialog that gets files from the appropriate import directory.
2765    Arguments are used the same as in :func:`wx.FileDialog`. Selection of
2766    multiple files is allowed if argument style includes wx.MULTIPLE.
2767
2768    The default initial directory (unless overridden with argument defaultDir)
2769    is found in G2frame.TutorialImportDir, config setting Import_directory or
2770    G2frame.LastImportDir, see :func:`GetImportPath`.
2771
2772    The path of the first file entered is used to set G2frame.LastImportDir
2773    and optionally config setting Import_directory.
2774
2775    :returns: a list of files or an empty list
2776    '''
2777    dlg = wx.FileDialog(G2frame, message, defaultDir, defaultFile, *args,
2778                        style=style, **kwargs)
2779    pth = GetImportPath(G2frame)
2780    if not defaultDir and pth: dlg.SetDirectory(pth)
2781    try:
2782        if dlg.ShowModal() == wx.ID_OK:
2783            if style & wx.MULTIPLE:
2784                filelist = dlg.GetPaths()
2785                if len(filelist) == 0: return []
2786            else:
2787                filelist = [dlg.GetPath(),]
2788            # not sure if we want to do this (why use wx.CHANGE_DIR?)
2789            if style & wx.CHANGE_DIR: # to get Mac/Linux to change directory like windows!
2790                os.chdir(dlg.GetDirectory())
2791        else: # cancel was pressed
2792            return []
2793    finally:
2794        dlg.Destroy()
2795    # save the path of the first file and reset the TutorialImportDir variable
2796    pth = os.path.split(os.path.abspath(filelist[0]))[0]
2797    if GSASIIpath.GetConfigValue('Save_paths'): SaveImportDirectory(pth)
2798    G2frame.LastImportDir = pth
2799    G2frame.TutorialImportDir = None
2800    return filelist
2801
2802def GetImportPath(G2frame):
2803    '''Determines the default location to use for importing files. Tries sequentially
2804    G2frame.TutorialImportDir, config var Import_directory and G2frame.LastImportDir.
2805   
2806    :returns: a string containing the path to be used when reading files or None
2807      if none of the above are specified.
2808    '''
2809    if G2frame.TutorialImportDir:
2810        if os.path.exists(G2frame.TutorialImportDir):
2811            return G2frame.TutorialImportDir
2812        elif GSASIIpath.GetConfigValue('debug'):
2813            print('Tutorial location (TutorialImportDir) not found: '+G2frame.TutorialImportDir)
2814    pth = GSASIIpath.GetConfigValue('Import_directory')
2815    if pth:
2816        pth = os.path.expanduser(pth)
2817        if os.path.exists(pth):
2818            return pth
2819        elif GSASIIpath.GetConfigValue('debug'):
2820            print('Ignoring Config Import_directory value: '+
2821                      GSASIIpath.GetConfigValue('Import_directory'))
2822    if G2frame.LastImportDir:
2823        if os.path.exists(G2frame.LastImportDir):
2824            return G2frame.LastImportDir
2825        elif GSASIIpath.GetConfigValue('debug'):
2826            print('Warning: G2frame.LastImportDir not found = '+G2frame.LastImportDir)
2827    return None
2828
2829def GetExportPath(G2frame):
2830    '''Determines the default location to use for writing files. Tries sequentially
2831    G2frame.LastExportDir and G2frame.LastGPXdir.
2832   
2833    :returns: a string containing the path to be used when writing files or '.'
2834      if none of the above are specified.
2835    '''
2836    if G2frame.LastExportDir:
2837        return G2frame.LastExportDir
2838    elif G2frame.LastGPXdir:
2839        return G2frame.LastGPXdir
2840    else:
2841        return '.'
2842
2843################################################################################
2844class SGMessageBox(wx.Dialog):
2845    ''' Special version of MessageBox that displays space group & super space group text
2846    in two blocks
2847    '''
2848    def __init__(self,parent,title,text,table,):
2849        wx.Dialog.__init__(self,parent,wx.ID_ANY,title,pos=wx.DefaultPosition,
2850            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2851        self.text = text
2852        self.table = table
2853        self.panel = wx.Panel(self)
2854        mainSizer = wx.BoxSizer(wx.VERTICAL)
2855        mainSizer.Add((0,10))
2856        for line in text:
2857            mainSizer.Add(wx.StaticText(self.panel,label='     %s     '%(line)),0,WACV)
2858        ncol = self.table[0].count(',')+1
2859        tableSizer = wx.FlexGridSizer(0,2*ncol+3,0,0)
2860        for j,item in enumerate(self.table):
2861            num,flds = item.split(')')
2862            tableSizer.Add(wx.StaticText(self.panel,label='     %s  '%(num+')')),0,WACV|wx.ALIGN_LEFT)           
2863            flds = flds.replace(' ','').split(',')
2864            for i,fld in enumerate(flds):
2865                if i < ncol-1:
2866                    tableSizer.Add(wx.StaticText(self.panel,label='%s, '%(fld)),0,WACV|wx.ALIGN_RIGHT)
2867                else:
2868                    tableSizer.Add(wx.StaticText(self.panel,label='%s'%(fld)),0,WACV|wx.ALIGN_RIGHT)
2869            if not j%2:
2870                tableSizer.Add((20,0))
2871        mainSizer.Add(tableSizer,0,wx.ALIGN_LEFT)
2872        btnsizer = wx.StdDialogButtonSizer()
2873        OKbtn = wx.Button(self.panel, wx.ID_OK)
2874        OKbtn.Bind(wx.EVT_BUTTON, self.OnOk)
2875        OKbtn.SetDefault()
2876        btnsizer.AddButton(OKbtn)
2877        btnsizer.Realize()
2878        mainSizer.Add((0,10))
2879        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
2880        self.panel.SetSizer(mainSizer)
2881        self.panel.Fit()
2882        self.Fit()
2883        size = self.GetSize()
2884        self.SetSize([size[0]+20,size[1]])
2885
2886    def Show(self):
2887        '''Use this method after creating the dialog to post it
2888        '''
2889        self.ShowModal()
2890        return
2891
2892    def OnOk(self,event):
2893        parent = self.GetParent()
2894        parent.Raise()
2895        self.EndModal(wx.ID_OK)
2896
2897################################################################################
2898class DisAglDialog(wx.Dialog):
2899    '''Distance/Angle Controls input dialog. After
2900    :meth:`ShowModal` returns, the results are found in
2901    dict :attr:`self.data`, which is accessed using :meth:`GetData`.
2902
2903    :param wx.Frame parent: reference to parent frame (or None)
2904    :param dict data: a dict containing the current
2905      search ranges or an empty dict, which causes default values
2906      to be used.
2907      Will be used to set element `DisAglCtls` in
2908      :ref:`Phase Tree Item <Phase_table>`
2909    :param dict default:  A dict containing the default
2910      search ranges for each element.
2911    :param bool Reset: if True (default), show Reset button
2912    :param bool Angle: if True (default), show angle radii
2913    '''
2914    def __init__(self,parent,data,default,Reset=True,Angle=True):
2915        text = 'Distance Angle Controls'
2916        if not Angle:
2917            text = 'Distance Controls'
2918        wx.Dialog.__init__(self,parent,wx.ID_ANY,text, 
2919            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
2920        self.default = default
2921        self.Reset = Reset
2922        self.Angle = Angle
2923        self.panel = None
2924        self._default(data,self.default)
2925        self.Draw(self.data)
2926               
2927    def _default(self,data,default):
2928        '''Set starting values for the search values, either from
2929        the input array or from defaults, if input is null
2930        '''
2931        if data:
2932            self.data = copy.deepcopy(data) # don't mess with originals
2933        else:
2934            self.data = {}
2935            self.data['Name'] = default['Name']
2936            self.data['Factors'] = [0.85,0.85]
2937            self.data['AtomTypes'] = default['AtomTypes']
2938            self.data['BondRadii'] = default['BondRadii'][:]
2939            self.data['AngleRadii'] = default['AngleRadii'][:]
2940
2941    def Draw(self,data):
2942        '''Creates the contents of the dialog. Normally called
2943        by :meth:`__init__`.
2944        '''
2945        if self.panel: self.panel.Destroy()
2946        self.panel = wx.Panel(self)
2947        mainSizer = wx.BoxSizer(wx.VERTICAL)
2948        mainSizer.Add(wx.StaticText(self.panel,-1,'Controls for phase '+data['Name']),
2949            0,WACV|wx.LEFT,10)
2950        mainSizer.Add((10,10),1)
2951       
2952        ncol = 3
2953        if not self.Angle:
2954            ncol=2
2955        radiiSizer = wx.FlexGridSizer(0,ncol,5,5)
2956        radiiSizer.Add(wx.StaticText(self.panel,-1,' Type'),0,WACV)
2957        radiiSizer.Add(wx.StaticText(self.panel,-1,'Bond radii'),0,WACV)
2958        if self.Angle:
2959            radiiSizer.Add(wx.StaticText(self.panel,-1,'Angle radii'),0,WACV)
2960        self.objList = {}
2961        for id,item in enumerate(self.data['AtomTypes']):
2962            radiiSizer.Add(wx.StaticText(self.panel,-1,' '+item),0,WACV)
2963            bRadii = ValidatedTxtCtrl(self.panel,data['BondRadii'],id,nDig=(10,3))
2964            radiiSizer.Add(bRadii,0,WACV)
2965            if self.Angle:
2966                aRadii = ValidatedTxtCtrl(self.panel,data['AngleRadii'],id,nDig=(10,3))
2967                radiiSizer.Add(aRadii,0,WACV)
2968        mainSizer.Add(radiiSizer,0,wx.EXPAND)
2969        if self.Angle:
2970            factorSizer = wx.FlexGridSizer(0,2,5,5)
2971            Names = ['Bond','Angle']
2972            for i,name in enumerate(Names):
2973                factorSizer.Add(wx.StaticText(self.panel,-1,name+' search factor'),0,WACV)
2974                bondFact = ValidatedTxtCtrl(self.panel,data['Factors'],i,nDig=(10,3))
2975                factorSizer.Add(bondFact)
2976            mainSizer.Add(factorSizer,0,wx.EXPAND)
2977       
2978        OkBtn = wx.Button(self.panel,-1,"Ok")
2979        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
2980        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
2981        btnSizer.Add((20,20),1)
2982        btnSizer.Add(OkBtn)
2983        if self.Reset:
2984            ResetBtn = wx.Button(self.panel,-1,'Reset')
2985            ResetBtn.Bind(wx.EVT_BUTTON, self.OnReset)
2986            btnSizer.Add(ResetBtn)
2987        btnSizer.Add((20,20),1)
2988        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
2989        self.panel.SetSizer(mainSizer)
2990        self.panel.Fit()
2991        self.Fit()
2992   
2993    def GetData(self):
2994        'Returns the values from the dialog'
2995        return self.data
2996       
2997    def OnOk(self,event):
2998        'Called when the OK button is pressed'
2999        parent = self.GetParent()
3000        parent.Raise()
3001        self.EndModal(wx.ID_OK)             
3002       
3003    def OnReset(self,event):
3004        'Called when the Reset button is pressed'
3005        data = {}
3006        self._default(data,self.default)
3007        wx.CallAfter(self.Draw,self.data)
3008               
3009################################################################################
3010class ShowLSParms(wx.Dialog):
3011    '''Create frame to show least-squares parameters
3012    '''
3013    def __init__(self,parent,title,parmDict,varyList,fullVaryList,
3014                 size=(375,430)):
3015       
3016        wx.Dialog.__init__(self,parent,wx.ID_ANY,title,size=size,
3017                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
3018        self.panel = wxscroll.ScrolledPanel(self)
3019        self.parmChoice = 'Phase'
3020        self.parmDict = parmDict
3021        self.varyList = varyList
3022        self.fullVaryList = fullVaryList
3023
3024        self.parmNames = parmDict.keys()
3025        self.parmNames.sort()
3026        splitNames = [item.split(':') for item in self.parmNames if len(item) > 3 and not isinstance(self.parmDict[item],basestring)]
3027        self.globNames = [':'.join(item) for item in splitNames if not item[0] and not item[1]]
3028        self.globVars = list(set([' ',]+[item[2] for item in splitNames if not item[0] and not item[1]]))
3029        self.globVars.sort()
3030        self.hisNames = [':'.join(item) for item in splitNames if not item[0] and item[1]]
3031        self.hisNums = list(set([int(item.split(':')[1]) for item in self.hisNames]))
3032        self.hisNums.sort()
3033        self.hisNums = [' ',]+[str(item) for item in self.hisNums]
3034        self.hisVars = list(set([' ',]+[item[2] for item in splitNames if not item[0]]))
3035        self.hisVars.sort()
3036        self.phasNames = [':'.join(item) for item in splitNames if not item[1] and 'is' not in item[2]]
3037        self.phasNums = [' ',]+list(set([item.split(':')[0] for item in self.phasNames]))
3038        if '' in self.phasNums: self.phasNums.remove('')
3039        self.phasVars = list(set([' ',]+[item[2] for item in splitNames if not item[1] and 'is' not in item[2]]))
3040        self.phasVars.sort()
3041        self.phasNums.sort()
3042        self.hapNames = [':'.join(item) for item in splitNames if item[0] and item[1]]
3043        self.hapVars = list(set([' ',]+[item[2] for item in splitNames if item[0] and item[1]]))
3044        self.hapVars.sort()
3045        self.hisNum = ' '
3046        self.phasNum = ' '
3047        self.varName = ' '
3048        self.listSel = 'Refined'
3049        self.DrawPanel()
3050       
3051           
3052    def DrawPanel(self):
3053           
3054        def _OnParmSel(event):
3055            self.parmChoice = parmSel.GetStringSelection()
3056            self.varName = ' '
3057            wx.CallLater(100,self.DrawPanel)
3058           
3059        def OnPhasSel(event):
3060            event.Skip()
3061            self.phasNum = phasSel.GetValue()
3062            self.varName = ' '
3063            wx.CallLater(100,self.DrawPanel)
3064
3065        def OnHistSel(event):
3066            event.Skip()
3067            self.hisNum = histSel.GetValue()
3068            self.varName = ' '
3069            wx.CallLater(100,self.DrawPanel)
3070           
3071        def OnVarSel(event):
3072            self.varName = varSel.GetValue()
3073            self.phasNum = ' '
3074            self.hisNum = ' '
3075            wx.CallLater(100,self.DrawPanel)
3076           
3077        def OnListSel(event):
3078            self.listSel = listSel.GetStringSelection()
3079            wx.CallLater(100,self.DrawPanel)
3080
3081        if self.panel.GetSizer(): self.panel.GetSizer().Clear(True)
3082        mainSizer = wx.BoxSizer(wx.VERTICAL)
3083        num = len(self.varyList)
3084        mainSizer.Add(wx.StaticText(self.panel,label=' Number of refined variables: '+str(num)),0)
3085        if len(self.varyList) != len(self.fullVaryList):
3086            num = len(self.fullVaryList) - len(self.varyList)
3087            mainSizer.Add(wx.StaticText(self.panel,label=' + '+str(num)+' parameters are varied via constraints'))
3088        choiceDict = {'Global':self.globNames,'Phase':self.phasNames,'Phase/Histo':self.hapNames,'Histogram':self.hisNames}
3089        choice = ['Phase','Phase/Histo','Histogram']
3090        if len(self.globNames):
3091            choice += ['Global',]
3092        parmSizer = wx.FlexGridSizer(0,3,5,5)
3093        parmSel = wx.RadioBox(self.panel,wx.ID_ANY,'Parameter type:',choices=choice,
3094            majorDimension=1,style=wx.RA_SPECIFY_COLS)
3095        parmSel.Bind(wx.EVT_RADIOBOX,_OnParmSel)
3096        parmSel.SetStringSelection(self.parmChoice)
3097        parmSizer.Add(parmSel,0)
3098        numSizer = wx.BoxSizer(wx.VERTICAL)
3099        numSizer.Add((5,25),0)
3100        if self.parmChoice in ['Phase','Phase/Histo'] and len(self.phasNums) > 1:
3101            numSizer.Add(wx.StaticText(self.panel,label='Phase'),0)
3102            phasSel = wx.ComboBox(self.panel,choices=self.phasNums,value=self.phasNum,
3103                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3104            phasSel.Bind(wx.EVT_COMBOBOX,OnPhasSel)
3105            numSizer.Add(phasSel,0)
3106        if self.parmChoice in ['Histogram','Phase/Histo'] and len(self.hisNums) > 1:
3107            numSizer.Add(wx.StaticText(self.panel,label='Histogram'),0)
3108            histSel = wx.ComboBox(self.panel,choices=self.hisNums,value=self.hisNum,
3109                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3110            histSel.Bind(wx.EVT_COMBOBOX,OnHistSel)
3111#            histSel = wx.TextCtrl(self.panel,size=(50,25),value='0',style=wx.TE_PROCESS_ENTER)
3112#            histSel.Bind(wx.EVT_TEXT_ENTER,OnHistSel)
3113#            histSel.Bind(wx.EVT_KILL_FOCUS,OnHistSel)
3114            numSizer.Add(histSel,0)
3115        parmSizer.Add(numSizer)
3116        varSizer = wx.BoxSizer(wx.VERTICAL)
3117        if self.parmChoice in ['Phase',]:
3118            varSel = wx.ComboBox(self.panel,choices=self.phasVars,value=self.varName,
3119                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3120            varSel.Bind(wx.EVT_COMBOBOX,OnVarSel)
3121        elif self.parmChoice in ['Histogram',]:
3122            varSel = wx.ComboBox(self.panel,choices=self.hisVars,value=self.varName,
3123                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3124            varSel.Bind(wx.EVT_COMBOBOX,OnVarSel)
3125        elif self.parmChoice in ['Phase/Histo',]:
3126            varSel = wx.ComboBox(self.panel,choices=self.hapVars,value=self.varName,
3127                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3128            varSel.Bind(wx.EVT_COMBOBOX,OnVarSel)
3129        if self.parmChoice != 'Global': 
3130            varSizer.Add(wx.StaticText(self.panel,label='Parameter'))
3131            varSizer.Add(varSel,0)
3132        parmSizer.Add(varSizer,0)
3133        mainSizer.Add(parmSizer,0)
3134        listChoice = ['All','Refined']
3135        listSel = wx.RadioBox(self.panel,wx.ID_ANY,'Parameter type:',choices=listChoice,
3136            majorDimension=0,style=wx.RA_SPECIFY_COLS)
3137        listSel.SetStringSelection(self.listSel)
3138        listSel.Bind(wx.EVT_RADIOBOX,OnListSel)
3139        mainSizer.Add(listSel,0)
3140        subSizer = wx.FlexGridSizer(cols=4,hgap=2,vgap=2)
3141        subSizer.Add((-1,-1))
3142        subSizer.Add(wx.StaticText(self.panel,wx.ID_ANY,'Parameter name  '))
3143        subSizer.Add(wx.StaticText(self.panel,wx.ID_ANY,'refine?'))
3144        subSizer.Add(wx.StaticText(self.panel,wx.ID_ANY,'value'),0,wx.ALIGN_RIGHT)
3145        explainRefine = False
3146        for name in choiceDict[self.parmChoice]:
3147            # skip entries without numerical values
3148            if isinstance(self.parmDict[name],basestring): continue
3149            if 'Refined' in self.listSel and (name not in self.fullVaryList
3150                                              ) and (name not in self.varyList):
3151                continue
3152            if 'Phase' in self.parmChoice:
3153                if self.phasNum != ' ' and name.split(':')[0] != self.phasNum: continue
3154            if 'Histo' in self.parmChoice:
3155                if self.hisNum != ' ' and name.split(':')[1] != self.hisNum: continue
3156            if (self.varName != ' ') and (self.varName not in name): continue
3157            try:
3158                value = G2py3.FormatSigFigs(self.parmDict[name])
3159            except TypeError:
3160                value = str(self.parmDict[name])+' -?' # unexpected
3161                #continue
3162            v = G2obj.getVarDescr(name)
3163            if v is None or v[-1] is None:
3164                subSizer.Add((-1,-1))
3165            else:               
3166                ch = HelpButton(self.panel,G2obj.fmtVarDescr(name))
3167                subSizer.Add(ch,0,wx.LEFT|wx.RIGHT|WACV|wx.ALIGN_CENTER,1)
3168            subSizer.Add(wx.StaticText(self.panel,wx.ID_ANY,str(name)))
3169            if name in self.varyList:
3170                subSizer.Add(wx.StaticText(self.panel,label='R'))   #TODO? maybe a checkbox for one stop refinemnt flag setting?
3171            elif name in self.fullVaryList:
3172                subSizer.Add(wx.StaticText(self.panel,label='C'))
3173                explainRefine = True
3174            else:
3175                subSizer.Add((-1,-1))
3176            subSizer.Add(wx.StaticText(self.panel,label=value),0,wx.ALIGN_RIGHT)
3177
3178        mainSizer.Add(subSizer,0)
3179        if explainRefine:
3180            mainSizer.Add(
3181                wx.StaticText(self.panel,label='"R" indicates a refined variable\n'+
3182                    '"C" indicates generated from a constraint'),0, wx.ALL,0)
3183        # make OK button
3184        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3185        btn = wx.Button(self.panel, wx.ID_CLOSE,"Close") 
3186        btn.Bind(wx.EVT_BUTTON,self._onClose)
3187        btnsizer.Add(btn)
3188        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3189        # Allow window to be enlarged but not made smaller
3190        self.panel.SetSizer(mainSizer)
3191        self.panel.SetAutoLayout(1)
3192        self.panel.SetupScrolling()
3193        self.panel.SetMinSize(self.GetSize())
3194
3195    def _onClose(self,event):
3196        self.EndModal(wx.ID_CANCEL)
3197
3198################################################################################
3199#####  Customized Grid Support
3200################################################################################           
3201class GSGrid(wg.Grid):
3202    '''Basic wx.Grid implementation
3203    '''
3204    def __init__(self, parent, name=''):
3205        wg.Grid.__init__(self,parent,-1,name=name)
3206        if hasattr(parent.TopLevelParent,'currentGrids'):
3207            parent.TopLevelParent.currentGrids.append(self)      # save a reference to the grid in the Frame
3208        self.SetScrollRate(0,0)         #GSAS-II grids have no scroll bars by default
3209           
3210    def Clear(self):
3211        wg.Grid.ClearGrid(self)
3212       
3213    def SetCellReadOnly(self,r,c,readonly=True):
3214        self.SetReadOnly(r,c,isReadOnly=readonly)
3215       
3216    def SetCellStyle(self,r,c,color="white",readonly=True):
3217        self.SetCellBackgroundColour(r,c,color)
3218        self.SetReadOnly(r,c,isReadOnly=readonly)
3219       
3220    def GetSelection(self):
3221        #this is to satisfy structure drawing stuff in G2plt when focus changes
3222        return None
3223
3224    def InstallGridToolTip(self, rowcolhintcallback,
3225                           colLblCallback=None,rowLblCallback=None):
3226        '''code to display a tooltip for each item on a grid
3227        from http://wiki.wxpython.org/wxGrid%20ToolTips (buggy!), expanded to
3228        column and row labels using hints from
3229        https://groups.google.com/forum/#!topic/wxPython-users/bm8OARRVDCs
3230
3231        :param function rowcolhintcallback: a routine that returns a text
3232          string depending on the selected row and column, to be used in
3233          explaining grid entries.
3234        :param function colLblCallback: a routine that returns a text
3235          string depending on the selected column, to be used in
3236          explaining grid columns (if None, the default), column labels
3237          do not get a tooltip.
3238        :param function rowLblCallback: a routine that returns a text
3239          string depending on the selected row, to be used in
3240          explaining grid rows (if None, the default), row labels
3241          do not get a tooltip.
3242        '''
3243        prev_rowcol = [None,None,None]
3244        def OnMouseMotion(event):
3245            # event.GetRow() and event.GetCol() would be nice to have here,
3246            # but as this is a mouse event, not a grid event, they are not
3247            # available and we need to compute them by hand.
3248            x, y = self.CalcUnscrolledPosition(event.GetPosition())
3249            row = self.YToRow(y)
3250            col = self.XToCol(x)
3251            hinttext = ''
3252            win = event.GetEventObject()
3253            if [row,col,win] == prev_rowcol: # no change from last position
3254                if event: event.Skip()
3255                return
3256            if win == self.GetGridWindow() and row >= 0 and col >= 0:
3257                hinttext = rowcolhintcallback(row, col)
3258            elif win == self.GetGridColLabelWindow() and col >= 0:
3259                if colLblCallback: hinttext = colLblCallback(col)
3260            elif win == self.GetGridRowLabelWindow() and row >= 0:
3261                if rowLblCallback: hinttext = rowLblCallback(row)
3262            else: # this should be the upper left corner, which is empty
3263                if event: event.Skip()
3264                return
3265            if hinttext is None: hinttext = ''
3266            win.SetToolTipString(hinttext)
3267            prev_rowcol[:] = [row,col,win]
3268            if event: event.Skip()
3269
3270        wx.EVT_MOTION(self.GetGridWindow(), OnMouseMotion)
3271        if colLblCallback: wx.EVT_MOTION(self.GetGridColLabelWindow(), OnMouseMotion)
3272        if rowLblCallback: wx.EVT_MOTION(self.GetGridRowLabelWindow(), OnMouseMotion)
3273                                                   
3274################################################################################           
3275class Table(wg.PyGridTableBase):
3276    '''Basic data table for use with GSgrid
3277    '''
3278    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
3279        wg.PyGridTableBase.__init__(self)
3280        self.colLabels = colLabels
3281        self.rowLabels = rowLabels
3282        self.dataTypes = types
3283        self.data = data
3284       
3285    def AppendRows(self, numRows=1):
3286        self.data.append([])
3287        return True
3288       
3289    def CanGetValueAs(self, row, col, typeName):
3290        if self.dataTypes:
3291            colType = self.dataTypes[col].split(':')[0]
3292            if typeName == colType:
3293                return True
3294            else:
3295                return False
3296        else:
3297            return False
3298
3299    def CanSetValueAs(self, row, col, typeName):
3300        return self.CanGetValueAs(row, col, typeName)
3301
3302    def DeleteRow(self,pos):
3303        data = self.GetData()
3304        self.SetData([])
3305        new = []
3306        for irow,row in enumerate(data):
3307            if irow <> pos:
3308                new.append(row)
3309        self.SetData(new)
3310       
3311    def GetColLabelValue(self, col):
3312        if self.colLabels:
3313            return self.colLabels[col]
3314           
3315    def GetData(self):
3316        data = []
3317        for row in range(self.GetNumberRows()):
3318            data.append(self.GetRowValues(row))
3319        return data
3320       
3321    def GetNumberCols(self):
3322        try:
3323            return len(self.colLabels)
3324        except TypeError:
3325            return None
3326       
3327    def GetNumberRows(self):
3328        return len(self.data)
3329       
3330    def GetRowLabelValue(self, row):
3331        if self.rowLabels:
3332            return self.rowLabels[row]
3333       
3334    def GetColValues(self, col):
3335        data = []
3336        for row in range(self.GetNumberRows()):
3337            data.append(self.GetValue(row, col))
3338        return data
3339       
3340    def GetRowValues(self, row):
3341        data = []
3342        for col in range(self.GetNumberCols()):
3343            data.append(self.GetValue(row, col))
3344        return data
3345       
3346    def GetTypeName(self, row, col):
3347        try:
3348            if self.data[row][col] is None: return None
3349            return self.dataTypes[col]
3350        except (TypeError,IndexError):
3351            return None
3352
3353    def GetValue(self, row, col):
3354        try:
3355            if self.data[row][col] is None: return ""
3356            return self.data[row][col]
3357        except IndexError:
3358            return None
3359           
3360    def InsertRows(self, pos, rows):
3361        for row in range(rows):
3362            self.data.insert(pos,[])
3363            pos += 1
3364       
3365    def IsEmptyCell(self,row,col):
3366        try:
3367            return not self.data[row][col]
3368        except IndexError:
3369            return True
3370       
3371    def OnKeyPress(self, event):
3372        dellist = self.GetSelectedRows()
3373        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
3374            grid = self.GetView()
3375            for i in dellist: grid.DeleteRow(i)
3376               
3377    def SetColLabelValue(self, col, label):
3378        numcols = self.GetNumberCols()
3379        if col > numcols-1:
3380            self.colLabels.append(label)
3381        else:
3382            self.colLabels[col]=label
3383       
3384    def SetData(self,data):
3385        for row in range(len(data)):
3386            self.SetRowValues(row,data[row])
3387               
3388    def SetRowLabelValue(self, row, label):
3389        self.rowLabels[row]=label
3390           
3391    def SetRowValues(self,row,data):
3392        self.data[row] = data
3393           
3394    def SetValue(self, row, col, value):
3395        def innerSetValue(row, col, value):
3396            try:
3397                self.data[row][col] = value
3398            except TypeError:
3399                return
3400            except IndexError: # has this been tested?
3401                #print row,col,value
3402                # add a new row
3403                if row > self.GetNumberRows():
3404                    self.data.append([''] * self.GetNumberCols())
3405                elif col > self.GetNumberCols():
3406                    for row in range(self.GetNumberRows()): # bug fixed here
3407                        self.data[row].append('')
3408                #print self.data
3409                self.data[row][col] = value
3410        innerSetValue(row, col, value)
3411
3412################################################################################
3413class GridFractionEditor(wg.PyGridCellEditor):
3414    '''A grid cell editor class that allows entry of values as fractions as well
3415    as sine and cosine values [as s() and c()]
3416    '''
3417    def __init__(self,grid):
3418        wg.PyGridCellEditor.__init__(self)
3419
3420    def Create(self, parent, id, evtHandler):
3421        self._tc = wx.TextCtrl(parent, id, "")
3422        self._tc.SetInsertionPoint(0)
3423        self.SetControl(self._tc)
3424
3425        if evtHandler:
3426            self._tc.PushEventHandler(evtHandler)
3427
3428        self._tc.Bind(wx.EVT_CHAR, self.OnChar)
3429
3430    def SetSize(self, rect):
3431        self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2,
3432                               wx.SIZE_ALLOW_MINUS_ONE)
3433
3434    def BeginEdit(self, row, col, grid):
3435        self.startValue = grid.GetTable().GetValue(row, col)
3436        self._tc.SetValue(str(self.startValue))
3437        self._tc.SetInsertionPointEnd()
3438        self._tc.SetFocus()
3439        self._tc.SetSelection(0, self._tc.GetLastPosition())
3440
3441    def EndEdit(self, row, col, grid, oldVal=None):
3442        changed = False
3443
3444        self.nextval = self.startValue
3445        val = self._tc.GetValue().lower().strip()
3446        if val != self.startValue:
3447            changed = True
3448            neg = False
3449            if val.startswith('-'):
3450                neg = True
3451                val = val[1:]
3452            # allow old GSAS s20 and c20 etc for sind(20) and cosd(20)
3453            if val.startswith('s') and '(' not in val:
3454                val = 'sind('+val.strip('s')+')'
3455            elif val.startswith('c') and '(' not in val:
3456                val = 'cosd('+val.strip('c')+')'
3457            if neg:
3458                val = '-' + val
3459            val = G2py3.FormulaEval(val)
3460            if val is not None:
3461                self.nextval = val
3462            else:
3463                return None
3464            if oldVal is None: # this arg appears in 2.9+; before, we should go ahead & change the table
3465                grid.GetTable().SetValue(row, col, val) # update the table
3466            # otherwise self.ApplyEdit gets called
3467
3468        self.startValue = ''
3469        self._tc.SetValue('')
3470        return changed
3471   
3472    def ApplyEdit(self, row, col, grid):
3473        """ Called only in wx >= 2.9
3474        Save the value of the control into the grid if EndEdit() returns as True
3475        """
3476        grid.GetTable().SetValue(row, col, self.nextval) # update the table
3477
3478    def Reset(self):
3479        self._tc.SetValue(self.startValue)
3480        self._tc.SetInsertionPointEnd()
3481
3482    def Clone(self,grid):
3483        return GridFractionEditor(grid)
3484
3485    def StartingKey(self, evt):
3486        self.OnChar(evt)
3487        if evt.GetSkipped():
3488            self._tc.EmulateKeyPress(evt)
3489
3490    def OnChar(self, evt):
3491        key = evt.GetKeyCode()
3492        if key < 32 or key >= 127:
3493            evt.Skip()
3494        elif chr(key).lower() in '.+-*/0123456789cosind()':
3495            evt.Skip()
3496        else:
3497            evt.StopPropagation()
3498           
3499################################################################################
3500#####  Customized Notebook
3501################################################################################           
3502class GSNoteBook(wx.aui.AuiNotebook):
3503    '''Notebook used in various locations; implemented with wx.aui extension
3504    '''
3505    def __init__(self, parent, name='',size = None,style=wx.aui.AUI_NB_TOP |
3506        wx.aui.AUI_NB_SCROLL_BUTTONS):
3507        wx.aui.AuiNotebook.__init__(self, parent, style=style)
3508        if size: self.SetSize(size)
3509        self.parent = parent
3510        self.PageChangeHandler = None
3511       
3512    def PageChangeEvent(self,event):
3513        pass
3514#        G2frame = self.parent.G2frame
3515#        page = event.GetSelection()
3516#        if self.PageChangeHandler:
3517#            if log.LogInfo['Logging']:
3518#                log.MakeTabLog(
3519#                    G2frame.dataWindow.GetTitle(),
3520#                    G2frame.dataDisplay.GetPageText(page)
3521#                    )
3522#            self.PageChangeHandler(event)
3523           
3524#    def Bind(self,eventtype,handler,*args,**kwargs):
3525#        '''Override the Bind() function so that page change events can be trapped
3526#        '''
3527#        if eventtype == wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED:
3528#            self.PageChangeHandler = handler
3529#            wx.aui.AuiNotebook.Bind(self,eventtype,self.PageChangeEvent)
3530#            return
3531#        wx.aui.AuiNotebook.Bind(self,eventtype,handler,*args,**kwargs)
3532                                                     
3533    def Clear(self):       
3534        GSNoteBook.DeleteAllPages(self)
3535       
3536    def FindPage(self,name):
3537        numPage = self.GetPageCount()
3538        for page in range(numPage):
3539            if self.GetPageText(page) == name:
3540                return page
3541
3542    def ChangeSelection(self,page):
3543        # in wx.Notebook ChangeSelection is like SetSelection, but it
3544        # does not invoke the event related to pressing the tab button
3545        # I don't see a way to do that in aui.
3546        oldPage = self.GetSelection()
3547        self.SetSelection(page)
3548        return oldPage
3549
3550    # def __getattribute__(self,name):
3551    #     '''This method provides a way to print out a message every time
3552    #     that a method in a class is called -- to see what all the calls
3553    #     might be, or where they might be coming from.
3554    #     Cute trick for debugging!
3555    #     '''
3556    #     attr = object.__getattribute__(self, name)
3557    #     if hasattr(attr, '__call__'):
3558    #         def newfunc(*args, **kwargs):
3559    #             print('GSauiNoteBook calling %s' %attr.__name__)
3560    #             result = attr(*args, **kwargs)
3561    #             return result
3562    #         return newfunc
3563    #     else:
3564    #         return attr
3565           
3566################################################################################
3567#### Help support routines
3568################################################################################
3569class MyHelp(wx.Menu):
3570    '''
3571    A class that creates the contents of a help menu.
3572    The menu will start with two entries:
3573
3574    * 'Help on <helpType>': where helpType is a reference to an HTML page to
3575      be opened
3576    * About: opens an About dialog using OnHelpAbout. N.B. on the Mac this
3577      gets moved to the App menu to be consistent with Apple style.
3578
3579    NOTE: for this to work properly with respect to system menus, the title
3580    for the menu must be &Help, or it will not be processed properly:
3581
3582    ::
3583
3584       menu.Append(menu=MyHelp(self,...),title="&Help")
3585
3586    '''
3587    def __init__(self,frame,includeTree=False,morehelpitems=[]):
3588        wx.Menu.__init__(self,'')
3589        self.HelpById = {}
3590        self.frame = frame
3591        self.Append(help='', id=wx.ID_ABOUT, kind=wx.ITEM_NORMAL,
3592            text='&About GSAS-II')
3593        frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT)
3594        if GSASIIpath.whichsvn():
3595            helpobj = self.Append(
3596                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
3597                text='&Check for updates')
3598            frame.Bind(wx.EVT_MENU, self.OnCheckUpdates, helpobj)
3599            helpobj = self.Append(
3600                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
3601                text='&Regress to an old GSAS-II version')
3602            frame.Bind(wx.EVT_MENU, self.OnSelectVersion, helpobj)
3603            # if GSASIIpath.svnTestBranch():
3604            #     msg = "&Switch back to standard GSAS-II version"
3605            # else:
3606            #     msg = "&Switch to test (2frame) GSAS-II version"
3607            # helpobj = self.Append(
3608            #     help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,text=msg)
3609            # frame.Bind(wx.EVT_MENU, self.OnSelectBranch, helpobj)
3610        # provide special help topic names for extra items in help menu
3611        for lbl,indx in morehelpitems:
3612            helpobj = self.Append(text=lbl,
3613                id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
3614            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
3615            self.HelpById[helpobj.GetId()] = indx
3616        # add help lookup(s) in gsasii.html
3617        self.AppendSeparator()
3618        if includeTree:
3619            helpobj = self.Append(text='Help on Data tree',
3620                                  id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
3621            frame.Bind(wx.EVT_MENU, self.OnHelpById, id=helpobj.GetId())
3622            self.HelpById[helpobj.GetId()] = 'Data tree'
3623        helpobj = self.Append(text='Help on current data tree item',id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
3624        frame.Bind(wx.EVT_MENU, self.OnHelpById, id=helpobj.GetId())
3625       
3626    def OnHelpById(self,event):
3627        '''Called when Help on... is pressed in a menu. Brings up a web page
3628        for documentation. Uses the helpKey value from the dataWindow window
3629        unless a special help key value has been defined for this menu id in
3630        self.HelpById
3631
3632        Note that self should now (2frame) be child of the main window (G2frame)
3633        '''
3634        if hasattr(self.frame,'dataWindow'):  # Debug code: check this is called from menu in G2frame
3635            # should always be true in 2 Frame version
3636            dW = self.frame.dataWindow
3637        else:
3638            print('help error: not called from standard menu?')
3639            print self
3640            return           
3641        try:
3642            helpKey = dW.helpKey # look up help from helpKey in data window
3643            #if GSASIIpath.GetConfigValue('debug'): print 'dataWindow help: key=',helpKey
3644        except AttributeError:
3645            helpKey = ''
3646            if GSASIIpath.GetConfigValue('debug'): print('No helpKey for current dataWindow!')
3647        helpType = self.HelpById.get(event.GetId(),helpKey) # see if there is a special help topic
3648        #if GSASIIpath.GetConfigValue('debug'): print 'helpKey=',helpKey,'  helpType=',helpType
3649        if helpType == 'Tutorials':
3650            dlg = OpenTutorial(self.frame)
3651            dlg.ShowModal()
3652            dlg.Destroy()
3653            return
3654        else:
3655            ShowHelp(helpType,self.frame)
3656
3657    def OnHelpAbout(self, event):
3658        "Display an 'About GSAS-II' box"
3659        import GSASII
3660        info = wx.AboutDialogInfo()
3661        info.Name = 'GSAS-II'
3662        ver = GSASIIpath.svnGetRev()
3663        if ver: 
3664            info.Version = 'Revision '+str(ver)+' (svn), version '+GSASII.__version__
3665        else:
3666            info.Version = 'Revision '+str(GSASIIpath.GetVersionNumber())+' (.py files), version '+GSASII.__version__
3667        #info.Developers = ['Robert B. Von Dreele','Brian H. Toby']
3668        info.Copyright = ('(c) ' + time.strftime('%Y') +
3669''' Argonne National Laboratory
3670This product includes software developed
3671by the UChicago Argonne, LLC, as
3672Operator of Argonne National Laboratory.''')
3673        info.Description = '''General Structure Analysis System-II (GSAS-II)
3674Robert B. Von Dreele and Brian H. Toby
3675
3676Please cite as:
3677  B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013)
3678For small angle use cite:
3679  R.B. Von Dreele, J. Appl. Cryst. 47, 1748-9 (2014)
3680For DIFFaX use cite:
3681  M.M.J. Treacy, J.M. Newsam & M.W. Deem,
3682  Proc. Roy. Soc. Lond. A 433, 499-520 (1991)
3683'''
3684        info.WebSite = ("https://subversion.xray.aps.anl.gov/trac/pyGSAS","GSAS-II home page")
3685        wx.AboutBox(info)
3686
3687    def OnCheckUpdates(self,event):
3688        '''Check if the GSAS-II repository has an update for the current source files
3689        and perform that update if requested.
3690        '''           
3691        if not GSASIIpath.whichsvn():
3692            dlg = wx.MessageDialog(self.frame,
3693                                   'No Subversion','Cannot update GSAS-II because subversion (svn) was not found.',
3694                                   wx.OK)
3695            dlg.ShowModal()
3696            dlg.Destroy()
3697            return
3698        wx.BeginBusyCursor()
3699        local = GSASIIpath.svnGetRev()
3700        if local is None: 
3701            wx.EndBusyCursor()
3702            dlg = wx.MessageDialog(self.frame,
3703                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
3704                                   'Subversion error',
3705                                   wx.OK)
3706            dlg.ShowModal()
3707            dlg.Destroy()
3708            return
3709        print 'Installed GSAS-II version: '+local
3710        repos = GSASIIpath.svnGetRev(local=False)
3711        wx.EndBusyCursor()
3712        # has the current branch disappeared? If so, switch to the trunk -- not fully tested
3713        if (repos is None and "not found" in GSASIIpath.svnLastError.lower()
3714            and "path" in GSASIIpath.svnLastError.lower()):
3715            print('Repository is gone, will switch to trunk')
3716            GSASIIpath.svnSwitch2branch()
3717            return
3718        elif repos is None: 
3719            dlg = wx.MessageDialog(self.frame,
3720                                   'Unable to access the GSAS-II server. Is this computer on the internet?',
3721                                   'Server unavailable',
3722                                   wx.OK)
3723            dlg.ShowModal()
3724            dlg.Destroy()
3725            return
3726        print 'GSAS-II version on server: '+repos
3727        if local == repos:
3728            dlg = wx.MessageDialog(self.frame,
3729                                   'GSAS-II is up-to-date. Version '+local+' is already loaded.',
3730                                   'GSAS-II Up-to-date',
3731                                   wx.OK)
3732            dlg.ShowModal()
3733            dlg.Destroy()
3734            return
3735        mods = GSASIIpath.svnFindLocalChanges()
3736        if mods:
3737            dlg = wx.MessageDialog(self.frame,
3738                                   'You have version '+local+
3739                                   ' of GSAS-II installed, but the current version is '+repos+
3740                                   '. However, '+str(len(mods))+
3741                                   ' file(s) on your local computer have been modified.'
3742                                   ' Updating will attempt to merge your local changes with '
3743                                   'the latest GSAS-II version, but if '
3744                                   'conflicts arise, local changes will be '
3745                                   'discarded. It is also possible that the '
3746                                   'local changes my prevent GSAS-II from running. '
3747                                   'Press OK to start an update if this is acceptable:',
3748                                   'Local GSAS-II Mods',
3749                                   wx.OK|wx.CANCEL)
3750            if dlg.ShowModal() != wx.ID_OK:
3751                dlg.Destroy()
3752                return
3753            else:
3754                dlg.Destroy()
3755        else:
3756            dlg = wx.MessageDialog(self.frame,
3757                                   'You have version '+local+
3758                                   ' of GSAS-II installed, but the current version is '+repos+
3759                                   '. Press OK to start an update:',
3760                                   'GSAS-II Updates',
3761                                   wx.OK|wx.CANCEL)
3762            if dlg.ShowModal() != wx.ID_OK:
3763                dlg.Destroy()
3764                return
3765            dlg.Destroy()
3766        print 'start updates'
3767        dlg = wx.MessageDialog(self.frame,
3768                               'Your project will now be saved, GSAS-II will exit and an update '
3769                               'will be performed and GSAS-II will restart. Press Cancel to '
3770                               'abort the update',
3771                               'Start update?',
3772                               wx.OK|wx.CANCEL)
3773        if dlg.ShowModal() != wx.ID_OK:
3774            dlg.Destroy()
3775            return
3776        dlg.Destroy()
3777        self.frame.OnFileSave(event)
3778        GPX = self.frame.GSASprojectfile
3779        GSASIIpath.svnUpdateProcess(projectfile=GPX)
3780        return
3781
3782    def OnSelectVersion(self,event):
3783        '''Allow the user to select a specific version of GSAS-II
3784        '''
3785        if not GSASIIpath.whichsvn():
3786            dlg = wx.MessageDialog(self,'No Subversion','Cannot update GSAS-II because subversion (svn) '+
3787                                   'was not found.'
3788                                   ,wx.OK)
3789            dlg.ShowModal()
3790            return
3791        local = GSASIIpath.svnGetRev()
3792        if local is None: 
3793            dlg = wx.MessageDialog(self.frame,
3794                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
3795                                   'Subversion error',
3796                                   wx.OK)
3797            dlg.ShowModal()
3798            dlg.Destroy()
3799            return
3800        mods = GSASIIpath.svnFindLocalChanges()
3801        if mods:
3802            dlg = wx.MessageDialog(self.frame,
3803                                   'You have version '+local+
3804                                   ' of GSAS-II installed'
3805                                   '. However, '+str(len(mods))+
3806                                   ' file(s) on your local computer have been modified.'
3807                                   ' Downdating will attempt to merge your local changes with '
3808                                   'the selected GSAS-II version. '
3809                                   'Downdating is not encouraged because '
3810                                   'if merging is not possible, your local changes will be '
3811                                   'discarded. It is also possible that the '
3812                                   'local changes my prevent GSAS-II from running. '
3813                                   'Press OK to continue anyway.',
3814                                   'Local GSAS-II Mods',
3815                                   wx.OK|wx.CANCEL)
3816            if dlg.ShowModal() != wx.ID_OK:
3817                dlg.Destroy()
3818                return
3819            dlg.Destroy()
3820        if GSASIIpath.svnGetRev(local=False) is None:
3821            dlg = wx.MessageDialog(self.frame,
3822                                   'Error obtaining current GSAS-II version. Is internet access working correctly?',
3823                                   'Subversion error',
3824                                   wx.OK)
3825            dlg.ShowModal()
3826            dlg.Destroy()
3827            return
3828        dlg = downdate(parent=self.frame)
3829        if dlg.ShowModal() == wx.ID_OK:
3830            ver = dlg.getVersion()
3831        else:
3832            dlg.Destroy()
3833            return
3834        dlg.Destroy()
3835        print('start regress to '+str(ver))
3836        self.frame.OnFileSave(event)
3837        GPX = self.frame.GSASprojectfile
3838        GSASIIpath.svnUpdateProcess(projectfile=GPX,version=str(ver))
3839        return
3840
3841    # def OnSelectBranch(self,event):
3842    #     '''Allow the user to select branch of GSAS-II or return to trunk
3843    #     N.B. Name of branch to use is hard-coded here. Must contain a slash
3844    #     '''
3845    #     testbranch = '/branch/2frame'
3846    #     if not GSASIIpath.svnTestBranch():
3847    #         dlg = wx.MessageDialog(self.frame,
3848    #                                'Switching to test GSAS-II version',
3849    #                                'Confirm Switch',
3850    #                                wx.OK|wx.CANCEL)
3851    #         if dlg.ShowModal() != wx.ID_OK: return
3852    #         branch = testbranch
3853    #     else:
3854    #         dlg = wx.MessageDialog(self.frame,
3855    #                                'Switching back to standard GSAS-II version',
3856    #                                'Confirm Switch',
3857    #                                wx.OK|wx.CANCEL)
3858    #         if dlg.ShowModal() != wx.ID_OK: return
3859    #         branch = 'trunk'
3860    #     print('start switch')
3861    #     self.frame.OnFileSave(event)
3862    #     GPX = self.frame.GSASprojectfile
3863    #     GSASIIpath.svnUpdateProcess(projectfile=GPX,branch=branch)
3864
3865################################################################################
3866class HelpButton(wx.Button):
3867    '''Create a help button that displays help information.
3868    The text is displayed in a modal message window.
3869
3870    TODO: it might be nice if it were non-modal: e.g. it stays around until
3871    the parent is deleted or the user closes it, but this did not work for
3872    me.
3873
3874    :param parent: the panel which will be the parent of the button
3875    :param str msg: the help text to be displayed
3876    '''
3877    def __init__(self,parent,msg):
3878        if sys.platform == "darwin": 
3879            wx.Button.__init__(self,parent,wx.ID_HELP)
3880        else:
3881            wx.Button.__init__(self,parent,wx.ID_ANY,'?',style=wx.BU_EXACTFIT)
3882        self.Bind(wx.EVT_BUTTON,self._onPress)
3883        self.msg=StripIndents(msg)
3884        self.parent = parent
3885    def _onClose(self,event):
3886        self.dlg.EndModal(wx.ID_CANCEL)
3887    def _onPress(self,event):
3888        'Respond to a button press by displaying the requested text'
3889        #dlg = wx.MessageDialog(self.parent,self.msg,'Help info',wx.OK)
3890        self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', 
3891                        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
3892        #self.dlg.SetBackgroundColour(wx.WHITE)
3893        mainSizer = wx.BoxSizer(wx.VERTICAL)
3894        txt = wx.StaticText(self.dlg,wx.ID_ANY,self.msg)
3895        mainSizer.Add(txt,1,wx.ALL|wx.EXPAND,10)
3896        txt.SetBackgroundColour(wx.WHITE)
3897
3898        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3899        btn = wx.Button(self.dlg, wx.ID_CLOSE) 
3900        btn.Bind(wx.EVT_BUTTON,self._onClose)
3901        btnsizer.Add(btn)
3902        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3903        self.dlg.SetSizer(mainSizer)
3904        mainSizer.Fit(self.dlg)
3905        self.dlg.CenterOnParent()
3906        self.dlg.ShowModal()
3907        self.dlg.Destroy()
3908################################################################################
3909class MyHtmlPanel(wx.Panel):
3910    '''Defines a panel to display HTML help information, as an alternative to
3911    displaying help information in a web browser.
3912    '''
3913    def __init__(self, frame, id):
3914        self.frame = frame
3915        wx.Panel.__init__(self, frame, id)
3916        sizer = wx.BoxSizer(wx.VERTICAL)
3917        back = wx.Button(self, -1, "Back")
3918        back.Bind(wx.EVT_BUTTON, self.OnBack)
3919        self.htmlwin = G2HtmlWindow(self, id, size=(750,450))
3920        sizer.Add(self.htmlwin, 1,wx.EXPAND)
3921        sizer.Add(back, 0, wx.ALIGN_LEFT, 0)
3922        self.SetSizer(sizer)
3923        sizer.Fit(frame)       
3924        self.Bind(wx.EVT_SIZE,self.OnHelpSize)
3925    def OnHelpSize(self,event):         #does the job but weirdly!!
3926        anchor = self.htmlwin.GetOpenedAnchor()
3927        if anchor:           
3928            self.htmlwin.ScrollToAnchor(anchor)
3929            wx.CallAfter(self.htmlwin.ScrollToAnchor,anchor)
3930            if event: event.Skip()
3931    def OnBack(self, event):
3932        self.htmlwin.HistoryBack()
3933    def LoadFile(self,file):
3934        pos = file.rfind('#')
3935        if pos != -1:
3936            helpfile = file[:pos]
3937            helpanchor = file[pos+1:]
3938        else:
3939            helpfile = file
3940            helpanchor = None
3941        self.htmlwin.LoadPage(helpfile)
3942        if helpanchor is not None:
3943            self.htmlwin.ScrollToAnchor(helpanchor)
3944            xs,ys = self.htmlwin.GetViewStart()
3945            self.htmlwin.Scroll(xs,ys-1)
3946################################################################################
3947class G2HtmlWindow(wx.html.HtmlWindow):
3948    '''Displays help information in a primitive HTML browser type window
3949    '''
3950    def __init__(self, parent, *args, **kwargs):
3951        self.parent = parent
3952        wx.html.HtmlWindow.__init__(self, parent, *args, **kwargs)
3953    def LoadPage(self, *args, **kwargs):
3954        wx.html.HtmlWindow.LoadPage(self, *args, **kwargs)
3955        self.TitlePage()
3956    def OnLinkClicked(self, *args, **kwargs):
3957        wx.html.HtmlWindow.OnLinkClicked(self, *args, **kwargs)
3958        xs,ys = self.GetViewStart()
3959        self.Scroll(xs,ys-1)
3960        self.TitlePage()
3961    def HistoryBack(self, *args, **kwargs):
3962        wx.html.HtmlWindow.HistoryBack(self, *args, **kwargs)
3963        self.TitlePage()
3964    def TitlePage(self):
3965        self.parent.frame.SetTitle(self.GetOpenedPage() + ' -- ' + 
3966            self.GetOpenedPageTitle())
3967
3968################################################################################
3969def StripIndents(msg):
3970    'Strip indentation from multiline strings'
3971    msg1 = msg.replace('\n ','\n')
3972    while msg != msg1:
3973        msg = msg1
3974        msg1 = msg.replace('\n ','\n')
3975    return msg.replace('\n\t','\n')
3976
3977def StripUnicode(string,subs='.'):
3978    '''Strip non-ASCII characters from strings
3979   
3980    :param str string: string to strip Unicode characters from
3981    :param str subs: character(s) to place into string in place of each
3982      Unicode character. Defaults to '.'
3983
3984    :returns: a new string with only ASCII characters
3985    '''
3986    s = ''
3987    for c in string:
3988        if ord(c) < 128:
3989            s += c
3990        else:
3991            s += subs
3992    return s.encode('ascii','replace')
3993       
3994################################################################################
3995# configuration routines (for editing config.py)
3996def SaveGPXdirectory(path):
3997    if GSASIIpath.GetConfigValue('Starting_directory') == path: return
3998    vars = GetConfigValsDocs()
3999    try:
4000        vars['Starting_directory'][1] = path
4001        if GSASIIpath.GetConfigValue('debug'): print('Saving GPX path: '+path)
4002        SaveConfigVars(vars)
4003    except KeyError:
4004        pass
4005
4006def SaveImportDirectory(path):
4007    if GSASIIpath.GetConfigValue('Import_directory') == path: return
4008    vars = GetConfigValsDocs()
4009    try:
4010        vars['Import_directory'][1] = path
4011        if GSASIIpath.GetConfigValue('debug'): print('Saving Import path: '+path)
4012        SaveConfigVars(vars)
4013    except KeyError:
4014        pass
4015
4016def GetConfigValsDocs():
4017    '''Reads the module referenced in fname (often <module>.__file__) and
4018    return a dict with names of global variables as keys.
4019    For each global variable, the value contains four items:
4020
4021    :returns: a dict where keys are names defined in module config_example.py
4022      where the value is a list of four items, as follows:
4023
4024         * item 0: the default value
4025         * item 1: the current value
4026         * item 2: the initial value (starts same as item 1)
4027         * item 3: the "docstring" that follows variable definition
4028
4029    '''
4030    import config_example
4031    import ast
4032    fname = os.path.splitext(config_example.__file__)[0]+'.py' # convert .pyc to .py
4033    with open(fname, 'r') as f:
4034        fstr = f.read()
4035    fstr = fstr.replace('\r\n', '\n').replace('\r', '\n')
4036    if not fstr.endswith('\n'):
4037        fstr += '\n'
4038    tree = ast.parse(fstr)
4039    d = {}
4040    key = None
4041    for node in ast.walk(tree):
4042        if isinstance(node,ast.Assign):
4043            key = node.targets[0].id
4044            d[key] = [config_example.__dict__.get(key),
4045                      GSASIIpath.configDict.get(key),
4046                      GSASIIpath.configDict.get(key),'']
4047        elif isinstance(node,ast.Expr) and key:
4048            d[key][3] = node.value.s.strip()
4049        else:
4050            key = None
4051    return d
4052
4053def SaveConfigVars(vars,parent=None):
4054    '''Write the current config variable values to config.py
4055
4056    :params dict vars: a dictionary of variable settings and meanings as
4057      created in :func:`GetConfigValsDocs`.
4058    :param parent: wx.Frame object or None (default) for parent
4059      of error message if no file can be written.
4060    :returns: True if unable to write the file, None otherwise
4061    '''
4062    # try to write to where an old config file is located
4063    try:
4064        import config
4065        savefile = config.__file__
4066    except ImportError: # no config.py file yet
4067        savefile = os.path.join(GSASIIpath.path2GSAS2,'config.py')
4068    # try to open file for write
4069    try:
4070        savefile = os.path.splitext(savefile)[0]+'.py' # convert .pyc to .py
4071        fp = open(savefile,'w')
4072    except IOError:  # can't write there, write in local mods directory
4073        # create a local mods directory, if needed
4074        g2local = os.path.expanduser('~/.G2local/')
4075        if not os.path.exists(g2local):
4076            try:
4077                print(u'Creating directory '+g2local)
4078                os.mkdir(g2local)
4079            except:
4080                if parent:
4081                    G2MessageBox(parent,u'Error trying to create directory '+g2local,
4082                        'Unable to save')
4083                else:
4084                    print(u'Error trying to create directory '+g2local)
4085                return True
4086            sys.path.insert(0,os.path.expanduser('~/.G2local/'))
4087        savefile = os.path.join(os.path.expanduser('~/.G2local/'),'config.py')
4088        try:
4089            fp = open(savefile,'w')
4090        except IOError:
4091            if parent:
4092                G2MessageBox(parent,'Error trying to write configuration to '+savefile,
4093                    'Unable to save')
4094            else:
4095                print('Error trying to write configuration to '+savefile)
4096            return True
4097    import datetime
4098    fp.write("'''\n")
4099    fp.write("*config.py: Configuration options*\n----------------------------------\n")
4100    fp.write("This file created in SelectConfigSetting on {:%d %b %Y %H:%M}\n".
4101             format(datetime.datetime.now()))
4102    fp.write("'''\n\n")
4103    fp.write("import os.path\n")
4104    fp.write("import GSASIIpath\n\n")
4105    for var in sorted(vars.keys(),key=lambda s: s.lower()):
4106        if vars[var][1] is None: continue
4107        if vars[var][1] == '': continue
4108        if vars[var][0] == vars[var][1]: continue
4109        try:
4110            float(vars[var][1]) # test for number
4111            fp.write(var + ' = ' + str(vars[var][1])+'\n')
4112        except:
4113            try:
4114                eval(vars[var][1]) # test for an expression
4115                fp.write(var + ' = ' + str(vars[var][1])+'\n')
4116            except: # must be a string
4117                varstr = vars[var][1]
4118                if '\\' in varstr:
4119                    fp.write(var + ' = os.path.normpath("' + varstr.replace('\\','/') +'")\n')
4120                else:
4121                    fp.write(var + ' = "' + str(varstr)+'"\n')
4122        if vars[var][3]:
4123            fp.write("'''" + str(vars[var][3]) + "\n'''\n\n")
4124    fp.close()
4125    print('wrote file '+savefile)
4126
4127class SelectConfigSetting(wx.Dialog):
4128    '''Dialog to select configuration variables and set associated values.
4129    '''
4130    def __init__(self,parent=None):
4131        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
4132        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Set Config Variable', style=style)
4133        self.sizer = wx.BoxSizer(wx.VERTICAL)
4134        self.vars = GetConfigValsDocs()
4135       
4136        label = wx.StaticText(
4137            self,  wx.ID_ANY,
4138            'Select a GSAS-II configuration variable to change'
4139            )
4140        self.sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4141        self.choice = {}
4142        btn = G2ChoiceButton(self, sorted(self.vars.keys(),key=lambda s: s.lower()),
4143            strLoc=self.choice,strKey=0,onChoice=self.OnSelection)
4144        btn.SetLabel("")
4145        self.sizer.Add(btn)
4146
4147        self.varsizer = wx.BoxSizer(wx.VERTICAL)
4148        self.sizer.Add(self.varsizer,1,wx.ALL|wx.EXPAND,1)
4149       
4150        self.doclbl = wx.StaticBox(self, wx.ID_ANY, "")
4151        self.doclblsizr = wx.StaticBoxSizer(self.doclbl)
4152        self.docinfo = wx.StaticText(self,  wx.ID_ANY, "")
4153        self.doclblsizr.Add(self.docinfo, 0, wx.ALIGN_LEFT|wx.ALL, 5)
4154        self.sizer.Add(self.doclblsizr, 0, wx.ALIGN_LEFT|wx.EXPAND|wx.ALL, 5)
4155        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
4156        self.saveBtn = wx.Button(self,-1,"Save current settings")
4157        btnsizer.Add(self.saveBtn, 0, wx.ALL, 2) 
4158        self.saveBtn.Bind(wx.EVT_BUTTON, self.OnSave)
4159        self.saveBtn.Enable(False)
4160        self.applyBtn = wx.Button(self,-1,"Use current (no save)")
4161        btnsizer.Add(self.applyBtn, 0, wx.ALL, 2) 
4162        self.applyBtn.Bind(wx.EVT_BUTTON, self.OnApplyChanges)
4163        self.applyBtn.Enable(False)
4164       
4165        btn = wx.Button(self,wx.ID_CANCEL)
4166        btnsizer.Add(btn, 0, wx.ALL, 2) 
4167        self.sizer.Add(btnsizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 
4168               
4169        self.SetSizer(self.sizer)
4170        self.sizer.Fit(self)
4171        self.CenterOnParent()
4172       
4173    def OnChange(self,event=None):
4174        ''' Check if anything been changed. Turn the save button on/off.
4175        '''
4176        for var in self.vars:
4177            if self.vars[var][0] is None and self.vars[var][1] is not None:
4178                # make blank strings into None, if that is the default
4179                if self.vars[var][1].strip() == '': self.vars[var][1] = None
4180            if self.vars[var][1] != self.vars[var][2]:
4181                #print 'changed',var,self.vars[var][:3]
4182                self.saveBtn.Enable(True)
4183                self.applyBtn.Enable(True)
4184                break
4185        else:
4186            self.saveBtn.Enable(False)
4187            self.applyBtn.Enable(False)
4188        try:
4189            self.resetBtn.Enable(True)
4190        except:
4191            pass
4192       
4193    def OnApplyChanges(self,event=None):
4194        'Set config variables to match the current settings'
4195        GSASIIpath.SetConfigValue(self.vars)
4196        self.EndModal(wx.ID_OK)
4197        import GSASIImpsubs as G2mp
4198        G2mp.ResetMP()
4199       
4200    def OnSave(self,event):
4201        '''Write the config variables to config.py and then set them
4202        as the current settings
4203        '''
4204        if not SaveConfigVars(self.vars,parent=self):
4205            self.OnApplyChanges() # force a reload of the config settings
4206        else:
4207            self.EndModal(wx.ID_OK)
4208
4209    def OnBoolSelect(self,event):
4210        'Respond to a change in a True/False variable'
4211        rb = event.GetEventObject()
4212        var = self.choice[0]
4213        self.vars[var][1] = (rb.GetSelection() == 0)
4214        self.OnChange()
4215        wx.CallAfter(self.OnSelection)
4216       
4217    def onSelDir(self,event):
4218        'Select a directory from a menu'
4219        dlg = wx.DirDialog(self, "Choose a directory:",style=wx.DD_DEFAULT_STYLE)
4220        if dlg.ShowModal() == wx.ID_OK:
4221            var = self.choice[0]
4222            self.vars[var][1] = dlg.GetPath()
4223            self.strEd.SetValue(self.vars[var][1])
4224            self.OnChange()
4225        dlg.Destroy()
4226       
4227    def OnSelection(self):
4228        'show a selected variable'
4229        def OnNewColorBar(event):
4230            self.vars['Contour_color'][1] = self.colSel.GetValue()
4231            self.OnChange(event)
4232
4233        self.varsizer.DeleteWindows()
4234        var = self.choice[0]
4235        showdef = True
4236        if var not in self.vars:
4237            raise Exception,"How did this happen?"
4238        if type(self.vars[var][0]) is int:
4239            ed = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=int,OKcontrol=self.OnChange)
4240            self.varsizer.Add(ed, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4241        elif type(self.vars[var][0]) is float:
4242            ed = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=float,OKcontrol=self.OnChange)
4243            self.varsizer.Add(ed, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4244        elif type(self.vars[var][0]) is bool:
4245            showdef = False
4246            lbl = "value for "+var
4247            ch = []
4248            for i,v in enumerate((True,False)):
4249                s = str(v)
4250                if v == self.vars[var][0]:
4251                    defopt = i
4252                    s += ' (default)'
4253                ch += [s]
4254            rb = wx.RadioBox(self, wx.ID_ANY, lbl, wx.DefaultPosition, wx.DefaultSize,
4255                ch, 1, wx.RA_SPECIFY_COLS)
4256            # set initial value
4257            if self.vars[var][1] is None:
4258                rb.SetSelection(defopt)
4259            elif self.vars[var][1]:
4260                rb.SetSelection(0)
4261            else:
4262                rb.SetSelection(1)
4263            rb.Bind(wx.EVT_RADIOBOX,self.OnBoolSelect)
4264            self.varsizer.Add(rb, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4265        else:
4266            if var.endswith('_directory') or var.endswith('_location'):
4267                btn = wx.Button(self,wx.ID_ANY,'Select from dialog...')
4268                sz = (400,-1)
4269            else:
4270                btn = None
4271                sz = (250,-1)
4272            if var == 'Contour_color':
4273                if self.vars[var][1] is None:
4274                    self.vars[var][1] = 'paired'
4275                colorList = sorted([m for m in mpl.cm.datad.keys() ],key=lambda s: s.lower())   #if not m.endswith("_r")
4276                self.colSel = wx.ComboBox(self,value=self.vars[var][1],choices=colorList,
4277                    style=wx.CB_READONLY|wx.CB_DROPDOWN)
4278                self.colSel.Bind(wx.EVT_COMBOBOX, OnNewColorBar)
4279                self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4280            else:
4281                self.strEd = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=str,
4282                    OKcontrol=self.OnChange,size=sz)
4283                if self.vars[var][1] is not None:
4284                    self.strEd.SetValue(self.vars[var][1])
4285                self.varsizer.Add(self.strEd, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4286            if btn:
4287                btn.Bind(wx.EVT_BUTTON,self.onSelDir)
4288                self.varsizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 
4289        # button for reset to default value
4290        lbl = "Reset to Default"
4291        if showdef: # spell out default when needed
4292            lbl += ' (='+str(self.vars[var][0])+')'
4293            #label = wx.StaticText(self,  wx.ID_ANY, 'Default value = '+str(self.vars[var][0]))
4294            #self.varsizer.Add(label, 0, wx.ALIGN_LEFT|wx.ALL, 5)
4295        self.resetBtn = wx.Button(self,-1,lbl)
4296        self.resetBtn.Bind(wx.EVT_BUTTON, self.OnClear)
4297        if self.vars[var][1] is not None and self.vars[var][1] != '': # show current value, if one
4298            #label = wx.StaticText(self,  wx.ID_ANY, 'Current value = '+str(self.vars[var][1]))
4299            #self.varsizer.Add(label, 0, wx.ALIGN_LEFT|wx.ALL, 5)
4300            self.resetBtn.Enable(True)
4301        else:
4302            self.resetBtn.Enable(False)
4303        self.varsizer.Add(self.resetBtn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4304        # show meaning, if defined
4305        self.doclbl.SetLabel("Description of "+str(var)) 
4306        if self.vars[var][3]:
4307            self.docinfo.SetLabel(self.vars[var][3])
4308        else:
4309            self.docinfo.SetLabel("(not documented)")
4310        self.sizer.Fit(self)
4311        self.CenterOnParent()
4312        wx.CallAfter(self.SendSizeEvent)
4313
4314    def OnClear(self, event):
4315        var = self.choice[0]
4316        self.vars[var][1] = self.vars[var][0]
4317        self.OnChange()
4318        wx.CallAfter(self.OnSelection)
4319       
4320################################################################################
4321class downdate(wx.Dialog):
4322    '''Dialog to allow a user to select a version of GSAS-II to install
4323    '''
4324    def __init__(self,parent=None):
4325        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
4326        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style)
4327        pnl = wx.Panel(self)
4328        sizer = wx.BoxSizer(wx.VERTICAL)
4329        insver = GSASIIpath.svnGetRev(local=True)
4330        curver = int(GSASIIpath.svnGetRev(local=False))
4331        label = wx.StaticText(
4332            pnl,  wx.ID_ANY,
4333            'Select a specific GSAS-II version to install'
4334            )
4335        sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
4336        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
4337        sizer1.Add(
4338            wx.StaticText(pnl,  wx.ID_ANY,
4339                          'Currently installed version: '+str(insver)),
4340            0, wx.ALIGN_CENTRE|wx.ALL, 5)
4341        sizer.Add(sizer1)
4342        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
4343        sizer1.Add(
4344            wx.StaticText(pnl,  wx.ID_ANY,
4345                          'Select GSAS-II version to install: '),
4346            0, wx.ALIGN_CENTRE|wx.ALL, 5)
4347        self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1))
4348        self.spin.SetRange(1, curver)
4349        self.spin.SetValue(curver)
4350        self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
4351        self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
4352        sizer1.Add(self.spin)
4353        sizer.Add(sizer1)
4354
4355        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
4356        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
4357
4358        self.text = wx.StaticText(pnl,  wx.ID_ANY, "")
4359        sizer.Add(self.text, 0, wx.ALIGN_LEFT|wx.EXPAND|wx.ALL, 5)
4360
4361        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
4362        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
4363        sizer.Add(
4364            wx.StaticText(
4365                pnl,  wx.ID_ANY,
4366                'If "Install" is pressed, your project will be saved;\n'
4367                'GSAS-II will exit; The specified version will be loaded\n'
4368                'and GSAS-II will restart. Press "Cancel" to abort.'),
4369            0, wx.EXPAND|wx.ALL, 10)
4370        btnsizer = wx.StdDialogButtonSizer()
4371        btn = wx.Button(pnl, wx.ID_OK, "Install")
4372        btn.SetDefault()
4373        btnsizer.AddButton(btn)
4374        btn = wx.Button(pnl, wx.ID_CANCEL)
4375        btnsizer.AddButton(btn)
4376        btnsizer.Realize()
4377        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
4378        pnl.SetSizer(sizer)
4379        sizer.Fit(self)
4380        self.topsizer=sizer
4381        self.CenterOnParent()
4382        self._onSpin(None)
4383
4384    def _onSpin(self,event):
4385        'Called to load info about the selected version in the dialog'
4386        if event: event.Skip()
4387        ver = self.spin.GetValue()
4388        d = GSASIIpath.svnGetLog(version=ver)
4389        date = d.get('date','?').split('T')[0]
4390        s = '(Version '+str(ver)+' created '+date
4391        s += ' by '+d.get('author','?')+')'
4392        msg = d.get('msg')
4393        if msg: s += '\n\nComment: '+msg
4394        self.text.SetLabel(s)
4395        self.topsizer.Fit(self)
4396
4397    def getVersion(self):
4398        'Get the version number in the dialog'
4399        return self.spin.GetValue()
4400
4401################################################################################
4402#### Display Help information
4403################################################################################
4404# define some globals
4405htmlPanel = None
4406htmlFrame = None
4407htmlFirstUse = True
4408#helpLocDict = {}  # to be implemented if we ever split gsasii.html over multiple files
4409path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # save location of this file
4410def ShowHelp(helpType,frame):
4411    '''Called to bring up a web page for documentation.'''
4412    global htmlFirstUse,htmlPanel,htmlFrame
4413    # no defined link to use, create a default based on key
4414    helplink = 'gsasII.html'
4415    if helpType:
4416        helplink += '#'+helpType.replace(')','').replace('(','_').replace(' ','_')
4417    # determine if a web browser or the internal viewer should be used for help info
4418    if GSASIIpath.GetConfigValue('Help_mode'):
4419        helpMode = GSASIIpath.GetConfigValue('Help_mode')
4420    else:
4421        helpMode = 'browser'
4422    if helpMode == 'internal':
4423        helplink = os.path.join(path2GSAS2,'help',helplink)
4424        try:
4425            htmlPanel.LoadFile(helplink)
4426            htmlFrame.Raise()
4427        except:
4428            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
4429            htmlFrame.Show(True)
4430            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
4431            htmlPanel = MyHtmlPanel(htmlFrame,-1)
4432            htmlPanel.LoadFile(helplink)
4433    else:
4434        if sys.platform == "darwin": # for Mac, force use of safari to preserve anchors on file URLs
4435            wb = webbrowser.MacOSXOSAScript('safari')
4436        else:
4437            wb = webbrowser
4438        helplink = os.path.join(path2GSAS2,'help',helplink)
4439        pfx = "file://"
4440        if sys.platform.lower().startswith('win'):
4441            pfx = ''
4442        #if GSASIIpath.GetConfigValue('debug'): print 'Help link=',pfx+helplink
4443        if htmlFirstUse:
4444            wb.open_new(pfx+helplink)
4445            htmlFirstUse = False
4446        else:
4447            wb.open(pfx+helplink, new=0, autoraise=True)
4448
4449def ShowWebPage(URL,frame):
4450    '''Called to show a tutorial web page.
4451    '''
4452    global htmlFirstUse,htmlPanel,htmlFrame
4453    # determine if a web browser or the internal viewer should be used for help info
4454    if GSASIIpath.GetConfigValue('Help_mode'):
4455        helpMode = GSASIIpath.GetConfigValue('Help_mode')
4456    else:
4457        helpMode = 'browser'
4458    if helpMode == 'internal':
4459        try:
4460            htmlPanel.LoadFile(URL)
4461            htmlFrame.Raise()
4462        except:
4463            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
4464            htmlFrame.Show(True)
4465            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
4466            htmlPanel = MyHtmlPanel(htmlFrame,-1)
4467            htmlPanel.LoadFile(URL)
4468    else:
4469        if URL.startswith('http'): 
4470            pfx = ''
4471        elif sys.platform.lower().startswith('win'):
4472            pfx = ''
4473        else:
4474            pfx = "file://"
4475        if htmlFirstUse:
4476            webbrowser.open_new(pfx+URL)
4477            htmlFirstUse = False
4478        else:
4479            webbrowser.open(pfx+URL, new=0, autoraise=True)
4480
4481################################################################################
4482#### Tutorials support
4483################################################################################
4484G2BaseURL = "https://subversion.xray.aps.anl.gov/pyGSAS"
4485# N.B. tutorialCatalog is generated by routine catalog.py, which also generates the appropriate
4486# empty directories (.../MT/* .../trunk/GSASII/* *=[help,Exercises])
4487tutorialCatalog = (
4488    # tutorial dir,      web page file name,      title for page
4489
4490    ['StartingGSASII', 'Starting GSAS.htm', 'Starting GSAS-II'],
4491       
4492    ['LabData', 'Laboratory X.htm', 'Fitting laboratory X-ray powder data for fluoroapatite'],
4493    ['CWNeutron', 'Neutron CW Powder Data.htm', 'CW Neutron Powder fit for Yttrium-Iron Garnet'],
4494
4495    ['FitPeaks', 'Fit Peaks.htm', 'Fitting individual peaks & autoindexing'],
4496    ['CFjadarite', 'Charge Flipping in GSAS.htm', '     Charge Flipping structure solution for jadarite'],
4497    ['CFsucrose', 'Charge Flipping - sucrose.htm','     Charge Flipping structure solution for sucrose'],
4498    ['BkgFit', 'FitBkgTut.htm',  'Fitting the Starting Background using Fixed Points'],
4499       
4500    ['CWCombined', 'Combined refinement.htm', 'Combined X-ray/CW-neutron refinement of PbSO4'],
4501    ['TOF-CW Joint Refinement', 'TOF combined XN Rietveld refinement in GSAS.htm', 'Combined X-ray/TOF-neutron Rietveld refinement'],
4502    ['SeqRefine', 'SequentialTutorial.htm', 'Sequential refinement of multiple datasets'],
4503    ['SeqParametric', 'ParametricFitting.htm', '     Parametric Fitting and Pseudo Variables for Sequential Fits'],
4504       
4505    ['StackingFaults-I', 'Stacking Faults-I.htm', 'Stacking fault simulations for diamond'],
4506    ['StackingFaults-II', 'Stacking Faults II.htm', 'Stacking fault simulations for Keokuk kaolinite'],
4507    ['StackingFaults-III', 'Stacking Faults-III.htm', 'Stacking fault simulations for Georgia kaolinite'],
4508       
4509    ['CFXraySingleCrystal', 'CFSingleCrystal.htm', 'Charge Flipping structure solution with Xray single crystal data'],       
4510    ['TOF Charge Flipping', 'Charge Flipping with TOF single crystal data in GSASII.htm', 'Charge flipping with neutron TOF single crystal data'],
4511    ['MCsimanneal', 'MCSA in GSAS.htm', 'Monte-Carlo simulated annealing structure determination'],
4512       
4513    ['MerohedralTwins', 'Merohedral twin refinement in GSAS.htm', 'Merohedral twin refinements'],
4514
4515    ['2DCalibration', 'Calibration of an area detector in GSAS.htm', 'Calibration of an area detector'],
4516    ['2DIntegration', 'Integration of area detector data in GSAS.htm', '     Integration of area detector data'],
4517    ['TOF Calibration', 'Calibration of a TOF powder diffractometer.htm', 'Calibration of a Neutron TOF diffractometer'],
4518    ['TOF Single Crystal Refinement', 'TOF single crystal refinement in GSAS.htm', 'Single crystal refinement from TOF data'],
4519       
4520    ['2DStrain', 'Strain fitting of 2D data in GSAS-II.htm', 'Strain fitting of 2D data'],
4521    ['2DTexture', 'Texture analysis of 2D data in GSAS-II.htm', 'Texture analysis of 2D data'],
4522             
4523    ['SAsize', 'Small Angle Size Distribution.htm', 'Small angle x-ray data size distribution (alumina powder)'],
4524    ['SAfit', 'Fitting Small Angle Scattering Data.htm', '     Fitting small angle x-ray data (alumina powder)'],
4525    ['SAimages', 'Small Angle Image Processing.htm', 'Image Processing of small angle x-ray data'],
4526    ['SAseqref', 'Sequential Refinement of Small Angle Scattering Data.htm', 'Sequential refinement with small angle scattering data'],
4527   
4528    #['ExampleDir', 'ExamplePage.html', 'Example Tutorial Title'],
4529    )
4530
4531class OpenTutorial(wx.Dialog):
4532    '''Open a tutorial web page, optionally copying the web page, screen images and
4533    data file(s) to the local disk.
4534    '''
4535   
4536    def __init__(self,parent):
4537        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
4538        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style)
4539        self.G2frame = self.frame = parent
4540        pnl = wx.Panel(self)
4541        sizer = wx.BoxSizer(wx.VERTICAL)
4542        sizer1 = wx.BoxSizer(wx.HORIZONTAL)       
4543        label = wx.StaticText(
4544            pnl,  wx.ID_ANY,
4545            'Select the tutorial to be run and the mode of access'
4546            )
4547        msg = '''GSAS-II tutorials and their sample data files
4548        require a fair amount of storage space; few users will
4549        use all of them. This dialog allows users to load selected
4550        tutorials (along with their sample data) to their computer;
4551        optionally all tutorials can be downloaded.
4552
4553        Downloaded tutorials can be viewed and run without internet
4554        access. Tutorials can also be viewed without download, but
4555        users will need to download the sample data files manually.
4556
4557        The location used to download tutorials is set using the
4558        "Set download location" which is saved as the "Tutorial_location"
4559        configuration option see File/Preference or the
4560        config_example.py file. System managers can select to have
4561        tutorial files installed at a shared location.
4562        '''
4563        self.SetTutorialPath()
4564        hlp = HelpButton(pnl,msg)
4565        sizer1.Add((-1,-1),1, wx.EXPAND, 0)
4566        sizer1.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 0)
4567        sizer1.Add((-1,-1),1, wx.EXPAND, 0)
4568        sizer1.Add(hlp,0,wx.ALIGN_RIGHT|wx.ALL)
4569        sizer.Add(sizer1,0,wx.EXPAND|wx.ALL,0)
4570        sizer.Add((10,10))
4571        sizer0 = wx.BoxSizer(wx.HORIZONTAL)       
4572        sizer1a = wx.BoxSizer(wx.VERTICAL)
4573        sizer1b = wx.BoxSizer(wx.VERTICAL)
4574        btn = wx.Button(pnl, wx.ID_ANY, "Download a tutorial and view")
4575        btn.Bind(wx.EVT_BUTTON, self.SelectAndDownload)
4576        sizer1a.Add(btn,0,WACV)
4577        btn = wx.Button(pnl, wx.ID_ANY, "Select from downloaded tutorials")
4578        btn.Bind(wx.EVT_BUTTON, self.onSelectDownloaded)
4579        sizer1a.Add(btn,0,WACV)
4580        btn = wx.Button(pnl, wx.ID_ANY, "Browse tutorial on web")
4581        btn.Bind(wx.EVT_BUTTON, self.onWebBrowse)
4582        sizer1a.Add(btn,0,WACV)
4583        btn = wx.Button(pnl, wx.ID_ANY, "Update downloaded tutorials")
4584        btn.Bind(wx.EVT_BUTTON, self.UpdateDownloaded)
4585        sizer1b.Add(btn,0,WACV)
4586        btn = wx.Button(pnl, wx.ID_ANY, "Download all tutorials")
4587        btn.Bind(wx.EVT_BUTTON, self.DownloadAll)
4588        sizer1b.Add(btn,0,WACV)
4589        sizer0.Add(sizer1a,0,wx.EXPAND|wx.ALL,0)
4590        sizer0.Add(sizer1b,0,wx.EXPAND|wx.ALL,0)
4591        sizer.Add(sizer0,5,wx.EXPAND|wx.ALL,5)
4592       
4593        sizer.Add((10,10))
4594        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
4595        btn = wx.Button(pnl, wx.ID_ANY, "Set download location")
4596        btn.Bind(wx.EVT_BUTTON, self.SelectDownloadLoc)
4597        sizer1.Add(btn,0,WACV)
4598        self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,self.tutorialPath)
4599        sizer1.Add(self.dataLoc,0,WACV)
4600        sizer.Add(sizer1)
4601       
4602        btnsizer = wx.StdDialogButtonSizer()
4603        btn = wx.Button(pnl, wx.ID_CANCEL,"Done")
4604        btnsizer.AddButton(btn)
4605        btnsizer.Realize()
4606        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
4607        pnl.SetSizer(sizer)
4608        sizer.Fit(self)
4609        self.topsizer=sizer
4610        self.CenterOnParent()
4611
4612    def SetTutorialPath(self):
4613        '''Get the tutorial location if set; if not pick a default
4614        directory in a logical place
4615        '''
4616        if GSASIIpath.GetConfigValue('Tutorial_location'):
4617            self.tutorialPath = os.path.abspath(GSASIIpath.GetConfigValue('Tutorial_location'))
4618        elif (sys.platform.lower().startswith('win') and
4619              os.path.exists(os.path.abspath(os.path.expanduser('~/My Documents')))):
4620            self.tutorialPath = os.path.abspath(os.path.expanduser('~/My Documents/G2tutorials'))
4621        elif (sys.platform.lower().startswith('win') and
4622              os.path.exists(os.path.abspath(os.path.expanduser('~/Documents')))):
4623            self.tutorialPath = os.path.abspath(os.path.expanduser('~/Documents/G2tutorials'))
4624        else:
4625            self.tutorialPath = os.path.abspath(os.path.expanduser('~/G2tutorials'))
4626
4627    def SelectAndDownload(self,event):
4628        '''Make a list of all tutorials on web and allow user to choose one to
4629        download and then view
4630        '''
4631        indices = [j for j,i in enumerate(tutorialCatalog)
4632            if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))]
4633        if not indices:
4634            G2MessageBox(self,'All tutorials are downloaded','None to download')
4635            return
4636        choices = [tutorialCatalog[i][2] for i in indices]
4637        selected = self.ChooseTutorial(choices)
4638        if selected is None: return
4639        j = indices[selected]
4640        fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1])
4641        fulldir = os.path.join(self.tutorialPath,tutorialCatalog[j][0])
4642        URL = G2BaseURL+'/Tutorials/'+tutorialCatalog[j][0]+'/'
4643        if GSASIIpath.svnInstallDir(URL,fulldir):
4644            ShowWebPage(fullpath,self.frame)
4645        else:
4646            G2MessageBox(self,'Error downloading tutorial','Download error')
4647        self.EndModal(wx.ID_OK)
4648        self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data')
4649
4650    def onSelectDownloaded(self,event):
4651        '''Select a previously downloaded tutorial
4652        '''
4653        indices = [j for j,i in enumerate(tutorialCatalog)
4654            if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))]
4655        if not indices:
4656            G2MessageBox(self,
4657                         'There are no downloaded tutorials in '+self.tutorialPath,
4658                         'None downloaded')
4659            return
4660        choices = [tutorialCatalog[i][2] for i in indices]
4661        selected = self.ChooseTutorial(choices)
4662        if selected is None: return
4663        j = indices[selected]
4664        fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1])
4665        self.EndModal(wx.ID_OK)
4666        ShowWebPage(fullpath,self.frame)
4667        self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data')
4668       
4669    def onWebBrowse(self,event):
4670        '''Make a list of all tutorials on web and allow user to view one.
4671        '''
4672        choices = [i[2] for i in tutorialCatalog]
4673        selected = self.ChooseTutorial(choices)
4674        if selected is None: return       
4675        tutdir = tutorialCatalog[selected][0]
4676        tutfil = tutorialCatalog[selected][1]
4677        # open web page remotely, don't worry about data
4678        URL = G2BaseURL+'/Tutorials/'+tutdir+'/'+tutfil
4679        self.EndModal(wx.ID_OK)
4680        ShowWebPage(URL,self.frame)
4681       
4682    def ChooseTutorial(self,choices):
4683        'choose a tutorial from a list'
4684        def onDoubleClick(event):
4685            'double-click closes the dialog'
4686            dlg.EndModal(wx.ID_OK)
4687        dlg = wx.Dialog(self,wx.ID_ANY,
4688                        'Select a tutorial to view. NB: indented ones require prerequisite',
4689                        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
4690        pnl = wx.Panel(dlg)
4691        sizer = wx.BoxSizer(wx.VERTICAL)
4692        listbox = wx.ListBox(pnl, wx.ID_ANY, choices=choices,
4693                             size=(450, 100),
4694                             style=wx.LB_SINGLE)
4695        sizer.Add(listbox,1,WACV|wx.EXPAND|wx.ALL,1)
4696        listbox.Bind(wx.EVT_LISTBOX_DCLICK, onDoubleClick)
4697        sizer.Add((10,10))
4698        btnsizer = wx.StdDialogButtonSizer()
4699        btn = wx.Button(pnl, wx.ID_CANCEL)
4700        btnsizer.AddButton(btn)
4701        OKbtn = wx.Button(pnl, wx.ID_OK)
4702        OKbtn.SetDefault()
4703        btnsizer.AddButton(OKbtn)
4704        btnsizer.Realize()
4705        sizer.Add((-1,5))
4706        sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
4707       
4708        pnl.SetSizer(sizer)
4709        sizer.Fit(dlg)
4710        self.CenterOnParent()
4711        if dlg.ShowModal() != wx.ID_OK:
4712            dlg.Destroy()
4713            return
4714        selected = listbox.GetSelection()
4715        dlg.Destroy()
4716        wx.Yield() # close window right away so user sees something happen
4717        if selected < 0: return
4718        return selected
4719
4720    def UpdateDownloaded(self,event):
4721        'Find the downloaded tutorials and run an svn update on them'
4722        updated = 0
4723        for i in tutorialCatalog:
4724            if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])): continue
4725            print('Updating '+i[0])
4726            GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0]))
4727            updated += 0
4728        if not updated:
4729            G2MessageBox(self,'Warning, you have no downloaded tutorials','None Downloaded')
4730        self.EndModal(wx.ID_OK)
4731       
4732    def DownloadAll(self,event):
4733        'Download or update all tutorials'
4734        fail = ''
4735        for i in tutorialCatalog:
4736            if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])):
4737                print('Updating '+i[0])
4738                GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0]))
4739            else:
4740                fulldir = os.path.join(self.tutorialPath,i[0])
4741                URL = G2BaseURL+'/Tutorials/'+i[0]+'/'
4742                if not GSASIIpath.svnInstallDir(URL,fulldir):
4743                    if fail: fail += ', '
4744                    fail += i[0]
4745        if fail: 
4746            G2MessageBox(self,'Error downloading tutorial(s)\n\t'+fail,'Download error')
4747        self.EndModal(wx.ID_OK)
4748                   
4749    def SelectDownloadLoc(self,event):
4750        '''Select a download location,
4751        Cancel resets to the default
4752        '''
4753        dlg = wx.DirDialog(self, "Choose a directory for tutorial downloads:",
4754                           defaultPath=self.tutorialPath)#,style=wx.DD_DEFAULT_STYLE)
4755                           #)
4756        try:
4757            if dlg.ShowModal() != wx.ID_OK:
4758                return
4759            pth = dlg.GetPath()
4760        finally:
4761            dlg.Destroy()
4762
4763        if not os.path.exists(pth):
4764            try:
4765                os.makedirs(pth)    #failing for no obvious reason
4766            except OSError:
4767                msg = 'The selected directory is not valid.\n\t'
4768                msg += pth
4769                msg += '\n\nAn attempt to create the directory failed'
4770                G2MessageBox(self.frame,msg)
4771                return
4772        if os.path.exists(os.path.join(pth,"help")) and os.path.exists(os.path.join(pth,"Exercises")):
4773            print("Note that you may have old tutorial files in the following directories")
4774            print('\t'+os.path.join(pth,"help"))
4775            print('\t'+os.path.join(pth,"Exercises"))
4776            print('Subdirectories in the above can be deleted to save space\n\n')
4777        self.tutorialPath = pth
4778        self.dataLoc.SetLabel(self.tutorialPath)
4779        if GSASIIpath.GetConfigValue('Tutorial_location') == pth: return
4780        vars = GetConfigValsDocs()
4781        try:
4782            vars['Tutorial_location'][1] = pth
4783            if GSASIIpath.GetConfigValue('debug'): print('Saving Tutorial_location: '+pth)
4784            GSASIIpath.SetConfigValue(vars)
4785            SaveConfigVars(vars)
4786        except KeyError:
4787            pass
4788           
4789if __name__ == '__main__':
4790    app = wx.PySimpleApp()
4791    GSASIIpath.InvokeDebugOpts()
4792    frm = wx.Frame(None) # create a frame
4793    frm.Show(True)
4794   
4795    #======================================================================
4796    # test Grid with GridFractionEditor
4797    #======================================================================
4798    # tbl = [[1.,2.,3.],[1.1,2.1,3.1]]
4799    # colTypes = 3*[wg.GRID_VALUE_FLOAT+':10,5',]
4800    # Gtbl = Table(tbl,types=colTypes,rowLabels=['a','b'],colLabels=['1','2','3'])
4801    # Grid = GSGrid(frm)
4802    # Grid.SetTable(Gtbl,True)
4803    # for i in (0,1,2):
4804    #     attr = wx.grid.GridCellAttr()
4805    #     attr.IncRef()
4806    #     attr.SetEditor(GridFractionEditor(Grid))
4807    #     Grid.SetColAttr(i, attr)
4808    # frm.SetSize((400,200))
4809    # app.MainLoop()
4810    # sys.exit()
4811    #======================================================================
4812    # test Tutorial access
4813    #======================================================================
4814    # dlg = OpenTutorial(frm)
4815    # if dlg.ShowModal() == wx.ID_OK:
4816    #     print "OK"
4817    # else:
4818    #     print "Cancel"
4819    # dlg.Destroy()
4820    # sys.exit()
4821    #======================================================================
4822    # test ScrolledMultiEditor
4823    #======================================================================
4824    # Data1 = {
4825    #      'Order':1,
4826    #      'omega':'string',
4827    #      'chi':2.0,
4828    #      'phi':'',
4829    #      }
4830    # elemlst = sorted(Data1.keys())
4831    # prelbl = sorted(Data1.keys())
4832    # dictlst = len(elemlst)*[Data1,]
4833    #Data2 = [True,False,False,True]
4834    #Checkdictlst = len(Data2)*[Data2,]
4835    #Checkelemlst = range(len(Checkdictlst))
4836    # print 'before',Data1,'\n',Data2
4837    # dlg = ScrolledMultiEditor(
4838    #     frm,dictlst,elemlst,prelbl,
4839    #     checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
4840    #     checklabel="Refine?",
4841    #     header="test")
4842    # if dlg.ShowModal() == wx.ID_OK:
4843    #     print "OK"
4844    # else:
4845    #     print "Cancel"
4846    # print 'after',Data1,'\n',Data2
4847    # dlg.Destroy()
4848    Data3 = {
4849         'Order':1.0,
4850         'omega':1.1,
4851         'chi':2.0,
4852         'phi':2.3,
4853         'Order1':1.0,
4854         'omega1':1.1,
4855         'chi1':2.0,
4856         'phi1':2.3,
4857         'Order2':1.0,
4858         'omega2':1.1,
4859         'chi2':2.0,
4860         'phi2':2.3,
4861         }
4862    elemlst = sorted(Data3.keys())
4863    dictlst = len(elemlst)*[Data3,]
4864    prelbl = elemlst[:]
4865    prelbl[0]="this is a much longer label to stretch things out"
4866    Data2 = len(elemlst)*[False,]
4867    Data2[1] = Data2[3] = True
4868    Checkdictlst = len(elemlst)*[Data2,]
4869    Checkelemlst = range(len(Checkdictlst))
4870    #print 'before',Data3,'\n',Data2
4871    #print dictlst,"\n",elemlst
4872    #print Checkdictlst,"\n",Checkelemlst
4873    # dlg = ScrolledMultiEditor(
4874    #     frm,dictlst,elemlst,prelbl,
4875    #     checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
4876    #     checklabel="Refine?",
4877    #     header="test",CopyButton=True)
4878    # if dlg.ShowModal() == wx.ID_OK:
4879    #     print "OK"
4880    # else:
4881    #     print "Cancel"
4882    #print 'after',Data3,'\n',Data2
4883
4884    # Data2 = list(range(100))
4885    # elemlst += range(2,6)
4886    # postlbl += range(2,6)
4887    # dictlst += len(range(2,6))*[Data2,]
4888
4889    # prelbl = range(len(elemlst))
4890    # postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
4891    # header="""This is a longer\nmultiline and perhaps silly header"""
4892    # dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
4893    #                           header=header,CopyButton=True)
4894    # print Data1
4895    # if dlg.ShowModal() == wx.ID_OK:
4896    #     for d,k in zip(dictlst,elemlst):
4897    #         print k,d[k]
4898    # dlg.Destroy()
4899    # if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
4900    #                            header=header):
4901    #     for d,k in zip(dictlst,elemlst):
4902    #         print k,d[k]
4903
4904    #======================================================================
4905    # test G2MultiChoiceDialog
4906    #======================================================================
4907    choices = []
4908    for i in range(21):
4909        choices.append("option_"+str(i))
4910    od = {
4911        'label_1':'This is a bool','value_1':True,
4912        'label_2':'This is a int','value_2':-1,
4913        'label_3':'This is a float','value_3':1.0,
4914        'label_4':'This is a string','value_4':'test',}
4915    dlg = G2MultiChoiceDialog(frm, 'Sequential refinement',
4916                              'Select dataset to include',
4917                              choices,extraOpts=od)
4918    sel = range(2,11,2)
4919    dlg.SetSelections(sel)
4920    dlg.SetSelections((1,5))
4921    if dlg.ShowModal() == wx.ID_OK:
4922        for sel in dlg.GetSelections():
4923            print sel,choices[sel]
4924    print od
4925    od = {}
4926    dlg = G2MultiChoiceDialog(frm, 'Sequential refinement',
4927                              'Select dataset to include',
4928                              choices,extraOpts=od)
4929    sel = range(2,11,2)
4930    dlg.SetSelections(sel)
4931    dlg.SetSelections((1,5))
4932    if dlg.ShowModal() == wx.ID_OK: pass
4933    #======================================================================
4934    # test wx.MultiChoiceDialog
4935    #======================================================================
4936    # dlg = wx.MultiChoiceDialog(frm, 'Sequential refinement',
4937    #                           'Select dataset to include',
4938    #                           choices)
4939    # sel = range(2,11,2)
4940    # dlg.SetSelections(sel)
4941    # dlg.SetSelections((1,5))
4942    # if dlg.ShowModal() == wx.ID_OK:
4943    #     for sel in dlg.GetSelections():
4944    #         print sel,choices[sel]
4945
4946    # pnl = wx.Panel(frm)
4947    # siz = wx.BoxSizer(wx.VERTICAL)
4948
4949    # td = {'Goni':200.,'a':1.,'calc':1./3.,'string':'s'}
4950    # for key in sorted(td):
4951    #     txt = ValidatedTxtCtrl(pnl,td,key)
4952    #     siz.Add(txt)
4953    # pnl.SetSizer(siz)
4954    # siz.Fit(frm)
4955    # app.MainLoop()
4956    # print td
Note: See TracBrowser for help on using the repository browser.