source: trunk/GSASIIgrid.py @ 1183

Last change on this file since 1183 was 1183, checked in by toby, 8 years ago

fixup number formatting; update produced doc files

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 169.8 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2014-01-05 15:22:22 +0000 (Sun, 05 Jan 2014) $
5# $Author: toby $
6# $Revision: 1183 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1183 2014-01-05 15:22:22Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIgrid: Basic GUI routines*
12--------------------------------
13
14'''
15import wx
16import wx.grid as wg
17import wx.wizard as wz
18import wx.aui
19import wx.lib.scrolledpanel as wxscroll
20import time
21import copy
22import cPickle
23import sys
24import numpy as np
25import numpy.ma as ma
26import os.path
27import wx.html        # could postpone this for quicker startup
28import webbrowser     # could postpone this for quicker startup
29import GSASIIpath
30GSASIIpath.SetVersionNumber("$Revision: 1183 $")
31import GSASIImath as G2mth
32import GSASIIIO as G2IO
33import GSASIIlattice as G2lat
34import GSASIIplot as G2plt
35import GSASIIpwdGUI as G2pdG
36import GSASIIimgGUI as G2imG
37import GSASIIphsGUI as G2phG
38import GSASIIspc as G2spc
39import GSASIImapvars as G2mv
40import GSASIIconstrGUI as G2cnstG
41import GSASIIrestrGUI as G2restG
42import GSASIIpy3 as G2py3
43import GSASIIobj as G2obj
44
45# trig functions in degrees
46sind = lambda x: np.sin(x*np.pi/180.)
47tand = lambda x: np.tan(x*np.pi/180.)
48cosd = lambda x: np.cos(x*np.pi/180.)
49
50# globals we will use later
51__version__ = None # gets overridden in GSASII.py
52path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # save location of this file
53helpLocDict = {}
54htmlPanel = None
55htmlFrame = None
56helpMode = 'browser'
57#if sys.platform.lower().startswith('win'): helpMode = 'internal' # need a global control to set this
58   
59htmlFirstUse = True
60
61[ wxID_FOURCALC, wxID_FOURSEARCH, wxID_FOURCLEAR, wxID_PEAKSMOVE, wxID_PEAKSCLEAR, 
62    wxID_CHARGEFLIP, wxID_PEAKSUNIQUE, wxID_PEAKSDELETE, wxID_PEAKSDA,
63    wxID_PEAKSDISTVP, wxID_PEAKSVIEWPT, wxID_FINDEQVPEAKS,wxID_SHOWBONDS,wxID_MULTIMCSA,
64    wxID_SINGLEMCSA
65] = [wx.NewId() for item in range(15)]
66
67[ wxID_PWDRADD, wxID_HKLFADD,wxID_PWDANALYSIS,wxID_DATADELETE,
68] = [wx.NewId() for item in range(4)]
69
70[ wxID_ATOMSEDITADD, wxID_ATOMSEDITINSERT, wxID_ATOMSEDITDELETE, wxID_ATOMSREFINE, 
71    wxID_ATOMSMODIFY, wxID_ATOMSTRANSFORM, wxID_ATOMSVIEWADD, wxID_ATOMVIEWINSERT,
72    wxID_RELOADDRAWATOMS,wxID_ATOMSDISAGL,wxID_ATOMMOVE,
73    wxID_ASSIGNATMS2RB,wxID_ATOMSPDISAGL, wxID_ISODISP,
74] = [wx.NewId() for item in range(14)]
75
76[ wxID_DRAWATOMSTYLE, wxID_DRAWATOMLABEL, wxID_DRAWATOMCOLOR, wxID_DRAWATOMRESETCOLOR, 
77    wxID_DRAWVIEWPOINT, wxID_DRAWTRANSFORM, wxID_DRAWDELETE, wxID_DRAWFILLCELL, 
78    wxID_DRAWADDEQUIV, wxID_DRAWFILLCOORD, wxID_DRAWDISAGLTOR,  wxID_DRAWPLANE,
79    wxID_DRAWDISTVP,
80] = [wx.NewId() for item in range(13)]
81
82[ wxID_DRAWRESTRBOND, wxID_DRAWRESTRANGLE, wxID_DRAWRESTRPLANE, wxID_DRAWRESTRCHIRAL,
83] = [wx.NewId() for item in range(4)]
84
85[ wxID_ADDMCSAATOM,wxID_ADDMCSARB,wxID_CLEARMCSARB,wxID_MOVEMCSA,wxID_MCSACLEARRESULTS,
86] = [wx.NewId() for item in range(5)]
87
88[ wxID_CLEARTEXTURE,wxID_REFINETEXTURE,
89] = [wx.NewId() for item in range(2)]
90
91[ wxID_PAWLEYLOAD, wxID_PAWLEYESTIMATE, wxID_PAWLEYUPDATE,
92] = [wx.NewId() for item in range(3)]
93
94[ wxID_IMCALIBRATE,wxID_IMRECALIBRATE,wxID_IMINTEGRATE, wxID_IMCLEARCALIB, 
95    wxID_IMCOPYCONTROLS, wxID_INTEGRATEALL, wxID_IMSAVECONTROLS, wxID_IMLOADCONTROLS,
96] = [wx.NewId() for item in range(8)]
97
98[ wxID_MASKCOPY, wxID_MASKSAVE, wxID_MASKLOAD,wxID_NEWMASKSPOT,wxID_NEWMASKARC,wxID_NEWMASKRING,
99  wxID_NEWMASKFRAME, wxID_NEWMASKPOLY,
100] = [wx.NewId() for item in range(8)]
101
102
103[ wxID_STRSTACOPY, wxID_STRSTAFIT, wxID_STRSTASAVE, wxID_STRSTALOAD,wxID_APPENDDZERO,
104] = [wx.NewId() for item in range(5)]
105
106[ wxID_BACKCOPY,wxID_LIMITCOPY,wxID_SAMPLECOPY, wxID_BACKFLAGCOPY, wxID_SAMPLEFLAGCOPY,
107    wxID_SAMPLESAVE, wxID_SAMPLELOAD,wxID_ADDEXCLREGION,
108] = [wx.NewId() for item in range(8)]
109
110[ wxID_INSTPRMRESET,wxID_CHANGEWAVETYPE,wxID_INSTCOPY, wxID_INSTFLAGCOPY, wxID_INSTLOAD,
111    wxID_INSTSAVE,
112] = [wx.NewId() for item in range(6)]
113
114[ wxID_UNDO,wxID_LSQPEAKFIT,wxID_LSQONECYCLE,wxID_RESETSIGGAM,wxID_CLEARPEAKS,wxID_AUTOSEARCH,
115] = [wx.NewId() for item in range(6)]
116
117[  wxID_INDXRELOAD, wxID_INDEXPEAKS, wxID_REFINECELL, wxID_COPYCELL, wxID_MAKENEWPHASE,
118] = [wx.NewId() for item in range(5)]
119
120[ wxID_CONSTRAINTADD,wxID_EQUIVADD,wxID_HOLDADD,wxID_FUNCTADD,
121  wxID_CONSPHASE, wxID_CONSHIST, wxID_CONSHAP, wxID_CONSGLOBAL,
122] = [wx.NewId() for item in range(8)]
123
124[ wxID_RESTRAINTADD, wxID_RESTSELPHASE,wxID_RESTDELETE, wxID_RESRCHANGEVAL, 
125    wxID_RESTCHANGEESD,wxID_AARESTRAINTADD,wxID_AARESTRAINTPLOT,
126] = [wx.NewId() for item in range(7)]
127
128[ wxID_RIGIDBODYADD,wxID_DRAWDEFINERB,wxID_RIGIDBODYIMPORT,wxID_RESIDUETORSSEQ,
129    wxID_AUTOFINDRESRB,wxID_GLOBALRESREFINE,wxID_RBREMOVEALL,wxID_COPYRBPARMS,
130    wxID_GLOBALTHERM,
131] = [wx.NewId() for item in range(9)]
132
133[ wxID_SAVESEQSEL,
134] = [wx.NewId() for item in range(1)]
135
136[ wxID_SELECTPHASE,
137] = [wx.NewId() for item in range(1)]
138
139[ wxID_PDFCOPYCONTROLS, wxID_PDFSAVECONTROLS, wxID_PDFLOADCONTROLS, 
140    wxID_PDFCOMPUTE, wxID_PDFCOMPUTEALL, wxID_PDFADDELEMENT, wxID_PDFDELELEMENT,
141] = [wx.NewId() for item in range(7)]
142
143VERY_LIGHT_GREY = wx.Colour(235,235,235)
144################################################################################
145#### GSAS-II class definitions
146################################################################################
147       
148class ValidatedTxtCtrl(wx.TextCtrl):
149    '''Create a TextCtrl widget that uses a validator to prevent the
150    entry of inappropriate characters and changes color to highlight
151    when invalid input is supplied. As valid values are typed,
152    they are placed into the dict or list where the initial value
153    came from. The type of the initial value must be int,
154    float or str or None (see :obj:`key` and :obj:`typeHint`);
155    this type (or the one in :obj:`typeHint`) is preserved.
156
157    Float values can be entered in the TextCtrl as numbers or also
158    as algebraic expressions using operators + - / \* () and \*\*,
159    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
160    as well as appreviations s, sin, c, cos, t, tan and sq.
161
162    :param wx.Panel parent: name of panel or frame that will be
163      the parent to the TextCtrl. Can be None.
164
165    :param dict/list loc: the dict or list with the initial value to be
166      placed in the TextCtrl.
167
168    :param int/str key: the dict key or the list index for the value to be
169      edited by the TextCtrl. The ``loc[key]`` element must exist, but may
170      have value None. If None, the type for the element is taken from
171      :obj:`typeHint` and the value for the control is set initially
172      blank (and thus invalid.) This is a way to specify a field without a
173      default value: a user must set a valid value.
174      If the value is not None, it must have a base
175      type of int, float, str or unicode; the TextCrtl will be initialized
176      from this value.
177     
178    :param list [nDig,nPlc]: number of digits & places after decimal to use
179      for display of float. Alternately, None can be specified which causes
180      numbers to be displayed with approximately 5 significant figures
181      (Default=None).
182
183    :param bool notBlank: if True (default) blank values are invalid
184      for str inputs.
185     
186    :param number min: minimum allowed valid value. If None (default) the
187      lower limit is unbounded.
188
189    :param number max: maximum allowed valid value. If None (default) the
190      upper limit is unbounded
191
192    :param function OKcontrol: specifies a function or method that will be
193      called when the input is validated. The called function is supplied
194      with one argument which is False if the TextCtrl contains an invalid
195      value and True if the value is valid.
196      Note that this function should check all values
197      in the dialog when True, since other entries might be invalid.
198      The default for this is None, which indicates no function should
199      be called.
200
201    :param function OnLeave: specifies a function or method that will be
202      called when the focus for the control is lost.
203      The called function is supplied with (at present) three keyword arguments:
204
205         * invalid: (*bool*) True if the value for the TextCtrl is invalid
206         * value:   (*int/float/str*)  the value contained in the TextCtrl
207         * tc:      (*wx.TextCtrl*)  the TextCtrl name
208
209      The number of keyword arguments may be increased in the future, if needs arise,
210      so it is best to code these functions with a \*\*kwargs argument so they will
211      continue to run without errors
212
213      The default for OnLeave is None, which indicates no function should
214      be called.
215
216    :param type typeHint: the value of typeHint is overrides the initial value
217      for the dict/list element ``loc[key]``, if set to
218      int or float, which specifies the type for input to the TextCtrl.
219      Defaults as None, which is ignored.
220
221    :param bool CIFinput: for str input, indicates that only printable
222      ASCII characters may be entered into the TextCtrl. Forces output
223      to be ASCII rather than Unicode. For float and int input, allows
224      use of a single '?' or '.' character as valid input.
225
226    :param (other): other optional keyword parameters for the
227      wx.TextCtrl widget such as Size or Style may be specified.
228
229    '''
230    def __init__(self,parent,loc,key,nDig=None,notBlank=True,min=None,max=None,
231                 OKcontrol=None,OnLeave=None,typeHint=None,
232                 CIFinput=False, **kw):
233        # save passed values needed outside __init__
234        self.result = loc
235        self.key = key
236        self.nDig = nDig
237        self.OKcontrol=OKcontrol
238        self.OnLeave = OnLeave
239        self.CIFinput = CIFinput
240        self.type = str
241        # initialization
242        self.invalid = False   # indicates if the control has invalid contents
243        self.evaluated = False # set to True when the validator recognizes an expression
244        val = loc[key]
245        if isinstance(val,int) or typeHint is int:
246            self.type = int
247            wx.TextCtrl.__init__(
248                self,parent,wx.ID_ANY,
249                validator=NumberValidator(int,result=loc,key=key,
250                                          min=min,max=max,
251                                          OKcontrol=OKcontrol,
252                                          CIFinput=CIFinput),
253                **kw)
254            if val is not None:
255                self.SetValue(val)
256            else: # no default is invalid for a number
257                self.invalid = True
258                self._IndicateValidity()
259
260        elif isinstance(val,float) or typeHint is float:
261            self.type = float
262            wx.TextCtrl.__init__(
263                self,parent,wx.ID_ANY,
264                validator=NumberValidator(float,result=loc,key=key,
265                                          min=min,max=max,
266                                          OKcontrol=OKcontrol,
267                                          CIFinput=CIFinput),
268                **kw)
269            if val is not None:
270                self.SetValue(val)
271            else:
272                self.invalid = True
273                self._IndicateValidity()
274
275        elif isinstance(val,str) or isinstance(val,unicode):
276            if self.CIFinput:
277                wx.TextCtrl.__init__(
278                    self,parent,wx.ID_ANY,val,
279                    validator=ASCIIValidator(result=loc,key=key),
280                    **kw)
281            else:
282                wx.TextCtrl.__init__(self,parent,wx.ID_ANY,val,**kw)
283            if notBlank:
284                self.Bind(wx.EVT_CHAR,self._onStringKey)
285                self.ShowStringValidity() # test if valid input
286            else:
287                self.invalid = False
288                self.Bind(wx.EVT_CHAR,self._GetStringValue)
289        elif val is None:
290            raise Exception,("ValidatedTxtCtrl error: value of "+str(key)+
291                             " element is None and typeHint not defined as int or float")
292        else:
293            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
294                             ") type: "+str(type(val)))
295        # When the mouse is moved away or the widget loses focus
296        # display the last saved value, if an expression
297        self.Bind(wx.EVT_LEAVE_WINDOW, self._onLoseFocus)
298        self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
299
300    def SetValue(self,val):
301        self.invalid = False
302        if self.type is int:
303            try:
304                if int(val) != val:
305                    self.invalid = True
306                else:
307                    val = int(val)
308            except:
309                if self.CIFinput and (val == '?' or val == '.'):
310                    pass
311                else:
312                    self.invalid = True
313            wx.TextCtrl.SetValue(self,str(val))
314        elif self.type is float:
315            try:
316                float(val)
317            except:
318                if self.CIFinput and (val == '?' or val == '.'):
319                    pass
320                else:
321                    self.invalid = True
322            if self.nDig:
323                wx.TextCtrl.SetValue(self,str(G2py3.FormatValue(val,self.nDig)))
324            else:
325                wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)))
326        else:
327            wx.TextCtrl.SetValue(self,str(val))
328            self.ShowStringValidity() # test if valid input
329            return
330       
331        self._IndicateValidity()
332        if self.OKcontrol:
333            self.OKcontrol(not self.invalid)
334       
335    def _onStringKey(self,event):
336        event.Skip()
337        if self.invalid: # check for validity after processing the keystroke
338            wx.CallAfter(self.ShowStringValidity,True) # was invalid
339        else:
340            wx.CallAfter(self.ShowStringValidity,False) # was valid
341
342    def _IndicateValidity(self):
343        'Set the control colors to show invalid input'
344        if self.invalid:
345            self.SetForegroundColour("red")
346            self.SetBackgroundColour("yellow")
347            self.SetFocus()
348            self.Refresh()
349        else: # valid input
350            self.SetBackgroundColour(
351                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
352            self.SetForegroundColour("black")
353            self.Refresh()
354
355    def ShowStringValidity(self,previousInvalid=True):
356        '''Check if input is valid. Anytime the input is
357        invalid, call self.OKcontrol (if defined) because it is fast.
358        If valid, check for any other invalid entries only when
359        changing from invalid to valid, since that is slower.
360       
361        :param bool previousInvalid: True if the TextCtrl contents were
362          invalid prior to the current change.
363         
364        '''
365        val = self.GetValue().strip()
366        self.invalid = not val
367        self._IndicateValidity()
368        if self.invalid:
369            if self.OKcontrol:
370                self.OKcontrol(False)
371        elif self.OKcontrol and previousInvalid:
372            self.OKcontrol(True)
373        # always store the result
374        if self.CIFinput: # for CIF make results ASCII
375            self.result[self.key] = val.encode('ascii','replace') 
376        else:
377            self.result[self.key] = val
378
379    def _GetStringValue(self,event):
380        '''Get string input and store.
381        '''
382        event.Skip() # process keystroke
383        wx.CallAfter(self._SaveStringValue)
384       
385    def _SaveStringValue(self):
386        val = self.GetValue().strip()
387        # always store the result
388        if self.CIFinput: # for CIF make results ASCII
389            self.result[self.key] = val.encode('ascii','replace') 
390        else:
391            self.result[self.key] = val
392
393    def _onLoseFocus(self,event):
394        if self.evaluated: self.EvaluateExpression()
395        if self.OnLeave: self.OnLeave(invalid=self.invalid,
396                                      value=self.result[self.key],
397                                      tc=self)
398           
399    def EvaluateExpression(self):
400        '''Show the computed value when an expression is entered to the TextCtrl
401        Make sure that the number fits by truncating decimal places and switching
402        to scientific notation, as needed.
403        Called on loss of focus.
404        '''
405        if self.invalid: return # don't substitute for an invalid expression
406        if not self.evaluated: return # true when an expression is evaluated
407        if self.result is not None: # retrieve the stored result
408            self.SetValue(self.result[self.key])
409        self.evaluated = False # expression has been recast as value, reset flag
410       
411class NumberValidator(wx.PyValidator):
412    '''A validator to be used with a TextCtrl to prevent
413    entering characters other than digits, signs, and for float
414    input, a period and exponents.
415   
416    The value is checked for validity after every keystroke
417      If an invalid number is entered, the box is highlighted.
418      If the number is valid, it is saved in result[key]
419
420    :param type typ: the base data type. Must be int or float.
421
422    :param bool positiveonly: If True, negative integers are not allowed
423      (default False). This prevents the + or - keys from being pressed.
424      Used with typ=int; ignored for typ=float.
425
426    :param number min: Minimum allowed value. If None (default) the
427      lower limit is unbounded
428
429    :param number max: Maximum allowed value. If None (default) the
430      upper limit is unbounded
431     
432    :param dict/list result: List or dict where value should be placed when valid
433
434    :param any key: key to use for result (int for list)
435
436    :param function OKcontrol: function or class method to control
437      an OK button for a window.
438      Ignored if None (default)
439
440    :param bool CIFinput: allows use of a single '?' or '.' character
441      as valid input.
442     
443    '''
444    def __init__(self, typ, positiveonly=False, min=None, max=None,
445                 result=None, key=None, OKcontrol=None, CIFinput=False):
446        'Create the validator'
447        wx.PyValidator.__init__(self)
448        # save passed parameters
449        self.typ = typ
450        self.positiveonly = positiveonly
451        self.min = min
452        self.max = max
453        self.result = result
454        self.key = key
455        self.OKcontrol = OKcontrol
456        self.CIFinput = CIFinput
457        # set allowed keys by data type
458        self.Bind(wx.EVT_CHAR, self.OnChar)
459        if self.typ == int and self.positiveonly:
460            self.validchars = '0123456789'
461        elif self.typ == int:
462            self.validchars = '0123456789+-'
463        elif self.typ == float:
464            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
465            # also addition, subtraction, division, multiplication, exponentiation
466            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
467        else:
468            self.validchars = None
469            return
470        if self.CIFinput:
471            self.validchars += '?.'
472    def Clone(self):
473        'Create a copy of the validator, a strange, but required component'
474        return NumberValidator(typ=self.typ, 
475                               positiveonly=self.positiveonly,
476                               min=self.min, max=self.max,
477                               result=self.result, key=self.key,
478                               OKcontrol=self.OKcontrol,
479                               CIFinput=self.CIFinput)
480    def TransferToWindow(self):
481        'Needed by validator, strange, but required component'
482        return True # Prevent wxDialog from complaining.
483    def TransferFromWindow(self):
484        'Needed by validator, strange, but required component'
485        return True # Prevent wxDialog from complaining.
486    def TestValid(self,tc):
487        '''Check if the value is valid by casting the input string
488        into the current type.
489
490        Set the invalid variable in the TextCtrl object accordingly.
491
492        If the value is valid, save it in the dict/list where
493        the initial value was stored, if appropriate.
494
495        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
496          is associated with.
497        '''
498        tc.invalid = False # assume valid
499        if self.CIFinput:
500            val = tc.GetValue().strip()
501            if val == '?' or val == '.':
502                self.result[self.key] = val
503                return
504        try:
505            val = self.typ(tc.GetValue())
506        except (ValueError, SyntaxError) as e:
507            if self.typ is float: # for float values, see if an expression can be evaluated
508                val = G2py3.FormulaEval(tc.GetValue())
509                if val is None:
510                    tc.invalid = True
511                    return
512                else:
513                    tc.evaluated = True
514            else: 
515                tc.invalid = True
516                return
517        # if self.max != None and self.typ == int:
518        #     if val > self.max:
519        #         tc.invalid = True
520        # if self.min != None and self.typ == int:
521        #     if val < self.min:
522        #         tc.invalid = True  # invalid
523        if self.max != None:
524            if val > self.max:
525                tc.invalid = True
526        if self.min != None:
527            if val < self.min:
528                tc.invalid = True  # invalid
529        if self.key is not None and self.result is not None and not tc.invalid:
530            self.result[self.key] = val
531
532    def ShowValidity(self,tc):
533        '''Set the control colors to show invalid input
534
535        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
536          is associated with.
537
538        '''
539        if tc.invalid:
540            tc.SetForegroundColour("red")
541            tc.SetBackgroundColour("yellow")
542            tc.SetFocus()
543            tc.Refresh()
544            return False
545        else: # valid input
546            tc.SetBackgroundColour(
547                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
548            tc.SetForegroundColour("black")
549            tc.Refresh()
550            return True
551
552    def CheckInput(self,previousInvalid):
553        '''called to test every change to the TextCtrl for validity and
554        to change the appearance of the TextCtrl
555
556        Anytime the input is invalid, call self.OKcontrol
557        (if defined) because it is fast.
558        If valid, check for any other invalid entries only when
559        changing from invalid to valid, since that is slower.
560
561        :param bool previousInvalid: True if the TextCtrl contents were
562          invalid prior to the current change.
563        '''
564        tc = self.GetWindow()
565        self.TestValid(tc)
566        self.ShowValidity(tc)
567        # if invalid
568        if tc.invalid and self.OKcontrol:
569            self.OKcontrol(False)
570        if not tc.invalid and self.OKcontrol and previousInvalid:
571            self.OKcontrol(True)
572
573    def OnChar(self, event):
574        '''Called each type a key is pressed
575        ignores keys that are not allowed for int and float types
576        '''
577        key = event.GetKeyCode()
578        tc = self.GetWindow()
579        if key == wx.WXK_RETURN:
580            if tc.invalid:
581                self.CheckInput(True) 
582            else:
583                self.CheckInput(False) 
584            return
585        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
586            event.Skip()
587            if tc.invalid:
588                wx.CallAfter(self.CheckInput,True) 
589            else:
590                wx.CallAfter(self.CheckInput,False) 
591            return
592        elif chr(key) in self.validchars: # valid char pressed?
593            event.Skip()
594            if tc.invalid:
595                wx.CallAfter(self.CheckInput,True) 
596            else:
597                wx.CallAfter(self.CheckInput,False) 
598            return
599        if not wx.Validator_IsSilent(): wx.Bell()
600        return  # Returning without calling event.Skip, which eats the keystroke
601
602################################################################################
603class ASCIIValidator(wx.PyValidator):
604    '''A validator to be used with a TextCtrl to prevent
605    entering characters other than ASCII characters.
606   
607    The value is checked for validity after every keystroke
608      If an invalid number is entered, the box is highlighted.
609      If the number is valid, it is saved in result[key]
610
611    :param dict/list result: List or dict where value should be placed when valid
612
613    :param any key: key to use for result (int for list)
614
615    '''
616    def __init__(self, result=None, key=None):
617        'Create the validator'
618        import string
619        wx.PyValidator.__init__(self)
620        # save passed parameters
621        self.result = result
622        self.key = key
623        self.validchars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
624        self.Bind(wx.EVT_CHAR, self.OnChar)
625    def Clone(self):
626        'Create a copy of the validator, a strange, but required component'
627        return ASCIIValidator(result=self.result, key=self.key)
628        tc = self.GetWindow()
629        tc.invalid = False # make sure the validity flag is defined in parent
630    def TransferToWindow(self):
631        'Needed by validator, strange, but required component'
632        return True # Prevent wxDialog from complaining.
633    def TransferFromWindow(self):
634        'Needed by validator, strange, but required component'
635        return True # Prevent wxDialog from complaining.
636    def TestValid(self,tc):
637        '''Check if the value is valid by casting the input string
638        into ASCII.
639
640        Save it in the dict/list where the initial value was stored
641
642        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
643          is associated with.
644        '''
645        self.result[self.key] = tc.GetValue().encode('ascii','replace')
646
647    def OnChar(self, event):
648        '''Called each type a key is pressed
649        ignores keys that are not allowed for int and float types
650        '''
651        key = event.GetKeyCode()
652        tc = self.GetWindow()
653        if key == wx.WXK_RETURN:
654            self.TestValid(tc)
655            return
656        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
657            event.Skip()
658            self.TestValid(tc)
659            return
660        elif chr(key) in self.validchars: # valid char pressed?
661            event.Skip()
662            self.TestValid(tc)
663            return
664        if not wx.Validator_IsSilent():
665            wx.Bell()
666        return  # Returning without calling event.Skip, which eats the keystroke
667################################################################################
668class EnumSelector(wx.ComboBox):
669    '''A customized :class:`wxpython.ComboBox` that selects items from a list
670    of choices, but sets a dict (list) entry to the corresponding
671    entry from the input list of values.
672
673    :param wx.Panel parent: the parent to the :class:`~wxpython.ComboBox` (usually a
674      frame or panel)
675    :param dict dct: a dict (or list) to contain the value set
676      for the :class:`~wxpython.ComboBox`.
677    :param item: the dict key (or list index) where ``dct[item]`` will
678      be set to the value selected in the :class:`~wxpython.ComboBox`. Also, dct[item]
679      contains the starting value shown in the widget. If the value
680      does not match an entry in :data:`values`, the first value
681      in :data:`choices` is used as the default, but ``dct[item]`` is
682      not changed.   
683    :param list choices: a list of choices to be displayed to the
684      user such as
685      ::
686     
687      ["default","option 1","option 2",]
688
689      Note that these options will correspond to the entries in
690      :data:`values` (if specified) item by item.
691    :param list values: a list of values that correspond to
692      the options in :data:`choices`, such as
693      ::
694     
695      [0,1,2]
696     
697      The default for :data:`values` is to use the same list as
698      specified for :data:`choices`.
699    :param (other): additional keyword arguments accepted by
700      :class:`~wxpython.ComboBox` can be specified.
701    '''
702    def __init__(self,parent,dct,item,choices,values=None,**kw):
703        if values is None:
704            values = choices
705        if dct[item] in values:
706            i = values.index(dct[item])
707        else:
708            i = 0
709        startval = choices[i]
710        wx.ComboBox.__init__(self,parent,wx.ID_ANY,startval,
711                             choices = choices,
712                             style=wx.CB_DROPDOWN|wx.CB_READONLY,
713                             **kw)
714        self.choices = choices
715        self.values = values
716        self.dct = dct
717        self.item = item
718        self.Bind(wx.EVT_COMBOBOX, self.onSelection)
719    def onSelection(self,event):
720        # respond to a selection by setting the enum value in the CIF dictionary
721        if self.GetValue() in self.choices: # should always be true!
722            self.dct[self.item] = self.values[self.choices.index(self.GetValue())]
723        else:
724            self.dct[self.item] = self.values[0] # unknown
725
726################################################################################
727class G2CheckBox(wx.CheckBox):
728    '''A customized version of a CheckBox that automatically initializes
729    the control to match a supplied
730
731    :param wx.Panel parent: name of panel or frame that will be
732      the parent to the TextCtrl. Can be None.
733    :param str label: text to put on check button
734    :param dict/list loc: the dict or list with the initial value to be
735      placed in the CheckBox.
736    :param int/str key: the dict key or the list index for the value to be
737      edited by the CheckBox. The ``loc[key]`` element must exist.
738      The CheckBox will be initialized from this value.
739      If the value is anything other that True (or 1), it will be taken as
740      False.
741    '''
742    def __init__(self,parent,label,loc,key):
743        wx.CheckBox.__init__(self,parent,id=wx.ID_ANY,label=label)
744        self.loc = loc
745        self.key = key
746        self.SetValue(self.loc[self.key]==True)
747        self.Bind(wx.EVT_CHECKBOX, self._OnCheckBox)
748    def _OnCheckBox(self,event):
749        self.loc[self.key] = self.GetValue()
750################################################################################   
751def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[],
752                 title='Edit items',header='',size=(300,250),
753                             CopyButton=False):
754    '''Shell routine to call a ScrolledMultiEditor dialog. See
755    :class:`ScrolledMultiEditor` for parameter definitions.
756
757    :returns: True if the OK button is pressed; False if the window is closed
758      with the system menu or the Cancel button.
759
760    '''
761    dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl,
762                              title,header,size,
763                              CopyButton)
764    if dlg.ShowModal() == wx.ID_OK:
765        dlg.Destroy()
766        return True
767    else:
768        dlg.Destroy()
769        return False
770
771class ScrolledMultiEditor(wx.Dialog):
772    '''Define a window for editing a potentially large number of dict- or
773    list-contained values with validation for each item. Edited values are
774    automatically placed in their source location. If invalid entries
775    are provided, the TextCtrl is turned yellow and the OK button is disabled.
776
777    The type for each TextCtrl validation is determined by the
778    initial value of the entry (int, float or string).
779    Float values can be entered in the TextCtrl as numbers or also
780    as algebraic expressions using operators + - / \* () and \*\*,
781    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
782    as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq().
783
784    :param wx.Frame parent: name of parent window, or may be None
785
786    :param tuple dictlst: a list of dicts or lists containing values to edit
787
788    :param tuple elemlst: a list of keys for each item in a dictlst. Must have the
789      same length as dictlst.
790
791    :param wx.Frame parent: name of parent window, or may be None
792   
793    :param tuple prelbl: a list of labels placed before the TextCtrl for each
794      item (optional)
795   
796    :param tuple postlbl: a list of labels placed after the TextCtrl for each
797      item (optional)
798
799    :param str title: a title to place in the frame of the dialog
800
801    :param str header: text to place at the top of the window. May contain
802      new line characters.
803
804    :param wx.Size size: a size parameter that dictates the
805      size for the scrolled region of the dialog. The default is
806      (300,250).
807
808    :param bool CopyButton: if True adds a small button that copies the
809      value for the current row to all fields below (default is False)
810     
811    :param list minvals: optional list of minimum values for validation
812      of float or int values. Ignored if value is None.
813    :param list maxvals: optional list of maximum values for validation
814      of float or int values. Ignored if value is None.
815    :param list sizevals: optional list of wx.Size values for each input
816      widget. Ignored if value is None.
817    :returns: the wx.Dialog created here. Use method .ShowModal() to display it.
818   
819    *Example for use of ScrolledMultiEditor:*
820
821    ::
822
823        dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl,
824                                        header=header)
825        if dlg.ShowModal() == wx.ID_OK:
826             for d,k in zip(dictlst,elemlst):
827                 print d[k]
828
829    *Example definitions for dictlst and elemlst:*
830
831    ::
832     
833          dictlst = (dict1,list1,dict1,list1)
834          elemlst = ('a', 1, 2, 3)
835
836      This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited.
837   
838    Note that these items must have int, float or str values assigned to
839    them. The dialog will force these types to be retained. String values
840    that are blank are marked as invalid.
841    '''
842   
843    def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[],
844                 title='Edit items',header='',size=(300,250),
845                 CopyButton=False,
846                 minvals=[],maxvals=[],sizevals=[]):
847        if len(dictlst) != len(elemlst):
848            raise Exception,"ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))
849        wx.Dialog.__init__( # create dialog & sizer
850            self,parent,wx.ID_ANY,title,
851            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
852        mainSizer = wx.BoxSizer(wx.VERTICAL)
853        self.orig = []
854        self.dictlst = dictlst
855        self.elemlst = elemlst
856        self.ButtonIndex = {}
857        for d,i in zip(dictlst,elemlst):
858            self.orig.append(d[i])
859        # add a header if supplied
860        if header:
861            subSizer = wx.BoxSizer(wx.HORIZONTAL)
862            subSizer.Add((-1,-1),1,wx.EXPAND)
863            subSizer.Add(wx.StaticText(self,wx.ID_ANY,header))
864            subSizer.Add((-1,-1),1,wx.EXPAND)
865            mainSizer.Add(subSizer,0,wx.EXPAND,0)
866        # make OK button now, because we will need it for validation
867        self.OKbtn = wx.Button(self, wx.ID_OK)
868        self.OKbtn.SetDefault()
869        # create scrolled panel and sizer
870        panel = wxscroll.ScrolledPanel(
871            self, wx.ID_ANY,
872            size=size,
873            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
874        cols = 3
875        if CopyButton: cols += 1
876        subSizer = wx.FlexGridSizer(rows=len(dictlst),cols=cols,hgap=2,vgap=2)
877        self.ValidatedControlsList = [] # make list of TextCtrls
878        for i,(d,k) in enumerate(zip(dictlst,elemlst)):
879            if i >= len(prelbl): # label before TextCtrl, or put in a blank
880                subSizer.Add((-1,-1)) 
881            else:
882                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i])))
883            kargs = {}
884            if i < len(minvals):
885                if minvals[i] is not None: kargs['min']=minvals[i]
886            if i < len(maxvals):
887                if maxvals[i] is not None: kargs['max']=maxvals[i]
888            if i < len(sizevals):
889                if sizevals[i]: kargs['size']=sizevals[i]
890            if CopyButton:
891                import wx.lib.colourselect as wscs
892                but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP
893                                        # is there a way to test?
894                                        parent=panel,
895                                        colour=(255,255,200),
896                                        size=wx.Size(30,23),
897                                        style=wx.RAISED_BORDER)
898                but.Bind(wx.EVT_BUTTON, self._OnCopyButton)
899                but.SetToolTipString('Press to copy adjacent value to all rows below')
900                self.ButtonIndex[but] = i
901                subSizer.Add(but)
902            # create the validated TextCrtl, store it and add it to the sizer
903            ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,
904                                    **kargs)
905            self.ValidatedControlsList.append(ctrl)
906            subSizer.Add(ctrl)
907            if i >= len(postlbl): # label after TextCtrl, or put in a blank
908                subSizer.Add((-1,-1))
909            else:
910                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i])))
911        # finish up ScrolledPanel
912        panel.SetSizer(subSizer)
913        panel.SetAutoLayout(1)
914        panel.SetupScrolling()
915        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
916
917        # Sizer for OK/Close buttons. N.B. on Close changes are discarded
918        # by restoring the initial values
919        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
920        btnsizer.Add(self.OKbtn)
921        btn = wx.Button(self, wx.ID_CLOSE,"Cancel") 
922        btn.Bind(wx.EVT_BUTTON,self._onClose)
923        btnsizer.Add(btn)
924        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
925        # size out the window. Set it to be enlarged but not made smaller
926        self.SetSizer(mainSizer)
927        mainSizer.Fit(self)
928        self.SetMinSize(self.GetSize())
929
930    def _OnCopyButton(self,event):
931        'Implements the copy down functionality'
932        but = event.GetEventObject()
933        n = self.ButtonIndex.get(but)
934        if n is None: return
935        for i,(d,k,ctrl) in enumerate(zip(self.dictlst,self.elemlst,self.ValidatedControlsList)):
936            if i < n: continue
937            if i == n:
938                val = d[k]
939                continue
940            d[k] = val
941            ctrl.SetValue(val)
942
943    def _onClose(self,event):
944        'Restore original values & close the window'
945        for d,i,v in zip(self.dictlst,self.elemlst,self.orig):
946            d[i] = v
947        self.EndModal(wx.ID_CANCEL)
948       
949    def ControlOKButton(self,setvalue):
950        '''Enable or Disable the OK button for the dialog. Note that this is
951        passed into the ValidatedTxtCtrl for use by validators.
952
953        :param bool setvalue: if True, all entries in the dialog are
954          checked for validity. if False then the OK button is disabled.
955
956        '''
957        if setvalue: # turn button on, do only if all controls show as valid
958            for ctrl in self.ValidatedControlsList:
959                if ctrl.invalid:
960                    self.OKbtn.Disable()
961                    return
962            else:
963                self.OKbtn.Enable()
964        else:
965            self.OKbtn.Disable()
966
967################################################################################
968class SymOpDialog(wx.Dialog):
969    '''Class to select a symmetry operator
970    '''
971    def __init__(self,parent,SGData,New=True,ForceUnit=False):
972        wx.Dialog.__init__(self,parent,-1,'Select symmetry operator',
973            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
974        panel = wx.Panel(self)
975        self.SGData = SGData
976        self.New = New
977        self.Force = ForceUnit
978        self.OpSelected = [0,0,0,[0,0,0],False,False]
979        mainSizer = wx.BoxSizer(wx.VERTICAL)
980        if ForceUnit:
981            choice = ['No','Yes']
982            self.force = wx.RadioBox(panel,-1,'Force to unit cell?',choices=choice)
983            self.force.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
984            mainSizer.Add(self.force,0,wx.ALIGN_CENTER_VERTICAL)
985        mainSizer.Add((5,5),0)
986        if SGData['SGInv']:
987            choice = ['No','Yes']
988            self.inv = wx.RadioBox(panel,-1,'Choose inversion?',choices=choice)
989            self.inv.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
990            mainSizer.Add(self.inv,0,wx.ALIGN_CENTER_VERTICAL)
991        mainSizer.Add((5,5),0)
992        if SGData['SGLatt'] != 'P':
993            LattOp = G2spc.Latt2text(SGData['SGLatt']).split(';')
994            self.latt = wx.RadioBox(panel,-1,'Choose cell centering?',choices=LattOp)
995            self.latt.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
996            mainSizer.Add(self.latt,0,wx.ALIGN_CENTER_VERTICAL)
997        mainSizer.Add((5,5),0)
998        if SGData['SGLaue'] in ['-1','2/m','mmm','4/m','4/mmm']:
999            Ncol = 2
1000        else:
1001            Ncol = 3
1002        OpList = []
1003        for M,T in SGData['SGOps']:
1004            OpList.append(G2spc.MT2text(M,T))
1005        self.oprs = wx.RadioBox(panel,-1,'Choose space group operator?',choices=OpList,
1006            majorDimension=Ncol)
1007        self.oprs.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1008        mainSizer.Add(self.oprs,0,wx.ALIGN_CENTER_VERTICAL)
1009        mainSizer.Add((5,5),0)
1010        mainSizer.Add(wx.StaticText(panel,-1,"   Choose unit cell?"),0,wx.ALIGN_CENTER_VERTICAL)
1011        mainSizer.Add((5,5),0)
1012        cellSizer = wx.BoxSizer(wx.HORIZONTAL)
1013        cellSizer.Add((5,0),0)
1014        cellName = ['X','Y','Z']
1015        self.cell = []
1016        for i in range(3):
1017            self.cell.append(wx.SpinCtrl(panel,-1,cellName[i],size=wx.Size(50,20)))
1018            self.cell[-1].SetRange(-3,3)
1019            self.cell[-1].SetValue(0)
1020            self.cell[-1].Bind(wx.EVT_SPINCTRL, self.OnOpSelect)
1021            cellSizer.Add(self.cell[-1],0,wx.ALIGN_CENTER_VERTICAL)
1022        mainSizer.Add(cellSizer,0,)
1023        if self.New:
1024            choice = ['No','Yes']
1025            self.new = wx.RadioBox(panel,-1,'Generate new positions?',choices=choice)
1026            self.new.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1027            mainSizer.Add(self.new,0,wx.ALIGN_CENTER_VERTICAL)
1028        mainSizer.Add((5,5),0)
1029
1030        OkBtn = wx.Button(panel,-1,"Ok")
1031        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1032        cancelBtn = wx.Button(panel,-1,"Cancel")
1033        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1034        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1035        btnSizer.Add((20,20),1)
1036        btnSizer.Add(OkBtn)
1037        btnSizer.Add((20,20),1)
1038        btnSizer.Add(cancelBtn)
1039        btnSizer.Add((20,20),1)
1040
1041        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1042        panel.SetSizer(mainSizer)
1043        panel.Fit()
1044        self.Fit()
1045
1046    def OnOpSelect(self,event):
1047        if self.SGData['SGInv']:
1048            self.OpSelected[0] = self.inv.GetSelection()
1049        if self.SGData['SGLatt'] != 'P':
1050            self.OpSelected[1] = self.latt.GetSelection()
1051        self.OpSelected[2] = self.oprs.GetSelection()
1052        for i in range(3):
1053            self.OpSelected[3][i] = float(self.cell[i].GetValue())
1054        if self.New:
1055            self.OpSelected[4] = self.new.GetSelection()
1056        if self.Force:
1057            self.OpSelected[5] = self.force.GetSelection()
1058
1059    def GetSelection(self):
1060        return self.OpSelected
1061
1062    def OnOk(self,event):
1063        parent = self.GetParent()
1064        parent.Raise()
1065        self.EndModal(wx.ID_OK)
1066
1067    def OnCancel(self,event):
1068        parent = self.GetParent()
1069        parent.Raise()
1070        self.EndModal(wx.ID_CANCEL)
1071
1072class DisAglDialog(wx.Dialog):
1073    '''Distance/Angle Controls input dialog. After
1074    :meth:`ShowModal` returns, the results are found in
1075    dict :attr:`self.data`, which is accessed using :meth:`GetData`.
1076
1077    :param wx.Frame parent: reference to parent frame (or None)
1078    :param dict data: a dict containing the current
1079      search ranges or an empty dict, which causes default values
1080      to be used.
1081      Will be used to set element `DisAglCtls` in
1082      :ref:`Phase Tree Item <Phase_table>`
1083    :param dict default:  A dict containing the default
1084      search ranges for each element.
1085    '''
1086    def __init__(self,parent,data,default):
1087        wx.Dialog.__init__(self,parent,wx.ID_ANY,
1088                           'Distance Angle Controls', 
1089            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1090        self.default = default
1091        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1092        self._default(data,self.default)
1093        self.Draw(self.data)
1094               
1095    def _default(self,data,default):
1096        '''Set starting values for the search values, either from
1097        the input array or from defaults, if input is null
1098        '''
1099        if data:
1100            self.data = copy.deepcopy(data) # don't mess with originals
1101        else:
1102            self.data = {}
1103            self.data['Name'] = default['Name']
1104            self.data['Factors'] = [0.85,0.85]
1105            self.data['AtomTypes'] = default['AtomTypes']
1106            self.data['BondRadii'] = default['BondRadii'][:]
1107            self.data['AngleRadii'] = default['AngleRadii'][:]
1108
1109    def Draw(self,data):
1110        '''Creates the contents of the dialog. Normally called
1111        by :meth:`__init__`.
1112        '''
1113        self.panel.Destroy()
1114        self.panel = wx.Panel(self)
1115        mainSizer = wx.BoxSizer(wx.VERTICAL)
1116        mainSizer.Add(wx.StaticText(self.panel,-1,'Controls for phase '+data['Name']),
1117            0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
1118        mainSizer.Add((10,10),1)
1119       
1120        radiiSizer = wx.FlexGridSizer(2,3,5,5)
1121        radiiSizer.Add(wx.StaticText(self.panel,-1,' Type'),0,wx.ALIGN_CENTER_VERTICAL)
1122        radiiSizer.Add(wx.StaticText(self.panel,-1,'Bond radii'),0,wx.ALIGN_CENTER_VERTICAL)
1123        radiiSizer.Add(wx.StaticText(self.panel,-1,'Angle radii'),0,wx.ALIGN_CENTER_VERTICAL)
1124        self.objList = {}
1125        for id,item in enumerate(self.data['AtomTypes']):
1126            radiiSizer.Add(wx.StaticText(self.panel,-1,' '+item),0,wx.ALIGN_CENTER_VERTICAL)
1127            bRadii = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['BondRadii'][id]),style=wx.TE_PROCESS_ENTER)
1128            self.objList[bRadii.GetId()] = ['BondRadii',id]
1129            bRadii.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1130            bRadii.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1131            radiiSizer.Add(bRadii,0,wx.ALIGN_CENTER_VERTICAL)
1132            aRadii = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['AngleRadii'][id]),style=wx.TE_PROCESS_ENTER)
1133            self.objList[aRadii.GetId()] = ['AngleRadii',id]
1134            aRadii.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1135            aRadii.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1136            radiiSizer.Add(aRadii,0,wx.ALIGN_CENTER_VERTICAL)
1137        mainSizer.Add(radiiSizer,0,wx.EXPAND)
1138        factorSizer = wx.FlexGridSizer(2,2,5,5)
1139        Names = ['Bond','Angle']
1140        for i,name in enumerate(Names):
1141            factorSizer.Add(wx.StaticText(self.panel,-1,name+' search factor'),0,wx.ALIGN_CENTER_VERTICAL)
1142            bondFact = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['Factors'][i]),style=wx.TE_PROCESS_ENTER)
1143            self.objList[bondFact.GetId()] = ['Factors',i]
1144            bondFact.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1145            bondFact.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1146            factorSizer.Add(bondFact)
1147        mainSizer.Add(factorSizer,0,wx.EXPAND)
1148       
1149        OkBtn = wx.Button(self.panel,-1,"Ok")
1150        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1151        ResetBtn = wx.Button(self.panel,-1,'Reset')
1152        ResetBtn.Bind(wx.EVT_BUTTON, self.OnReset)
1153        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1154        btnSizer.Add((20,20),1)
1155        btnSizer.Add(OkBtn)
1156        btnSizer.Add(ResetBtn)
1157        btnSizer.Add((20,20),1)
1158        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1159        self.panel.SetSizer(mainSizer)
1160        self.panel.Fit()
1161        self.Fit()
1162   
1163    def OnRadiiVal(self,event):
1164        Obj = event.GetEventObject()
1165        item = self.objList[Obj.GetId()]
1166        try:
1167            self.data[item[0]][item[1]] = float(Obj.GetValue())
1168        except ValueError:
1169            pass
1170        Obj.SetValue("%.3f"%(self.data[item[0]][item[1]]))          #reset in case of error
1171       
1172    def GetData(self):
1173        'Returns the values from the dialog'
1174        return self.data
1175       
1176    def OnOk(self,event):
1177        'Called when the OK button is pressed'
1178        parent = self.GetParent()
1179        parent.Raise()
1180        self.EndModal(wx.ID_OK)             
1181       
1182    def OnReset(self,event):
1183        'Called when the Reset button is pressed'
1184        data = {}
1185        self._default(data,self.default)
1186        self.Draw(self.data)
1187       
1188class PickTwoDialog(wx.Dialog):
1189    '''This does not seem to be in use
1190    '''
1191    def __init__(self,parent,title,prompt,names,choices):
1192        wx.Dialog.__init__(self,parent,-1,title, 
1193            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1194        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1195        self.prompt = prompt
1196        self.choices = choices
1197        self.names = names
1198        self.Draw()
1199
1200    def Draw(self):
1201        Indx = {}
1202       
1203        def OnSelection(event):
1204            Obj = event.GetEventObject()
1205            id = Indx[Obj.GetId()]
1206            self.choices[id] = Obj.GetValue().encode()  #to avoid Unicode versions
1207            self.Draw()
1208           
1209        self.panel.DestroyChildren()
1210        self.panel.Destroy()
1211        self.panel = wx.Panel(self)
1212        mainSizer = wx.BoxSizer(wx.VERTICAL)
1213        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1214        for isel,name in enumerate(self.choices):
1215            lineSizer = wx.BoxSizer(wx.HORIZONTAL)
1216            lineSizer.Add(wx.StaticText(self.panel,-1,'Reference atom '+str(isel+1)),0,wx.ALIGN_CENTER)
1217            nameList = self.names[:]
1218            if isel:
1219                if self.choices[0] in nameList:
1220                    nameList.remove(self.choices[0])
1221            choice = wx.ComboBox(self.panel,-1,value=name,choices=nameList,
1222                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1223            Indx[choice.GetId()] = isel
1224            choice.Bind(wx.EVT_COMBOBOX, OnSelection)
1225            lineSizer.Add(choice,0,wx.ALIGN_CENTER)
1226            mainSizer.Add(lineSizer)
1227        OkBtn = wx.Button(self.panel,-1,"Ok")
1228        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1229        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1230        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1231        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1232        btnSizer.Add((20,20),1)
1233        btnSizer.Add(OkBtn)
1234        btnSizer.Add(CancelBtn)
1235        btnSizer.Add((20,20),1)
1236        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1237        self.panel.SetSizer(mainSizer)
1238        self.panel.Fit()
1239        self.Fit()
1240       
1241    def GetSelection(self):
1242        return self.choices
1243
1244    def OnOk(self,event):
1245        parent = self.GetParent()
1246        parent.Raise()
1247        self.EndModal(wx.ID_OK)             
1248       
1249    def OnCancel(self,event):
1250        parent = self.GetParent()
1251        parent.Raise()
1252        self.EndModal(wx.ID_CANCEL)
1253       
1254class SingleFloatDialog(wx.Dialog):
1255    'Dialog to obtain a single float value from user'
1256    def __init__(self,parent,title,prompt,value,limits=[0.,1.],format='%.5g'):
1257        wx.Dialog.__init__(self,parent,-1,title, 
1258            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1259        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1260        self.limits = limits
1261        self.value = value
1262        self.prompt = prompt
1263        self.format = format
1264        self.Draw()
1265       
1266    def Draw(self):
1267       
1268        def OnValItem(event):
1269            try:
1270                val = float(valItem.GetValue())
1271                if val < self.limits[0] or val > self.limits[1]:
1272                    raise ValueError
1273            except ValueError:
1274                val = self.value
1275            self.value = val
1276            valItem.SetValue(self.format%(self.value))
1277           
1278        self.panel.Destroy()
1279        self.panel = wx.Panel(self)
1280        mainSizer = wx.BoxSizer(wx.VERTICAL)
1281        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1282        valItem = wx.TextCtrl(self.panel,-1,value=self.format%(self.value),style=wx.TE_PROCESS_ENTER)
1283        mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
1284        valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
1285        valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
1286        OkBtn = wx.Button(self.panel,-1,"Ok")
1287        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1288        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1289        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1290        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1291        btnSizer.Add((20,20),1)
1292        btnSizer.Add(OkBtn)
1293        btnSizer.Add(CancelBtn)
1294        btnSizer.Add((20,20),1)
1295        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1296        self.panel.SetSizer(mainSizer)
1297        self.panel.Fit()
1298        self.Fit()
1299
1300    def GetValue(self):
1301        return self.value
1302       
1303    def OnOk(self,event):
1304        parent = self.GetParent()
1305        parent.Raise()
1306        self.EndModal(wx.ID_OK)             
1307       
1308    def OnCancel(self,event):
1309        parent = self.GetParent()
1310        parent.Raise()
1311        self.EndModal(wx.ID_CANCEL)
1312
1313################################################################################
1314class SingleStringDialog(wx.Dialog):
1315    '''Dialog to obtain a single string value from user
1316   
1317    :param wx.Frame parent: name of parent frame
1318    :param str title: title string for dialog
1319    :param str prompt: string to tell use what they are inputting
1320    :param str value: default input value, if any
1321    '''
1322    def __init__(self,parent,title,prompt,value='',size=(200,-1)):
1323        wx.Dialog.__init__(self,parent,wx.ID_ANY,title, 
1324                           pos=wx.DefaultPosition,
1325                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1326        self.value = value
1327        self.prompt = prompt
1328        self.CenterOnParent()
1329        self.panel = wx.Panel(self)
1330        mainSizer = wx.BoxSizer(wx.VERTICAL)
1331        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1332        self.valItem = wx.TextCtrl(self.panel,-1,value=str(self.value),size=size)
1333        mainSizer.Add(self.valItem,0,wx.ALIGN_CENTER)
1334        btnsizer = wx.StdDialogButtonSizer()
1335        OKbtn = wx.Button(self.panel, wx.ID_OK)
1336        OKbtn.SetDefault()
1337        btnsizer.AddButton(OKbtn)
1338        btn = wx.Button(self.panel, wx.ID_CANCEL)
1339        btnsizer.AddButton(btn)
1340        btnsizer.Realize()
1341        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
1342        self.panel.SetSizer(mainSizer)
1343        self.panel.Fit()
1344        self.Fit()
1345
1346    def Show(self):
1347        '''Use this method after creating the dialog to post it
1348        :returns: True if the user pressed OK; False if the User pressed Cancel
1349        '''
1350        if self.ShowModal() == wx.ID_OK:
1351            self.value = self.valItem.GetValue()
1352            return True
1353        else:
1354            return False
1355
1356    def GetValue(self):
1357        '''Use this method to get the value entered by the user
1358        :returns: string entered by user
1359        '''
1360        return self.value
1361
1362################################################################################
1363class G2MultiChoiceDialog(wx.Dialog):
1364    '''A dialog similar to MultiChoiceDialog except that buttons are
1365    added to set all choices and to toggle all choices.
1366
1367    :param wx.Frame ParentFrame: reference to parent frame
1368    :param str title: heading above list of choices
1369    :param str header: Title to place on window frame
1370    :param list ChoiceList: a list of choices where one will be selected
1371    :param bool toggle: If True (default) the toggle and select all buttons
1372      are displayed
1373
1374    :param kw: optional keyword parameters for the wx.Dialog may
1375      be included such as size [which defaults to `(320,310)`] and
1376      style (which defaults to `wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL`);
1377      note that `wx.OK` and `wx.CANCEL` controls
1378      the presence of the eponymous buttons in the dialog.
1379    :returns: the name of the created dialog 
1380    '''
1381    def __init__(self,parent, title, header, ChoiceList, toggle=True, **kw):
1382        # process keyword parameters, notably Style
1383        options = {'size':(320,310), # default Frame keywords
1384                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
1385                   }
1386        options.update(kw)
1387        if options['style'] & wx.OK:
1388            useOK = True
1389            options['style'] ^= wx.OK
1390        else:
1391            useOK = False
1392        if options['style'] & wx.CANCEL:
1393            useCANCEL = True
1394            options['style'] ^= wx.CANCEL
1395        else:
1396            useCANCEL = False       
1397        # create the dialog frame
1398        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
1399        # fill the dialog
1400        Sizer = wx.BoxSizer(wx.VERTICAL)
1401        Sizer.Add(wx.StaticText(self,wx.ID_ANY,title),0,wx.ALL,12)
1402        self.clb = wx.CheckListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
1403        self.numchoices = len(ChoiceList)
1404        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
1405        Sizer.Add((-1,10))
1406        # set/toggle buttons
1407        if toggle:
1408            bSizer = wx.BoxSizer(wx.VERTICAL)
1409            setBut = wx.Button(self,wx.ID_ANY,'Set All')
1410            setBut.Bind(wx.EVT_BUTTON,self._SetAll)
1411            bSizer.Add(setBut,0,wx.ALIGN_CENTER)
1412            bSizer.Add((-1,5))
1413            togBut = wx.Button(self,wx.ID_ANY,'Toggle All')
1414            togBut.Bind(wx.EVT_BUTTON,self._ToggleAll)
1415            bSizer.Add(togBut,0,wx.ALIGN_CENTER)
1416            Sizer.Add(bSizer,0,wx.LEFT,12)
1417        # OK/Cancel buttons
1418        btnsizer = wx.StdDialogButtonSizer()
1419        if useOK:
1420            OKbtn = wx.Button(self, wx.ID_OK)
1421            OKbtn.SetDefault()
1422            btnsizer.AddButton(OKbtn)
1423        if useCANCEL:
1424            btn = wx.Button(self, wx.ID_CANCEL)
1425            btnsizer.AddButton(btn)
1426        btnsizer.Realize()
1427        Sizer.Add((-1,5))
1428        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
1429        Sizer.Add((-1,20))
1430        # OK done, let's get outa here
1431        self.SetSizer(Sizer)
1432    def GetSelections(self):
1433        'Returns a list of the indices for the selected choices'
1434        return [i for i in range(self.numchoices) if self.clb.IsChecked(i)]
1435    def _SetAll(self,event):
1436        'Set all choices on'
1437        self.clb.SetChecked(range(self.numchoices))
1438    def _ToggleAll(self,event):
1439        'flip the state of all choices'
1440        for i in range(self.numchoices):
1441            self.clb.Check(i,not self.clb.IsChecked(i))
1442
1443def ItemSelector(ChoiceList, ParentFrame=None,
1444                 title='Select an item',
1445                 size=None, header='Item Selector',
1446                 useCancel=True,multiple=False):
1447    ''' Provide a wx dialog to select a single item or multiple items from list of choices
1448
1449    :param list ChoiceList: a list of choices where one will be selected
1450    :param wx.Frame ParentFrame: Name of parent frame (default None)
1451    :param str title: heading above list of choices (default 'Select an item')
1452    :param wx.Size size: Size for dialog to be created (default None -- size as needed)
1453    :param str header: Title to place on window frame (default 'Item Selector')
1454    :param bool useCancel: If True (default) both the OK and Cancel buttons are offered
1455    :param bool multiple: If True then multiple items can be selected (default False)
1456   
1457    :returns: the selection index or None or a selection list if multiple is true
1458    '''
1459    if multiple:
1460        if useCancel:
1461            dlg = G2MultiChoiceDialog(
1462                ParentFrame,title, header, ChoiceList)
1463        else:
1464            dlg = G2MultiChoiceDialog(
1465                ParentFrame,title, header, ChoiceList,
1466                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1467    else:
1468        if useCancel:
1469            dlg = wx.SingleChoiceDialog(
1470                ParentFrame,title, header, ChoiceList)
1471        else:
1472            dlg = wx.SingleChoiceDialog(
1473                ParentFrame,title, header,ChoiceList,
1474                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1475    if size: dlg.SetSize(size)
1476    if dlg.ShowModal() == wx.ID_OK:
1477        if multiple:
1478            dlg.Destroy()
1479            return dlg.GetSelections()
1480        else:
1481            dlg.Destroy()
1482            return dlg.GetSelection()
1483    else:
1484        dlg.Destroy()
1485        return None
1486    dlg.Destroy()
1487
1488################################################################################
1489class GridFractionEditor(wg.PyGridCellEditor):
1490    '''A grid cell editor class that allows entry of values as fractions as well
1491    as sine and cosine values [as s() and c()]
1492    '''
1493    def __init__(self,grid):
1494        wg.PyGridCellEditor.__init__(self)
1495
1496    def Create(self, parent, id, evtHandler):
1497        self._tc = wx.TextCtrl(parent, id, "")
1498        self._tc.SetInsertionPoint(0)
1499        self.SetControl(self._tc)
1500
1501        if evtHandler:
1502            self._tc.PushEventHandler(evtHandler)
1503
1504        self._tc.Bind(wx.EVT_CHAR, self.OnChar)
1505
1506    def SetSize(self, rect):
1507        self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2,
1508                               wx.SIZE_ALLOW_MINUS_ONE)
1509
1510    def BeginEdit(self, row, col, grid):
1511        self.startValue = grid.GetTable().GetValue(row, col)
1512        self._tc.SetValue(str(self.startValue))
1513        self._tc.SetInsertionPointEnd()
1514        self._tc.SetFocus()
1515        self._tc.SetSelection(0, self._tc.GetLastPosition())
1516
1517    def EndEdit(self, row, col, grid):
1518        changed = False
1519
1520        val = self._tc.GetValue().lower()
1521       
1522        if val != self.startValue:
1523            changed = True
1524            neg = False
1525            if '-' in val:
1526                neg = True
1527            if '/' in val and '.' not in val:
1528                val += '.'
1529            elif 's' in val and not 'sind(' in val:
1530                if neg:
1531                    val = '-sind('+val.strip('-s')+')'
1532                else:
1533                    val = 'sind('+val.strip('s')+')'
1534            elif 'c' in val and not 'cosd(' in val:
1535                if neg:
1536                    val = '-cosd('+val.strip('-c')+')'
1537                else:
1538                    val = 'cosd('+val.strip('c')+')'
1539            try:
1540                val = float(eval(val))
1541            except (SyntaxError,NameError):
1542                val = self.startValue
1543            grid.GetTable().SetValue(row, col, val) # update the table
1544
1545        self.startValue = ''
1546        self._tc.SetValue('')
1547        return changed
1548
1549    def Reset(self):
1550        self._tc.SetValue(self.startValue)
1551        self._tc.SetInsertionPointEnd()
1552
1553    def Clone(self):
1554        return GridFractionEditor(grid)
1555
1556    def StartingKey(self, evt):
1557        self.OnChar(evt)
1558        if evt.GetSkipped():
1559            self._tc.EmulateKeyPress(evt)
1560
1561    def OnChar(self, evt):
1562        key = evt.GetKeyCode()
1563        if key == 15:
1564            return
1565        if key > 255:
1566            evt.Skip()
1567            return
1568        char = chr(key)
1569        if char in '.+-/0123456789cosind()':
1570            self._tc.WriteText(char)
1571        else:
1572            evt.Skip()
1573
1574################################################################################
1575class ShowLSParms(wx.Dialog):
1576    '''Create frame to show least-squares parameters
1577    '''
1578    def __init__(self,parent,title,parmDict,varyList,fullVaryList,
1579                 size=(300,430)):
1580        wx.Dialog.__init__(self,parent,wx.ID_ANY,title,size=size,
1581                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1582        mainSizer = wx.BoxSizer(wx.VERTICAL)
1583
1584        panel = wxscroll.ScrolledPanel(
1585            self, wx.ID_ANY,
1586            #size=size,
1587            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
1588        num = len(varyList)
1589        mainSizer.Add(wx.StaticText(self,wx.ID_ANY,'Number of refined variables: '+str(num)))
1590        if len(varyList) != len(fullVaryList):
1591            num = len(fullVaryList) - len(varyList)
1592            mainSizer.Add(wx.StaticText(self,wx.ID_ANY,' + '+str(num)+' parameters are varied via constraints'))
1593        subSizer = wx.FlexGridSizer(rows=len(parmDict)+1,cols=4,hgap=2,vgap=2)
1594        parmNames = parmDict.keys()
1595        parmNames.sort()
1596        subSizer.Add((-1,-1))
1597        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'Parameter name  '))
1598        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'refine?'))
1599        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'value'),0,wx.ALIGN_RIGHT)
1600        explainRefine = False
1601        for name in parmNames:
1602            # skip entries without numerical values
1603            if isinstance(parmDict[name],basestring): continue
1604            try:
1605                value = G2py3.FormatSigFigs(parmDict[name])
1606            except TypeError:
1607                value = str(parmDict[name])+' -?' # unexpected
1608                #continue
1609            v = G2obj.getVarDescr(name)
1610            if v is None or v[-1] is None:
1611                subSizer.Add((-1,-1))
1612            else:               
1613                ch = HelpButton(panel,G2obj.fmtVarDescr(name))
1614                subSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
1615            subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(name)))
1616            if name in varyList:
1617                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'R'))
1618            elif name in fullVaryList:
1619                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'C'))
1620                explainRefine = True
1621            else:
1622                subSizer.Add((-1,-1))
1623            subSizer.Add(wx.StaticText(panel,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
1624
1625        # finish up ScrolledPanel
1626        panel.SetSizer(subSizer)
1627        panel.SetAutoLayout(1)
1628        panel.SetupScrolling()
1629        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
1630
1631        if explainRefine:
1632            mainSizer.Add(
1633                wx.StaticText(self,wx.ID_ANY,
1634                          '"R" indicates a refined variable\n'+
1635                          '"C" indicates generated from a constraint'
1636                          ),
1637                0, wx.ALL,0)
1638        # make OK button
1639        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
1640        btn = wx.Button(self, wx.ID_CLOSE,"Close") 
1641        btn.Bind(wx.EVT_BUTTON,self._onClose)
1642        btnsizer.Add(btn)
1643        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1644        # Allow window to be enlarged but not made smaller
1645        self.SetSizer(mainSizer)
1646        self.SetMinSize(self.GetSize())
1647
1648    def _onClose(self,event):
1649        self.EndModal(wx.ID_CANCEL)
1650 
1651################################################################################
1652class downdate(wx.Dialog):
1653    '''Dialog to allow a user to select a version of GSAS-II to install
1654    '''
1655    def __init__(self,parent=None):
1656        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1657        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style)
1658        pnl = wx.Panel(self)
1659        sizer = wx.BoxSizer(wx.VERTICAL)
1660        insver = GSASIIpath.svnGetRev(local=True)
1661        curver = int(GSASIIpath.svnGetRev(local=False))
1662        label = wx.StaticText(
1663            pnl,  wx.ID_ANY,
1664            'Select a specific GSAS-II version to install'
1665            )
1666        sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
1667        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
1668        sizer1.Add(
1669            wx.StaticText(pnl,  wx.ID_ANY,
1670                          'Currently installed version: '+str(insver)),
1671            0, wx.ALIGN_CENTRE|wx.ALL, 5)
1672        sizer.Add(sizer1)
1673        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
1674        sizer1.Add(
1675            wx.StaticText(pnl,  wx.ID_ANY,
1676                          'Select GSAS-II version to install: '),
1677            0, wx.ALIGN_CENTRE|wx.ALL, 5)
1678        self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1))
1679        self.spin.SetRange(1, curver)
1680        self.spin.SetValue(curver)
1681        self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
1682        self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
1683        sizer1.Add(self.spin)
1684        sizer.Add(sizer1)
1685
1686        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
1687        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
1688
1689        self.text = wx.StaticText(pnl,  wx.ID_ANY, "")
1690        sizer.Add(self.text, 0, wx.ALIGN_LEFT|wx.EXPAND|wx.ALL, 5)
1691
1692        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
1693        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
1694        sizer.Add(
1695            wx.StaticText(
1696                pnl,  wx.ID_ANY,
1697                'If "Install" is pressed, your project will be saved;\n'
1698                'GSAS-II will exit; The specified version will be loaded\n'
1699                'and GSAS-II will restart. Press "Cancel" to abort.'),
1700            0, wx.EXPAND|wx.ALL, 10)
1701        btnsizer = wx.StdDialogButtonSizer()
1702        btn = wx.Button(pnl, wx.ID_OK, "Install")
1703        btn.SetDefault()
1704        btnsizer.AddButton(btn)
1705        btn = wx.Button(pnl, wx.ID_CANCEL)
1706        btnsizer.AddButton(btn)
1707        btnsizer.Realize()
1708        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1709        pnl.SetSizer(sizer)
1710        sizer.Fit(self)
1711        self.topsizer=sizer
1712        self.CenterOnParent()
1713        self._onSpin(None)
1714
1715    def _onSpin(self,event):
1716        'Called to load info about the selected version in the dialog'
1717        ver = self.spin.GetValue()
1718        d = GSASIIpath.svnGetLog(version=ver)
1719        date = d.get('date','?').split('T')[0]
1720        s = '(Version '+str(ver)+' created '+date
1721        s += ' by '+d.get('author','?')+')'
1722        msg = d.get('msg')
1723        if msg: s += '\n\nComment: '+msg
1724        self.text.SetLabel(s)
1725        self.topsizer.Fit(self)
1726
1727    def getVersion(self):
1728        'Get the version number in the dialog'
1729        return self.spin.GetValue()
1730
1731################################################################################
1732class MyHelp(wx.Menu):
1733    '''
1734    A class that creates the contents of a help menu.
1735    The menu will start with two entries:
1736
1737    * 'Help on <helpType>': where helpType is a reference to an HTML page to
1738      be opened
1739    * About: opens an About dialog using OnHelpAbout. N.B. on the Mac this
1740      gets moved to the App menu to be consistent with Apple style.
1741
1742    NOTE: for this to work properly with respect to system menus, the title
1743    for the menu must be &Help, or it will not be processed properly:
1744
1745    ::
1746
1747       menu.Append(menu=MyHelp(self,...),title="&Help")
1748
1749    '''
1750    def __init__(self,frame,helpType=None,helpLbl=None,morehelpitems=[],title=''):
1751        wx.Menu.__init__(self,title)
1752        self.HelpById = {}
1753        self.frame = frame
1754        self.Append(help='', id=wx.ID_ABOUT, kind=wx.ITEM_NORMAL,
1755            text='&About GSAS-II')
1756        frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT)
1757        if GSASIIpath.whichsvn():
1758            helpobj = self.Append(
1759                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
1760                text='&Check for updates')
1761            frame.Bind(wx.EVT_MENU, self.OnCheckUpdates, helpobj)
1762            helpobj = self.Append(
1763                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
1764                text='&Regress to an old GSAS-II version')
1765            frame.Bind(wx.EVT_MENU, self.OnSelectVersion, helpobj)
1766        for lbl,indx in morehelpitems:
1767            helpobj = self.Append(text=lbl,
1768                id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1769            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1770            self.HelpById[helpobj.GetId()] = indx
1771        # add a help item only when helpType is specified
1772        if helpType is not None:
1773            self.AppendSeparator()
1774            if helpLbl is None: helpLbl = helpType
1775            helpobj = self.Append(text='Help on '+helpLbl,
1776                                  id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1777            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1778            self.HelpById[helpobj.GetId()] = helpType
1779       
1780    def OnHelpById(self,event):
1781        '''Called when Help on... is pressed in a menu. Brings up
1782        a web page for documentation.
1783        '''
1784        helpType = self.HelpById.get(event.GetId())
1785        if helpType is None:
1786            print 'Error: help lookup failed!',event.GetEventObject()
1787            print 'id=',event.GetId()
1788        else:
1789            ShowHelp(helpType,self.frame)
1790
1791    def OnHelpAbout(self, event):
1792        "Display an 'About GSAS-II' box"
1793        global __version__
1794        info = wx.AboutDialogInfo()
1795        info.Name = 'GSAS-II'
1796        ver = GSASIIpath.svnGetRev()
1797        if ver: 
1798            info.Version = 'Revision '+str(ver)+' (svn), version '+__version__
1799        else:
1800            info.Version = 'Revision '+str(GSASIIpath.GetVersionNumber())+' (.py files), version '+__version__
1801        #info.Developers = ['Robert B. Von Dreele','Brian H. Toby']
1802        info.Copyright = ('(c) ' + time.strftime('%Y') +
1803''' Argonne National Laboratory
1804This product includes software developed
1805by the UChicago Argonne, LLC, as
1806Operator of Argonne National Laboratory.''')
1807        info.Description = '''General Structure Analysis System-II (GSAS-II)
1808Robert B. Von Dreele and Brian H. Toby
1809
1810Please cite as:
1811B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013) '''
1812
1813        info.WebSite = ("https://subversion.xray.aps.anl.gov/trac/pyGSAS","GSAS-II home page")
1814        wx.AboutBox(info)
1815
1816    def OnCheckUpdates(self,event):
1817        '''Check if the GSAS-II repository has an update for the current source files
1818        and perform that update if requested.
1819        '''
1820        if not GSASIIpath.whichsvn():
1821            dlg = wx.MessageDialog(self.frame,
1822                                   'No Subversion','Cannot update GSAS-II because subversion (svn) was not found.',
1823                                   wx.OK)
1824            dlg.ShowModal()
1825            dlg.Destroy()
1826            return
1827        wx.BeginBusyCursor()
1828        local = GSASIIpath.svnGetRev()
1829        if local is None: 
1830            wx.EndBusyCursor()
1831            dlg = wx.MessageDialog(self.frame,
1832                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
1833                                   'Subversion error',
1834                                   wx.OK)
1835            dlg.ShowModal()
1836            dlg.Destroy()
1837            return
1838        print 'Installed GSAS-II version: '+local
1839        repos = GSASIIpath.svnGetRev(local=False)
1840        wx.EndBusyCursor()
1841        if repos is None: 
1842            dlg = wx.MessageDialog(self.frame,
1843                                   'Unable to access the GSAS-II server. Is this computer on the internet?',
1844                                   'Server unavailable',
1845                                   wx.OK)
1846            dlg.ShowModal()
1847            dlg.Destroy()
1848            return
1849        print 'GSAS-II version on server: '+repos
1850        if local == repos:
1851            dlg = wx.MessageDialog(self.frame,
1852                                   'GSAS-II is up-to-date. Version '+local+' is already loaded.',
1853                                   'GSAS-II Up-to-date',
1854                                   wx.OK)
1855            dlg.ShowModal()
1856            dlg.Destroy()
1857            return
1858        mods = GSASIIpath.svnFindLocalChanges()
1859        if mods:
1860            dlg = wx.MessageDialog(self.frame,
1861                                   'You have version '+local+
1862                                   ' of GSAS-II installed, but the current version is '+repos+
1863                                   '. However, '+str(len(mods))+
1864                                   ' file(s) on your local computer have been modified.'
1865                                   ' Updating will attempt to merge your local changes with '
1866                                   'the latest GSAS-II version, but if '
1867                                   'conflicts arise, local changes will be '
1868                                   'discarded. It is also possible that the '
1869                                   'local changes my prevent GSAS-II from running. '
1870                                   'Press OK to start an update if this is acceptable:',
1871                                   'Local GSAS-II Mods',
1872                                   wx.OK|wx.CANCEL)
1873            if dlg.ShowModal() != wx.ID_OK:
1874                dlg.Destroy()
1875                return
1876            else:
1877                dlg.Destroy()
1878        else:
1879            dlg = wx.MessageDialog(self.frame,
1880                                   'You have version '+local+
1881                                   ' of GSAS-II installed, but the current version is '+repos+
1882                                   '. Press OK to start an update:',
1883                                   'GSAS-II Updates',
1884                                   wx.OK|wx.CANCEL)
1885            if dlg.ShowModal() != wx.ID_OK:
1886                dlg.Destroy()
1887                return
1888            dlg.Destroy()
1889        print 'start updates'
1890        dlg = wx.MessageDialog(self.frame,
1891                               'Your project will now be saved, GSAS-II will exit and an update '
1892                               'will be performed and GSAS-II will restart. Press Cancel to '
1893                               'abort the update',
1894                               'Start update?',
1895                               wx.OK|wx.CANCEL)
1896        if dlg.ShowModal() != wx.ID_OK:
1897            dlg.Destroy()
1898            return
1899        dlg.Destroy()
1900        self.frame.OnFileSave(event)
1901        GSASIIpath.svnUpdateProcess(projectfile=self.frame.GSASprojectfile)
1902        return
1903
1904    def OnSelectVersion(self,event):
1905        '''Allow the user to select a specific version of GSAS-II
1906        '''
1907        if not GSASIIpath.whichsvn():
1908            dlg = wx.MessageDialog(self,'No Subversion','Cannot update GSAS-II because subversion (svn) '+
1909                                   'was not found.'
1910                                   ,wx.OK)
1911            dlg.ShowModal()
1912            return
1913        local = GSASIIpath.svnGetRev()
1914        if local is None: 
1915            dlg = wx.MessageDialog(self.frame,
1916                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
1917                                   'Subversion error',
1918                                   wx.OK)
1919            dlg.ShowModal()
1920            return
1921        mods = GSASIIpath.svnFindLocalChanges()
1922        if mods:
1923            dlg = wx.MessageDialog(self.frame,
1924                                   'You have version '+local+
1925                                   ' of GSAS-II installed'
1926                                   '. However, '+str(len(mods))+
1927                                   ' file(s) on your local computer have been modified.'
1928                                   ' Downdating will attempt to merge your local changes with '
1929                                   'the selected GSAS-II version. '
1930                                   'Downdating is not encouraged because '
1931                                   'if merging is not possible, your local changes will be '
1932                                   'discarded. It is also possible that the '
1933                                   'local changes my prevent GSAS-II from running. '
1934                                   'Press OK to continue anyway.',
1935                                   'Local GSAS-II Mods',
1936                                   wx.OK|wx.CANCEL)
1937            if dlg.ShowModal() != wx.ID_OK:
1938                dlg.Destroy()
1939                return
1940            dlg.Destroy()
1941        dlg = downdate(parent=self.frame)
1942        if dlg.ShowModal() == wx.ID_OK:
1943            ver = dlg.getVersion()
1944        else:
1945            dlg.Destroy()
1946            return
1947        dlg.Destroy()
1948        print('start regress to '+str(ver))
1949        GSASIIpath.svnUpdateProcess(
1950            projectfile=self.frame.GSASprojectfile,
1951            version=str(ver)
1952            )
1953        self.frame.OnFileSave(event)
1954        return
1955
1956################################################################################
1957class AddHelp(wx.Menu):
1958    '''For the Mac: creates an entry to the help menu of type
1959    'Help on <helpType>': where helpType is a reference to an HTML page to
1960    be opened.
1961
1962    NOTE: when appending this menu (menu.Append) be sure to set the title to
1963    '&Help' so that wx handles it correctly.
1964    '''
1965    def __init__(self,frame,helpType,helpLbl=None,title=''):
1966        wx.Menu.__init__(self,title)
1967        self.frame = frame
1968        if helpLbl is None: helpLbl = helpType
1969        # add a help item only when helpType is specified
1970        helpobj = self.Append(text='Help on '+helpLbl,
1971                              id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
1972        frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
1973        self.HelpById = helpType
1974       
1975    def OnHelpById(self,event):
1976        '''Called when Help on... is pressed in a menu. Brings up
1977        a web page for documentation.
1978        '''
1979        ShowHelp(self.HelpById,self.frame)
1980
1981################################################################################
1982class HelpButton(wx.Button):
1983    '''Create a help button that displays help information.
1984    The text is displayed in a modal message window.
1985
1986    TODO: it might be nice if it were non-modal: e.g. it stays around until
1987    the parent is deleted or the user closes it, but this did not work for
1988    me.
1989
1990    :param parent: the panel which will be the parent of the button
1991    :param str msg: the help text to be displayed
1992    '''
1993    def __init__(self,parent,msg):
1994        if sys.platform == "darwin": 
1995            wx.Button.__init__(self,parent,wx.ID_HELP)
1996        else:
1997            wx.Button.__init__(self,parent,wx.ID_ANY,'?',style=wx.BU_EXACTFIT)
1998        self.Bind(wx.EVT_BUTTON,self._onPress)
1999        self.msg=msg
2000        self.parent = parent
2001    def _onClose(self,event):
2002        self.dlg.EndModal(wx.ID_CANCEL)
2003    def _onPress(self,event):
2004        'Respond to a button press by displaying the requested text'
2005        #dlg = wx.MessageDialog(self.parent,self.msg,'Help info',wx.OK)
2006        self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', 
2007                        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2008        #self.dlg.SetBackgroundColour(wx.WHITE)
2009        mainSizer = wx.BoxSizer(wx.VERTICAL)
2010        txt = wx.StaticText(self.dlg,wx.ID_ANY,self.msg)
2011        mainSizer.Add(txt,1,wx.ALL|wx.EXPAND,10)
2012        txt.SetBackgroundColour(wx.WHITE)
2013
2014        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2015        btn = wx.Button(self.dlg, wx.ID_CLOSE) 
2016        btn.Bind(wx.EVT_BUTTON,self._onClose)
2017        btnsizer.Add(btn)
2018        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2019        self.dlg.SetSizer(mainSizer)
2020        mainSizer.Fit(self.dlg)
2021        self.dlg.ShowModal()
2022        self.dlg.Destroy()
2023################################################################################
2024class MyHtmlPanel(wx.Panel):
2025    '''Defines a panel to display HTML help information, as an alternative to
2026    displaying help information in a web browser.
2027    '''
2028    def __init__(self, frame, id):
2029        self.frame = frame
2030        wx.Panel.__init__(self, frame, id)
2031        sizer = wx.BoxSizer(wx.VERTICAL)
2032        back = wx.Button(self, -1, "Back")
2033        back.Bind(wx.EVT_BUTTON, self.OnBack)
2034        self.htmlwin = G2HtmlWindow(self, id, size=(750,450))
2035        sizer.Add(self.htmlwin, 1,wx.EXPAND)
2036        sizer.Add(back, 0, wx.ALIGN_LEFT, 0)
2037        self.SetSizer(sizer)
2038        sizer.Fit(frame)       
2039        self.Bind(wx.EVT_SIZE,self.OnHelpSize)
2040    def OnHelpSize(self,event):         #does the job but weirdly!!
2041        anchor = self.htmlwin.GetOpenedAnchor()
2042        if anchor:           
2043            self.htmlwin.ScrollToAnchor(anchor)
2044            wx.CallAfter(self.htmlwin.ScrollToAnchor,anchor)
2045            event.Skip()
2046    def OnBack(self, event):
2047        self.htmlwin.HistoryBack()
2048    def LoadFile(self,file):
2049        pos = file.rfind('#')
2050        if pos != -1:
2051            helpfile = file[:pos]
2052            helpanchor = file[pos+1:]
2053        else:
2054            helpfile = file
2055            helpanchor = None
2056        self.htmlwin.LoadPage(helpfile)
2057        if helpanchor is not None:
2058            self.htmlwin.ScrollToAnchor(helpanchor)
2059            xs,ys = self.htmlwin.GetViewStart()
2060            self.htmlwin.Scroll(xs,ys-1)
2061
2062class G2HtmlWindow(wx.html.HtmlWindow):
2063    '''Displays help information in a primitive HTML browser type window
2064    '''
2065    def __init__(self, parent, *args, **kwargs):
2066        self.parent = parent
2067        wx.html.HtmlWindow.__init__(self, parent, *args, **kwargs)
2068    def LoadPage(self, *args, **kwargs):
2069        wx.html.HtmlWindow.LoadPage(self, *args, **kwargs)
2070        self.TitlePage()
2071    def OnLinkClicked(self, *args, **kwargs):
2072        wx.html.HtmlWindow.OnLinkClicked(self, *args, **kwargs)
2073        xs,ys = self.GetViewStart()
2074        self.Scroll(xs,ys-1)
2075        self.TitlePage()
2076    def HistoryBack(self, *args, **kwargs):
2077        wx.html.HtmlWindow.HistoryBack(self, *args, **kwargs)
2078        self.TitlePage()
2079    def TitlePage(self):
2080        self.parent.frame.SetTitle(self.GetOpenedPage() + ' -- ' + 
2081            self.GetOpenedPageTitle())
2082
2083################################################################################
2084class DataFrame(wx.Frame):
2085    '''Create the data item window and all the entries in menus used in
2086    that window. For Linux and windows, the menu entries are created for the
2087    current data item window, but in the Mac the menu is accessed from all
2088    windows. This means that a different menu is posted depending on which
2089    data item is posted. On the Mac, all the menus contain the data tree menu
2090    items, but additional menus are added specific to the data item.
2091
2092    Note that while the menus are created here,
2093    the binding for the menus is done later in various GSASII*GUI modules,
2094    where the functions to be called are defined.
2095    '''
2096    def Bind(self,*args,**kwargs):
2097        '''Override the Bind() function: on the Mac the binding is to
2098        the main window, so that menus operate with any window on top.
2099        For other platforms, call the default wx.Frame Bind()
2100        '''
2101        if sys.platform == "darwin": # mac
2102            self.G2frame.Bind(*args,**kwargs)
2103        else:
2104            wx.Frame.Bind(self,*args,**kwargs)     
2105       
2106    def PrefillDataMenu(self,menu,helpType,helpLbl=None,empty=False):
2107        '''Create the "standard" part of data frame menus. Note that on Linux and
2108        Windows nothing happens here. On Mac, this menu duplicates the
2109        tree menu, but adds an extra help command for the data item and a separator.
2110        '''
2111        self.datamenu = menu
2112        self.helpType = helpType
2113        self.helpLbl = helpLbl
2114        if sys.platform == "darwin": # mac                         
2115            self.G2frame.FillMainMenu(menu) # add the data tree menu items
2116            if not empty:
2117                menu.Append(wx.Menu(title=''),title='|') # add a separator
2118       
2119    def PostfillDataMenu(self,empty=False):
2120        '''Create the "standard" part of data frame menus. Note that on Linux and
2121        Windows, this is the standard help Menu. On Mac, this menu duplicates the
2122        tree menu, but adds an extra help command for the data item and a separator.
2123        '''
2124        menu = self.datamenu
2125        helpType = self.helpType
2126        helpLbl = self.helpLbl
2127        if sys.platform == "darwin": # mac
2128            if not empty:
2129                menu.Append(wx.Menu(title=''),title='|') # add another separator
2130            menu.Append(AddHelp(self.G2frame,helpType=helpType, helpLbl=helpLbl),
2131                        title='&Help')
2132        else: # other
2133            menu.Append(menu=MyHelp(self,helpType=helpType, helpLbl=helpLbl),
2134                        title='&Help')
2135
2136    def _init_menus(self):
2137        'define all GSAS-II data frame menus'
2138
2139        # for use where no menu or data frame help is provided
2140        self.BlankMenu = wx.MenuBar()
2141       
2142        # Controls
2143        self.ControlsMenu = wx.MenuBar()
2144        self.PrefillDataMenu(self.ControlsMenu,helpType='Controls',empty=True)
2145        self.PostfillDataMenu(empty=True)
2146       
2147        # Notebook
2148        self.DataNotebookMenu = wx.MenuBar() 
2149        self.PrefillDataMenu(self.DataNotebookMenu,helpType='Notebook',empty=True)
2150        self.PostfillDataMenu(empty=True)
2151       
2152        # Comments
2153        self.DataCommentsMenu = wx.MenuBar()
2154        self.PrefillDataMenu(self.DataCommentsMenu,helpType='Comments',empty=True)
2155        self.PostfillDataMenu(empty=True)
2156       
2157        # Constraints
2158        self.ConstraintMenu = wx.MenuBar()
2159        self.PrefillDataMenu(self.ConstraintMenu,helpType='Constraints')
2160        self.ConstraintTab = wx.Menu(title='')
2161        self.ConstraintMenu.Append(menu=self.ConstraintTab, title='Select tab')
2162        for id,txt in (
2163            (wxID_CONSPHASE,'Phase'),
2164            (wxID_CONSHAP,'Histogram/Phase'),
2165            (wxID_CONSHIST,'Histogram'),
2166            (wxID_CONSGLOBAL,'Global')):
2167            self.ConstraintTab.Append(
2168                id=id, kind=wx.ITEM_NORMAL,text=txt,
2169                help='Select '+txt+' constraint editing tab')
2170        self.ConstraintEdit = wx.Menu(title='')
2171        self.ConstraintMenu.Append(menu=self.ConstraintEdit, title='Edit')
2172        self.ConstraintEdit.Append(id=wxID_HOLDADD, kind=wx.ITEM_NORMAL,text='Add hold',
2173            help='Add hold on a parameter value')
2174        self.ConstraintEdit.Append(id=wxID_EQUIVADD, kind=wx.ITEM_NORMAL,text='Add equivalence',
2175            help='Add equivalence between parameter values')
2176        self.ConstraintEdit.Append(id=wxID_CONSTRAINTADD, kind=wx.ITEM_NORMAL,text='Add constraint',
2177            help='Add constraint on parameter values')
2178        self.ConstraintEdit.Append(id=wxID_FUNCTADD, kind=wx.ITEM_NORMAL,text='Add New Var',
2179            help='Add variable composed of existing parameter')
2180
2181        # item = self.ConstraintEdit.Append(id=wx.ID_ANY,kind=wx.ITEM_NORMAL,text='Update GUI')
2182        # def UpdateGSASIIconstrGUI(event):
2183        #     import GSASIIconstrGUI
2184        #     reload(GSASIIconstrGUI)
2185        #     import GSASIIobj
2186        #     reload(GSASIIobj)
2187        # self.Bind(wx.EVT_MENU,UpdateGSASIIconstrGUI,id=item.GetId())
2188
2189        self.PostfillDataMenu()
2190       
2191        # Rigid bodies
2192        self.VectorRBEdit = wx.Menu(title='')
2193        self.VectorRBEdit.Append(id=wxID_RIGIDBODYADD, kind=wx.ITEM_NORMAL,text='Add rigid body',
2194            help='Add vector rigid body')
2195        self.ResidueRBMenu = wx.Menu(title='')
2196        self.ResidueRBMenu.Append(id=wxID_RIGIDBODYIMPORT, kind=wx.ITEM_NORMAL,text='Import XYZ',
2197            help='Import rigid body XYZ from file')
2198        self.ResidueRBMenu.Append(id=wxID_RESIDUETORSSEQ, kind=wx.ITEM_NORMAL,text='Define sequence',
2199            help='Define torsion sequence')
2200        self.ResidueRBMenu.Append(id=wxID_RIGIDBODYADD, kind=wx.ITEM_NORMAL,text='Import residues',
2201            help='Import residue rigid bodies from macro file')
2202           
2203        self.RigidBodyMenu = wx.MenuBar()
2204        self.PrefillDataMenu(self.RigidBodyMenu,helpType='Rigid bodies')
2205        self.RigidBodyMenu.Append(menu=self.VectorRBEdit, title='Edit')       
2206        self.PostfillDataMenu()
2207           
2208        # Restraints
2209        self.RestraintEdit = wx.Menu(title='')
2210        self.RestraintEdit.Append(id=wxID_RESTSELPHASE, kind=wx.ITEM_NORMAL,text='Select phase',
2211            help='Select phase')
2212        self.RestraintEdit.Append(id=wxID_RESTRAINTADD, kind=wx.ITEM_NORMAL,text='Add restraints',
2213            help='Add restraints')
2214        self.RestraintEdit.Enable(wxID_RESTRAINTADD,True)    #gets disenabled if macromolecule phase
2215        self.RestraintEdit.Append(id=wxID_AARESTRAINTADD, kind=wx.ITEM_NORMAL,text='Add residue restraints',
2216            help='Add residue based restraints for macromolecules from macro file')
2217        self.RestraintEdit.Enable(wxID_AARESTRAINTADD,False)    #gets enabled if macromolecule phase
2218        self.RestraintEdit.Append(id=wxID_AARESTRAINTPLOT, kind=wx.ITEM_NORMAL,text='Plot residue restraints',
2219            help='Plot selected residue based restraints for macromolecules from macro file')
2220        self.RestraintEdit.Enable(wxID_AARESTRAINTPLOT,False)    #gets enabled if macromolecule phase
2221        self.RestraintEdit.Append(id=wxID_RESRCHANGEVAL, kind=wx.ITEM_NORMAL,text='Change value',
2222            help='Change observed value')
2223        self.RestraintEdit.Append(id=wxID_RESTCHANGEESD, kind=wx.ITEM_NORMAL,text='Change esd',
2224            help='Change esd in observed value')
2225        self.RestraintEdit.Append(id=wxID_RESTDELETE, kind=wx.ITEM_NORMAL,text='Delete restraints',
2226            help='Delete selected restraints')
2227
2228        self.RestraintMenu = wx.MenuBar()
2229        self.PrefillDataMenu(self.RestraintMenu,helpType='Restraints')
2230        self.RestraintMenu.Append(menu=self.RestraintEdit, title='Edit')
2231        self.PostfillDataMenu()
2232           
2233        # Sequential results
2234        self.SequentialMenu = wx.MenuBar()
2235        self.PrefillDataMenu(self.SequentialMenu,helpType='Sequential',helpLbl='Sequential Refinement')
2236        self.SequentialFile = wx.Menu(title='')
2237        self.SequentialMenu.Append(menu=self.SequentialFile, title='File')
2238        self.SequentialFile.Append(id=wxID_SAVESEQSEL, kind=wx.ITEM_NORMAL,text='Save...',
2239            help='Save selected sequential refinement results')
2240        self.PostfillDataMenu()
2241           
2242        # PDR
2243        self.ErrorMenu = wx.MenuBar()
2244        self.PrefillDataMenu(self.ErrorMenu,helpType='PWD Analysis',helpLbl='Powder Fit Error Analysis')
2245        self.ErrorAnal = wx.Menu(title='')
2246        self.ErrorMenu.Append(menu=self.ErrorAnal,title='Analysis')
2247        self.ErrorAnal.Append(id=wxID_PWDANALYSIS,kind=wx.ITEM_NORMAL,text='Analyze',
2248            help='Error analysis on powder pattern')
2249        self.PostfillDataMenu()
2250           
2251        # PDR / Limits
2252        self.LimitMenu = wx.MenuBar()
2253        self.PrefillDataMenu(self.LimitMenu,helpType='Limits')
2254        self.LimitEdit = wx.Menu(title='')
2255        self.LimitMenu.Append(menu=self.LimitEdit, title='Edit')
2256        self.LimitEdit.Append(id=wxID_LIMITCOPY, kind=wx.ITEM_NORMAL,text='Copy',
2257            help='Copy limits to other histograms')
2258        self.LimitEdit.Append(id=wxID_ADDEXCLREGION, kind=wx.ITEM_NORMAL,text='Add exclude',
2259            help='Add excluded region - select a point on plot; drag to adjust')           
2260        self.PostfillDataMenu()
2261           
2262        # PDR / Background
2263        self.BackMenu = wx.MenuBar()
2264        self.PrefillDataMenu(self.BackMenu,helpType='Background')
2265        self.BackEdit = wx.Menu(title='')
2266        self.BackMenu.Append(menu=self.BackEdit, title='File')
2267        self.BackEdit.Append(id=wxID_BACKCOPY, kind=wx.ITEM_NORMAL,text='Copy',
2268            help='Copy background parameters to other histograms')
2269        self.BackEdit.Append(id=wxID_BACKFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2270            help='Copy background refinement flags to other histograms')
2271        self.PostfillDataMenu()
2272           
2273        # PDR / Instrument Parameters
2274        self.InstMenu = wx.MenuBar()
2275        self.PrefillDataMenu(self.InstMenu,helpType='Instrument Parameters')
2276        self.InstEdit = wx.Menu(title='')
2277        self.InstMenu.Append(menu=self.InstEdit, title='Operations')
2278        self.InstEdit.Append(help='Reset instrument profile parameters to default', 
2279            id=wxID_INSTLOAD, kind=wx.ITEM_NORMAL,text='Load profile...')
2280        self.InstEdit.Append(help='Load instrument profile parameters from file', 
2281            id=wxID_INSTSAVE, kind=wx.ITEM_NORMAL,text='Save profile...')
2282        self.InstEdit.Append(help='Save instrument profile parameters to file', 
2283            id=wxID_INSTPRMRESET, kind=wx.ITEM_NORMAL,text='Reset profile')
2284        self.InstEdit.Append(help='Copy instrument profile parameters to other histograms', 
2285            id=wxID_INSTCOPY, kind=wx.ITEM_NORMAL,text='Copy')
2286        self.InstEdit.Append(id=wxID_INSTFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2287            help='Copy instrument parameter refinement flags to other histograms')
2288#        self.InstEdit.Append(help='Change radiation type (Ka12 - synch)',
2289#            id=wxID_CHANGEWAVETYPE, kind=wx.ITEM_NORMAL,text='Change radiation')
2290        self.PostfillDataMenu()
2291       
2292        # PDR / Sample Parameters
2293        self.SampleMenu = wx.MenuBar()
2294        self.PrefillDataMenu(self.SampleMenu,helpType='Sample Parameters')
2295        self.SampleEdit = wx.Menu(title='')
2296        self.SampleMenu.Append(menu=self.SampleEdit, title='File')
2297        self.SampleEdit.Append(id=wxID_SAMPLELOAD, kind=wx.ITEM_NORMAL,text='Load',
2298            help='Load sample parameters from file')
2299        self.SampleEdit.Append(id=wxID_SAMPLESAVE, kind=wx.ITEM_NORMAL,text='Save',
2300            help='Save sample parameters to file')
2301        self.SampleEdit.Append(id=wxID_SAMPLECOPY, kind=wx.ITEM_NORMAL,text='Copy',
2302            help='Copy refinable sample parameters to other histograms')
2303        self.SampleEdit.Append(id=wxID_SAMPLEFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2304            help='Copy sample parameter refinement flags to other histograms')
2305        self.PostfillDataMenu()
2306
2307        # PDR / Peak List
2308        self.PeakMenu = wx.MenuBar()
2309        self.PrefillDataMenu(self.PeakMenu,helpType='Peak List')
2310        self.PeakEdit = wx.Menu(title='')
2311        self.PeakMenu.Append(menu=self.PeakEdit, title='Peak Fitting')
2312        self.AutoSearch = self.PeakEdit.Append(help='Automatic peak search', 
2313            id=wxID_AUTOSEARCH, kind=wx.ITEM_NORMAL,text='Auto search')
2314        self.UnDo = self.PeakEdit.Append(help='Undo last least squares refinement', 
2315            id=wxID_UNDO, kind=wx.ITEM_NORMAL,text='UnDo')
2316        self.PeakFit = self.PeakEdit.Append(id=wxID_LSQPEAKFIT, kind=wx.ITEM_NORMAL,text='LSQ PeakFit', 
2317            help='Peak fitting via least-squares' )
2318        self.PFOneCycle = self.PeakEdit.Append(id=wxID_LSQONECYCLE, kind=wx.ITEM_NORMAL,text='LSQ one cycle', 
2319            help='One cycle of Peak fitting via least-squares' )
2320        self.PeakEdit.Append(id=wxID_RESETSIGGAM, kind=wx.ITEM_NORMAL, 
2321            text='Reset sig and gam',help='Reset sigma and gamma to global fit' )
2322        self.PeakEdit.Append(id=wxID_CLEARPEAKS, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2323            help='Clear the peak list' )
2324        self.PostfillDataMenu()
2325        self.UnDo.Enable(False)
2326        self.PeakFit.Enable(False)
2327        self.PFOneCycle.Enable(False)
2328       
2329        # PDR / Index Peak List
2330        self.IndPeaksMenu = wx.MenuBar()
2331        self.PrefillDataMenu(self.IndPeaksMenu,helpType='Index Peak List')
2332        self.IndPeaksEdit = wx.Menu(title='')
2333        self.IndPeaksMenu.Append(menu=self.IndPeaksEdit,title='Operations')
2334        self.IndPeaksEdit.Append(help='Load/Reload index peaks from peak list',id=wxID_INDXRELOAD, 
2335            kind=wx.ITEM_NORMAL,text='Load/Reload')
2336        self.PostfillDataMenu()
2337       
2338        # PDR / Unit Cells List
2339        self.IndexMenu = wx.MenuBar()
2340        self.PrefillDataMenu(self.IndexMenu,helpType='Unit Cells List')
2341        self.IndexEdit = wx.Menu(title='')
2342        self.IndexMenu.Append(menu=self.IndexEdit, title='Cell Index/Refine')
2343        self.IndexPeaks = self.IndexEdit.Append(help='', id=wxID_INDEXPEAKS, kind=wx.ITEM_NORMAL,
2344            text='Index Cell')
2345        self.CopyCell = self.IndexEdit.Append( id=wxID_COPYCELL, kind=wx.ITEM_NORMAL,text='Copy Cell', 
2346            help='Copy selected unit cell from indexing to cell refinement fields')
2347        self.RefineCell = self.IndexEdit.Append( id=wxID_REFINECELL, kind=wx.ITEM_NORMAL, 
2348            text='Refine Cell',help='Refine unit cell parameters from indexed peaks')
2349        self.MakeNewPhase = self.IndexEdit.Append( id=wxID_MAKENEWPHASE, kind=wx.ITEM_NORMAL,
2350            text='Make new phase',help='Make new phase from selected unit cell')
2351        self.PostfillDataMenu()
2352        self.IndexPeaks.Enable(False)
2353        self.CopyCell.Enable(False)
2354        self.RefineCell.Enable(False)
2355        self.MakeNewPhase.Enable(False)
2356       
2357        # PDR / Reflection Lists
2358        self.ReflMenu = wx.MenuBar()
2359        self.PrefillDataMenu(self.ReflMenu,helpType='Reflection List')
2360        self.ReflEdit = wx.Menu(title='')
2361        self.ReflMenu.Append(menu=self.ReflEdit, title='Reflection List')
2362        self.SelectPhase = self.ReflEdit.Append(help='Select phase for reflection list',id=wxID_SELECTPHASE, 
2363            kind=wx.ITEM_NORMAL,text='Select phase')
2364        self.PostfillDataMenu()
2365       
2366        # IMG / Image Controls
2367        self.ImageMenu = wx.MenuBar()
2368        self.PrefillDataMenu(self.ImageMenu,helpType='Image Controls')
2369        self.ImageEdit = wx.Menu(title='')
2370        self.ImageMenu.Append(menu=self.ImageEdit, title='Operations')
2371        self.ImageEdit.Append(help='Calibrate detector by fitting to calibrant lines', 
2372            id=wxID_IMCALIBRATE, kind=wx.ITEM_NORMAL,text='Calibrate')
2373        self.ImageEdit.Append(help='Recalibrate detector by fitting to calibrant lines', 
2374            id=wxID_IMRECALIBRATE, kind=wx.ITEM_NORMAL,text='Recalibrate')
2375        self.ImageEdit.Append(help='Clear calibration data points and rings',id=wxID_IMCLEARCALIB, 
2376            kind=wx.ITEM_NORMAL,text='Clear calibration')
2377        self.ImageEdit.Append(help='Integrate selected image',id=wxID_IMINTEGRATE, 
2378            kind=wx.ITEM_NORMAL,text='Integrate')
2379        self.ImageEdit.Append(help='Integrate all images selected from list',id=wxID_INTEGRATEALL,
2380            kind=wx.ITEM_NORMAL,text='Integrate all')
2381        self.ImageEdit.Append(help='Copy image controls to other images', 
2382            id=wxID_IMCOPYCONTROLS, kind=wx.ITEM_NORMAL,text='Copy Controls')
2383        self.ImageEdit.Append(help='Save image controls to file', 
2384            id=wxID_IMSAVECONTROLS, kind=wx.ITEM_NORMAL,text='Save Controls')
2385        self.ImageEdit.Append(help='Load image controls from file', 
2386            id=wxID_IMLOADCONTROLS, kind=wx.ITEM_NORMAL,text='Load Controls')
2387        self.PostfillDataMenu()
2388           
2389        # IMG / Masks
2390        self.MaskMenu = wx.MenuBar()
2391        self.PrefillDataMenu(self.MaskMenu,helpType='Image Masks')
2392        self.MaskEdit = wx.Menu(title='')
2393        self.MaskMenu.Append(menu=self.MaskEdit, title='Operations')
2394        submenu = wx.Menu()
2395        self.MaskEdit.AppendMenu(
2396            wx.ID_ANY,'Create new', submenu,
2397            help=''
2398            )
2399        self.MaskEdit.Append(help='Copy mask to other images', 
2400            id=wxID_MASKCOPY, kind=wx.ITEM_NORMAL,text='Copy mask')
2401        self.MaskEdit.Append(help='Save mask to file', 
2402            id=wxID_MASKSAVE, kind=wx.ITEM_NORMAL,text='Save mask')
2403        self.MaskEdit.Append(help='Load mask from file', 
2404            id=wxID_MASKLOAD, kind=wx.ITEM_NORMAL,text='Load mask')
2405        submenu.Append(help='Create an arc mask with mouse input', 
2406            id=wxID_NEWMASKARC, kind=wx.ITEM_NORMAL,text='Arc mask')
2407        submenu.Append(help='Create a frame mask with mouse input', 
2408            id=wxID_NEWMASKFRAME, kind=wx.ITEM_NORMAL,text='Frame mask')
2409        submenu.Append(help='Create a polygon mask with mouse input', 
2410            id=wxID_NEWMASKPOLY, kind=wx.ITEM_NORMAL,text='Polygon mask')
2411        submenu.Append(help='Create a ring mask with mouse input', 
2412            id=wxID_NEWMASKRING, kind=wx.ITEM_NORMAL,text='Ring mask')
2413        submenu.Append(help='Create a spot mask with mouse input', 
2414            id=wxID_NEWMASKSPOT, kind=wx.ITEM_NORMAL,text='Spot mask')
2415        self.PostfillDataMenu()
2416           
2417        # IMG / Stress/Strain
2418        self.StrStaMenu = wx.MenuBar()
2419        self.PrefillDataMenu(self.StrStaMenu,helpType='Stress/Strain')
2420        self.StrStaEdit = wx.Menu(title='')
2421        self.StrStaMenu.Append(menu=self.StrStaEdit, title='Operations')
2422        self.StrStaEdit.Append(help='Append d-zero for one ring', 
2423            id=wxID_APPENDDZERO, kind=wx.ITEM_NORMAL,text='Append d-zero')
2424        self.StrStaEdit.Append(help='Fit stress/strain data', 
2425            id=wxID_STRSTAFIT, kind=wx.ITEM_NORMAL,text='Fit stress/strain')
2426        self.StrStaEdit.Append(help='Copy stress/strain data to other images', 
2427            id=wxID_STRSTACOPY, kind=wx.ITEM_NORMAL,text='Copy stress/strain')
2428        self.StrStaEdit.Append(help='Save stress/strain data to file', 
2429            id=wxID_STRSTASAVE, kind=wx.ITEM_NORMAL,text='Save stress/strain')
2430        self.StrStaEdit.Append(help='Load stress/strain data from file', 
2431            id=wxID_STRSTALOAD, kind=wx.ITEM_NORMAL,text='Load stress/strain')
2432        self.PostfillDataMenu()
2433           
2434        # PDF / PDF Controls
2435        self.PDFMenu = wx.MenuBar()
2436        self.PrefillDataMenu(self.PDFMenu,helpType='PDF Controls')
2437        self.PDFEdit = wx.Menu(title='')
2438        self.PDFMenu.Append(menu=self.PDFEdit, title='PDF Controls')
2439        self.PDFEdit.Append(help='Add element to sample composition',id=wxID_PDFADDELEMENT, kind=wx.ITEM_NORMAL,
2440            text='Add element')
2441        self.PDFEdit.Append(help='Delete element from sample composition',id=wxID_PDFDELELEMENT, kind=wx.ITEM_NORMAL,
2442            text='Delete element')
2443        self.PDFEdit.Append(help='Copy PDF controls', id=wxID_PDFCOPYCONTROLS, kind=wx.ITEM_NORMAL,
2444            text='Copy controls')
2445        #        self.PDFEdit.Append(help='Load PDF controls from file',id=wxID_PDFLOADCONTROLS, kind=wx.ITEM_NORMAL,
2446        #            text='Load Controls')
2447        #        self.PDFEdit.Append(help='Save PDF controls to file', id=wxID_PDFSAVECONTROLS, kind=wx.ITEM_NORMAL,
2448        #            text='Save controls')
2449        self.PDFEdit.Append(help='Compute PDF', id=wxID_PDFCOMPUTE, kind=wx.ITEM_NORMAL,
2450            text='Compute PDF')
2451        self.PDFEdit.Append(help='Compute all PDFs', id=wxID_PDFCOMPUTEALL, kind=wx.ITEM_NORMAL,
2452            text='Compute all PDFs')
2453        self.PostfillDataMenu()
2454       
2455        # Phase / General tab
2456        self.DataGeneral = wx.MenuBar()
2457        self.PrefillDataMenu(self.DataGeneral,helpType='General', helpLbl='Phase/General')
2458        self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab')
2459        self.GeneralCalc = wx.Menu(title='')
2460        self.DataGeneral.Append(menu=self.GeneralCalc,title='Compute')
2461        self.GeneralCalc.Append(help='Compute Fourier map',id=wxID_FOURCALC, kind=wx.ITEM_NORMAL,
2462            text='Fourier map')
2463        self.GeneralCalc.Append(help='Search Fourier map',id=wxID_FOURSEARCH, kind=wx.ITEM_NORMAL,
2464            text='Search map')
2465        self.GeneralCalc.Append(help='Run charge flipping',id=wxID_CHARGEFLIP, kind=wx.ITEM_NORMAL,
2466            text='Charge flipping')
2467        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
2468            text='Clear map')
2469        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
2470            text='MC/SA')
2471        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
2472            text='Multi MC/SA')            #currently not useful
2473        self.PostfillDataMenu()
2474       
2475        # Phase / Data tab
2476        self.DataMenu = wx.MenuBar()
2477        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
2478        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2479        self.DataEdit = wx.Menu(title='')
2480        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
2481        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
2482            help='Select new powder histograms to be used for this phase')
2483        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
2484            help='Select new single crystal histograms to be used for this phase')
2485        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Remove histograms',
2486            help='Remove histograms from use for this phase')
2487        self.PostfillDataMenu()
2488           
2489        # Phase / Atoms tab
2490        self.AtomsMenu = wx.MenuBar()
2491        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
2492        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2493        self.AtomEdit = wx.Menu(title='')
2494        self.AtomCompute = wx.Menu(title='')
2495        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
2496        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
2497        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
2498            help='Appended as an H atom')
2499        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
2500            help='Appended as an H atom')
2501        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
2502            help='Select atom row to insert before; inserted as an H atom')
2503        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
2504            help='Select atom row to insert before; inserted as an H atom')
2505        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
2506            help='Select single atom to move')
2507        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
2508            help='Select atoms to delete first')
2509        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
2510            help='Select atoms to refine first')
2511        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
2512            help='Select atoms to modify first')
2513        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
2514            help='Select atoms to transform first')
2515        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
2516            help='Reload atom drawing list')
2517        submenu = wx.Menu()
2518        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
2519            help='Reimport atoms from file; sequence must match')
2520        # setup a cascade menu for the formats that have been defined
2521        self.ReImportMenuId = {}  # points to readers for each menu entry
2522        for reader in self.G2frame.ImportPhaseReaderlist:
2523            item = submenu.Append(
2524                wx.ID_ANY,help=reader.longFormatName,
2525                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
2526            self.ReImportMenuId[item.GetId()] = reader
2527        item = submenu.Append(
2528            wx.ID_ANY,
2529            help='Reimport coordinates, try to determine format from file',
2530            kind=wx.ITEM_NORMAL,
2531            text='guess format from file')
2532        self.ReImportMenuId[item.GetId()] = None # try all readers
2533
2534        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Show Distances && Angles',
2535            help='Compute distances & angles for selected atoms')
2536        self.AtomCompute.Append(id=wxID_ATOMSPDISAGL, kind=wx.ITEM_NORMAL,text='Save Distances && Angles',
2537            help='Compute distances & angles for selected atoms')
2538        self.AtomCompute.ISOcalc = self.AtomCompute.Append(
2539            id=wxID_ISODISP, kind=wx.ITEM_NORMAL,
2540            text='Compute ISODISTORT mode values',
2541            help='Compute values of ISODISTORT modes from atom parameters')
2542        self.PostfillDataMenu()
2543                 
2544        # Phase / Draw Options tab
2545        self.DataDrawOptions = wx.MenuBar()
2546        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options')
2547        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
2548        self.PostfillDataMenu()
2549       
2550        # Phase / Draw Atoms tab
2551        self.DrawAtomsMenu = wx.MenuBar()
2552        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
2553        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2554        self.DrawAtomEdit = wx.Menu(title='')
2555        self.DrawAtomCompute = wx.Menu(title='')
2556        self.DrawAtomRestraint = wx.Menu(title='')
2557        self.DrawAtomRigidBody = wx.Menu(title='')
2558        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
2559        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
2560        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
2561        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
2562        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
2563            help='Select atoms first')
2564        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
2565            help='Select atoms first')
2566        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
2567            help='Select atoms first')
2568        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
2569            help='Resets all atom colors to defaults')
2570        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
2571            help='View point is 1st atom selected')
2572        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
2573            help='Add symmetry & cell equivalents to drawing set from selected atoms')
2574        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
2575            help='Transform selected atoms by symmetry & cell translations')
2576        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
2577            help='Fill coordination sphere for selected atoms')           
2578        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
2579            help='Fill unit cell with selected atoms')
2580        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
2581            help='Delete atoms from drawing set')
2582        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2583            help='Compute distance of selected atoms from view point')   
2584        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
2585            help='Compute distance, angle or torsion for 2-4 selected atoms')   
2586        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
2587            help='Compute best plane for 4+ selected atoms')   
2588        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
2589            help='Add bond restraint for selected atoms (2)')
2590        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
2591            help='Add angle restraint for selected atoms (3: one end 1st)')
2592        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
2593            help='Add plane restraint for selected atoms (4+)')
2594        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
2595            help='Add chiral restraint for selected atoms (4: center atom 1st)')
2596        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
2597            help='Define rigid body with selected atoms')
2598        self.PostfillDataMenu()
2599
2600        # Phase / MCSA tab
2601        self.MCSAMenu = wx.MenuBar()
2602        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
2603        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2604        self.MCSAEdit = wx.Menu(title='')
2605        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
2606        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
2607            help='Add single atom to MC/SA model')
2608        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
2609            help='Add rigid body to MC/SA model' )
2610        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
2611            help='Clear all atoms & rigid bodies from MC/SA model' )
2612        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
2613            help='Move MC/SA solution to atom list' )
2614        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
2615            help='Clear table of MC/SA results' )
2616        self.PostfillDataMenu()
2617           
2618        # Phase / Texture tab
2619        self.TextureMenu = wx.MenuBar()
2620        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
2621        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2622        self.TextureEdit = wx.Menu(title='')
2623        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
2624        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture', 
2625            help='Refine the texture coefficients from sequential Pawley results')
2626        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
2627            help='Clear the texture coefficients' )
2628        self.PostfillDataMenu()
2629           
2630        # Phase / Pawley tab
2631        self.PawleyMenu = wx.MenuBar()
2632        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
2633        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2634        self.PawleyEdit = wx.Menu(title='')
2635        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
2636        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
2637            help='Initialize Pawley reflection list')
2638        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
2639            help='Estimate initial Pawley intensities')
2640        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
2641            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
2642        self.PostfillDataMenu()
2643           
2644        # Phase / Map peaks tab
2645        self.MapPeaksMenu = wx.MenuBar()
2646        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
2647        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2648        self.MapPeaksEdit = wx.Menu(title='')
2649        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
2650        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
2651            help='Move selected peaks to atom list')
2652        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
2653            help='View point is 1st peak selected')
2654        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2655            help='Compute distance of selected peaks from view point')   
2656        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
2657            help='Hide or show bonds between peak positions')   
2658        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
2659            help='Calculate distance or angle for selection')
2660        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
2661            help='Find equivalent peaks')
2662        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
2663            help='Select unique set')
2664        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
2665            help='Delete selected peaks')
2666        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2667            help='Clear the map peak list')
2668        self.PostfillDataMenu()
2669
2670        # Phase / Rigid bodies tab
2671        self.RigidBodiesMenu = wx.MenuBar()
2672        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
2673        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2674        self.RigidBodiesEdit = wx.Menu(title='')
2675        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
2676        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
2677            help='Select & position rigid body in structure of existing atoms')
2678        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
2679            help='Auto find of residue RBs in macromolecule')
2680        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
2681            help='Copy rigid body location & TLS parameters')
2682        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
2683            help='Global setting of residue thermal motion models')
2684        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
2685            help='Global setting of residue RB refinement flags')
2686        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
2687            help='Remove all rigid body assignment for atoms')
2688        self.PostfillDataMenu()
2689    # end of GSAS-II menu definitions
2690       
2691    def _init_ctrls(self, parent,name=None,size=None,pos=None):
2692        wx.Frame.__init__(self,parent=parent,
2693            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
2694            size=size,pos=pos,title='GSAS-II data display')
2695        self._init_menus()
2696        if name:
2697            self.SetLabel(name)
2698        self.Show()
2699       
2700    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
2701        self.G2frame = frame
2702        self._init_ctrls(parent,name,size,pos)
2703        self.data = data
2704        clientSize = wx.ClientDisplayRect()
2705        Size = self.GetSize()
2706        xPos = clientSize[2]-Size[0]
2707        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
2708        self.AtomGrid = []
2709        self.selectedRow = 0
2710       
2711    def setSizePosLeft(self,Width):
2712        clientSize = wx.ClientDisplayRect()
2713        Width[1] = min(Width[1],clientSize[2]-300)
2714        Width[0] = max(Width[0],300)
2715        self.SetSize(Width)
2716#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
2717       
2718    def Clear(self):
2719        self.ClearBackground()
2720        self.DestroyChildren()
2721                   
2722################################################################################
2723#####  GSNotebook
2724################################################################################           
2725       
2726class GSNoteBook(wx.aui.AuiNotebook):
2727    '''Notebook used in various locations; implemented with wx.aui extension
2728    '''
2729    def __init__(self, parent, name='',size = None):
2730        wx.aui.AuiNotebook.__init__(self, parent, -1,
2731                                    style=wx.aui.AUI_NB_TOP |
2732                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
2733        if size: self.SetSize(size)
2734                                                     
2735    def Clear(self):       
2736        GSNoteBook.DeleteAllPages(self)
2737       
2738    def FindPage(self,name):
2739        numPage = self.GetPageCount()
2740        for page in range(numPage):
2741            if self.GetPageText(page) == name:
2742                return page
2743
2744    def ChangeSelection(self,page):
2745        # in wx.Notebook ChangeSelection is like SetSelection, but it
2746        # does not invoke the event related to pressing the tab button
2747        # I don't see a way to do that in aui.
2748        oldPage = self.GetSelection()
2749        self.SetSelection(page)
2750        return oldPage
2751
2752    # def __getattribute__(self,name):
2753    #     '''This method provides a way to print out a message every time
2754    #     that a method in a class is called -- to see what all the calls
2755    #     might be, or where they might be coming from.
2756    #     Cute trick for debugging!
2757    #     '''
2758    #     attr = object.__getattribute__(self, name)
2759    #     if hasattr(attr, '__call__'):
2760    #         def newfunc(*args, **kwargs):
2761    #             print('GSauiNoteBook calling %s' %attr.__name__)
2762    #             result = attr(*args, **kwargs)
2763    #             return result
2764    #         return newfunc
2765    #     else:
2766    #         return attr
2767           
2768################################################################################
2769#####  GSGrid
2770################################################################################           
2771       
2772class GSGrid(wg.Grid):
2773    '''Basic wx.Grid implementation
2774    '''
2775    def __init__(self, parent, name=''):
2776        wg.Grid.__init__(self,parent,-1,name=name)                   
2777        #self.SetSize(parent.GetClientSize())
2778        # above removed to speed drawing of initial grid
2779        # does not appear to be needed
2780           
2781    def Clear(self):
2782        wg.Grid.ClearGrid(self)
2783       
2784    def SetCellStyle(self,r,c,color="white",readonly=True):
2785        self.SetCellBackgroundColour(r,c,color)
2786        self.SetReadOnly(r,c,isReadOnly=readonly)
2787       
2788    def GetSelection(self):
2789        #this is to satisfy structure drawing stuff in G2plt when focus changes
2790        return None
2791                                               
2792################################################################################
2793#####  Table
2794################################################################################           
2795       
2796class Table(wg.PyGridTableBase):
2797    '''Basic data table for use with GSgrid
2798    '''
2799    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
2800        wg.PyGridTableBase.__init__(self)
2801        self.colLabels = colLabels
2802        self.rowLabels = rowLabels
2803        self.dataTypes = types
2804        self.data = data
2805       
2806    def AppendRows(self, numRows=1):
2807        self.data.append([])
2808        return True
2809       
2810    def CanGetValueAs(self, row, col, typeName):
2811        if self.dataTypes:
2812            colType = self.dataTypes[col].split(':')[0]
2813            if typeName == colType:
2814                return True
2815            else:
2816                return False
2817        else:
2818            return False
2819
2820    def CanSetValueAs(self, row, col, typeName):
2821        return self.CanGetValueAs(row, col, typeName)
2822
2823    def DeleteRow(self,pos):
2824        data = self.GetData()
2825        self.SetData([])
2826        new = []
2827        for irow,row in enumerate(data):
2828            if irow <> pos:
2829                new.append(row)
2830        self.SetData(new)
2831       
2832    def GetColLabelValue(self, col):
2833        if self.colLabels:
2834            return self.colLabels[col]
2835           
2836    def GetData(self):
2837        data = []
2838        for row in range(self.GetNumberRows()):
2839            data.append(self.GetRowValues(row))
2840        return data
2841       
2842    def GetNumberCols(self):
2843        try:
2844            return len(self.colLabels)
2845        except TypeError:
2846            return None
2847       
2848    def GetNumberRows(self):
2849        return len(self.data)
2850       
2851    def GetRowLabelValue(self, row):
2852        if self.rowLabels:
2853            return self.rowLabels[row]
2854       
2855    def GetColValues(self, col):
2856        data = []
2857        for row in range(self.GetNumberRows()):
2858            data.append(self.GetValue(row, col))
2859        return data
2860       
2861    def GetRowValues(self, row):
2862        data = []
2863        for col in range(self.GetNumberCols()):
2864            data.append(self.GetValue(row, col))
2865        return data
2866       
2867    def GetTypeName(self, row, col):
2868        try:
2869            return self.dataTypes[col]
2870        except TypeError:
2871            return None
2872
2873    def GetValue(self, row, col):
2874        try:
2875            return self.data[row][col]
2876        except IndexError:
2877            return None
2878           
2879    def InsertRows(self, pos, rows):
2880        for row in range(rows):
2881            self.data.insert(pos,[])
2882            pos += 1
2883       
2884    def IsEmptyCell(self,row,col):
2885        try:
2886            return not self.data[row][col]
2887        except IndexError:
2888            return True
2889       
2890    def OnKeyPress(self, event):
2891        dellist = self.GetSelectedRows()
2892        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
2893            grid = self.GetView()
2894            for i in dellist: grid.DeleteRow(i)
2895               
2896    def SetColLabelValue(self, col, label):
2897        numcols = self.GetNumberCols()
2898        if col > numcols-1:
2899            self.colLabels.append(label)
2900        else:
2901            self.colLabels[col]=label
2902       
2903    def SetData(self,data):
2904        for row in range(len(data)):
2905            self.SetRowValues(row,data[row])
2906               
2907    def SetRowLabelValue(self, row, label):
2908        self.rowLabels[row]=label
2909           
2910    def SetRowValues(self,row,data):
2911        self.data[row] = data
2912           
2913    def SetValue(self, row, col, value):
2914        def innerSetValue(row, col, value):
2915            try:
2916                self.data[row][col] = value
2917            except TypeError:
2918                return
2919            except IndexError:
2920                print row,col,value
2921                # add a new row
2922                if row > self.GetNumberRows():
2923                    self.data.append([''] * self.GetNumberCols())
2924                elif col > self.GetNumberCols():
2925                    for row in range(self.GetNumberRows):
2926                        self.data[row].append('')
2927                print self.data
2928                self.data[row][col] = value
2929        innerSetValue(row, col, value)
2930       
2931################################################################################
2932#### Help
2933################################################################################
2934
2935def ShowHelp(helpType,frame):
2936    '''Called to bring up a web page for documentation.'''
2937    global htmlFirstUse
2938    # look up a definition for help info from dict
2939    helplink = helpLocDict.get(helpType)
2940    if helplink is None:
2941        # no defined link to use, create a default based on key
2942        helplink = 'gsasII.html#'+helpType.replace(' ','_')
2943    helplink = os.path.join(path2GSAS2,'help',helplink)
2944    if helpMode == 'internal':
2945        try:
2946            htmlPanel.LoadFile(helplink)
2947            htmlFrame.Raise()
2948        except:
2949            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
2950            htmlFrame.Show(True)
2951            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
2952            htmlPanel = MyHtmlPanel(htmlFrame,-1)
2953            htmlPanel.LoadFile(helplink)
2954    else:
2955        pfx = "file://"
2956        if sys.platform.lower().startswith('win'):
2957            pfx = ''
2958        if htmlFirstUse:
2959            webbrowser.open_new(pfx+helplink)
2960            htmlFirstUse = False
2961        else:
2962            webbrowser.open(pfx+helplink, new=0, autoraise=True)
2963
2964################################################################################
2965#####  Notebook
2966################################################################################           
2967       
2968def UpdateNotebook(G2frame,data):
2969    '''Called when the data tree notebook entry is selected. Allows for
2970    editing of the text in that tree entry
2971    '''
2972    def OnNoteBook(event):
2973        data = G2frame.dataDisplay.GetValue()
2974        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
2975                   
2976    if G2frame.dataDisplay:
2977        G2frame.dataDisplay.Destroy()
2978    G2frame.dataFrame.SetLabel('Notebook')
2979    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2980        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
2981    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
2982    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
2983    for line in data:
2984        G2frame.dataDisplay.AppendText(line+"\n")
2985    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
2986    G2frame.dataFrame.setSizePosLeft([400,250])
2987           
2988################################################################################
2989#####  Controls
2990################################################################################           
2991       
2992def UpdateControls(G2frame,data):
2993    '''Edit overall GSAS-II controls in main Controls data tree entry
2994    '''
2995    #patch
2996    if 'deriv type' not in data:
2997        data = {}
2998        data['deriv type'] = 'analytic Hessian'
2999        data['min dM/M'] = 0.0001
3000        data['shift factor'] = 1.
3001        data['max cyc'] = 3       
3002        data['F**2'] = True
3003        data['minF/sig'] = 0
3004    if 'shift factor' not in data:
3005        data['shift factor'] = 1.
3006    if 'max cyc' not in data:
3007        data['max cyc'] = 3
3008    if 'F**2' not in data:
3009        data['F**2'] = True
3010        data['minF/sig'] = 0
3011    if 'Author' not in data:
3012        data['Author'] = 'no name'
3013    if 'FreePrm1' not in data:
3014        data['FreePrm1'] = 'Sample humidity (%)'
3015    if 'FreePrm2' not in data:
3016        data['FreePrm2'] = 'Sample voltage (V)'
3017    if 'FreePrm3' not in data:
3018        data['FreePrm3'] = 'Applied load (MN)'
3019    #end patch
3020
3021    def SeqSizer():
3022       
3023        def OnSelectData(event):
3024            choices = ['All',]+GetPatternTreeDataNames(G2frame,['PWDR',])
3025            sel = []
3026            if 'Seq Data' in data:
3027                for item in data['Seq Data']:
3028                    sel.append(choices.index(item))
3029            names = []
3030            dlg = wx.MultiChoiceDialog(G2frame,'Select data:','Sequential refinement',choices)
3031            dlg.SetSelections(sel)
3032            if dlg.ShowModal() == wx.ID_OK:
3033                sel = dlg.GetSelections()
3034                for i in sel: names.append(choices[i])
3035                if 'All' in names:
3036                    names = choices[1:]
3037                data['Seq Data'] = names               
3038            dlg.Destroy()
3039            reverseSel.Enable(True)
3040           
3041        def OnReverse(event):
3042            data['Reverse Seq'] = reverseSel.GetValue()
3043                   
3044        seqSizer = wx.BoxSizer(wx.HORIZONTAL)
3045        seqSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement Powder Data: '),0,wx.ALIGN_CENTER_VERTICAL)
3046        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3047        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3048        seqSizer.Add(selSeqData,0,wx.ALIGN_CENTER_VERTICAL)
3049        seqSizer.Add((5,0),0)
3050        reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3051        reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3052        if 'Seq Data' not in data:
3053            reverseSel.Enable(False)
3054        if 'Reverse Seq' in data:
3055            reverseSel.SetValue(data['Reverse Seq'])
3056        seqSizer.Add(reverseSel,0,wx.ALIGN_CENTER_VERTICAL)
3057        return seqSizer
3058       
3059    def LSSizer():       
3060       
3061        def OnDerivType(event):
3062            data['deriv type'] = derivSel.GetValue()
3063            derivSel.SetValue(data['deriv type'])
3064            wx.CallAfter(UpdateControls,G2frame,data)
3065           
3066        def OnConvergence(event):
3067            try:
3068                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3069            except ValueError:
3070                value = 0.0001
3071            data['min dM/M'] = value
3072            Cnvrg.SetValue('%.2g'%(value))
3073           
3074        def OnMaxCycles(event):
3075            data['max cyc'] = int(maxCyc.GetValue())
3076            maxCyc.SetValue(str(data['max cyc']))
3077                       
3078        def OnFactor(event):
3079            try:
3080                value = min(max(float(Factr.GetValue()),0.00001),100.)
3081            except ValueError:
3082                value = 1.0
3083            data['shift factor'] = value
3084            Factr.SetValue('%.5f'%(value))
3085           
3086        def OnFsqRef(event):
3087            data['F**2'] = fsqRef.GetValue()
3088       
3089        def OnMinSig(event):
3090            try:
3091                value = min(max(float(minSig.GetValue()),0.),5.)
3092            except ValueError:
3093                value = 1.0
3094            data['minF/sig'] = value
3095            minSig.SetValue('%.2f'%(value))
3096
3097        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3098        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,wx.ALIGN_CENTER_VERTICAL)
3099        Choice=['analytic Jacobian','numeric','analytic Hessian']
3100        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3101            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3102        derivSel.SetValue(data['deriv type'])
3103        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3104           
3105        LSSizer.Add(derivSel,0,wx.ALIGN_CENTER_VERTICAL)
3106        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,wx.ALIGN_CENTER_VERTICAL)
3107        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3108        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3109        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3110        LSSizer.Add(Cnvrg,0,wx.ALIGN_CENTER_VERTICAL)
3111        if 'Hessian' in data['deriv type']:
3112            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,wx.ALIGN_CENTER_VERTICAL)
3113            Choice = ['0','1','2','3','5','10','15','20']
3114            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3115                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3116            maxCyc.SetValue(str(data['max cyc']))
3117            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3118            LSSizer.Add(maxCyc,0,wx.ALIGN_CENTER_VERTICAL)
3119        else:
3120            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,wx.ALIGN_CENTER_VERTICAL)
3121            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3122            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3123            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3124            LSSizer.Add(Factr,0,wx.ALIGN_CENTER_VERTICAL)
3125        if G2frame.Sngl:
3126            LSSizer.Add((1,0),)
3127            LSSizer.Add((1,0),)
3128            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3129            fsqRef.SetValue(data['F**2'])
3130            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3131            LSSizer.Add(fsqRef,0,wx.ALIGN_CENTER_VERTICAL)
3132            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,wx.ALIGN_CENTER_VERTICAL)
3133            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3134            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3135            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3136            LSSizer.Add(minSig,0,wx.ALIGN_CENTER_VERTICAL)
3137        return LSSizer
3138       
3139    def AuthSizer():
3140
3141        def OnAuthor(event):
3142            data['Author'] = auth.GetValue()
3143
3144        Author = data['Author']
3145        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3146        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,wx.ALIGN_CENTER_VERTICAL)
3147        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3148        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3149        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3150        authSizer.Add(auth,0,wx.ALIGN_CENTER_VERTICAL)
3151        return authSizer
3152       
3153       
3154    if G2frame.dataDisplay:
3155        G2frame.dataDisplay.Destroy()
3156    if not G2frame.dataFrame.GetStatusBar():
3157        Status = G2frame.dataFrame.CreateStatusBar()
3158        Status.SetStatusText('')
3159    G2frame.dataFrame.SetLabel('Controls')
3160    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3161    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3162    mainSizer = wx.BoxSizer(wx.VERTICAL)
3163    mainSizer.Add((5,5),0)
3164    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,wx.ALIGN_CENTER_VERTICAL)   
3165    mainSizer.Add(LSSizer())
3166    mainSizer.Add((5,5),0)
3167    mainSizer.Add(SeqSizer())
3168    mainSizer.Add((5,5),0)
3169    mainSizer.Add(AuthSizer())
3170    mainSizer.Add((5,5),0)
3171       
3172    mainSizer.Layout()   
3173    G2frame.dataDisplay.SetSizer(mainSizer)
3174    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3175    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3176     
3177################################################################################
3178#####  Comments
3179################################################################################           
3180       
3181def UpdateComments(G2frame,data):                   
3182
3183    if G2frame.dataDisplay:
3184        G2frame.dataDisplay.Destroy()
3185    G2frame.dataFrame.SetLabel('Comments')
3186    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3187        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3188    for line in data:
3189        G2frame.dataDisplay.AppendText(line+'\n')
3190    G2frame.dataFrame.setSizePosLeft([400,250])
3191           
3192################################################################################
3193#####  Sequential Results
3194################################################################################           
3195       
3196def UpdateSeqResults(G2frame,data):
3197    """
3198    Called when the Sequential Results data tree entry is selected
3199    to show results from a sequential refinement.
3200   
3201    :param wx.Frame G2frame: main GSAS-II data tree windows
3202
3203    :param dict data: a dictionary containing the following items: 
3204
3205            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3206            * 'varyList' - list of variables - identical over all refinements in sequence
3207            * 'histName' - dictionaries for all data sets processed, which contains:
3208
3209              * 'variables'- result[0] from leastsq call
3210              * 'varyList' - list of variables; same as above
3211              * 'sig' - esds for variables
3212              * 'covMatrix' - covariance matrix from individual refinement
3213              * 'title' - histogram name; same as dict item name
3214              * 'newAtomDict' - new atom parameters after shifts applied
3215              * 'newCellDict' - new cell parameters after shifts to A0-A5 applied'
3216    """
3217    if not data:
3218        print 'No sequential refinement results'
3219        return
3220    histNames = data['histNames']
3221       
3222    def GetSampleParms():
3223        sampleParmDict = {'Temperature':300.,'Pressure':1.,
3224                          'FreePrm1':0.,'FreePrm2':0.,'FreePrm3':0.,}
3225        sampleParm = {}
3226        for name in histNames:
3227            Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3228            sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3229            for item in sampleParmDict:
3230                sampleParmDict[item].append(sampleData[item])
3231        for item in sampleParmDict:
3232            frstValue = sampleParmDict[item][0]
3233            if np.any(np.array(sampleParmDict[item])-frstValue):
3234                sampleParm[item] = sampleParmDict[item]           
3235        return sampleParm
3236           
3237    def GetRwps():
3238        Rwps = []
3239        for name in histNames:
3240            Rwps.append(data[name]['Rvals']['Rwp'])
3241        return Rwps
3242           
3243    def GetSigData(parm):
3244        sigData = []
3245        for name in histNames:
3246            sigList = data[name]['sig']
3247            if colLabels[parm] in atomList:
3248                sigData.append(sigList[colLabels.index(atomList[colLabels[parm]])-1])
3249            elif colLabels[parm] in cellList:
3250                sigData.append(sigList[colLabels.index(cellList[colLabels[parm]])-1])
3251            else:
3252                sigData.append(sigList[parm-1])
3253        return sigData
3254   
3255    def Select(event):
3256        cols = G2frame.dataDisplay.GetSelectedCols()
3257        rows = G2frame.dataDisplay.GetSelectedRows()
3258        if cols:
3259            plotData = []
3260            plotSig = []
3261            plotNames = []
3262            for col in cols:
3263                plotData.append(G2frame.SeqTable.GetColValues(col))
3264                if col:     #not Rwp
3265                    plotSig.append(GetSigData(col))
3266                else:
3267                    plotSig.append(0.0)
3268                plotNames.append(G2frame.SeqTable.GetColLabelValue(col))
3269            plotData = np.array(plotData)
3270            G2plt.PlotSeq(G2frame,plotData,plotSig,plotNames,sampleParms)
3271        elif rows:
3272            name = histNames[rows[0]]       #only does 1st one selected
3273            G2plt.PlotCovariance(G2frame,data[name])
3274           
3275    def OnSaveSelSeq(event):       
3276        cols = G2frame.dataDisplay.GetSelectedCols()
3277        if cols:
3278            numRows = G2frame.SeqTable.GetNumberRows()
3279            dataNames = []
3280            saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(numRows)]
3281            saveData = []
3282            for col in cols:
3283                dataNames.append(G2frame.SeqTable.GetColLabelValue(col))
3284                if col:     #not Rwp
3285                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),GetSigData(col)))
3286                else:
3287                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),0.0))
3288            lenName = len(saveNames[0])
3289            saveData = np.swapaxes(np.array(saveData),0,1)
3290            dlg = wx.FileDialog(G2frame, 'Choose text output file for your selection', '.', '', 
3291                'Text output file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
3292            try:
3293                if dlg.ShowModal() == wx.ID_OK:
3294                    SeqTextFile = dlg.GetPath()
3295                    SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile)
3296                    SeqFile = open(SeqTextFile,'w')
3297                    line = %s  '%('name'.center(lenName))
3298                    for item in dataNames:
3299                        line += ' %12s %12s '%(item.center(12),'esd'.center(12))
3300                    line += '\n'
3301                    SeqFile.write(line)
3302                    for i,item in enumerate(saveData):
3303                        line = " '%s' "%(saveNames[i])
3304                        for val,esd in item:
3305                            line += ' %12.6f %12.6f '%(val,esd)
3306                        line += '\n'
3307                        SeqFile.write(line)
3308                    SeqFile.close()
3309            finally:
3310                dlg.Destroy()
3311           
3312               
3313    if G2frame.dataDisplay:
3314        G2frame.dataDisplay.Destroy()
3315    atomList = {}
3316    newAtomDict = data[histNames[0]]['newAtomDict']
3317    for item in newAtomDict:
3318        if item in data['varyList']:
3319            atomList[newAtomDict[item][0]] = item
3320    cellList = {}
3321    newCellDict = data[histNames[0]]['newCellDict']
3322    for item in newCellDict:
3323        if item in data['varyList']:
3324            cellList[newCellDict[item][0]] = item
3325    sampleParms = GetSampleParms()
3326    Rwps = GetRwps()
3327    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
3328    G2frame.dataFrame.SetLabel('Sequential refinement results')
3329    G2frame.dataFrame.CreateStatusBar()
3330    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
3331    colLabels = ['Rwp',]+data['varyList']+atomList.keys()+cellList.keys()
3332    Types = (len(data['varyList']+atomList.keys()+cellList.keys())+1)*[wg.GRID_VALUE_FLOAT,]
3333    seqList = [[Rwps[i],]+list(data[name]['variables']) for i,name in enumerate(histNames)]
3334    for i,item in enumerate(seqList):
3335        newAtomDict = data[histNames[i]]['newAtomDict']
3336        newCellDict = data[histNames[i]]['newCellDict']
3337        item += [newAtomDict[atomList[parm]][1] for parm in atomList.keys()]
3338        item += [newCellDict[cellList[parm]][1] for parm in cellList.keys()]
3339    G2frame.SeqTable = Table(seqList,colLabels=colLabels,rowLabels=histNames,types=Types)
3340    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
3341    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
3342    G2frame.dataDisplay.EnableEditing(False)
3343    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, Select)
3344    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
3345    G2frame.dataDisplay.SetMargins(0,0)
3346    G2frame.dataDisplay.AutoSizeColumns(True)
3347    G2frame.dataFrame.setSizePosLeft([700,350])
3348       
3349################################################################################
3350#####  Main PWDR panel
3351################################################################################           
3352       
3353def UpdatePWHKPlot(G2frame,kind,item):
3354    '''Called when the histogram main tree entry is called. Displays the
3355    histogram weight factor, refinement statistics for the histogram
3356    and the range of data for a simulation.
3357
3358    Also invokes a plot of the histogram.
3359    '''
3360    def onEditSimRange(event):
3361        'Edit simulation range'
3362        inp = [
3363            min(data[1][0]),
3364            max(data[1][0]),
3365            None
3366            ]
3367        inp[2] = (inp[1] - inp[0])/(len(data[1][0])-1.)
3368        names = ('start angle', 'end angle', 'step size')
3369        dictlst = [inp] * len(inp)
3370        elemlst = range(len(inp))
3371        dlg = ScrolledMultiEditor(
3372            G2frame,[inp] * len(inp), range(len(inp)), names,
3373            header='Edit simulation range',
3374            minvals=(0.001,0.001,0.0001),
3375            maxvals=(180.,180.,.1),
3376            )
3377        dlg.CenterOnParent()
3378        val = dlg.ShowModal()
3379        dlg.Destroy()
3380        if val != wx.ID_OK: return
3381        if inp[0] > inp[1]:
3382            end,start,step = inp
3383        else:               
3384            start,end,step = inp
3385        step = abs(step)
3386        N = int((end-start)/step)+1
3387        newdata = np.linspace(start,end,N,True)
3388        if len(newdata) < 2: return # too small a range - reject
3389        data[1] = [newdata,np.zeros_like(newdata),np.ones_like(newdata),
3390            np.zeros_like(newdata),np.zeros_like(newdata),np.zeros_like(newdata)]
3391        Tmin = newdata[0]
3392        Tmax = newdata[-1]
3393        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,item,'Limits'),
3394            [(Tmin,Tmax),[Tmin,Tmax]])
3395        UpdatePWHKPlot(G2frame,kind,item) # redisplay data screen
3396
3397    def OnErrorAnalysis(event):
3398        G2plt.PlotDeltSig(G2frame,kind)
3399       
3400    def OnWtFactor(event):
3401        try:
3402            val = float(wtval.GetValue())
3403        except ValueError:
3404            val = data[0]['wtFactor']
3405        data[0]['wtFactor'] = val
3406        wtval.SetValue('%.3f'%(val))
3407           
3408    data = G2frame.PatternTree.GetItemPyData(item)
3409#patches
3410    if 'wtFactor' not in data[0]:
3411        data[0] = {'wtFactor':1.0}
3412    if isinstance(data[1],list) and kind == 'HKLF':
3413        RefData = {'RefList':[],'FF':[]}
3414        for ref in data[1]:
3415            RefData['RefList'].append(ref[:11]+[ref[13],])
3416            RefData['FF'].append(ref[14])
3417        data[1] = RefData
3418        G2frame.PatternTree.SetItemPyData(item,data)
3419#end patches
3420    if G2frame.dataDisplay:
3421        G2frame.dataDisplay.Destroy()
3422    SetDataMenuBar(G2frame,G2frame.dataFrame.ErrorMenu)
3423    G2frame.dataFrame.Bind(wx.EVT_MENU,OnErrorAnalysis, id=wxID_PWDANALYSIS)
3424    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3425   
3426    mainSizer = wx.BoxSizer(wx.VERTICAL)
3427    mainSizer.Add((5,5),)
3428    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
3429    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,wx.ALIGN_CENTER_VERTICAL)
3430    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
3431    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
3432    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
3433    wtSizer.Add(wtval,0,wx.ALIGN_CENTER_VERTICAL)
3434    mainSizer.Add(wtSizer)
3435    if data[0].get('Dummy'):
3436        simSizer = wx.BoxSizer(wx.HORIZONTAL)
3437        Tmin = min(data[1][0])
3438        Tmax = max(data[1][0])
3439        num = len(data[1][0])
3440        step = (Tmax - Tmin)/(num-1)
3441        t = u'2\u03b8' # 2theta
3442        lbl =  u'Simulation range: {:.2f} to {:.2f} {:s}\nwith {:.4f} steps ({:d} points)'
3443        lbl += u'\n(Edit range resets observed intensities).'
3444        lbl = lbl.format(Tmin,Tmax,t,step,num)
3445        simSizer.Add(wx.StaticText(G2frame.dataDisplay,wx.ID_ANY,lbl),
3446                    0,wx.ALIGN_CENTER_VERTICAL)
3447        but = wx.Button(G2frame.dataDisplay,wx.ID_ANY,"Edit range")
3448        but.Bind(wx.EVT_BUTTON,onEditSimRange)
3449        simSizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL)
3450        mainSizer.Add(simSizer)
3451    if 'Nobs' in data[0]:
3452        mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
3453            ' Data residual wR: %.3f%% on %d observations'%(data[0]['wR'],data[0]['Nobs'])))
3454        for value in data[0]:
3455            if 'Nref' in value:
3456                mainSizer.Add((5,5),)
3457                pfx = value.split('Nref')[0]
3458                name = data[0][pfx.split(':')[0]+'::Name']
3459                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' For phase '+name+':'))
3460                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
3461                    u' Unweighted phase residuals RF\u00b2: %.3f%%, RF: %.3f%% on %d reflections  '% \
3462                    (data[0][pfx+'Rf^2'],data[0][pfx+'Rf'],data[0][value])))
3463    mainSizer.Add((5,5),)
3464    mainSizer.Layout()   
3465    G2frame.dataDisplay.SetSizer(mainSizer)
3466    Size = mainSizer.Fit(G2frame.dataFrame)
3467    Size[1] += 10
3468    G2frame.dataFrame.setSizePosLeft(Size)
3469    G2frame.PatternTree.SetItemPyData(item,data)
3470    if kind == 'PWDR':
3471        G2plt.PlotPatterns(G2frame,newPlot=True)
3472    elif kind == 'HKLF':
3473        G2plt.PlotSngl(G2frame,newPlot=True)
3474                 
3475################################################################################
3476#####  HKLF controls
3477################################################################################           
3478       
3479def UpdateHKLControls(G2frame,data):
3480    '''Needs a doc string
3481    '''
3482   
3483    def OnScaleSlider(event):
3484        scale = int(scaleSel.GetValue())/1000.
3485        scaleSel.SetValue(int(scale*1000.))
3486        data['Scale'] = scale*1.
3487        G2plt.PlotSngl(G2frame)
3488       
3489    def OnLayerSlider(event):
3490        layer = layerSel.GetValue()
3491        data['Layer'] = layer
3492        G2plt.PlotSngl(G2frame)
3493       
3494    def OnSelZone(event):
3495        data['Zone'] = zoneSel.GetValue()
3496        izone = zones.index(data['Zone'])
3497        layerSel.SetRange(maxValue=HKLmax[izone],minValue=HKLmin[izone])
3498        G2plt.PlotSngl(G2frame,newPlot=True)
3499       
3500    def OnSelType(event):
3501        data['Type'] = typeSel.GetValue()
3502        G2plt.PlotSngl(G2frame)
3503       
3504    def SetStatusLine():
3505        Status.SetStatusText("")
3506                                     
3507    if G2frame.dataDisplay:
3508        G2frame.dataDisplay.Destroy()
3509    if not G2frame.dataFrame.GetStatusBar():
3510        Status = G2frame.dataFrame.CreateStatusBar()
3511    SetStatusLine()
3512    zones = ['100','010','001']
3513    HKLmax = data['HKLmax']
3514    HKLmin = data['HKLmin']
3515    typeChoices = ['Fosq','Fo','|DFsq|/sig','|DFsq|>sig','|DFsq|>3sig']
3516    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3517    SetDataMenuBar(G2frame)
3518    G2frame.dataFrame.SetTitle('HKL Plot Controls')
3519    mainSizer = wx.BoxSizer(wx.VERTICAL)
3520    mainSizer.Add((5,10),0)
3521   
3522    scaleSizer = wx.BoxSizer(wx.HORIZONTAL)
3523    scaleSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Scale'),0,
3524        wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
3525    scaleSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=1000,minValue=1,
3526        style=wx.SL_HORIZONTAL,value=int(data['Scale']*10))
3527    scaleSizer.Add(scaleSel,1,wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
3528    scaleSel.SetLineSize(10)
3529    scaleSel.SetPageSize(10)
3530    scaleSel.Bind(wx.EVT_SLIDER, OnScaleSlider)
3531    mainSizer.Add(scaleSizer,0,wx.EXPAND|wx.RIGHT)
3532    mainSizer.Add((0,10),0)   
3533   
3534    zoneSizer = wx.BoxSizer(wx.HORIZONTAL)
3535    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Zone  '),0,
3536        wx.ALIGN_CENTER_VERTICAL)
3537    zoneSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Zone'],choices=['100','010','001'],
3538        style=wx.CB_READONLY|wx.CB_DROPDOWN)
3539    zoneSel.Bind(wx.EVT_COMBOBOX, OnSelZone)
3540    zoneSizer.Add(zoneSel,0,wx.ALIGN_CENTER_VERTICAL)
3541    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Plot type  '),0,
3542        wx.ALIGN_CENTER_VERTICAL)       
3543    typeSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Type'],choices=typeChoices,
3544        style=wx.CB_READONLY|wx.CB_DROPDOWN)
3545    typeSel.Bind(wx.EVT_COMBOBOX, OnSelType)
3546    zoneSizer.Add(typeSel,0,wx.ALIGN_CENTER_VERTICAL)
3547    zoneSizer.Add((10,0),0)   
3548    mainSizer.Add(zoneSizer,0,wx.EXPAND|wx.RIGHT)
3549    mainSizer.Add((0,10),0)   
3550       
3551    izone = zones.index(data['Zone'])
3552    layerSizer = wx.BoxSizer(wx.HORIZONTAL)
3553    layerSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Layer'),0,
3554        wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
3555    layerSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=HKLmax[izone],minValue=HKLmin[izone],
3556        style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_LABELS,value=0)
3557    layerSel.SetLineSize(1)
3558    layerSel.SetPageSize(1)
3559    layerSel.Bind(wx.EVT_SLIDER, OnLayerSlider)   
3560    layerSizer.Add(layerSel,1,wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
3561    layerSizer.Add((10,0),0)   
3562    mainSizer.Add(layerSizer,1,wx.EXPAND|wx.RIGHT)
3563
3564       
3565    mainSizer.Layout()   
3566    G2frame.dataDisplay.SetSizer(mainSizer)
3567    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3568    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3569
3570################################################################################
3571#####  Pattern tree routines
3572################################################################################           
3573       
3574def GetPatternTreeDataNames(G2frame,dataTypes):
3575    '''Needs a doc string
3576    '''
3577    names = []
3578    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
3579    while item:
3580        name = G2frame.PatternTree.GetItemText(item)
3581        if name[:4] in dataTypes:
3582            names.append(name)
3583        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
3584    return names
3585                         
3586def GetPatternTreeItemId(G2frame, parentId, itemText):
3587    '''Needs a doc string
3588    '''
3589    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
3590    while item:
3591        if G2frame.PatternTree.GetItemText(item) == itemText:
3592            return item
3593        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
3594    return 0               
3595
3596def MovePatternTreeToGrid(G2frame,item):
3597    '''Needs a doc string
3598    '''
3599   
3600#    print G2frame.PatternTree.GetItemText(item)
3601   
3602    oldPage = None # will be set later if already on a Phase item
3603    if G2frame.dataFrame:
3604        SetDataMenuBar(G2frame)
3605        if G2frame.dataFrame.GetLabel() == 'Comments':
3606            try:
3607                data = [G2frame.dataDisplay.GetValue()]
3608                G2frame.dataDisplay.Clear() 
3609                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
3610                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
3611            except:     #clumsy but avoids dead window problem when opening another project
3612                pass
3613        elif G2frame.dataFrame.GetLabel() == 'Notebook':
3614            try:
3615                data = [G2frame.dataDisplay.GetValue()]
3616                G2frame.dataDisplay.Clear() 
3617                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
3618                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
3619            except:     #clumsy but avoids dead window problem when opening another project
3620                pass
3621        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
3622            if G2frame.dataDisplay: 
3623                oldPage = G2frame.dataDisplay.GetSelection()
3624        G2frame.dataFrame.Clear()
3625        G2frame.dataFrame.SetLabel('')
3626    else:
3627        #create the frame for the data item window
3628        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
3629        G2frame.dataFrame.PhaseUserSize = None
3630       
3631    G2frame.dataFrame.Raise()           
3632    G2frame.PickId = 0
3633    parentID = G2frame.root
3634    for i in G2frame.ExportPattern: i.Enable(False)
3635    defWid = [250,150]
3636    if item != G2frame.root:
3637        parentID = G2frame.PatternTree.GetItemParent(item)
3638    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
3639        G2frame.PatternId = item
3640        G2frame.PickId = item
3641        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
3642            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
3643            G2frame.PatternId = 0
3644            for i in G2frame.ExportPattern: i.Enable(False)
3645            data = G2frame.PatternTree.GetItemPyData(item)
3646            UpdateNotebook(G2frame,data)
3647        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
3648            G2frame.PatternId = 0
3649            for i in G2frame.ExportPattern: i.Enable(False)
3650            data = G2frame.PatternTree.GetItemPyData(item)
3651            if not data:           #fill in defaults
3652                data = copy.copy(G2obj.DefaultControls)    #least squares controls
3653                G2frame.PatternTree.SetItemPyData(item,data)                             
3654            for i in G2frame.Refine: i.Enable(True)
3655            for i in G2frame.SeqRefine: i.Enable(True)
3656            UpdateControls(G2frame,data)
3657        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
3658            data = G2frame.PatternTree.GetItemPyData(item)
3659            UpdateSeqResults(G2frame,data)           
3660        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
3661            data = G2frame.PatternTree.GetItemPyData(item)
3662            G2frame.dataFrame.setSizePosLeft(defWid)
3663            text = ''
3664            if 'Rvals' in data:
3665                Nvars = len(data['varyList'])
3666                Rvals = data['Rvals']
3667                text = '\nFinal residuals: \nwR = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
3668                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
3669                if 'lamMax' in Rvals:
3670                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
3671            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3672                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
3673            G2plt.PlotCovariance(G2frame,data)
3674        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
3675            data = G2frame.PatternTree.GetItemPyData(item)
3676            G2cnstG.UpdateConstraints(G2frame,data)
3677        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
3678            data = G2frame.PatternTree.GetItemPyData(item)
3679            G2cnstG.UpdateRigidBodies(G2frame,data)
3680        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
3681            data = G2frame.PatternTree.GetItemPyData(item)
3682            Phases = G2frame.GetPhaseData()
3683            phase = ''
3684            phaseName = ''
3685            if Phases:
3686                phaseName = Phases.keys()[0]
3687            G2frame.dataFrame.setSizePosLeft(defWid)
3688            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
3689        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
3690            G2frame.Image = item
3691            G2plt.PlotImage(G2frame,newPlot=True)
3692        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
3693            G2plt.PlotPowderLines(G2frame)
3694        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
3695            for i in G2frame.ExportPattern: i.Enable(True)
3696            UpdatePWHKPlot(G2frame,'PWDR',item)
3697        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
3698            G2frame.Sngl = item
3699            UpdatePWHKPlot(G2frame,'HKLF',item)
3700        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
3701            G2frame.PatternId = item
3702            for i in G2frame.ExportPDF: i.Enable(True)
3703            G2plt.PlotISFG(G2frame,type='S(Q)')
3704        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
3705            G2frame.dataFrame.setSizePosLeft(defWid)
3706            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3707                value='Select one phase to see its parameters')           
3708    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
3709        G2frame.PickId = item
3710        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3711        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
3712    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
3713        G2frame.PickId = item
3714        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3715        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
3716    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
3717        G2frame.PickId = item
3718        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3719        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
3720    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
3721        G2frame.PickId = item
3722        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3723        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
3724    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
3725        G2frame.PickId = item
3726        data = G2frame.PatternTree.GetItemPyData(item)
3727        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
3728    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
3729        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
3730        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3731        G2frame.PickId = item
3732        data = G2frame.PatternTree.GetItemPyData(item)
3733        UpdateComments(G2frame,data)
3734    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
3735        G2frame.dataFrame.SetTitle('Image Controls')
3736        G2frame.PickId = item
3737        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3738        masks = G2frame.PatternTree.GetItemPyData(
3739            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
3740        data = G2frame.PatternTree.GetItemPyData(item)
3741        G2imG.UpdateImageControls(G2frame,data,masks)
3742        G2plt.PlotImage(G2frame)
3743    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
3744        G2frame.dataFrame.SetTitle('Masks')
3745        G2frame.PickId = item
3746        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3747        data = G2frame.PatternTree.GetItemPyData(item)
3748        G2imG.UpdateMasks(G2frame,data)
3749        G2plt.PlotImage(G2frame)
3750    elif G2frame.PatternTree.GetItemText(item) == 'Stress/Strain':
3751        G2frame.dataFrame.SetTitle('Stress/Strain')
3752        G2frame.PickId = item
3753        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3754        data = G2frame.PatternTree.GetItemPyData(item)
3755        G2imG.UpdateStressStrain(G2frame,data)
3756        G2plt.PlotImage(G2frame)
3757    elif G2frame.PatternTree.GetItemText(item) == 'HKL Plot Controls':
3758        G2frame.PickId = item
3759        G2frame.Sngl = G2frame.PatternTree.GetItemParent(item)
3760        data = G2frame.PatternTree.GetItemPyData(item)
3761        UpdateHKLControls(G2frame,data)
3762        G2plt.PlotSngl(G2frame)
3763    elif G2frame.PatternTree.GetItemText(item) == 'PDF Controls':
3764        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3765        for i in G2frame.ExportPDF: i.Enable(True)
3766        G2frame.PickId = item
3767        data = G2frame.PatternTree.GetItemPyData(item)
3768        G2pdG.UpdatePDFGrid(G2frame,data)
3769        G2plt.PlotISFG(G2frame,type='I(Q)')
3770        G2plt.PlotISFG(G2frame,type='S(Q)')
3771        G2plt.PlotISFG(G2frame,type='F(Q)')
3772        G2plt.PlotISFG(G2frame,type='G(R)')
3773    elif G2frame.PatternTree.GetItemText(item) == 'Peak List':
3774        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3775        for i in G2frame.ExportPeakList: i.Enable(True)
3776        G2frame.PickId = item
3777        data = G2frame.PatternTree.GetItemPyData(item)
3778        G2pdG.UpdatePeakGrid(G2frame,data)
3779        G2plt.PlotPatterns(G2frame)
3780    elif G2frame.PatternTree.GetItemText(item) == 'Background':
3781        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3782        G2frame.PickId = item
3783        data = G2frame.PatternTree.GetItemPyData(item)
3784        G2pdG.UpdateBackground(G2frame,data)
3785        G2plt.PlotPatterns(G2frame)
3786    elif G2frame.PatternTree.GetItemText(item) == 'Limits':
3787        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3788        G2frame.PickId = item
3789        data = G2frame.PatternTree.GetItemPyData(item)
3790        G2pdG.UpdateLimitsGrid(G2frame,data)
3791        G2plt.PlotPatterns(G2frame)
3792    elif G2frame.PatternTree.GetItemText(item) == 'Instrument Parameters':
3793        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3794        G2frame.PickId = item
3795        data = G2frame.PatternTree.GetItemPyData(item)[0]
3796        G2pdG.UpdateInstrumentGrid(G2frame,data)
3797        G2plt.PlotPeakWidths(G2frame)
3798    elif G2frame.PatternTree.GetItemText(item) == 'Sample Parameters':
3799        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3800        G2frame.PickId = item
3801        data = G2frame.PatternTree.GetItemPyData(item)
3802
3803        if 'Temperature' not in data:           #temp fix for old gpx files
3804            data = {'Scale':[1.0,True],'Type':'Debye-Scherrer','Absorption':[0.0,False],'DisplaceX':[0.0,False],
3805                'DisplaceY':[0.0,False],'Diffuse':[],'Temperature':300.,'Pressure':1.0,
3806                    'FreePrm1':0.,'FreePrm2':0.,'FreePrm3':0.,
3807                    'Gonio. radius':200.0}
3808            G2frame.PatternTree.SetItemPyData(item,data)
3809   
3810        G2pdG.UpdateSampleGrid(G2frame,data)
3811        G2plt.PlotPatterns(G2frame)
3812    elif G2frame.PatternTree.GetItemText(item) == 'Index Peak List':
3813        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3814        for i in G2frame.ExportPeakList: i.Enable(True)
3815        G2frame.PickId = item
3816        data = G2frame.PatternTree.GetItemPyData(item)
3817        G2pdG.UpdateIndexPeaksGrid(G2frame,data)
3818        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
3819            G2plt.PlotPowderLines(G2frame)
3820        else:
3821            G2plt.PlotPatterns(G2frame)
3822    elif G2frame.PatternTree.GetItemText(item) == 'Unit Cells List':
3823        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3824        G2frame.PickId = item
3825        data = G2frame.PatternTree.GetItemPyData(item)
3826        if not data:
3827            data.append([0,0.0,4,25.0,0,'P1',1,1,1,90,90,90]) #zero error flag, zero value, max Nc/No, start volume
3828            data.append([0,0,0,0,0,0,0,0,0,0,0,0,0,0])      #Bravais lattice flags
3829            data.append([])                                 #empty cell list
3830            data.append([])                                 #empty dmin
3831            G2frame.PatternTree.SetItemPyData(item,data)                             
3832        G2pdG.UpdateUnitCellsGrid(G2frame,data)
3833        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
3834            G2plt.PlotPowderLines(G2frame)
3835        else:
3836            G2plt.PlotPatterns(G2frame)
3837    elif G2frame.PatternTree.GetItemText(item) == 'Reflection Lists':   #powder reflections
3838        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3839        G2frame.PickId = item
3840        data = G2frame.PatternTree.GetItemPyData(item)
3841        G2frame.RefList = ''
3842        if len(data):
3843            G2frame.RefList = data.keys()[0]
3844        G2pdG.UpdateReflectionGrid(G2frame,data)
3845        G2plt.PlotPatterns(G2frame)
3846    elif G2frame.PatternTree.GetItemText(item) == 'Reflection List':    #HKLF reflections
3847        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3848        name = G2frame.PatternTree.GetItemText(G2frame.PatternId)
3849        data = G2frame.PatternTree.GetItemPyData(G2frame.PatternId)
3850        G2pdG.UpdateReflectionGrid(G2frame,data,HKLF=True,Name=name)
3851
3852def SetDataMenuBar(G2frame,menu=None):
3853    '''Set the menu for the data frame. On the Mac put this
3854    menu for the data tree window instead.
3855
3856    Note that data frame items do not have menus, for these (menu=None)
3857    display a blank menu or on the Mac display the standard menu for
3858    the data tree window.
3859    '''
3860    if sys.platform == "darwin":
3861        if menu is None:
3862            G2frame.SetMenuBar(G2frame.GSASIIMenu)
3863        else:
3864            G2frame.SetMenuBar(menu)
3865    else:
3866        if menu is None:
3867            G2frame.dataFrame.SetMenuBar(G2frame.dataFrame.BlankMenu)
3868        else:
3869            G2frame.dataFrame.SetMenuBar(menu)
3870
3871def HorizontalLine(sizer,parent):
3872    '''Draws a horizontal line as wide as the window.
3873    This shows up on the Mac as a very thin line, no matter what I do
3874    '''
3875    line = wx.StaticLine(parent,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
3876    sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
3877
3878if __name__ == '__main__':
3879    # test ScrolledMultiEditor
3880    app = wx.PySimpleApp()
3881    frm = wx.Frame(None) # create a frame
3882    frm.Show(True)
3883    Data1 = {
3884        'Order':1,
3885        'omega':'string',
3886        'chi':2.0,
3887        'phi':'',
3888        }
3889    elemlst = sorted(Data1.keys())
3890    postlbl = sorted(Data1.keys())
3891    dictlst = len(elemlst)*[Data1,]
3892
3893    Data2 = list(range(100))
3894    elemlst += range(2,6)
3895    postlbl += range(2,6)
3896    dictlst += len(range(2,6))*[Data2,]
3897
3898    prelbl = range(len(elemlst))
3899    postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
3900    header="""This is a longer\nmultiline and perhaps silly header"""
3901    dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
3902                              header=header,CopyButton=True)
3903    print Data1
3904    if dlg.ShowModal() == wx.ID_OK:
3905        for d,k in zip(dictlst,elemlst):
3906            print k,d[k]
3907    dlg.Destroy()
3908    if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
3909                               header=header):
3910        for d,k in zip(dictlst,elemlst):
3911            print k,d[k]
3912
3913#app.MainLoop()
Note: See TracBrowser for help on using the repository browser.