source: trunk/GSASIIctrls.py @ 1719

Last change on this file since 1719 was 1719, checked in by toby, 7 years ago

implement default starting and import directories

  • Property svn:eol-style set to native
File size: 98.4 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIctrls - Custom GSAS-II GUI controls
3########### SVN repository information ###################
4# $Date: $
5# $Author: $
6# $Revision: $
7# $URL: $
8# $Id: $
9########### SVN repository information ###################
10'''
11*GSASIIctrls: Custom GUI controls*
12-------------------------------------------
13
14A library of GUI controls for reuse throughout GSAS-II
15
16(at present many are still in GSASIIgrid, but with time will be moved here)
17
18'''
19import wx
20# import wx.grid as wg
21# import wx.wizard as wz
22# import wx.aui
23import wx.lib.scrolledpanel as wxscroll
24import time
25import copy
26# import cPickle
27import sys
28import os
29# import numpy as np
30# import numpy.ma as ma
31# import scipy.optimize as so
32import wx.html        # could postpone this for quicker startup
33import webbrowser     # could postpone this for quicker startup
34
35import GSASIIpath
36GSASIIpath.SetVersionNumber("$Revision: 1614 $")
37# import GSASIImath as G2mth
38# import GSASIIIO as G2IO
39# import GSASIIstrIO as G2stIO
40# import GSASIIlattice as G2lat
41# import GSASIIplot as G2plt
42import GSASIIpwdGUI as G2pdG
43# import GSASIIimgGUI as G2imG
44# import GSASIIphsGUI as G2phG
45# import GSASIIspc as G2spc
46# import GSASIImapvars as G2mv
47# import GSASIIconstrGUI as G2cnstG
48# import GSASIIrestrGUI as G2restG
49import GSASIIpy3 as G2py3
50# import GSASIIobj as G2obj
51# import GSASIIexprGUI as G2exG
52import GSASIIlog as log
53
54# Define a short names for convenience
55WHITE = (255,255,255)
56DULL_YELLOW = (230,230,190)
57VERY_LIGHT_GREY = wx.Colour(235,235,235)
58WACV = wx.ALIGN_CENTER_VERTICAL
59
60################################################################################
61#### Tree Control
62################################################################################
63class G2TreeCtrl(wx.TreeCtrl):
64    '''Create a wrapper around the standard TreeCtrl so we can "wrap"
65    various events.
66   
67    This logs when a tree item is selected (in :meth:`onSelectionChanged`)
68
69    This also wraps lists and dicts pulled out of the tree to track where
70    they were retrieved from.
71    '''
72    def __init__(self,parent=None,*args,**kwargs):
73        super(self.__class__,self).__init__(parent=parent,*args,**kwargs)
74        self.G2frame = parent.GetParent()
75        self.root = self.AddRoot('Loaded Data: ')
76        self.SelectionChanged = None
77        log.LogInfo['Tree'] = self
78
79    def _getTreeItemsList(self,item):
80        '''Get the full tree hierarchy from a reference to a tree item.
81        Note that this effectively hard-codes phase and histogram names in the
82        returned list. We may want to make these names relative in the future.
83        '''
84        textlist = [self.GetItemText(item)]
85        parent = self.GetItemParent(item)
86        while parent:
87            if parent == self.root: break
88            textlist.insert(0,self.GetItemText(parent))
89            parent = self.GetItemParent(parent)
90        return textlist
91
92    def onSelectionChanged(self,event):
93        '''Log each press on a tree item here.
94        '''
95        if self.SelectionChanged:
96            textlist = self._getTreeItemsList(event.GetItem())
97            if log.LogInfo['Logging'] and event.GetItem() != self.root:
98                textlist[0] = self.GetRelativeHistNum(textlist[0])
99                if textlist[0] == "Phases" and len(textlist) > 1:
100                    textlist[1] = self.GetRelativePhaseNum(textlist[1])
101                log.MakeTreeLog(textlist)
102            self.SelectionChanged(event)
103
104    def Bind(self,eventtype,handler,*args,**kwargs):
105        '''Override the Bind() function so that page change events can be trapped
106        '''
107        if eventtype == wx.EVT_TREE_SEL_CHANGED:
108            self.SelectionChanged = handler
109            wx.TreeCtrl.Bind(self,eventtype,self.onSelectionChanged)
110            return
111        wx.TreeCtrl.Bind(self,eventtype,handler,*args,**kwargs)
112
113    # commented out, disables Logging
114    # def GetItemPyData(self,*args,**kwargs):
115    #    '''Override the standard method to wrap the contents
116    #    so that the source can be logged when changed
117    #    '''
118    #    data = super(self.__class__,self).GetItemPyData(*args,**kwargs)
119    #    textlist = self._getTreeItemsList(args[0])
120    #    if type(data) is dict:
121    #        return log.dictLogged(data,textlist)
122    #    if type(data) is list:
123    #        return log.listLogged(data,textlist)
124    #    if type(data) is tuple: #N.B. tuples get converted to lists
125    #        return log.listLogged(list(data),textlist)
126    #    return data
127
128    def GetRelativeHistNum(self,histname):
129        '''Returns list with a histogram type and a relative number for that
130        histogram, or the original string if not a histogram
131        '''
132        histtype = histname.split()[0]
133        if histtype != histtype.upper(): # histograms (only) have a keyword all in caps
134            return histname
135        item, cookie = self.GetFirstChild(self.root)
136        i = 0
137        while item:
138            itemtext = self.GetItemText(item)
139            if itemtext == histname:
140                return histtype,i
141            elif itemtext.split()[0] == histtype:
142                i += 1
143            item, cookie = self.GetNextChild(self.root, cookie)
144        else:
145            raise Exception("Histogram not found: "+histname)
146
147    def ConvertRelativeHistNum(self,histtype,histnum):
148        '''Converts a histogram type and relative histogram number to a
149        histogram name in the current project
150        '''
151        item, cookie = self.GetFirstChild(self.root)
152        i = 0
153        while item:
154            itemtext = self.GetItemText(item)
155            if itemtext.split()[0] == histtype:
156                if i == histnum: return itemtext
157                i += 1
158            item, cookie = self.GetNextChild(self.root, cookie)
159        else:
160            raise Exception("Histogram #'+str(histnum)+' of type "+histtype+' not found')
161       
162    def GetRelativePhaseNum(self,phasename):
163        '''Returns a phase number if the string matches a phase name
164        or else returns the original string
165        '''
166        item, cookie = self.GetFirstChild(self.root)
167        while item:
168            itemtext = self.GetItemText(item)
169            if itemtext == "Phases":
170                parent = item
171                item, cookie = self.GetFirstChild(parent)
172                i = 0
173                while item:
174                    itemtext = self.GetItemText(item)
175                    if itemtext == phasename:
176                        return i
177                    item, cookie = self.GetNextChild(parent, cookie)
178                    i += 1
179                else:
180                    return phasename # not a phase name
181            item, cookie = self.GetNextChild(self.root, cookie)
182        else:
183            raise Exception("No phases found ")
184
185    def ConvertRelativePhaseNum(self,phasenum):
186        '''Converts relative phase number to a phase name in
187        the current project
188        '''
189        item, cookie = self.GetFirstChild(self.root)
190        while item:
191            itemtext = self.GetItemText(item)
192            if itemtext == "Phases":
193                parent = item
194                item, cookie = self.GetFirstChild(parent)
195                i = 0
196                while item:
197                    if i == phasenum:
198                        return self.GetItemText(item)
199                    item, cookie = self.GetNextChild(parent, cookie)
200                    i += 1
201                else:
202                    raise Exception("Phase "+str(phasenum)+" not found")
203            item, cookie = self.GetNextChild(self.root, cookie)
204        else:
205            raise Exception("No phases found ")
206
207################################################################################
208#### TextCtrl that stores input as entered with optional validation
209################################################################################
210class ValidatedTxtCtrl(wx.TextCtrl):
211    '''Create a TextCtrl widget that uses a validator to prevent the
212    entry of inappropriate characters and changes color to highlight
213    when invalid input is supplied. As valid values are typed,
214    they are placed into the dict or list where the initial value
215    came from. The type of the initial value must be int,
216    float or str or None (see :obj:`key` and :obj:`typeHint`);
217    this type (or the one in :obj:`typeHint`) is preserved.
218
219    Float values can be entered in the TextCtrl as numbers or also
220    as algebraic expressions using operators + - / \* () and \*\*,
221    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
222    as well as appreviations s, sin, c, cos, t, tan and sq.
223
224    :param wx.Panel parent: name of panel or frame that will be
225      the parent to the TextCtrl. Can be None.
226
227    :param dict/list loc: the dict or list with the initial value to be
228      placed in the TextCtrl.
229
230    :param int/str key: the dict key or the list index for the value to be
231      edited by the TextCtrl. The ``loc[key]`` element must exist, but may
232      have value None. If None, the type for the element is taken from
233      :obj:`typeHint` and the value for the control is set initially
234      blank (and thus invalid.) This is a way to specify a field without a
235      default value: a user must set a valid value.
236      If the value is not None, it must have a base
237      type of int, float, str or unicode; the TextCrtl will be initialized
238      from this value.
239     
240    :param list nDig: number of digits & places ([nDig,nPlc]) after decimal to use
241      for display of float. Alternately, None can be specified which causes
242      numbers to be displayed with approximately 5 significant figures
243      (Default=None).
244
245    :param bool notBlank: if True (default) blank values are invalid
246      for str inputs.
247     
248    :param number min: minimum allowed valid value. If None (default) the
249      lower limit is unbounded.
250
251    :param number max: maximum allowed valid value. If None (default) the
252      upper limit is unbounded
253
254    :param function OKcontrol: specifies a function or method that will be
255      called when the input is validated. The called function is supplied
256      with one argument which is False if the TextCtrl contains an invalid
257      value and True if the value is valid.
258      Note that this function should check all values
259      in the dialog when True, since other entries might be invalid.
260      The default for this is None, which indicates no function should
261      be called.
262
263    :param function OnLeave: specifies a function or method that will be
264      called when the focus for the control is lost.
265      The called function is supplied with (at present) three keyword arguments:
266
267         * invalid: (*bool*) True if the value for the TextCtrl is invalid
268         * value:   (*int/float/str*)  the value contained in the TextCtrl
269         * tc:      (*wx.TextCtrl*)  the TextCtrl name
270
271      The number of keyword arguments may be increased in the future should needs arise,
272      so it is best to code these functions with a \*\*kwargs argument so they will
273      continue to run without errors
274
275      The default for OnLeave is None, which indicates no function should
276      be called.
277
278    :param type typeHint: the value of typeHint is overrides the initial value
279      for the dict/list element ``loc[key]``, if set to
280      int or float, which specifies the type for input to the TextCtrl.
281      Defaults as None, which is ignored.
282
283    :param bool CIFinput: for str input, indicates that only printable
284      ASCII characters may be entered into the TextCtrl. Forces output
285      to be ASCII rather than Unicode. For float and int input, allows
286      use of a single '?' or '.' character as valid input.
287
288    :param dict OnLeaveArgs: a dict with keyword args that are passed to
289      the :attr:`OnLeave` function. Defaults to ``{}``
290
291    :param (other): other optional keyword parameters for the
292      wx.TextCtrl widget such as size or style may be specified.
293
294    '''
295    def __init__(self,parent,loc,key,nDig=None,notBlank=True,min=None,max=None,
296                 OKcontrol=None,OnLeave=None,typeHint=None,
297                 CIFinput=False, OnLeaveArgs={}, **kw):
298        # save passed values needed outside __init__
299        self.result = loc
300        self.key = key
301        self.nDig = nDig
302        self.OKcontrol=OKcontrol
303        self.OnLeave = OnLeave
304        self.OnLeaveArgs = OnLeaveArgs
305        self.CIFinput = CIFinput
306        self.type = str
307        # initialization
308        self.invalid = False   # indicates if the control has invalid contents
309        self.evaluated = False # set to True when the validator recognizes an expression
310        val = loc[key]
311        if isinstance(val,int) or typeHint is int:
312            self.type = int
313            wx.TextCtrl.__init__(
314                self,parent,wx.ID_ANY,
315                validator=NumberValidator(int,result=loc,key=key,
316                                          min=min,max=max,
317                                          OKcontrol=OKcontrol,
318                                          CIFinput=CIFinput),
319                **kw)
320            if val is not None:
321                self._setValue(val)
322            else: # no default is invalid for a number
323                self.invalid = True
324                self._IndicateValidity()
325
326        elif isinstance(val,float) or typeHint is float:
327            self.type = float
328            wx.TextCtrl.__init__(
329                self,parent,wx.ID_ANY,
330                validator=NumberValidator(float,result=loc,key=key,
331                                          min=min,max=max,
332                                          OKcontrol=OKcontrol,
333                                          CIFinput=CIFinput),
334                **kw)
335            if val is not None:
336                self._setValue(val)
337            else:
338                self.invalid = True
339                self._IndicateValidity()
340
341        elif isinstance(val,str) or isinstance(val,unicode):
342            if self.CIFinput:
343                wx.TextCtrl.__init__(
344                    self,parent,wx.ID_ANY,val,
345                    validator=ASCIIValidator(result=loc,key=key),
346                    **kw)
347            else:
348                wx.TextCtrl.__init__(self,parent,wx.ID_ANY,val,**kw)
349            if notBlank:
350                self.Bind(wx.EVT_CHAR,self._onStringKey)
351                self.ShowStringValidity() # test if valid input
352            else:
353                self.invalid = False
354                self.Bind(wx.EVT_CHAR,self._GetStringValue)
355        elif val is None:
356            raise Exception,("ValidatedTxtCtrl error: value of "+str(key)+
357                             " element is None and typeHint not defined as int or float")
358        else:
359            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
360                             ") type: "+str(type(val)))
361        # When the mouse is moved away or the widget loses focus,
362        # display the last saved value, if an expression
363        #self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
364        self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus)
365        self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
366        # patch for wx 2.9 on Mac
367        i,j= wx.__version__.split('.')[0:2]
368        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
369            self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
370
371    def SetValue(self,val):
372        if self.result is not None: # note that this bypasses formatting
373            self.result[self.key] = val
374            log.LogVarChange(self.result,self.key)
375        self._setValue(val)
376
377    def _setValue(self,val):
378        self.invalid = False
379        if self.type is int:
380            try:
381                if int(val) != val:
382                    self.invalid = True
383                else:
384                    val = int(val)
385            except:
386                if self.CIFinput and (val == '?' or val == '.'):
387                    pass
388                else:
389                    self.invalid = True
390            wx.TextCtrl.SetValue(self,str(val))
391        elif self.type is float:
392            try:
393                val = float(val) # convert strings, if needed
394            except:
395                if self.CIFinput and (val == '?' or val == '.'):
396                    pass
397                else:
398                    self.invalid = True
399            if self.nDig:
400                wx.TextCtrl.SetValue(self,str(G2py3.FormatValue(val,self.nDig)))
401            else:
402                wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)).rstrip('0'))
403        else:
404            wx.TextCtrl.SetValue(self,str(val))
405            self.ShowStringValidity() # test if valid input
406            return
407       
408        self._IndicateValidity()
409        if self.OKcontrol:
410            self.OKcontrol(not self.invalid)
411
412    def OnKeyDown(self,event):
413        'Special callback for wx 2.9+ on Mac where backspace is not processed by validator'
414        key = event.GetKeyCode()
415        if key in [wx.WXK_BACK, wx.WXK_DELETE]:
416            if self.Validator: wx.CallAfter(self.Validator.TestValid,self)
417        if key == wx.WXK_RETURN:
418            self._onLoseFocus(None)
419        event.Skip()
420                   
421    def _onStringKey(self,event):
422        event.Skip()
423        if self.invalid: # check for validity after processing the keystroke
424            wx.CallAfter(self.ShowStringValidity,True) # was invalid
425        else:
426            wx.CallAfter(self.ShowStringValidity,False) # was valid
427
428    def _IndicateValidity(self):
429        'Set the control colors to show invalid input'
430        if self.invalid:
431            self.SetForegroundColour("red")
432            self.SetBackgroundColour("yellow")
433            self.SetFocus()
434            self.Refresh()
435        else: # valid input
436            self.SetBackgroundColour(
437                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
438            self.SetForegroundColour("black")
439            self.Refresh()
440
441    def ShowStringValidity(self,previousInvalid=True):
442        '''Check if input is valid. Anytime the input is
443        invalid, call self.OKcontrol (if defined) because it is fast.
444        If valid, check for any other invalid entries only when
445        changing from invalid to valid, since that is slower.
446       
447        :param bool previousInvalid: True if the TextCtrl contents were
448          invalid prior to the current change.
449         
450        '''
451        val = self.GetValue().strip()
452        self.invalid = not val
453        self._IndicateValidity()
454        if self.invalid:
455            if self.OKcontrol:
456                self.OKcontrol(False)
457        elif self.OKcontrol and previousInvalid:
458            self.OKcontrol(True)
459        # always store the result
460        if self.CIFinput: # for CIF make results ASCII
461            self.result[self.key] = val.encode('ascii','replace') 
462        else:
463            self.result[self.key] = val
464        log.LogVarChange(self.result,self.key)
465
466    def _GetStringValue(self,event):
467        '''Get string input and store.
468        '''
469        event.Skip() # process keystroke
470        wx.CallAfter(self._SaveStringValue)
471       
472    def _SaveStringValue(self):
473        val = self.GetValue().strip()
474        # always store the result
475        if self.CIFinput: # for CIF make results ASCII
476            self.result[self.key] = val.encode('ascii','replace') 
477        else:
478            self.result[self.key] = val
479        log.LogVarChange(self.result,self.key)
480
481    def _onLoseFocus(self,event):
482        if self.evaluated:
483            self.EvaluateExpression()
484        elif self.result is not None: # show formatted result, as Bob wants
485            self._setValue(self.result[self.key])
486        if self.OnLeave: self.OnLeave(invalid=self.invalid,
487                                      value=self.result[self.key],
488                                      tc=self,
489                                      **self.OnLeaveArgs)
490        if event: event.Skip()
491
492    def EvaluateExpression(self):
493        '''Show the computed value when an expression is entered to the TextCtrl
494        Make sure that the number fits by truncating decimal places and switching
495        to scientific notation, as needed.
496        Called on loss of focus, enter, etc..
497        '''
498        if self.invalid: return # don't substitute for an invalid expression
499        if not self.evaluated: return # true when an expression is evaluated
500        if self.result is not None: # retrieve the stored result
501            self._setValue(self.result[self.key])
502        self.evaluated = False # expression has been recast as value, reset flag
503       
504class NumberValidator(wx.PyValidator):
505    '''A validator to be used with a TextCtrl to prevent
506    entering characters other than digits, signs, and for float
507    input, a period and exponents.
508   
509    The value is checked for validity after every keystroke
510      If an invalid number is entered, the box is highlighted.
511      If the number is valid, it is saved in result[key]
512
513    :param type typ: the base data type. Must be int or float.
514
515    :param bool positiveonly: If True, negative integers are not allowed
516      (default False). This prevents the + or - keys from being pressed.
517      Used with typ=int; ignored for typ=float.
518
519    :param number min: Minimum allowed value. If None (default) the
520      lower limit is unbounded
521
522    :param number max: Maximum allowed value. If None (default) the
523      upper limit is unbounded
524     
525    :param dict/list result: List or dict where value should be placed when valid
526
527    :param any key: key to use for result (int for list)
528
529    :param function OKcontrol: function or class method to control
530      an OK button for a window.
531      Ignored if None (default)
532
533    :param bool CIFinput: allows use of a single '?' or '.' character
534      as valid input.
535     
536    '''
537    def __init__(self, typ, positiveonly=False, min=None, max=None,
538                 result=None, key=None, OKcontrol=None, CIFinput=False):
539        'Create the validator'
540        wx.PyValidator.__init__(self)
541        # save passed parameters
542        self.typ = typ
543        self.positiveonly = positiveonly
544        self.min = min
545        self.max = max
546        self.result = result
547        self.key = key
548        self.OKcontrol = OKcontrol
549        self.CIFinput = CIFinput
550        # set allowed keys by data type
551        self.Bind(wx.EVT_CHAR, self.OnChar)
552        if self.typ == int and self.positiveonly:
553            self.validchars = '0123456789'
554        elif self.typ == int:
555            self.validchars = '0123456789+-'
556        elif self.typ == float:
557            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
558            # also addition, subtraction, division, multiplication, exponentiation
559            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
560        else:
561            self.validchars = None
562            return
563        if self.CIFinput:
564            self.validchars += '?.'
565    def Clone(self):
566        'Create a copy of the validator, a strange, but required component'
567        return NumberValidator(typ=self.typ, 
568                               positiveonly=self.positiveonly,
569                               min=self.min, max=self.max,
570                               result=self.result, key=self.key,
571                               OKcontrol=self.OKcontrol,
572                               CIFinput=self.CIFinput)
573    def TransferToWindow(self):
574        'Needed by validator, strange, but required component'
575        return True # Prevent wxDialog from complaining.
576    def TransferFromWindow(self):
577        'Needed by validator, strange, but required component'
578        return True # Prevent wxDialog from complaining.
579    def TestValid(self,tc):
580        '''Check if the value is valid by casting the input string
581        into the current type.
582
583        Set the invalid variable in the TextCtrl object accordingly.
584
585        If the value is valid, save it in the dict/list where
586        the initial value was stored, if appropriate.
587
588        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
589          is associated with.
590        '''
591        tc.invalid = False # assume valid
592        if self.CIFinput:
593            val = tc.GetValue().strip()
594            if val == '?' or val == '.':
595                self.result[self.key] = val
596                log.LogVarChange(self.result,self.key)
597                return
598        try:
599            val = self.typ(tc.GetValue())
600        except (ValueError, SyntaxError) as e:
601            if self.typ is float: # for float values, see if an expression can be evaluated
602                val = G2py3.FormulaEval(tc.GetValue())
603                if val is None:
604                    tc.invalid = True
605                    return
606                else:
607                    tc.evaluated = True
608            else: 
609                tc.invalid = True
610                return
611        # if self.max != None and self.typ == int:
612        #     if val > self.max:
613        #         tc.invalid = True
614        # if self.min != None and self.typ == int:
615        #     if val < self.min:
616        #         tc.invalid = True  # invalid
617        if self.max != None:
618            if val > self.max:
619                tc.invalid = True
620        if self.min != None:
621            if val < self.min:
622                tc.invalid = True  # invalid
623        if self.key is not None and self.result is not None and not tc.invalid:
624            self.result[self.key] = val
625            log.LogVarChange(self.result,self.key)
626
627    def ShowValidity(self,tc):
628        '''Set the control colors to show invalid input
629
630        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
631          is associated with.
632
633        '''
634        if tc.invalid:
635            tc.SetForegroundColour("red")
636            tc.SetBackgroundColour("yellow")
637            tc.SetFocus()
638            tc.Refresh()
639            return False
640        else: # valid input
641            tc.SetBackgroundColour(
642                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
643            tc.SetForegroundColour("black")
644            tc.Refresh()
645            return True
646
647    def CheckInput(self,previousInvalid):
648        '''called to test every change to the TextCtrl for validity and
649        to change the appearance of the TextCtrl
650
651        Anytime the input is invalid, call self.OKcontrol
652        (if defined) because it is fast.
653        If valid, check for any other invalid entries only when
654        changing from invalid to valid, since that is slower.
655
656        :param bool previousInvalid: True if the TextCtrl contents were
657          invalid prior to the current change.
658        '''
659        tc = self.GetWindow()
660        self.TestValid(tc)
661        self.ShowValidity(tc)
662        # if invalid
663        if tc.invalid and self.OKcontrol:
664            self.OKcontrol(False)
665        if not tc.invalid and self.OKcontrol and previousInvalid:
666            self.OKcontrol(True)
667
668    def OnChar(self, event):
669        '''Called each type a key is pressed
670        ignores keys that are not allowed for int and float types
671        '''
672        key = event.GetKeyCode()
673        tc = self.GetWindow()
674        if key == wx.WXK_RETURN:
675            if tc.invalid:
676                self.CheckInput(True) 
677            else:
678                self.CheckInput(False) 
679            return
680        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
681            event.Skip()
682            if tc.invalid:
683                wx.CallAfter(self.CheckInput,True) 
684            else:
685                wx.CallAfter(self.CheckInput,False) 
686            return
687        elif chr(key) in self.validchars: # valid char pressed?
688            event.Skip()
689            if tc.invalid:
690                wx.CallAfter(self.CheckInput,True) 
691            else:
692                wx.CallAfter(self.CheckInput,False) 
693            return
694        if not wx.Validator_IsSilent(): wx.Bell()
695        return  # Returning without calling event.Skip, which eats the keystroke
696
697class ASCIIValidator(wx.PyValidator):
698    '''A validator to be used with a TextCtrl to prevent
699    entering characters other than ASCII characters.
700   
701    The value is checked for validity after every keystroke
702      If an invalid number is entered, the box is highlighted.
703      If the number is valid, it is saved in result[key]
704
705    :param dict/list result: List or dict where value should be placed when valid
706
707    :param any key: key to use for result (int for list)
708
709    '''
710    def __init__(self, result=None, key=None):
711        'Create the validator'
712        import string
713        wx.PyValidator.__init__(self)
714        # save passed parameters
715        self.result = result
716        self.key = key
717        self.validchars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
718        self.Bind(wx.EVT_CHAR, self.OnChar)
719    def Clone(self):
720        'Create a copy of the validator, a strange, but required component'
721        return ASCIIValidator(result=self.result, key=self.key)
722        tc = self.GetWindow()
723        tc.invalid = False # make sure the validity flag is defined in parent
724    def TransferToWindow(self):
725        'Needed by validator, strange, but required component'
726        return True # Prevent wxDialog from complaining.
727    def TransferFromWindow(self):
728        'Needed by validator, strange, but required component'
729        return True # Prevent wxDialog from complaining.
730    def TestValid(self,tc):
731        '''Check if the value is valid by casting the input string
732        into ASCII.
733
734        Save it in the dict/list where the initial value was stored
735
736        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
737          is associated with.
738        '''
739        self.result[self.key] = tc.GetValue().encode('ascii','replace')
740        log.LogVarChange(self.result,self.key)
741
742    def OnChar(self, event):
743        '''Called each type a key is pressed
744        ignores keys that are not allowed for int and float types
745        '''
746        key = event.GetKeyCode()
747        tc = self.GetWindow()
748        if key == wx.WXK_RETURN:
749            self.TestValid(tc)
750            return
751        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
752            event.Skip()
753            self.TestValid(tc)
754            return
755        elif chr(key) in self.validchars: # valid char pressed?
756            event.Skip()
757            self.TestValid(tc)
758            return
759        if not wx.Validator_IsSilent():
760            wx.Bell()
761        return  # Returning without calling event.Skip, which eats the keystroke
762
763################################################################################
764#### Edit a large number of values
765################################################################################
766def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[],
767                 title='Edit items',header='',size=(300,250),
768                             CopyButton=False, **kw):
769    '''Shell routine to call a ScrolledMultiEditor dialog. See
770    :class:`ScrolledMultiEditor` for parameter definitions.
771
772    :returns: True if the OK button is pressed; False if the window is closed
773      with the system menu or the Cancel button.
774
775    '''
776    dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl,
777                              title,header,size,
778                              CopyButton, **kw)
779    if dlg.ShowModal() == wx.ID_OK:
780        dlg.Destroy()
781        return True
782    else:
783        dlg.Destroy()
784        return False
785
786class ScrolledMultiEditor(wx.Dialog):
787    '''Define a window for editing a potentially large number of dict- or
788    list-contained values with validation for each item. Edited values are
789    automatically placed in their source location. If invalid entries
790    are provided, the TextCtrl is turned yellow and the OK button is disabled.
791
792    The type for each TextCtrl validation is determined by the
793    initial value of the entry (int, float or string).
794    Float values can be entered in the TextCtrl as numbers or also
795    as algebraic expressions using operators + - / \* () and \*\*,
796    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
797    as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq().
798
799    :param wx.Frame parent: name of parent window, or may be None
800
801    :param tuple dictlst: a list of dicts or lists containing values to edit
802
803    :param tuple elemlst: a list of keys for each item in a dictlst. Must have the
804      same length as dictlst.
805
806    :param wx.Frame parent: name of parent window, or may be None
807   
808    :param tuple prelbl: a list of labels placed before the TextCtrl for each
809      item (optional)
810   
811    :param tuple postlbl: a list of labels placed after the TextCtrl for each
812      item (optional)
813
814    :param str title: a title to place in the frame of the dialog
815
816    :param str header: text to place at the top of the window. May contain
817      new line characters.
818
819    :param wx.Size size: a size parameter that dictates the
820      size for the scrolled region of the dialog. The default is
821      (300,250).
822
823    :param bool CopyButton: if True adds a small button that copies the
824      value for the current row to all fields below (default is False)
825     
826    :param list minvals: optional list of minimum values for validation
827      of float or int values. Ignored if value is None.
828    :param list maxvals: optional list of maximum values for validation
829      of float or int values. Ignored if value is None.
830    :param list sizevals: optional list of wx.Size values for each input
831      widget. Ignored if value is None.
832     
833    :param tuple checkdictlst: an optional list of dicts or lists containing bool
834      values (similar to dictlst).
835    :param tuple checkelemlst: an optional list of dicts or lists containing bool
836      key values (similar to elemlst). Must be used with checkdictlst.
837    :param string checklabel: a string to use for each checkbutton
838     
839    :returns: the wx.Dialog created here. Use method .ShowModal() to display it.
840   
841    *Example for use of ScrolledMultiEditor:*
842
843    ::
844
845        dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl,
846                                        header=header)
847        if dlg.ShowModal() == wx.ID_OK:
848             for d,k in zip(dictlst,elemlst):
849                 print d[k]
850
851    *Example definitions for dictlst and elemlst:*
852
853    ::
854     
855          dictlst = (dict1,list1,dict1,list1)
856          elemlst = ('a', 1, 2, 3)
857
858      This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited.
859   
860    Note that these items must have int, float or str values assigned to
861    them. The dialog will force these types to be retained. String values
862    that are blank are marked as invalid.
863    '''
864   
865    def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[],
866                 title='Edit items',header='',size=(300,250),
867                 CopyButton=False,
868                 minvals=[],maxvals=[],sizevals=[],
869                 checkdictlst=[], checkelemlst=[], checklabel=""):
870        if len(dictlst) != len(elemlst):
871            raise Exception,"ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))
872        if len(checkdictlst) != len(checkelemlst):
873            raise Exception,"ScrolledMultiEditor error: len(checkdictlst) != len(checkelemlst) "+str(len(checkdictlst))+" != "+str(len(checkelemlst))
874        wx.Dialog.__init__( # create dialog & sizer
875            self,parent,wx.ID_ANY,title,
876            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
877        mainSizer = wx.BoxSizer(wx.VERTICAL)
878        self.orig = []
879        self.dictlst = dictlst
880        self.elemlst = elemlst
881        self.checkdictlst = checkdictlst
882        self.checkelemlst = checkelemlst
883        self.StartCheckValues = [checkdictlst[i][checkelemlst[i]] for i in range(len(checkdictlst))]
884        self.ButtonIndex = {}
885        for d,i in zip(dictlst,elemlst):
886            self.orig.append(d[i])
887        # add a header if supplied
888        if header:
889            subSizer = wx.BoxSizer(wx.HORIZONTAL)
890            subSizer.Add((-1,-1),1,wx.EXPAND)
891            subSizer.Add(wx.StaticText(self,wx.ID_ANY,header))
892            subSizer.Add((-1,-1),1,wx.EXPAND)
893            mainSizer.Add(subSizer,0,wx.EXPAND,0)
894        # make OK button now, because we will need it for validation
895        self.OKbtn = wx.Button(self, wx.ID_OK)
896        self.OKbtn.SetDefault()
897        # create scrolled panel and sizer
898        panel = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=size,
899            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
900        cols = 4
901        if CopyButton: cols += 1
902        subSizer = wx.FlexGridSizer(cols=cols,hgap=2,vgap=2)
903        self.ValidatedControlsList = [] # make list of TextCtrls
904        self.CheckControlsList = [] # make list of CheckBoxes
905        for i,(d,k) in enumerate(zip(dictlst,elemlst)):
906            if i >= len(prelbl): # label before TextCtrl, or put in a blank
907                subSizer.Add((-1,-1)) 
908            else:
909                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i])))
910            kargs = {}
911            if i < len(minvals):
912                if minvals[i] is not None: kargs['min']=minvals[i]
913            if i < len(maxvals):
914                if maxvals[i] is not None: kargs['max']=maxvals[i]
915            if i < len(sizevals):
916                if sizevals[i]: kargs['size']=sizevals[i]
917            if CopyButton:
918                import wx.lib.colourselect as wscs
919                but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP
920                                        # is there a way to test?
921                                        parent=panel,
922                                        colour=(255,255,200),
923                                        size=wx.Size(30,23),
924                                        style=wx.RAISED_BORDER)
925                but.Bind(wx.EVT_BUTTON, self._OnCopyButton)
926                but.SetToolTipString('Press to copy adjacent value to all rows below')
927                self.ButtonIndex[but] = i
928                subSizer.Add(but)
929            # create the validated TextCrtl, store it and add it to the sizer
930            ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,
931                                    **kargs)
932            self.ValidatedControlsList.append(ctrl)
933            subSizer.Add(ctrl)
934            if i < len(postlbl): # label after TextCtrl, or put in a blank
935                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i])))
936            else:
937                subSizer.Add((-1,-1))
938            if i < len(checkdictlst):
939                ch = G2CheckBox(panel,checklabel,checkdictlst[i],checkelemlst[i])
940                self.CheckControlsList.append(ch)
941                subSizer.Add(ch)                   
942            else:
943                subSizer.Add((-1,-1))
944        # finish up ScrolledPanel
945        panel.SetSizer(subSizer)
946        panel.SetAutoLayout(1)
947        panel.SetupScrolling()
948        # patch for wx 2.9 on Mac
949        i,j= wx.__version__.split('.')[0:2]
950        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
951            panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1]))       
952        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
953
954        # Sizer for OK/Close buttons. N.B. on Close changes are discarded
955        # by restoring the initial values
956        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
957        btnsizer.Add(self.OKbtn)
958        btn = wx.Button(self, wx.ID_CLOSE,"Cancel") 
959        btn.Bind(wx.EVT_BUTTON,self._onClose)
960        btnsizer.Add(btn)
961        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
962        # size out the window. Set it to be enlarged but not made smaller
963        self.SetSizer(mainSizer)
964        mainSizer.Fit(self)
965        self.SetMinSize(self.GetSize())
966
967    def _OnCopyButton(self,event):
968        'Implements the copy down functionality'
969        but = event.GetEventObject()
970        n = self.ButtonIndex.get(but)
971        if n is None: return
972        for i,(d,k,ctrl) in enumerate(zip(self.dictlst,self.elemlst,self.ValidatedControlsList)):
973            if i < n: continue
974            if i == n:
975                val = d[k]
976                continue
977            d[k] = val
978            ctrl.SetValue(val)
979        for i in range(len(self.checkdictlst)):
980            if i < n: continue
981            self.checkdictlst[i][self.checkelemlst[i]] = self.checkdictlst[n][self.checkelemlst[n]]
982            self.CheckControlsList[i].SetValue(self.checkdictlst[i][self.checkelemlst[i]])
983    def _onClose(self,event):
984        'Used on Cancel: Restore original values & close the window'
985        for d,i,v in zip(self.dictlst,self.elemlst,self.orig):
986            d[i] = v
987        for i in range(len(self.checkdictlst)):
988            self.checkdictlst[i][self.checkelemlst[i]] = self.StartCheckValues[i]
989        self.EndModal(wx.ID_CANCEL)
990       
991    def ControlOKButton(self,setvalue):
992        '''Enable or Disable the OK button for the dialog. Note that this is
993        passed into the ValidatedTxtCtrl for use by validators.
994
995        :param bool setvalue: if True, all entries in the dialog are
996          checked for validity. if False then the OK button is disabled.
997
998        '''
999        if setvalue: # turn button on, do only if all controls show as valid
1000            for ctrl in self.ValidatedControlsList:
1001                if ctrl.invalid:
1002                    self.OKbtn.Disable()
1003                    return
1004            else:
1005                self.OKbtn.Enable()
1006        else:
1007            self.OKbtn.Disable()
1008
1009################################################################################
1010####
1011################################################################################
1012def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem):
1013    '''Select a variable from a list, then edit it and select histograms
1014    to copy it to.
1015
1016    :param wx.Frame G2frame: main GSAS-II frame
1017    :param dict array: the array (dict or list) where values to be edited are kept
1018    :param list labelLst: labels for each data item
1019    :param list elemKeysLst: a list of lists of keys needed to be applied (see below)
1020      to obtain the value of each parameter
1021    :param list dspLst: list list of digits to be displayed (10,4) is 10 digits
1022      with 4 decimal places. Can be None.
1023    :param list refFlgElem: a list of lists of keys needed to be applied (see below)
1024      to obtain the refine flag for each parameter or None if the parameter
1025      does not have refine flag.
1026
1027    Example::
1028      array = data
1029      labelLst = ['v1','v2']
1030      elemKeysLst = [['v1'], ['v2',0]]
1031      refFlgElem = [None, ['v2',1]]
1032
1033     * The value for v1 will be in data['v1'] and this cannot be refined while,
1034     * The value for v2 will be in data['v2'][0] and its refinement flag is data['v2'][1]
1035    '''
1036    def unkey(dct,keylist):
1037        '''dive into a nested set of dicts/lists applying keys in keylist
1038        consecutively
1039        '''
1040        d = dct
1041        for k in keylist:
1042            d = d[k]
1043        return d
1044
1045    def OnChoice(event):
1046        'Respond when a parameter is selected in the Choice box'
1047        valSizer.DeleteWindows()
1048        lbl = event.GetString()
1049        copyopts['currentsel'] = lbl
1050        i = labelLst.index(lbl)
1051        OKbtn.Enable(True)
1052        ch.SetLabel(lbl)
1053        args = {}
1054        if dspLst[i]:
1055            args = {'nDig':dspLst[i]}
1056        Val = ValidatedTxtCtrl(
1057            dlg,
1058            unkey(array,elemKeysLst[i][:-1]),
1059            elemKeysLst[i][-1],
1060            **args)
1061        copyopts['startvalue'] = unkey(array,elemKeysLst[i])
1062        #unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] =
1063        valSizer.Add(Val,0,wx.LEFT,5)
1064        dlg.SendSizeEvent()
1065       
1066    # SelectEdit1Var execution begins here
1067    saveArray = copy.deepcopy(array) # keep original values
1068    TreeItemType = G2frame.PatternTree.GetItemText(G2frame.PickId)
1069    copyopts = {'InTable':False,"startvalue":None,'currentsel':None}       
1070    hst = G2frame.PatternTree.GetItemText(G2frame.PatternId)
1071    histList = G2pdG.GetHistsLikeSelected(G2frame)
1072    if not histList:
1073        G2frame.ErrorDialog('No match','No histograms match '+hst,G2frame.dataFrame)
1074        return
1075    dlg = wx.Dialog(G2frame.dataDisplay,wx.ID_ANY,'Set a parameter value',
1076        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1077    mainSizer = wx.BoxSizer(wx.VERTICAL)
1078    mainSizer.Add((5,5))
1079    subSizer = wx.BoxSizer(wx.HORIZONTAL)
1080    subSizer.Add((-1,-1),1,wx.EXPAND)
1081    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Select a parameter and set a new value'))
1082    subSizer.Add((-1,-1),1,wx.EXPAND)
1083    mainSizer.Add(subSizer,0,wx.EXPAND,0)
1084    mainSizer.Add((0,10))
1085
1086    subSizer = wx.FlexGridSizer(0,2,5,0)
1087    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Parameter: '))
1088    ch = wx.Choice(dlg, wx.ID_ANY, choices = sorted(labelLst))
1089    ch.SetSelection(-1)
1090    ch.Bind(wx.EVT_CHOICE, OnChoice)
1091    subSizer.Add(ch)
1092    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Value: '))
1093    valSizer = wx.BoxSizer(wx.HORIZONTAL)
1094    subSizer.Add(valSizer)
1095    mainSizer.Add(subSizer)
1096
1097    mainSizer.Add((-1,20))
1098    subSizer = wx.BoxSizer(wx.HORIZONTAL)
1099    subSizer.Add(G2CheckBox(dlg, 'Edit in table ', copyopts, 'InTable'))
1100    mainSizer.Add(subSizer)
1101
1102    btnsizer = wx.StdDialogButtonSizer()
1103    OKbtn = wx.Button(dlg, wx.ID_OK,'Continue')
1104    OKbtn.Enable(False)
1105    OKbtn.SetDefault()
1106    OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
1107    btnsizer.AddButton(OKbtn)
1108    btn = wx.Button(dlg, wx.ID_CANCEL)
1109    btnsizer.AddButton(btn)
1110    btnsizer.Realize()
1111    mainSizer.Add((-1,5),1,wx.EXPAND,1)
1112    mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
1113    mainSizer.Add((-1,10))
1114
1115    dlg.SetSizer(mainSizer)
1116    dlg.CenterOnParent()
1117    if dlg.ShowModal() != wx.ID_OK:
1118        array.update(saveArray)
1119        dlg.Destroy()
1120        return
1121    dlg.Destroy()
1122
1123    copyList = []
1124    lbl = copyopts['currentsel']
1125    dlg = G2MultiChoiceDialog(
1126        G2frame.dataFrame, 
1127        'Copy parameter '+lbl+' from\n'+hst,
1128        'Copy parameters', histList)
1129    dlg.CenterOnParent()
1130    try:
1131        if dlg.ShowModal() == wx.ID_OK:
1132            for i in dlg.GetSelections(): 
1133                copyList.append(histList[i])
1134        else:
1135            # reset the parameter since cancel was pressed
1136            array.update(saveArray)
1137            return
1138    finally:
1139        dlg.Destroy()
1140
1141    prelbl = [hst]
1142    i = labelLst.index(lbl)
1143    keyLst = elemKeysLst[i]
1144    refkeys = refFlgElem[i]
1145    dictlst = [unkey(array,keyLst[:-1])]
1146    if refkeys is not None:
1147        refdictlst = [unkey(array,refkeys[:-1])]
1148    else:
1149        refdictlst = None
1150    Id = GetPatternTreeItemId(G2frame,G2frame.root,hst)
1151    hstData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Instrument Parameters'))[0]
1152    for h in copyList:
1153        Id = GetPatternTreeItemId(G2frame,G2frame.root,h)
1154        instData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Instrument Parameters'))[0]
1155        if len(hstData) != len(instData) or hstData['Type'][0] != instData['Type'][0]:  #don't mix data types or lam & lam1/lam2 parms!
1156            print h+' not copied - instrument parameters not commensurate'
1157            continue
1158        hData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,TreeItemType))
1159        if TreeItemType == 'Instrument Parameters':
1160            hData = hData[0]
1161        #copy the value if it is changed or we will not edit in a table
1162        valNow = unkey(array,keyLst)
1163        if copyopts['startvalue'] != valNow or not copyopts['InTable']:
1164            unkey(hData,keyLst[:-1])[keyLst[-1]] = valNow
1165        prelbl += [h]
1166        dictlst += [unkey(hData,keyLst[:-1])]
1167        if refdictlst is not None:
1168            refdictlst += [unkey(hData,refkeys[:-1])]
1169    if refdictlst is None:
1170        args = {}
1171    else:
1172        args = {'checkdictlst':refdictlst,
1173                'checkelemlst':len(dictlst)*[refkeys[-1]],
1174                'checklabel':'Refine?'}
1175    if copyopts['InTable']:
1176        dlg = ScrolledMultiEditor(
1177            G2frame.dataDisplay,dictlst,
1178            len(dictlst)*[keyLst[-1]],prelbl,
1179            header='Editing parameter '+lbl,
1180            CopyButton=True,**args)
1181        dlg.CenterOnParent()
1182        if dlg.ShowModal() != wx.ID_OK:
1183            array.update(saveArray)
1184        dlg.Destroy()
1185
1186################################################################################
1187#### Custom checkbox that saves values into dict/list as used
1188################################################################################
1189class G2CheckBox(wx.CheckBox):
1190    '''A customized version of a CheckBox that automatically initializes
1191    the control to a supplied list or dict entry and updates that
1192    entry as the widget is used.
1193
1194    :param wx.Panel parent: name of panel or frame that will be
1195      the parent to the widget. Can be None.
1196    :param str label: text to put on check button
1197    :param dict/list loc: the dict or list with the initial value to be
1198      placed in the CheckBox.
1199    :param int/str key: the dict key or the list index for the value to be
1200      edited by the CheckBox. The ``loc[key]`` element must exist.
1201      The CheckBox will be initialized from this value.
1202      If the value is anything other that True (or 1), it will be taken as
1203      False.
1204    '''
1205    def __init__(self,parent,label,loc,key):
1206        wx.CheckBox.__init__(self,parent,id=wx.ID_ANY,label=label)
1207        self.loc = loc
1208        self.key = key
1209        self.SetValue(self.loc[self.key]==True)
1210        self.Bind(wx.EVT_CHECKBOX, self._OnCheckBox)
1211    def _OnCheckBox(self,event):
1212        self.loc[self.key] = self.GetValue()
1213        log.LogVarChange(self.loc,self.key)
1214
1215################################################################################
1216####
1217################################################################################
1218class PickTwoDialog(wx.Dialog):
1219    '''This does not seem to be in use
1220    '''
1221    def __init__(self,parent,title,prompt,names,choices):
1222        wx.Dialog.__init__(self,parent,-1,title, 
1223            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1224        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1225        self.prompt = prompt
1226        self.choices = choices
1227        self.names = names
1228        self.Draw()
1229
1230    def Draw(self):
1231        Indx = {}
1232       
1233        def OnSelection(event):
1234            Obj = event.GetEventObject()
1235            id = Indx[Obj.GetId()]
1236            self.choices[id] = Obj.GetValue().encode()  #to avoid Unicode versions
1237            self.Draw()
1238           
1239        self.panel.DestroyChildren()
1240        self.panel.Destroy()
1241        self.panel = wx.Panel(self)
1242        mainSizer = wx.BoxSizer(wx.VERTICAL)
1243        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1244        for isel,name in enumerate(self.choices):
1245            lineSizer = wx.BoxSizer(wx.HORIZONTAL)
1246            lineSizer.Add(wx.StaticText(self.panel,-1,'Reference atom '+str(isel+1)),0,wx.ALIGN_CENTER)
1247            nameList = self.names[:]
1248            if isel:
1249                if self.choices[0] in nameList:
1250                    nameList.remove(self.choices[0])
1251            choice = wx.ComboBox(self.panel,-1,value=name,choices=nameList,
1252                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1253            Indx[choice.GetId()] = isel
1254            choice.Bind(wx.EVT_COMBOBOX, OnSelection)
1255            lineSizer.Add(choice,0,WACV)
1256            mainSizer.Add(lineSizer)
1257        OkBtn = wx.Button(self.panel,-1,"Ok")
1258        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1259        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1260        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1261        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1262        btnSizer.Add((20,20),1)
1263        btnSizer.Add(OkBtn)
1264        btnSizer.Add(CancelBtn)
1265        btnSizer.Add((20,20),1)
1266        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1267        self.panel.SetSizer(mainSizer)
1268        self.panel.Fit()
1269        self.Fit()
1270       
1271    def GetSelection(self):
1272        return self.choices
1273
1274    def OnOk(self,event):
1275        parent = self.GetParent()
1276        parent.Raise()
1277        self.EndModal(wx.ID_OK)             
1278       
1279    def OnCancel(self,event):
1280        parent = self.GetParent()
1281        parent.Raise()
1282        self.EndModal(wx.ID_CANCEL)
1283
1284################################################################################
1285#### Column-order selection
1286################################################################################
1287
1288def GetItemOrder(parent,keylist,vallookup,posdict):
1289    '''Creates a panel where items can be ordered into columns
1290   
1291    :param list keylist: is a list of keys for column assignments
1292    :param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
1293       Each inner dict contains variable names as keys and their associated values
1294    :param dict posdict: is a dict keyed by names in keylist where each item is a dict.
1295       Each inner dict contains column numbers as keys and their associated
1296       variable name as a value. This is used for both input and output.
1297       
1298    '''
1299    dlg = wx.Dialog(parent,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1300    sizer = wx.BoxSizer(wx.VERTICAL)
1301    spanel = OrderBox(dlg,keylist,vallookup,posdict)
1302    spanel.Fit()
1303    sizer.Add(spanel,1,wx.EXPAND)
1304    btnsizer = wx.StdDialogButtonSizer()
1305    btn = wx.Button(dlg, wx.ID_OK)
1306    btn.SetDefault()
1307    btnsizer.AddButton(btn)
1308    #btn = wx.Button(dlg, wx.ID_CANCEL)
1309    #btnsizer.AddButton(btn)
1310    btnsizer.Realize()
1311    sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.ALL, 5)
1312    dlg.SetSizer(sizer)
1313    sizer.Fit(dlg)
1314    val = dlg.ShowModal()
1315
1316################################################################################
1317####
1318################################################################################
1319class OrderBox(wxscroll.ScrolledPanel):
1320    '''Creates a panel with scrollbars where items can be ordered into columns
1321   
1322    :param list keylist: is a list of keys for column assignments
1323    :param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
1324      Each inner dict contains variable names as keys and their associated values
1325    :param dict posdict: is a dict keyed by names in keylist where each item is a dict.
1326      Each inner dict contains column numbers as keys and their associated
1327      variable name as a value. This is used for both input and output.
1328     
1329    '''
1330    def __init__(self,parent,keylist,vallookup,posdict,*arg,**kw):
1331        self.keylist = keylist
1332        self.vallookup = vallookup
1333        self.posdict = posdict
1334        self.maxcol = 0
1335        for nam in keylist:
1336            posdict = self.posdict[nam]
1337            if posdict.keys():
1338                self.maxcol = max(self.maxcol, max(posdict))
1339        wxscroll.ScrolledPanel.__init__(self,parent,wx.ID_ANY,*arg,**kw)
1340        self.GBsizer = wx.GridBagSizer(4,4)
1341        self.SetBackgroundColour(WHITE)
1342        self.SetSizer(self.GBsizer)
1343        colList = [str(i) for i in range(self.maxcol+2)]
1344        for i in range(self.maxcol+1):
1345            wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
1346            wid.SetBackgroundColour(DULL_YELLOW)
1347            wid.SetMinSize((50,-1))
1348            self.GBsizer.Add(wid,(0,i),flag=wx.ALIGN_CENTER|wx.EXPAND)
1349        self.chceDict = {}
1350        for row,nam in enumerate(self.keylist):
1351            posdict = self.posdict[nam]
1352            for col in posdict:
1353                lbl = posdict[col]
1354                pnl = wx.Panel(self,wx.ID_ANY)
1355                pnl.SetBackgroundColour(VERY_LIGHT_GREY)
1356                insize = wx.BoxSizer(wx.VERTICAL)
1357                wid = wx.Choice(pnl,wx.ID_ANY,choices=colList)
1358                insize.Add(wid,0,wx.EXPAND|wx.BOTTOM,3)
1359                wid.SetSelection(col)
1360                self.chceDict[wid] = (row,col)
1361                wid.Bind(wx.EVT_CHOICE,self.OnChoice)
1362                wid = wx.StaticText(pnl,wx.ID_ANY,lbl)
1363                insize.Add(wid,0,flag=wx.EXPAND)
1364                val = G2py3.FormatSigFigs(self.vallookup[nam][lbl],maxdigits=8)
1365                wid = wx.StaticText(pnl,wx.ID_ANY,'('+val+')')
1366                insize.Add(wid,0,flag=wx.EXPAND)
1367                pnl.SetSizer(insize)
1368                self.GBsizer.Add(pnl,(row+1,col),flag=wx.EXPAND)
1369        self.SetAutoLayout(1)
1370        self.SetupScrolling()
1371        self.SetMinSize((
1372            min(700,self.GBsizer.GetSize()[0]),
1373            self.GBsizer.GetSize()[1]+20))
1374    def OnChoice(self,event):
1375        '''Called when a column is assigned to a variable
1376        '''
1377        row,col = self.chceDict[event.EventObject] # which variable was this?
1378        newcol = event.Selection # where will it be moved?
1379        if newcol == col:
1380            return # no change: nothing to do!
1381        prevmaxcol = self.maxcol # save current table size
1382        key = self.keylist[row] # get the key for the current row
1383        lbl = self.posdict[key][col] # selected variable name
1384        lbl1 = self.posdict[key].get(col+1,'') # next variable name, if any
1385        # if a posXXX variable is selected, and the next variable is posXXX, move them together
1386        repeat = 1
1387        if lbl[:3] == 'pos' and lbl1[:3] == 'int' and lbl[3:] == lbl1[3:]:
1388            repeat = 2
1389        for i in range(repeat): # process the posXXX and then the intXXX (or a single variable)
1390            col += i
1391            newcol += i
1392            if newcol in self.posdict[key]:
1393                # find first non-blank after newcol
1394                for mtcol in range(newcol+1,self.maxcol+2):
1395                    if mtcol not in self.posdict[key]: break
1396                l1 = range(mtcol,newcol,-1)+[newcol]
1397                l = range(mtcol-1,newcol-1,-1)+[col]
1398            else:
1399                l1 = [newcol]
1400                l = [col]
1401            # move all of the items, starting from the last column
1402            for newcol,col in zip(l1,l):
1403                #print 'moving',col,'to',newcol
1404                self.posdict[key][newcol] = self.posdict[key][col]
1405                del self.posdict[key][col]
1406                self.maxcol = max(self.maxcol,newcol)
1407                obj = self.GBsizer.FindItemAtPosition((row+1,col))
1408                self.GBsizer.SetItemPosition(obj.GetWindow(),(row+1,newcol))
1409                for wid in obj.GetWindow().Children:
1410                    if wid in self.chceDict:
1411                        self.chceDict[wid] = (row,newcol)
1412                        wid.SetSelection(self.chceDict[wid][1])
1413        # has the table gotten larger? If so we need new column heading(s)
1414        if prevmaxcol != self.maxcol:
1415            for i in range(prevmaxcol+1,self.maxcol+1):
1416                wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
1417                wid.SetBackgroundColour(DULL_YELLOW)
1418                wid.SetMinSize((50,-1))
1419                self.GBsizer.Add(wid,(0,i),flag=wx.ALIGN_CENTER|wx.EXPAND)
1420            colList = [str(i) for i in range(self.maxcol+2)]
1421            for wid in self.chceDict:
1422                wid.SetItems(colList)
1423                wid.SetSelection(self.chceDict[wid][1])
1424        self.GBsizer.Layout()
1425        self.FitInside()
1426
1427################################################################################
1428#### Help support routines
1429################################################################################
1430################################################################################
1431class MyHelp(wx.Menu):
1432    '''
1433    A class that creates the contents of a help menu.
1434    The menu will start with two entries:
1435
1436    * 'Help on <helpType>': where helpType is a reference to an HTML page to
1437      be opened
1438    * About: opens an About dialog using OnHelpAbout. N.B. on the Mac this
1439      gets moved to the App menu to be consistent with Apple style.
1440
1441    NOTE: for this to work properly with respect to system menus, the title
1442    for the menu must be &Help, or it will not be processed properly:
1443
1444    ::
1445
1446       menu.Append(menu=MyHelp(self,...),title="&Help")
1447
1448    '''
1449    def __init__(self,frame,helpType=None,helpLbl=None,morehelpitems=[],title=''):
1450        wx.Menu.__init__(self,title)
1451        self.HelpById = {}
1452        self.frame = frame
1453        self.Append(help='', id=wx.ID_ABOUT, kind=wx.ITEM_NORMAL,
1454            text='&About GSAS-II')
1455        frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT)
1456        if GSASIIpath.whichsvn():
1457            helpobj = self.Append(
1458                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
1459                text='&Check for updates')
1460            frame.Bind(wx.EVT_MENU, self.OnCheckUpdates, helpobj)
1461            helpobj = self.Append(
1462                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
1463                text='&Regress to an old GSAS-II version')
1464            frame.Bind(wx.EVT_MENU, self.OnSelectVersion, helpobj)
1465        for lbl,indx in morehelpitems:
1466            helpobj = self.Append(text=lbl,
1467                id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1468            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1469            self.HelpById[helpobj.GetId()] = indx
1470        # add a help item only when helpType is specified
1471        if helpType is not None:
1472            self.AppendSeparator()
1473            if helpLbl is None: helpLbl = helpType
1474            helpobj = self.Append(text='Help on '+helpLbl,
1475                                  id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1476            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1477            self.HelpById[helpobj.GetId()] = helpType
1478       
1479    def OnHelpById(self,event):
1480        '''Called when Help on... is pressed in a menu. Brings up
1481        a web page for documentation.
1482        '''
1483        helpType = self.HelpById.get(event.GetId())
1484        if helpType is None:
1485            print 'Error: help lookup failed!',event.GetEventObject()
1486            print 'id=',event.GetId()
1487        elif helpType == 'OldTutorials': # this will go away
1488            #self.frame.Tutorials = True
1489            ShowHelp(helpType,self.frame)
1490        elif helpType == 'Tutorials': 
1491            dlg = OpenTutorial(self.frame)
1492            dlg.ShowModal()
1493            #if dlg.ShowModal() == wx.ID_OK:
1494                #self.frame.Tutorials = True
1495            dlg.Destroy()
1496            return
1497        else:
1498            ShowHelp(helpType,self.frame)
1499
1500    def OnHelpAbout(self, event):
1501        "Display an 'About GSAS-II' box"
1502        import GSASII
1503        info = wx.AboutDialogInfo()
1504        info.Name = 'GSAS-II'
1505        ver = GSASIIpath.svnGetRev()
1506        if ver: 
1507            info.Version = 'Revision '+str(ver)+' (svn), version '+GSASII.__version__
1508        else:
1509            info.Version = 'Revision '+str(GSASIIpath.GetVersionNumber())+' (.py files), version '+GSASII.__version__
1510        #info.Developers = ['Robert B. Von Dreele','Brian H. Toby']
1511        info.Copyright = ('(c) ' + time.strftime('%Y') +
1512''' Argonne National Laboratory
1513This product includes software developed
1514by the UChicago Argonne, LLC, as
1515Operator of Argonne National Laboratory.''')
1516        info.Description = '''General Structure Analysis System-II (GSAS-II)
1517Robert B. Von Dreele and Brian H. Toby
1518
1519Please cite as:
1520B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013) '''
1521
1522        info.WebSite = ("https://subversion.xray.aps.anl.gov/trac/pyGSAS","GSAS-II home page")
1523        wx.AboutBox(info)
1524
1525    def OnCheckUpdates(self,event):
1526        '''Check if the GSAS-II repository has an update for the current source files
1527        and perform that update if requested.
1528        '''
1529        if not GSASIIpath.whichsvn():
1530            dlg = wx.MessageDialog(self.frame,
1531                                   'No Subversion','Cannot update GSAS-II because subversion (svn) was not found.',
1532                                   wx.OK)
1533            dlg.ShowModal()
1534            dlg.Destroy()
1535            return
1536        wx.BeginBusyCursor()
1537        local = GSASIIpath.svnGetRev()
1538        if local is None: 
1539            wx.EndBusyCursor()
1540            dlg = wx.MessageDialog(self.frame,
1541                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
1542                                   'Subversion error',
1543                                   wx.OK)
1544            dlg.ShowModal()
1545            dlg.Destroy()
1546            return
1547        print 'Installed GSAS-II version: '+local
1548        repos = GSASIIpath.svnGetRev(local=False)
1549        wx.EndBusyCursor()
1550        if repos is None: 
1551            dlg = wx.MessageDialog(self.frame,
1552                                   'Unable to access the GSAS-II server. Is this computer on the internet?',
1553                                   'Server unavailable',
1554                                   wx.OK)
1555            dlg.ShowModal()
1556            dlg.Destroy()
1557            return
1558        print 'GSAS-II version on server: '+repos
1559        if local == repos:
1560            dlg = wx.MessageDialog(self.frame,
1561                                   'GSAS-II is up-to-date. Version '+local+' is already loaded.',
1562                                   'GSAS-II Up-to-date',
1563                                   wx.OK)
1564            dlg.ShowModal()
1565            dlg.Destroy()
1566            return
1567        mods = GSASIIpath.svnFindLocalChanges()
1568        if mods:
1569            dlg = wx.MessageDialog(self.frame,
1570                                   'You have version '+local+
1571                                   ' of GSAS-II installed, but the current version is '+repos+
1572                                   '. However, '+str(len(mods))+
1573                                   ' file(s) on your local computer have been modified.'
1574                                   ' Updating will attempt to merge your local changes with '
1575                                   'the latest GSAS-II version, but if '
1576                                   'conflicts arise, local changes will be '
1577                                   'discarded. It is also possible that the '
1578                                   'local changes my prevent GSAS-II from running. '
1579                                   'Press OK to start an update if this is acceptable:',
1580                                   'Local GSAS-II Mods',
1581                                   wx.OK|wx.CANCEL)
1582            if dlg.ShowModal() != wx.ID_OK:
1583                dlg.Destroy()
1584                return
1585            else:
1586                dlg.Destroy()
1587        else:
1588            dlg = wx.MessageDialog(self.frame,
1589                                   'You have version '+local+
1590                                   ' of GSAS-II installed, but the current version is '+repos+
1591                                   '. Press OK to start an update:',
1592                                   'GSAS-II Updates',
1593                                   wx.OK|wx.CANCEL)
1594            if dlg.ShowModal() != wx.ID_OK:
1595                dlg.Destroy()
1596                return
1597            dlg.Destroy()
1598        print 'start updates'
1599        dlg = wx.MessageDialog(self.frame,
1600                               'Your project will now be saved, GSAS-II will exit and an update '
1601                               'will be performed and GSAS-II will restart. Press Cancel to '
1602                               'abort the update',
1603                               'Start update?',
1604                               wx.OK|wx.CANCEL)
1605        if dlg.ShowModal() != wx.ID_OK:
1606            dlg.Destroy()
1607            return
1608        dlg.Destroy()
1609        self.frame.OnFileSave(event)
1610        GSASIIpath.svnUpdateProcess(projectfile=self.frame.GSASprojectfile)
1611        return
1612
1613    def OnSelectVersion(self,event):
1614        '''Allow the user to select a specific version of GSAS-II
1615        '''
1616        if not GSASIIpath.whichsvn():
1617            dlg = wx.MessageDialog(self,'No Subversion','Cannot update GSAS-II because subversion (svn) '+
1618                                   'was not found.'
1619                                   ,wx.OK)
1620            dlg.ShowModal()
1621            return
1622        local = GSASIIpath.svnGetRev()
1623        if local is None: 
1624            dlg = wx.MessageDialog(self.frame,
1625                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
1626                                   'Subversion error',
1627                                   wx.OK)
1628            dlg.ShowModal()
1629            return
1630        mods = GSASIIpath.svnFindLocalChanges()
1631        if mods:
1632            dlg = wx.MessageDialog(self.frame,
1633                                   'You have version '+local+
1634                                   ' of GSAS-II installed'
1635                                   '. However, '+str(len(mods))+
1636                                   ' file(s) on your local computer have been modified.'
1637                                   ' Downdating will attempt to merge your local changes with '
1638                                   'the selected GSAS-II version. '
1639                                   'Downdating is not encouraged because '
1640                                   'if merging is not possible, your local changes will be '
1641                                   'discarded. It is also possible that the '
1642                                   'local changes my prevent GSAS-II from running. '
1643                                   'Press OK to continue anyway.',
1644                                   'Local GSAS-II Mods',
1645                                   wx.OK|wx.CANCEL)
1646            if dlg.ShowModal() != wx.ID_OK:
1647                dlg.Destroy()
1648                return
1649            dlg.Destroy()
1650        dlg = downdate(parent=self.frame)
1651        if dlg.ShowModal() == wx.ID_OK:
1652            ver = dlg.getVersion()
1653        else:
1654            dlg.Destroy()
1655            return
1656        dlg.Destroy()
1657        print('start regress to '+str(ver))
1658        GSASIIpath.svnUpdateProcess(
1659            projectfile=self.frame.GSASprojectfile,
1660            version=str(ver)
1661            )
1662        self.frame.OnFileSave(event)
1663        return
1664
1665################################################################################
1666class AddHelp(wx.Menu):
1667    '''For the Mac: creates an entry to the help menu of type
1668    'Help on <helpType>': where helpType is a reference to an HTML page to
1669    be opened.
1670
1671    NOTE: when appending this menu (menu.Append) be sure to set the title to
1672    '&Help' so that wx handles it correctly.
1673    '''
1674    def __init__(self,frame,helpType,helpLbl=None,title=''):
1675        wx.Menu.__init__(self,title)
1676        self.frame = frame
1677        if helpLbl is None: helpLbl = helpType
1678        # add a help item only when helpType is specified
1679        helpobj = self.Append(text='Help on '+helpLbl,
1680                              id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1681        frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1682        self.HelpById = helpType
1683       
1684    def OnHelpById(self,event):
1685        '''Called when Help on... is pressed in a menu. Brings up
1686        a web page for documentation.
1687        '''
1688        ShowHelp(self.HelpById,self.frame)
1689
1690################################################################################
1691class HelpButton(wx.Button):
1692    '''Create a help button that displays help information.
1693    The text is displayed in a modal message window.
1694
1695    TODO: it might be nice if it were non-modal: e.g. it stays around until
1696    the parent is deleted or the user closes it, but this did not work for
1697    me.
1698
1699    :param parent: the panel which will be the parent of the button
1700    :param str msg: the help text to be displayed
1701    '''
1702    def __init__(self,parent,msg):
1703        if sys.platform == "darwin": 
1704            wx.Button.__init__(self,parent,wx.ID_HELP)
1705        else:
1706            wx.Button.__init__(self,parent,wx.ID_ANY,'?',style=wx.BU_EXACTFIT)
1707        self.Bind(wx.EVT_BUTTON,self._onPress)
1708        self.msg=StripIndents(msg)
1709        self.parent = parent
1710    def _onClose(self,event):
1711        self.dlg.EndModal(wx.ID_CANCEL)
1712    def _onPress(self,event):
1713        'Respond to a button press by displaying the requested text'
1714        #dlg = wx.MessageDialog(self.parent,self.msg,'Help info',wx.OK)
1715        self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', 
1716                        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1717        #self.dlg.SetBackgroundColour(wx.WHITE)
1718        mainSizer = wx.BoxSizer(wx.VERTICAL)
1719        txt = wx.StaticText(self.dlg,wx.ID_ANY,self.msg)
1720        mainSizer.Add(txt,1,wx.ALL|wx.EXPAND,10)
1721        txt.SetBackgroundColour(wx.WHITE)
1722
1723        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
1724        btn = wx.Button(self.dlg, wx.ID_CLOSE) 
1725        btn.Bind(wx.EVT_BUTTON,self._onClose)
1726        btnsizer.Add(btn)
1727        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1728        self.dlg.SetSizer(mainSizer)
1729        mainSizer.Fit(self.dlg)
1730        self.dlg.ShowModal()
1731        self.dlg.Destroy()
1732################################################################################
1733class MyHtmlPanel(wx.Panel):
1734    '''Defines a panel to display HTML help information, as an alternative to
1735    displaying help information in a web browser.
1736    '''
1737    def __init__(self, frame, id):
1738        self.frame = frame
1739        wx.Panel.__init__(self, frame, id)
1740        sizer = wx.BoxSizer(wx.VERTICAL)
1741        back = wx.Button(self, -1, "Back")
1742        back.Bind(wx.EVT_BUTTON, self.OnBack)
1743        self.htmlwin = G2HtmlWindow(self, id, size=(750,450))
1744        sizer.Add(self.htmlwin, 1,wx.EXPAND)
1745        sizer.Add(back, 0, wx.ALIGN_LEFT, 0)
1746        self.SetSizer(sizer)
1747        sizer.Fit(frame)       
1748        self.Bind(wx.EVT_SIZE,self.OnHelpSize)
1749    def OnHelpSize(self,event):         #does the job but weirdly!!
1750        anchor = self.htmlwin.GetOpenedAnchor()
1751        if anchor:           
1752            self.htmlwin.ScrollToAnchor(anchor)
1753            wx.CallAfter(self.htmlwin.ScrollToAnchor,anchor)
1754            event.Skip()
1755    def OnBack(self, event):
1756        self.htmlwin.HistoryBack()
1757    def LoadFile(self,file):
1758        pos = file.rfind('#')
1759        if pos != -1:
1760            helpfile = file[:pos]
1761            helpanchor = file[pos+1:]
1762        else:
1763            helpfile = file
1764            helpanchor = None
1765        self.htmlwin.LoadPage(helpfile)
1766        if helpanchor is not None:
1767            self.htmlwin.ScrollToAnchor(helpanchor)
1768            xs,ys = self.htmlwin.GetViewStart()
1769            self.htmlwin.Scroll(xs,ys-1)
1770################################################################################
1771class G2HtmlWindow(wx.html.HtmlWindow):
1772    '''Displays help information in a primitive HTML browser type window
1773    '''
1774    def __init__(self, parent, *args, **kwargs):
1775        self.parent = parent
1776        wx.html.HtmlWindow.__init__(self, parent, *args, **kwargs)
1777    def LoadPage(self, *args, **kwargs):
1778        wx.html.HtmlWindow.LoadPage(self, *args, **kwargs)
1779        self.TitlePage()
1780    def OnLinkClicked(self, *args, **kwargs):
1781        wx.html.HtmlWindow.OnLinkClicked(self, *args, **kwargs)
1782        xs,ys = self.GetViewStart()
1783        self.Scroll(xs,ys-1)
1784        self.TitlePage()
1785    def HistoryBack(self, *args, **kwargs):
1786        wx.html.HtmlWindow.HistoryBack(self, *args, **kwargs)
1787        self.TitlePage()
1788    def TitlePage(self):
1789        self.parent.frame.SetTitle(self.GetOpenedPage() + ' -- ' + 
1790            self.GetOpenedPageTitle())
1791
1792################################################################################
1793def StripIndents(msg):
1794    'Strip indentation from multiline strings'
1795    msg1 = msg.replace('\n ','\n')
1796    while msg != msg1:
1797        msg = msg1
1798        msg1 = msg.replace('\n ','\n')
1799    return msg.replace('\n\t','\n')
1800
1801def G2MessageBox(parent,msg,title='Error'):
1802    '''Simple code to display a error or warning message
1803    '''
1804    dlg = wx.MessageDialog(parent,StripIndents(msg), title, wx.OK)
1805    dlg.ShowModal()
1806    dlg.Destroy()
1807       
1808################################################################################
1809class downdate(wx.Dialog):
1810    '''Dialog to allow a user to select a version of GSAS-II to install
1811    '''
1812    def __init__(self,parent=None):
1813        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1814        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style)
1815        pnl = wx.Panel(self)
1816        sizer = wx.BoxSizer(wx.VERTICAL)
1817        insver = GSASIIpath.svnGetRev(local=True)
1818        curver = int(GSASIIpath.svnGetRev(local=False))
1819        label = wx.StaticText(
1820            pnl,  wx.ID_ANY,
1821            'Select a specific GSAS-II version to install'
1822            )
1823        sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
1824        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
1825        sizer1.Add(
1826            wx.StaticText(pnl,  wx.ID_ANY,
1827                          'Currently installed version: '+str(insver)),
1828            0, wx.ALIGN_CENTRE|wx.ALL, 5)
1829        sizer.Add(sizer1)
1830        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
1831        sizer1.Add(
1832            wx.StaticText(pnl,  wx.ID_ANY,
1833                          'Select GSAS-II version to install: '),
1834            0, wx.ALIGN_CENTRE|wx.ALL, 5)
1835        self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1))
1836        self.spin.SetRange(1, curver)
1837        self.spin.SetValue(curver)
1838        self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
1839        self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
1840        sizer1.Add(self.spin)
1841        sizer.Add(sizer1)
1842
1843        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
1844        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
1845
1846        self.text = wx.StaticText(pnl,  wx.ID_ANY, "")
1847        sizer.Add(self.text, 0, wx.ALIGN_LEFT|wx.EXPAND|wx.ALL, 5)
1848
1849        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
1850        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
1851        sizer.Add(
1852            wx.StaticText(
1853                pnl,  wx.ID_ANY,
1854                'If "Install" is pressed, your project will be saved;\n'
1855                'GSAS-II will exit; The specified version will be loaded\n'
1856                'and GSAS-II will restart. Press "Cancel" to abort.'),
1857            0, wx.EXPAND|wx.ALL, 10)
1858        btnsizer = wx.StdDialogButtonSizer()
1859        btn = wx.Button(pnl, wx.ID_OK, "Install")
1860        btn.SetDefault()
1861        btnsizer.AddButton(btn)
1862        btn = wx.Button(pnl, wx.ID_CANCEL)
1863        btnsizer.AddButton(btn)
1864        btnsizer.Realize()
1865        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1866        pnl.SetSizer(sizer)
1867        sizer.Fit(self)
1868        self.topsizer=sizer
1869        self.CenterOnParent()
1870        self._onSpin(None)
1871
1872    def _onSpin(self,event):
1873        'Called to load info about the selected version in the dialog'
1874        ver = self.spin.GetValue()
1875        d = GSASIIpath.svnGetLog(version=ver)
1876        date = d.get('date','?').split('T')[0]
1877        s = '(Version '+str(ver)+' created '+date
1878        s += ' by '+d.get('author','?')+')'
1879        msg = d.get('msg')
1880        if msg: s += '\n\nComment: '+msg
1881        self.text.SetLabel(s)
1882        self.topsizer.Fit(self)
1883
1884    def getVersion(self):
1885        'Get the version number in the dialog'
1886        return self.spin.GetValue()
1887################################################################################
1888#### Display Help information
1889################################################################################
1890# define some globals
1891htmlPanel = None
1892htmlFrame = None
1893htmlFirstUse = True
1894helpLocDict = {}
1895path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # save location of this file
1896def ShowHelp(helpType,frame):
1897    '''Called to bring up a web page for documentation.'''
1898    global htmlFirstUse
1899    # look up a definition for help info from dict
1900    helplink = helpLocDict.get(helpType)
1901    if helplink is None:
1902        # no defined link to use, create a default based on key
1903        helplink = 'gsasII.html#'+helpType.replace(' ','_')
1904    helplink = os.path.join(path2GSAS2,'help',helplink)
1905    # determine if a web browser or the internal viewer should be used for help info
1906    if GSASIIpath.GetConfigValue('Help_mode'):
1907        helpMode = GSASIIpath.GetConfigValue('Help_mode')
1908    else:
1909        helpMode = 'browser'
1910    if helpMode == 'internal':
1911        try:
1912            htmlPanel.LoadFile(helplink)
1913            htmlFrame.Raise()
1914        except:
1915            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
1916            htmlFrame.Show(True)
1917            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
1918            htmlPanel = MyHtmlPanel(htmlFrame,-1)
1919            htmlPanel.LoadFile(helplink)
1920    else:
1921        pfx = "file://"
1922        if sys.platform.lower().startswith('win'):
1923            pfx = ''
1924        if htmlFirstUse:
1925            webbrowser.open_new(pfx+helplink)
1926            htmlFirstUse = False
1927        else:
1928            webbrowser.open(pfx+helplink, new=0, autoraise=True)
1929def ShowWebPage(URL,frame):
1930    '''Called to show a tutorial web page.
1931    '''
1932    global htmlFirstUse
1933    # determine if a web browser or the internal viewer should be used for help info
1934    if GSASIIpath.GetConfigValue('Help_mode'):
1935        helpMode = GSASIIpath.GetConfigValue('Help_mode')
1936    else:
1937        helpMode = 'browser'
1938    if helpMode == 'internal':
1939        try:
1940            htmlPanel.LoadFile(URL)
1941            htmlFrame.Raise()
1942        except:
1943            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
1944            htmlFrame.Show(True)
1945            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
1946            htmlPanel = MyHtmlPanel(htmlFrame,-1)
1947            htmlPanel.LoadFile(URL)
1948    else:
1949        if URL.startswith('http'): 
1950            pfx = ''
1951        elif sys.platform.lower().startswith('win'):
1952            pfx = ''
1953        else:
1954            pfx = "file://"
1955        if htmlFirstUse:
1956            webbrowser.open_new(pfx+URL)
1957            htmlFirstUse = False
1958        else:
1959            webbrowser.open(pfx+URL, new=0, autoraise=True)
1960################################################################################
1961#### Tutorials selector
1962################################################################################
1963G2BaseURL = "https://subversion.xray.aps.anl.gov/pyGSAS"
1964# N.B. tutorialCatalog is generated by routine catalog.py, which also generates the appropriate
1965# empty directories (.../MT/* .../trunk/GSASII/* *=[help,Exercises])
1966tutorialCatalog = (
1967    # tutorial dir,      exercise dir,      web page file name                                title for page
1968
1969    ['StartingGSASII', 'StartingGSASII', 'Starting GSAS.htm',
1970       'Starting GSAS-II'],
1971       
1972    ['FitPeaks', 'FitPeaks', 'Fit Peaks.htm',
1973       'Fitting individual peaks & autoindexing'],
1974       
1975    ['CWNeutron', 'CWNeutron', 'Neutron CW Powder Data.htm',
1976       'CW Neutron Powder fit for Yttrium-Iron Garnet'],
1977    ['LabData', 'LabData', 'Laboratory X.htm',
1978       'Fitting laboratory X-ray powder data for fluoroapatite'],
1979    ['CWCombined', 'CWCombined', 'Combined refinement.htm',
1980       'Combined X-ray/CW-neutron refinement of PbSO4'],
1981    ['TOF-CW Joint Refinement', 'TOF-CW Joint Refinement', 'TOF combined XN Rietveld refinement in GSAS.htm',
1982       'Combined X-ray/TOF-neutron Rietveld refinement'],
1983    ['SeqRefine', 'SeqRefine', 'SequentialTutorial.htm',
1984       'Sequential refinement of multiple datasets'],
1985    ['SeqParametric', 'SeqParametric', 'ParametricFitting.htm',
1986       'Parametric Fitting and Pseudo Variables for Sequential Fits'],
1987       
1988    ['CFjadarite', 'CFjadarite', 'Charge Flipping in GSAS.htm',
1989       'Charge Flipping structure solution for jadarite'],
1990    ['CFsucrose', 'CFsucrose', 'Charge Flipping - sucrose.htm',
1991       'Charge Flipping structure solution for sucrose'],
1992    ['TOF Charge Flipping', 'TOF Charge Flipping', 'Charge Flipping with TOF single crystal data in GSASII.htm',
1993       'Charge flipping with neutron TOF single crystal data'],
1994    ['MCsimanneal', 'MCsimanneal', 'MCSA in GSAS.htm',
1995       'Monte-Carlo simulated annealing structure'],
1996
1997    ['2DCalibration', '2DCalibration', 'Calibration of an area detector in GSAS.htm',
1998       'Calibration of an area detector'],
1999    ['2DIntegration', '2DIntegration', 'Integration of area detector data in GSAS.htm',
2000       'Integration of area detector data'],
2001    ['TOF Calibration', 'TOF Calibration', 'Calibration of a TOF powder diffractometer.htm',
2002       'Calibration of a Neutron TOF diffractometer'],
2003       
2004    ['2DStrain', '2DStrain', 'Strain fitting of 2D data in GSAS-II.htm',
2005       'Strain fitting of 2D data'],
2006       
2007    ['SAimages', 'SAimages', 'Small Angle Image Processing.htm',
2008       'Image Processing of small angle x-ray data'],
2009    ['SAfit', 'SAfit', 'Fitting Small Angle Scattering Data.htm',
2010       'Fitting small angle x-ray data (alumina powder)'],
2011    ['SAsize', 'SAsize', 'Small Angle Size Distribution.htm',
2012       'Small angle x-ray data size distribution (alumina powder)'],
2013    ['SAseqref', 'SAseqref', 'Sequential Refinement of Small Angle Scattering Data.htm',
2014       'Sequential refinement with small angle scattering data'],
2015   
2016    #['TOF Sequential Single Peak Fit', 'TOF Sequential Single Peak Fit', '', ''],
2017    #['TOF Single Crystal Refinement', 'TOF Single Crystal Refinement', '', ''],
2018    )
2019if GSASIIpath.GetConfigValue('Tutorial_location'):
2020    tutorialPath = GSASIIpath.GetConfigValue('Tutorial_location')
2021else:
2022    tutorialPath = GSASIIpath.path2GSAS2
2023
2024class OpenTutorial(wx.Dialog):
2025    '''Open a tutorial, optionally copying it to the local disk. Always copy
2026    the data files locally.
2027
2028    For now tutorials will always be copied into the source code tree, but it
2029    might be better to have an option to copy them somewhere else, for people
2030    who don't have write access to the GSAS-II source code location.
2031    '''
2032    # TODO: set default input-file open location to the download location
2033    def __init__(self,parent=None):
2034        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
2035        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style)
2036        self.frame = parent
2037        pnl = wx.Panel(self)
2038        sizer = wx.BoxSizer(wx.VERTICAL)
2039        label = wx.StaticText(
2040            pnl,  wx.ID_ANY,
2041            'Select the tutorial to be run and the mode of access'
2042            )
2043        sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
2044        msg = '''To save download time for GSAS-II tutorials and their
2045        sample data files are being moved out of the standard
2046        distribution. This dialog allows users to load selected
2047        tutorials to their computer.
2048
2049        Tutorials can be viewed over the internet or downloaded
2050        to this computer. The sample data can be downloaded or not,
2051        (but it is not possible to run the tutorial without the
2052        data). If no web access is available, tutorials that were
2053        previously downloaded can be viewed.
2054
2055        By default, files are downloaded into the location used
2056        for the GSAS-II distribution, but this may not be possible
2057        if the software is installed by a administrator. The
2058        download location can be changed using the "Set data
2059        location" or the "Tutorial_location" configuration option
2060        (see config_example.py).
2061        '''
2062        hlp = HelpButton(pnl,msg)
2063        sizer.Add(hlp,0,wx.ALIGN_RIGHT|wx.ALL)
2064        #======================================================================
2065        # # This is needed only until we get all the tutorials items moved
2066        # btn = wx.Button(pnl, wx.ID_ANY, "Open older tutorials")
2067        # btn.Bind(wx.EVT_BUTTON, self.OpenOld)
2068        # sizer.Add(btn,0,wx.ALIGN_CENTRE|wx.ALL)
2069        #======================================================================
2070        self.BrowseMode = 1
2071        choices = [
2072            'make local copy of tutorial and data, then open',
2073            'run from web (copy data locally)',
2074            'browse on web (data not loaded)', 
2075            'open from local tutorial copy',
2076        ]
2077        self.mode = wx.RadioBox(pnl,wx.ID_ANY,'access mode:',
2078                                wx.DefaultPosition, wx.DefaultSize,
2079                                choices, 1, wx.RA_SPECIFY_COLS)
2080        self.mode.SetSelection(self.BrowseMode)
2081        self.mode.Bind(wx.EVT_RADIOBOX, self.OnModeSelect)
2082        sizer.Add(self.mode,0,WACV)
2083        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
2084        btn = wx.Button(pnl, wx.ID_ANY, "Set download location")
2085        btn.Bind(wx.EVT_BUTTON, self.SelectDownloadLoc)
2086        sizer1.Add(btn,0,WACV)
2087        self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,tutorialPath)
2088        sizer1.Add(self.dataLoc,0,WACV)
2089        sizer.Add(sizer1)
2090        self.listbox = wx.ListBox(pnl, wx.ID_ANY, size=(450, 100), style=wx.LB_SINGLE)
2091        self.listbox.Bind(wx.EVT_LISTBOX, self.OnTutorialSelected)
2092        self.OnModeSelect(None)
2093        #self.FillListBox()
2094        sizer.Add(self.listbox,1,WACV|wx.EXPAND|wx.ALL,1)
2095       
2096        btnsizer = wx.StdDialogButtonSizer()
2097        btn = wx.Button(pnl, wx.ID_CANCEL)
2098        btnsizer.AddButton(btn)
2099        btnsizer.Realize()
2100        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2101        pnl.SetSizer(sizer)
2102        sizer.Fit(self)
2103        self.topsizer=sizer
2104        self.CenterOnParent()
2105    # def OpenOld(self,event):
2106    #     '''Open old tutorials. This is needed only until we get all the tutorials items moved
2107    #     '''
2108    #     self.EndModal(wx.ID_OK)
2109    #     self.frame.Tutorials = True
2110    #     ShowHelp('Tutorials',self.frame)
2111    def OnModeSelect(self,event):
2112        '''Respond when the mode is changed
2113        '''
2114        self.BrowseMode = self.mode.GetSelection()
2115        if self.BrowseMode == 3:
2116            import glob
2117            filelist = glob.glob(os.path.join(tutorialPath,'help','*','*.htm'))
2118            taillist = [os.path.split(f)[1] for f in filelist]
2119            itemlist = [tut[-1] for tut in tutorialCatalog if tut[2] in taillist]
2120        else:
2121            itemlist = [tut[-1] for tut in tutorialCatalog if tut[-1]]
2122        self.listbox.Clear()
2123        self.listbox.AppendItems(itemlist)
2124    def OnTutorialSelected(self,event):
2125        '''Respond when a tutorial is selected. Load tutorials and data locally,
2126        as needed and then display the page
2127        '''
2128        for tutdir,exedir,htmlname,title in tutorialCatalog:
2129            if title == event.GetString(): break
2130        else:
2131            raise Exception("Match to file not found")
2132        if self.BrowseMode == 0 or self.BrowseMode == 1:
2133            try: 
2134                self.ValidateTutorialDir(tutorialPath,G2BaseURL)
2135            except:
2136                G2MessageBox(self.frame,
2137            '''The selected directory is not valid.
2138           
2139            You must use a directory that you have write access
2140            to. You can reuse a directory previously used for
2141            downloads, but the help and Tutorials subdirectories
2142             must be created by this routine.
2143            ''')
2144                return
2145        self.dataLoc.SetLabel(tutorialPath)
2146        self.EndModal(wx.ID_OK)
2147        wx.BeginBusyCursor()
2148        if self.BrowseMode == 0:
2149            # xfer data & web page locally, then open web page
2150            self.LoadTutorial(tutdir,tutorialPath,G2BaseURL)
2151            self.LoadExercise(exedir,tutorialPath,G2BaseURL)
2152            URL = os.path.join(tutorialPath,'help',tutdir,htmlname)
2153            ShowWebPage(URL,self.frame)
2154            self.frame.ImportDir = os.path.join(tutorialPath,'Exercises',exedir)
2155        elif self.BrowseMode == 1:
2156            # xfer data locally, open web page remotely
2157            self.LoadExercise(exedir,tutorialPath,G2BaseURL)
2158            URL = os.path.join(G2BaseURL,'Tutorials',tutdir,htmlname)
2159            ShowWebPage(URL,self.frame)
2160            self.frame.ImportDir = os.path.join(tutorialPath,'Exercises',exedir)
2161        elif self.BrowseMode == 2:
2162            # open web page remotely, don't worry about data
2163            URL = os.path.join(G2BaseURL,'Tutorials',tutdir,htmlname)
2164            ShowWebPage(URL,self.frame)
2165            self.frame.ImportDir = os.path.join(tutorialPath,'Exercises',exedir)
2166        elif self.BrowseMode == 3:
2167            # open web page that has already been transferred
2168            URL = os.path.join(tutorialPath,'help',tutdir,htmlname)
2169            ShowWebPage(URL,self.frame)
2170        else:
2171            wx.EndBusyCursor()
2172            raise Exception("How did this happen!")
2173        wx.EndBusyCursor()
2174    def ValidateTutorialDir(self,fullpath=tutorialPath,baseURL=G2BaseURL):
2175        '''Load help to new directory or make sure existing directory looks correctly set up
2176        throws an exception if there is a problem.
2177        '''
2178        if os.path.exists(fullpath):
2179            if os.path.exists(os.path.join(fullpath,"help")):
2180                if not GSASIIpath.svnGetRev(os.path.join(fullpath,"help")):
2181                    print("Problem with "+fullpath+" dir help exists but is not in SVN")
2182                    raise Exception
2183            if os.path.exists(os.path.join(fullpath,"Exercises")):
2184                if not GSASIIpath.svnGetRev(os.path.join(fullpath,"Exercises")):
2185                    print("Problem with "+fullpath+" dir Exercises exists but is not in SVN")
2186                    raise Exception
2187            if (os.path.exists(os.path.join(fullpath,"help")) and
2188                    os.path.exists(os.path.join(fullpath,"Exercises"))):
2189                return True # both good
2190            elif (os.path.exists(os.path.join(fullpath,"help")) or
2191                    os.path.exists(os.path.join(fullpath,"Exercises"))):
2192                print("Problem: dir "+fullpath+" exists has either help or Exercises, not both")
2193                raise Exception
2194        wx.BeginBusyCursor()
2195        if not GSASIIpath.svnInstallDir(baseURL+"/MT",fullpath):
2196            wx.EndBusyCursor()
2197            print("Problem transferring empty directory from web")
2198            raise Exception
2199        wx.EndBusyCursor()
2200        return True
2201
2202    def LoadTutorial(self,tutorialname,fullpath=tutorialPath,baseURL=G2BaseURL):
2203        'Load a Tutorial to the selected location'
2204        if GSASIIpath.svnSwitchDir("help",tutorialname,baseURL+"/Tutorials",fullpath):
2205            return True
2206        print("Problem transferring Tutorial from web")
2207        raise Exception
2208       
2209    def LoadExercise(self,tutorialname,fullpath=tutorialPath,baseURL=G2BaseURL):
2210        'Load Exercise file(s) for a Tutorial to the selected location'
2211        if GSASIIpath.svnSwitchDir("Exercises",tutorialname,baseURL+"/Exercises",fullpath):
2212            return True
2213        print ("Problem transferring Exercise from web")
2214        raise Exception
2215       
2216    def SelectDownloadLoc(self,event):
2217        '''Select a download location,
2218        Cancel resets to the default
2219        '''
2220        global tutorialPath
2221        localpath = os.path.abspath(os.path.expanduser('~/G2tutorials'))
2222        dlg = wx.DirDialog(self, "Choose a directory for downloads:",
2223                           defaultPath=localpath)#,style=wx.DD_DEFAULT_STYLE)
2224                           #)
2225        if dlg.ShowModal() == wx.ID_OK:
2226            pth = dlg.GetPath()
2227        else:
2228            if GSASIIpath.GetConfigValue('Tutorial_location'):
2229                pth = GSASIIpath.GetConfigValue('Tutorial_location')
2230            else:
2231                pth = GSASIIpath.path2GSAS2
2232        if not os.path.exists(pth):
2233            try:
2234                os.makedirs(pth)
2235            except OSError:
2236                msg = 'The selected directory is not valid.\n\t'
2237                msg += pth
2238                msg += '\n\nAn attempt to create the directory failed'
2239                G2MessageBox(self.frame,msg)
2240                return
2241        self.ValidateTutorialDir(pth,G2BaseURL)
2242        try: 
2243            self.ValidateTutorialDir(pth,G2BaseURL)
2244            tutorialPath = pth
2245        except:
2246            G2MessageBox(self.frame,
2247            '''The selected directory is not valid.
2248           
2249            You must use a directory that you have write access
2250            to. You can reuse a directory previously used for
2251            downloads, but the help and Tutorials subdirectories
2252             must be created by this routine.
2253            ''')
2254        self.dataLoc.SetLabel(tutorialPath)
2255   
2256if __name__ == '__main__':
2257    app = wx.PySimpleApp()
2258    GSASIIpath.InvokeDebugOpts()
2259    frm = wx.Frame(None) # create a frame
2260    frm.Show(True)
2261    dlg = OpenTutorial(frm)
2262    if dlg.ShowModal() == wx.ID_OK:
2263        print "OK"
2264    else:
2265        print "Cancel"
2266    dlg.Destroy()
2267    import sys
2268    sys.exit()
2269    #======================================================================
2270    # test ScrolledMultiEditor
2271    #======================================================================
2272    # Data1 = {
2273    #      'Order':1,
2274    #      'omega':'string',
2275    #      'chi':2.0,
2276    #      'phi':'',
2277    #      }
2278    # elemlst = sorted(Data1.keys())
2279    # prelbl = sorted(Data1.keys())
2280    # dictlst = len(elemlst)*[Data1,]
2281    #Data2 = [True,False,False,True]
2282    #Checkdictlst = len(Data2)*[Data2,]
2283    #Checkelemlst = range(len(Checkdictlst))
2284    # print 'before',Data1,'\n',Data2
2285    # dlg = ScrolledMultiEditor(
2286    #     frm,dictlst,elemlst,prelbl,
2287    #     checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
2288    #     checklabel="Refine?",
2289    #     header="test")
2290    # if dlg.ShowModal() == wx.ID_OK:
2291    #     print "OK"
2292    # else:
2293    #     print "Cancel"
2294    # print 'after',Data1,'\n',Data2
2295    # dlg.Destroy()
2296    Data3 = {
2297         'Order':1.0,
2298         'omega':1.1,
2299         'chi':2.0,
2300         'phi':2.3,
2301         'Order1':1.0,
2302         'omega1':1.1,
2303         'chi1':2.0,
2304         'phi1':2.3,
2305         'Order2':1.0,
2306         'omega2':1.1,
2307         'chi2':2.0,
2308         'phi2':2.3,
2309         }
2310    elemlst = sorted(Data3.keys())
2311    dictlst = len(elemlst)*[Data3,]
2312    prelbl = elemlst[:]
2313    prelbl[0]="this is a much longer label to stretch things out"
2314    Data2 = len(elemlst)*[False,]
2315    Data2[1] = Data2[3] = True
2316    Checkdictlst = len(elemlst)*[Data2,]
2317    Checkelemlst = range(len(Checkdictlst))
2318    #print 'before',Data3,'\n',Data2
2319    #print dictlst,"\n",elemlst
2320    #print Checkdictlst,"\n",Checkelemlst
2321    dlg = ScrolledMultiEditor(
2322        frm,dictlst,elemlst,prelbl,
2323        checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
2324        checklabel="Refine?",
2325        header="test",CopyButton=True)
2326    if dlg.ShowModal() == wx.ID_OK:
2327        print "OK"
2328    else:
2329        print "Cancel"
2330    #print 'after',Data3,'\n',Data2
2331
2332    # Data2 = list(range(100))
2333    # elemlst += range(2,6)
2334    # postlbl += range(2,6)
2335    # dictlst += len(range(2,6))*[Data2,]
2336
2337    # prelbl = range(len(elemlst))
2338    # postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
2339    # header="""This is a longer\nmultiline and perhaps silly header"""
2340    # dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
2341    #                           header=header,CopyButton=True)
2342    # print Data1
2343    # if dlg.ShowModal() == wx.ID_OK:
2344    #     for d,k in zip(dictlst,elemlst):
2345    #         print k,d[k]
2346    # dlg.Destroy()
2347    # if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
2348    #                            header=header):
2349    #     for d,k in zip(dictlst,elemlst):
2350    #         print k,d[k]
2351
2352    #======================================================================
2353    # test G2MultiChoiceDialog
2354    #======================================================================
2355    # choices = []
2356    # for i in range(21):
2357    #     choices.append("option_"+str(i))
2358    # dlg = G2MultiChoiceDialog(frm, 'Sequential refinement',
2359    #                           'Select dataset to include',
2360    #                           choices)
2361    # sel = range(2,11,2)
2362    # dlg.SetSelections(sel)
2363    # dlg.SetSelections((1,5))
2364    # if dlg.ShowModal() == wx.ID_OK:
2365    #     for sel in dlg.GetSelections():
2366    #         print sel,choices[sel]
2367   
2368    #======================================================================
2369    # test wx.MultiChoiceDialog
2370    #======================================================================
2371    # dlg = wx.MultiChoiceDialog(frm, 'Sequential refinement',
2372    #                           'Select dataset to include',
2373    #                           choices)
2374    # sel = range(2,11,2)
2375    # dlg.SetSelections(sel)
2376    # dlg.SetSelections((1,5))
2377    # if dlg.ShowModal() == wx.ID_OK:
2378    #     for sel in dlg.GetSelections():
2379    #         print sel,choices[sel]
2380
2381    pnl = wx.Panel(frm)
2382    siz = wx.BoxSizer(wx.VERTICAL)
2383
2384    td = {'Goni':200.,'a':1.,'calc':1./3.,'string':'s'}
2385    for key in sorted(td):
2386        txt = ValidatedTxtCtrl(pnl,td,key)
2387        siz.Add(txt)
2388    pnl.SetSizer(siz)
2389    siz.Fit(frm)
2390    app.MainLoop()
2391    print td
Note: See TracBrowser for help on using the repository browser.