source: trunk/GSASIIgrid.py @ 1443

Last change on this file since 1443 was 1443, checked in by vondreele, 8 years ago

add calibration of lam, difC, etc. from index peak positions
new plotCalib routine

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 241.9 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2014-07-28 21:53:56 +0000 (Mon, 28 Jul 2014) $
5# $Author: vondreele $
6# $Revision: 1443 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1443 2014-07-28 21:53:56Z vondreele $
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 os
25import numpy as np
26import numpy.ma as ma
27import scipy.optimize as so
28import wx.html        # could postpone this for quicker startup
29import webbrowser     # could postpone this for quicker startup
30import GSASIIpath
31GSASIIpath.SetVersionNumber("$Revision: 1443 $")
32import GSASIImath as G2mth
33import GSASIIIO as G2IO
34import GSASIIstrIO as G2stIO
35import GSASIIlattice as G2lat
36import GSASIIplot as G2plt
37import GSASIIpwdGUI as G2pdG
38import GSASIIimgGUI as G2imG
39import GSASIIphsGUI as G2phG
40import GSASIIspc as G2spc
41import GSASIImapvars as G2mv
42import GSASIIconstrGUI as G2cnstG
43import GSASIIrestrGUI as G2restG
44import GSASIIpy3 as G2py3
45import GSASIIobj as G2obj
46import GSASIIexprGUI as G2exG
47
48# trig functions in degrees
49sind = lambda x: np.sin(x*np.pi/180.)
50tand = lambda x: np.tan(x*np.pi/180.)
51cosd = lambda x: np.cos(x*np.pi/180.)
52
53# globals we will use later
54__version__ = None # gets overridden in GSASII.py
55path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # save location of this file
56helpLocDict = {}
57htmlPanel = None
58htmlFrame = None
59helpMode = 'browser'
60#if sys.platform.lower().startswith('win'): helpMode = 'internal' # need a global control to set this
61   
62htmlFirstUse = True
63WACV = wx.ALIGN_CENTER_VERTICAL
64
65[ wxID_FOURCALC, wxID_FOURSEARCH, wxID_FOURCLEAR, wxID_PEAKSMOVE, wxID_PEAKSCLEAR, 
66    wxID_CHARGEFLIP, wxID_PEAKSUNIQUE, wxID_PEAKSDELETE, wxID_PEAKSDA,
67    wxID_PEAKSDISTVP, wxID_PEAKSVIEWPT, wxID_FINDEQVPEAKS,wxID_SHOWBONDS,wxID_MULTIMCSA,
68    wxID_SINGLEMCSA
69] = [wx.NewId() for item in range(15)]
70
71[ wxID_PWDRADD, wxID_HKLFADD, wxID_PWDANALYSIS, wxID_PWDCOPY, wxID_DATADELETE,
72] = [wx.NewId() for item in range(5)]
73
74[ wxID_ATOMSEDITADD, wxID_ATOMSEDITINSERT, wxID_ATOMSEDITDELETE, wxID_ATOMSREFINE, 
75    wxID_ATOMSMODIFY, wxID_ATOMSTRANSFORM, wxID_ATOMSVIEWADD, wxID_ATOMVIEWINSERT,
76    wxID_RELOADDRAWATOMS,wxID_ATOMSDISAGL,wxID_ATOMMOVE,
77    wxID_ASSIGNATMS2RB,wxID_ATOMSPDISAGL, wxID_ISODISP,
78] = [wx.NewId() for item in range(14)]
79
80[ wxID_DRAWATOMSTYLE, wxID_DRAWATOMLABEL, wxID_DRAWATOMCOLOR, wxID_DRAWATOMRESETCOLOR, 
81    wxID_DRAWVIEWPOINT, wxID_DRAWTRANSFORM, wxID_DRAWDELETE, wxID_DRAWFILLCELL, 
82    wxID_DRAWADDEQUIV, wxID_DRAWFILLCOORD, wxID_DRAWDISAGLTOR,  wxID_DRAWPLANE,
83    wxID_DRAWDISTVP,
84] = [wx.NewId() for item in range(13)]
85
86[ wxID_DRAWRESTRBOND, wxID_DRAWRESTRANGLE, wxID_DRAWRESTRPLANE, wxID_DRAWRESTRCHIRAL,
87] = [wx.NewId() for item in range(4)]
88
89[ wxID_ADDMCSAATOM,wxID_ADDMCSARB,wxID_CLEARMCSARB,wxID_MOVEMCSA,wxID_MCSACLEARRESULTS,
90] = [wx.NewId() for item in range(5)]
91
92[ wxID_CLEARTEXTURE,wxID_REFINETEXTURE,
93] = [wx.NewId() for item in range(2)]
94
95[ wxID_PAWLEYLOAD, wxID_PAWLEYESTIMATE, wxID_PAWLEYUPDATE,
96] = [wx.NewId() for item in range(3)]
97
98[ wxID_IMCALIBRATE,wxID_IMRECALIBRATE,wxID_IMINTEGRATE, wxID_IMCLEARCALIB, 
99    wxID_IMCOPYCONTROLS, wxID_INTEGRATEALL, wxID_IMSAVECONTROLS, wxID_IMLOADCONTROLS,
100] = [wx.NewId() for item in range(8)]
101
102[ wxID_MASKCOPY, wxID_MASKSAVE, wxID_MASKLOAD,wxID_NEWMASKSPOT,wxID_NEWMASKARC,wxID_NEWMASKRING,
103    wxID_NEWMASKFRAME, wxID_NEWMASKPOLY,
104] = [wx.NewId() for item in range(8)]
105
106[ wxID_STRSTACOPY, wxID_STRSTAFIT, wxID_STRSTASAVE, wxID_STRSTALOAD,wxID_APPENDDZERO,
107    wxID_STRSTAALLFIT,wxID_UPDATEDZERO,
108] = [wx.NewId() for item in range(7)]
109
110[ wxID_BACKCOPY,wxID_LIMITCOPY, wxID_SAMPLECOPY, wxID_SAMPLECOPYSOME, wxID_BACKFLAGCOPY, wxID_SAMPLEFLAGCOPY,
111    wxID_SAMPLESAVE, wxID_SAMPLELOAD,wxID_ADDEXCLREGION,wxID_SETSCALE,wxID_SAMPLE1VAL
112] = [wx.NewId() for item in range(11)]
113
114[ wxID_INSTPRMRESET,wxID_CHANGEWAVETYPE,wxID_INSTCOPY, wxID_INSTFLAGCOPY, wxID_INSTLOAD,
115    wxID_INSTSAVE, wxID_INST1VAL, wxID_INSTCALIB,
116] = [wx.NewId() for item in range(8)]
117
118[ wxID_UNDO,wxID_LSQPEAKFIT,wxID_LSQONECYCLE,wxID_RESETSIGGAM,wxID_CLEARPEAKS,wxID_AUTOSEARCH,
119] = [wx.NewId() for item in range(6)]
120
121[  wxID_INDXRELOAD, wxID_INDEXPEAKS, wxID_REFINECELL, wxID_COPYCELL, wxID_MAKENEWPHASE,
122] = [wx.NewId() for item in range(5)]
123
124[ wxID_CONSTRAINTADD,wxID_EQUIVADD,wxID_HOLDADD,wxID_FUNCTADD,
125  wxID_CONSPHASE, wxID_CONSHIST, wxID_CONSHAP, wxID_CONSGLOBAL,
126] = [wx.NewId() for item in range(8)]
127
128[ wxID_RESTRAINTADD, wxID_RESTSELPHASE,wxID_RESTDELETE, wxID_RESRCHANGEVAL, 
129    wxID_RESTCHANGEESD,wxID_AARESTRAINTADD,wxID_AARESTRAINTPLOT,
130] = [wx.NewId() for item in range(7)]
131
132[ wxID_RIGIDBODYADD,wxID_DRAWDEFINERB,wxID_RIGIDBODYIMPORT,wxID_RESIDUETORSSEQ,
133    wxID_AUTOFINDRESRB,wxID_GLOBALRESREFINE,wxID_RBREMOVEALL,wxID_COPYRBPARMS,
134    wxID_GLOBALTHERM,
135] = [wx.NewId() for item in range(9)]
136
137[ wxID_RENAMESEQSEL,wxID_SAVESEQSEL,wxID_SAVESEQSELCSV,wxID_PLOTSEQSEL,
138  wxADDSEQVAR,wxDELSEQVAR,wxEDITSEQVAR,wxCOPYPARFIT,
139  wxADDPARFIT,wxDELPARFIT,wxEDITPARFIT,wxDOPARFIT,
140] = [wx.NewId() for item in range(12)]
141
142[ wxID_MODELCOPY,wxID_MODELFIT,wxID_MODELADD,wxID_ELEMENTADD,wxID_ELEMENTDELETE,
143    wxID_ADDSUBSTANCE,wxID_LOADSUBSTANCE,wxID_DELETESUBSTANCE,wxID_COPYSUBSTANCE,
144    wxID_MODELUNDO,wxID_MODELFITALL,wxID_MODELCOPYFLAGS,
145] = [wx.NewId() for item in range(12)]
146
147[ wxID_SELECTPHASE,wxID_PWDHKLPLOT,wxID_PWD3DHKLPLOT,
148] = [wx.NewId() for item in range(3)]
149
150[ wxID_PDFCOPYCONTROLS, wxID_PDFSAVECONTROLS, wxID_PDFLOADCONTROLS, 
151    wxID_PDFCOMPUTE, wxID_PDFCOMPUTEALL, wxID_PDFADDELEMENT, wxID_PDFDELELEMENT,
152] = [wx.NewId() for item in range(7)]
153
154VERY_LIGHT_GREY = wx.Colour(235,235,235)
155################################################################################
156#### GSAS-II class definitions
157################################################################################
158       
159class ValidatedTxtCtrl(wx.TextCtrl):
160    '''Create a TextCtrl widget that uses a validator to prevent the
161    entry of inappropriate characters and changes color to highlight
162    when invalid input is supplied. As valid values are typed,
163    they are placed into the dict or list where the initial value
164    came from. The type of the initial value must be int,
165    float or str or None (see :obj:`key` and :obj:`typeHint`);
166    this type (or the one in :obj:`typeHint`) is preserved.
167
168    Float values can be entered in the TextCtrl as numbers or also
169    as algebraic expressions using operators + - / \* () and \*\*,
170    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
171    as well as appreviations s, sin, c, cos, t, tan and sq.
172
173    :param wx.Panel parent: name of panel or frame that will be
174      the parent to the TextCtrl. Can be None.
175
176    :param dict/list loc: the dict or list with the initial value to be
177      placed in the TextCtrl.
178
179    :param int/str key: the dict key or the list index for the value to be
180      edited by the TextCtrl. The ``loc[key]`` element must exist, but may
181      have value None. If None, the type for the element is taken from
182      :obj:`typeHint` and the value for the control is set initially
183      blank (and thus invalid.) This is a way to specify a field without a
184      default value: a user must set a valid value.
185      If the value is not None, it must have a base
186      type of int, float, str or unicode; the TextCrtl will be initialized
187      from this value.
188     
189    :param list nDig: number of digits & places ([nDig,nPlc]) after decimal to use
190      for display of float. Alternately, None can be specified which causes
191      numbers to be displayed with approximately 5 significant figures
192      (Default=None).
193
194    :param bool notBlank: if True (default) blank values are invalid
195      for str inputs.
196     
197    :param number min: minimum allowed valid value. If None (default) the
198      lower limit is unbounded.
199
200    :param number max: maximum allowed valid value. If None (default) the
201      upper limit is unbounded
202
203    :param function OKcontrol: specifies a function or method that will be
204      called when the input is validated. The called function is supplied
205      with one argument which is False if the TextCtrl contains an invalid
206      value and True if the value is valid.
207      Note that this function should check all values
208      in the dialog when True, since other entries might be invalid.
209      The default for this is None, which indicates no function should
210      be called.
211
212    :param function OnLeave: specifies a function or method that will be
213      called when the focus for the control is lost.
214      The called function is supplied with (at present) three keyword arguments:
215
216         * invalid: (*bool*) True if the value for the TextCtrl is invalid
217         * value:   (*int/float/str*)  the value contained in the TextCtrl
218         * tc:      (*wx.TextCtrl*)  the TextCtrl name
219
220      The number of keyword arguments may be increased in the future should needs arise,
221      so it is best to code these functions with a \*\*kwargs argument so they will
222      continue to run without errors
223
224      The default for OnLeave is None, which indicates no function should
225      be called.
226
227    :param type typeHint: the value of typeHint is overrides the initial value
228      for the dict/list element ``loc[key]``, if set to
229      int or float, which specifies the type for input to the TextCtrl.
230      Defaults as None, which is ignored.
231
232    :param bool CIFinput: for str input, indicates that only printable
233      ASCII characters may be entered into the TextCtrl. Forces output
234      to be ASCII rather than Unicode. For float and int input, allows
235      use of a single '?' or '.' character as valid input.
236
237    :param dict OnLeaveArgs: a dict with keyword args that are passed to
238      the :attr:`OnLeave` function. Defaults to ``{}``
239
240    :param (other): other optional keyword parameters for the
241      wx.TextCtrl widget such as size or style may be specified.
242
243    '''
244    def __init__(self,parent,loc,key,nDig=None,notBlank=True,min=None,max=None,
245                 OKcontrol=None,OnLeave=None,typeHint=None,
246                 CIFinput=False, OnLeaveArgs={}, **kw):
247        # save passed values needed outside __init__
248        self.result = loc
249        self.key = key
250        self.nDig = nDig
251        self.OKcontrol=OKcontrol
252        self.OnLeave = OnLeave
253        self.OnLeaveArgs = OnLeaveArgs
254        self.CIFinput = CIFinput
255        self.type = str
256        # initialization
257        self.invalid = False   # indicates if the control has invalid contents
258        self.evaluated = False # set to True when the validator recognizes an expression
259        val = loc[key]
260        if isinstance(val,int) or typeHint is int:
261            self.type = int
262            wx.TextCtrl.__init__(
263                self,parent,wx.ID_ANY,
264                validator=NumberValidator(int,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: # no default is invalid for a number
272                self.invalid = True
273                self._IndicateValidity()
274
275        elif isinstance(val,float) or typeHint is float:
276            self.type = float
277            wx.TextCtrl.__init__(
278                self,parent,wx.ID_ANY,
279                validator=NumberValidator(float,result=loc,key=key,
280                                          min=min,max=max,
281                                          OKcontrol=OKcontrol,
282                                          CIFinput=CIFinput),
283                **kw)
284            if val is not None:
285                self._setValue(val)
286            else:
287                self.invalid = True
288                self._IndicateValidity()
289
290        elif isinstance(val,str) or isinstance(val,unicode):
291            if self.CIFinput:
292                wx.TextCtrl.__init__(
293                    self,parent,wx.ID_ANY,val,
294                    validator=ASCIIValidator(result=loc,key=key),
295                    **kw)
296            else:
297                wx.TextCtrl.__init__(self,parent,wx.ID_ANY,val,**kw)
298            if notBlank:
299                self.Bind(wx.EVT_CHAR,self._onStringKey)
300                self.ShowStringValidity() # test if valid input
301            else:
302                self.invalid = False
303                self.Bind(wx.EVT_CHAR,self._GetStringValue)
304        elif val is None:
305            raise Exception,("ValidatedTxtCtrl error: value of "+str(key)+
306                             " element is None and typeHint not defined as int or float")
307        else:
308            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
309                             ") type: "+str(type(val)))
310        # When the mouse is moved away or the widget loses focus,
311        # display the last saved value, if an expression
312        #self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
313        self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus)
314        self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
315        # patch for wx 2.9 on Mac
316        i,j= wx.__version__.split('.')[0:2]
317        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
318            self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
319
320    def SetValue(self,val):
321        if self.result is not None: # note that this bypasses formatting
322            self.result[self.key] = val
323        self._setValue(val)
324
325    def _setValue(self,val):
326        self.invalid = False
327        if self.type is int:
328            try:
329                if int(val) != val:
330                    self.invalid = True
331                else:
332                    val = int(val)
333            except:
334                if self.CIFinput and (val == '?' or val == '.'):
335                    pass
336                else:
337                    self.invalid = True
338            wx.TextCtrl.SetValue(self,str(val))
339        elif self.type is float:
340            try:
341                float(val)
342            except:
343                if self.CIFinput and (val == '?' or val == '.'):
344                    pass
345                else:
346                    self.invalid = True
347            if self.nDig:
348                wx.TextCtrl.SetValue(self,str(G2py3.FormatValue(val,self.nDig)))
349            else:
350                wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)).rstrip('0'))
351        else:
352            wx.TextCtrl.SetValue(self,str(val))
353            self.ShowStringValidity() # test if valid input
354            return
355       
356        self._IndicateValidity()
357        if self.OKcontrol:
358            self.OKcontrol(not self.invalid)
359
360    def OnKeyDown(self,event):
361        'Special callback for wx 2.9+ on Mac where backspace is not processed by validator'
362        key = event.GetKeyCode()
363        if key in [wx.WXK_BACK, wx.WXK_DELETE]:
364            if self.Validator: wx.CallAfter(self.Validator.TestValid,self)
365        if key == wx.WXK_RETURN:
366            self._onLoseFocus(None)
367        event.Skip()
368                   
369    def _onStringKey(self,event):
370        event.Skip()
371        if self.invalid: # check for validity after processing the keystroke
372            wx.CallAfter(self.ShowStringValidity,True) # was invalid
373        else:
374            wx.CallAfter(self.ShowStringValidity,False) # was valid
375
376    def _IndicateValidity(self):
377        'Set the control colors to show invalid input'
378        if self.invalid:
379            self.SetForegroundColour("red")
380            self.SetBackgroundColour("yellow")
381            self.SetFocus()
382            self.Refresh()
383        else: # valid input
384            self.SetBackgroundColour(
385                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
386            self.SetForegroundColour("black")
387            self.Refresh()
388
389    def ShowStringValidity(self,previousInvalid=True):
390        '''Check if input is valid. Anytime the input is
391        invalid, call self.OKcontrol (if defined) because it is fast.
392        If valid, check for any other invalid entries only when
393        changing from invalid to valid, since that is slower.
394       
395        :param bool previousInvalid: True if the TextCtrl contents were
396          invalid prior to the current change.
397         
398        '''
399        val = self.GetValue().strip()
400        self.invalid = not val
401        self._IndicateValidity()
402        if self.invalid:
403            if self.OKcontrol:
404                self.OKcontrol(False)
405        elif self.OKcontrol and previousInvalid:
406            self.OKcontrol(True)
407        # always store the result
408        if self.CIFinput: # for CIF make results ASCII
409            self.result[self.key] = val.encode('ascii','replace') 
410        else:
411            self.result[self.key] = val
412
413    def _GetStringValue(self,event):
414        '''Get string input and store.
415        '''
416        event.Skip() # process keystroke
417        wx.CallAfter(self._SaveStringValue)
418       
419    def _SaveStringValue(self):
420        val = self.GetValue().strip()
421        # always store the result
422        if self.CIFinput: # for CIF make results ASCII
423            self.result[self.key] = val.encode('ascii','replace') 
424        else:
425            self.result[self.key] = val
426
427    def _onLoseFocus(self,event):
428        if self.evaluated:
429            self.EvaluateExpression()
430        elif self.result is not None: # show formatted result, as Bob wants
431            self._setValue(self.result[self.key])
432        if self.OnLeave: self.OnLeave(invalid=self.invalid,
433                                      value=self.result[self.key],
434                                      tc=self,
435                                      **self.OnLeaveArgs)
436        if event: event.Skip()
437
438    def EvaluateExpression(self):
439        '''Show the computed value when an expression is entered to the TextCtrl
440        Make sure that the number fits by truncating decimal places and switching
441        to scientific notation, as needed.
442        Called on loss of focus, enter, etc..
443        '''
444        if self.invalid: return # don't substitute for an invalid expression
445        if not self.evaluated: return # true when an expression is evaluated
446        if self.result is not None: # retrieve the stored result
447            self._setValue(self.result[self.key])
448        self.evaluated = False # expression has been recast as value, reset flag
449       
450class NumberValidator(wx.PyValidator):
451    '''A validator to be used with a TextCtrl to prevent
452    entering characters other than digits, signs, and for float
453    input, a period and exponents.
454   
455    The value is checked for validity after every keystroke
456      If an invalid number is entered, the box is highlighted.
457      If the number is valid, it is saved in result[key]
458
459    :param type typ: the base data type. Must be int or float.
460
461    :param bool positiveonly: If True, negative integers are not allowed
462      (default False). This prevents the + or - keys from being pressed.
463      Used with typ=int; ignored for typ=float.
464
465    :param number min: Minimum allowed value. If None (default) the
466      lower limit is unbounded
467
468    :param number max: Maximum allowed value. If None (default) the
469      upper limit is unbounded
470     
471    :param dict/list result: List or dict where value should be placed when valid
472
473    :param any key: key to use for result (int for list)
474
475    :param function OKcontrol: function or class method to control
476      an OK button for a window.
477      Ignored if None (default)
478
479    :param bool CIFinput: allows use of a single '?' or '.' character
480      as valid input.
481     
482    '''
483    def __init__(self, typ, positiveonly=False, min=None, max=None,
484                 result=None, key=None, OKcontrol=None, CIFinput=False):
485        'Create the validator'
486        wx.PyValidator.__init__(self)
487        # save passed parameters
488        self.typ = typ
489        self.positiveonly = positiveonly
490        self.min = min
491        self.max = max
492        self.result = result
493        self.key = key
494        self.OKcontrol = OKcontrol
495        self.CIFinput = CIFinput
496        # set allowed keys by data type
497        self.Bind(wx.EVT_CHAR, self.OnChar)
498        if self.typ == int and self.positiveonly:
499            self.validchars = '0123456789'
500        elif self.typ == int:
501            self.validchars = '0123456789+-'
502        elif self.typ == float:
503            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
504            # also addition, subtraction, division, multiplication, exponentiation
505            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
506        else:
507            self.validchars = None
508            return
509        if self.CIFinput:
510            self.validchars += '?.'
511    def Clone(self):
512        'Create a copy of the validator, a strange, but required component'
513        return NumberValidator(typ=self.typ, 
514                               positiveonly=self.positiveonly,
515                               min=self.min, max=self.max,
516                               result=self.result, key=self.key,
517                               OKcontrol=self.OKcontrol,
518                               CIFinput=self.CIFinput)
519    def TransferToWindow(self):
520        'Needed by validator, strange, but required component'
521        return True # Prevent wxDialog from complaining.
522    def TransferFromWindow(self):
523        'Needed by validator, strange, but required component'
524        return True # Prevent wxDialog from complaining.
525    def TestValid(self,tc):
526        '''Check if the value is valid by casting the input string
527        into the current type.
528
529        Set the invalid variable in the TextCtrl object accordingly.
530
531        If the value is valid, save it in the dict/list where
532        the initial value was stored, if appropriate.
533
534        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
535          is associated with.
536        '''
537        tc.invalid = False # assume valid
538        if self.CIFinput:
539            val = tc.GetValue().strip()
540            if val == '?' or val == '.':
541                self.result[self.key] = val
542                return
543        try:
544            val = self.typ(tc.GetValue())
545        except (ValueError, SyntaxError) as e:
546            if self.typ is float: # for float values, see if an expression can be evaluated
547                val = G2py3.FormulaEval(tc.GetValue())
548                if val is None:
549                    tc.invalid = True
550                    return
551                else:
552                    tc.evaluated = True
553            else: 
554                tc.invalid = True
555                return
556        # if self.max != None and self.typ == int:
557        #     if val > self.max:
558        #         tc.invalid = True
559        # if self.min != None and self.typ == int:
560        #     if val < self.min:
561        #         tc.invalid = True  # invalid
562        if self.max != None:
563            if val > self.max:
564                tc.invalid = True
565        if self.min != None:
566            if val < self.min:
567                tc.invalid = True  # invalid
568        if self.key is not None and self.result is not None and not tc.invalid:
569            self.result[self.key] = val
570
571    def ShowValidity(self,tc):
572        '''Set the control colors to show invalid input
573
574        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
575          is associated with.
576
577        '''
578        if tc.invalid:
579            tc.SetForegroundColour("red")
580            tc.SetBackgroundColour("yellow")
581            tc.SetFocus()
582            tc.Refresh()
583            return False
584        else: # valid input
585            tc.SetBackgroundColour(
586                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
587            tc.SetForegroundColour("black")
588            tc.Refresh()
589            return True
590
591    def CheckInput(self,previousInvalid):
592        '''called to test every change to the TextCtrl for validity and
593        to change the appearance of the TextCtrl
594
595        Anytime the input is invalid, call self.OKcontrol
596        (if defined) because it is fast.
597        If valid, check for any other invalid entries only when
598        changing from invalid to valid, since that is slower.
599
600        :param bool previousInvalid: True if the TextCtrl contents were
601          invalid prior to the current change.
602        '''
603        tc = self.GetWindow()
604        self.TestValid(tc)
605        self.ShowValidity(tc)
606        # if invalid
607        if tc.invalid and self.OKcontrol:
608            self.OKcontrol(False)
609        if not tc.invalid and self.OKcontrol and previousInvalid:
610            self.OKcontrol(True)
611
612    def OnChar(self, event):
613        '''Called each type a key is pressed
614        ignores keys that are not allowed for int and float types
615        '''
616        key = event.GetKeyCode()
617        tc = self.GetWindow()
618        if key == wx.WXK_RETURN:
619            if tc.invalid:
620                self.CheckInput(True) 
621            else:
622                self.CheckInput(False) 
623            return
624        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
625            event.Skip()
626            if tc.invalid:
627                wx.CallAfter(self.CheckInput,True) 
628            else:
629                wx.CallAfter(self.CheckInput,False) 
630            return
631        elif chr(key) in self.validchars: # valid char pressed?
632            event.Skip()
633            if tc.invalid:
634                wx.CallAfter(self.CheckInput,True) 
635            else:
636                wx.CallAfter(self.CheckInput,False) 
637            return
638        if not wx.Validator_IsSilent(): wx.Bell()
639        return  # Returning without calling event.Skip, which eats the keystroke
640
641################################################################################
642class ASCIIValidator(wx.PyValidator):
643    '''A validator to be used with a TextCtrl to prevent
644    entering characters other than ASCII characters.
645   
646    The value is checked for validity after every keystroke
647      If an invalid number is entered, the box is highlighted.
648      If the number is valid, it is saved in result[key]
649
650    :param dict/list result: List or dict where value should be placed when valid
651
652    :param any key: key to use for result (int for list)
653
654    '''
655    def __init__(self, result=None, key=None):
656        'Create the validator'
657        import string
658        wx.PyValidator.__init__(self)
659        # save passed parameters
660        self.result = result
661        self.key = key
662        self.validchars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
663        self.Bind(wx.EVT_CHAR, self.OnChar)
664    def Clone(self):
665        'Create a copy of the validator, a strange, but required component'
666        return ASCIIValidator(result=self.result, key=self.key)
667        tc = self.GetWindow()
668        tc.invalid = False # make sure the validity flag is defined in parent
669    def TransferToWindow(self):
670        'Needed by validator, strange, but required component'
671        return True # Prevent wxDialog from complaining.
672    def TransferFromWindow(self):
673        'Needed by validator, strange, but required component'
674        return True # Prevent wxDialog from complaining.
675    def TestValid(self,tc):
676        '''Check if the value is valid by casting the input string
677        into ASCII.
678
679        Save it in the dict/list where the initial value was stored
680
681        :param wx.TextCtrl tc: A reference to the TextCtrl that the validator
682          is associated with.
683        '''
684        self.result[self.key] = tc.GetValue().encode('ascii','replace')
685
686    def OnChar(self, event):
687        '''Called each type a key is pressed
688        ignores keys that are not allowed for int and float types
689        '''
690        key = event.GetKeyCode()
691        tc = self.GetWindow()
692        if key == wx.WXK_RETURN:
693            self.TestValid(tc)
694            return
695        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
696            event.Skip()
697            self.TestValid(tc)
698            return
699        elif chr(key) in self.validchars: # valid char pressed?
700            event.Skip()
701            self.TestValid(tc)
702            return
703        if not wx.Validator_IsSilent():
704            wx.Bell()
705        return  # Returning without calling event.Skip, which eats the keystroke
706################################################################################
707class EnumSelector(wx.ComboBox):
708    '''A customized :class:`wxpython.ComboBox` that selects items from a list
709    of choices, but sets a dict (list) entry to the corresponding
710    entry from the input list of values.
711
712    :param wx.Panel parent: the parent to the :class:`~wxpython.ComboBox` (usually a
713      frame or panel)
714    :param dict dct: a dict (or list) to contain the value set
715      for the :class:`~wxpython.ComboBox`.
716    :param item: the dict key (or list index) where ``dct[item]`` will
717      be set to the value selected in the :class:`~wxpython.ComboBox`. Also, dct[item]
718      contains the starting value shown in the widget. If the value
719      does not match an entry in :data:`values`, the first value
720      in :data:`choices` is used as the default, but ``dct[item]`` is
721      not changed.   
722    :param list choices: a list of choices to be displayed to the
723      user such as
724      ::
725     
726      ["default","option 1","option 2",]
727
728      Note that these options will correspond to the entries in
729      :data:`values` (if specified) item by item.
730    :param list values: a list of values that correspond to
731      the options in :data:`choices`, such as
732      ::
733     
734      [0,1,2]
735     
736      The default for :data:`values` is to use the same list as
737      specified for :data:`choices`.
738    :param (other): additional keyword arguments accepted by
739      :class:`~wxpython.ComboBox` can be specified.
740    '''
741    def __init__(self,parent,dct,item,choices,values=None,**kw):
742        if values is None:
743            values = choices
744        if dct[item] in values:
745            i = values.index(dct[item])
746        else:
747            i = 0
748        startval = choices[i]
749        wx.ComboBox.__init__(self,parent,wx.ID_ANY,startval,
750                             choices = choices,
751                             style=wx.CB_DROPDOWN|wx.CB_READONLY,
752                             **kw)
753        self.choices = choices
754        self.values = values
755        self.dct = dct
756        self.item = item
757        self.Bind(wx.EVT_COMBOBOX, self.onSelection)
758    def onSelection(self,event):
759        # respond to a selection by setting the enum value in the CIF dictionary
760        if self.GetValue() in self.choices: # should always be true!
761            self.dct[self.item] = self.values[self.choices.index(self.GetValue())]
762        else:
763            self.dct[self.item] = self.values[0] # unknown
764
765################################################################################
766class G2CheckBox(wx.CheckBox):
767    '''A customized version of a CheckBox that automatically initializes
768    the control to a supplied list or dict entry and updates that
769    entry as the widget is used.
770
771    :param wx.Panel parent: name of panel or frame that will be
772      the parent to the widget. Can be None.
773    :param str label: text to put on check button
774    :param dict/list loc: the dict or list with the initial value to be
775      placed in the CheckBox.
776    :param int/str key: the dict key or the list index for the value to be
777      edited by the CheckBox. The ``loc[key]`` element must exist.
778      The CheckBox will be initialized from this value.
779      If the value is anything other that True (or 1), it will be taken as
780      False.
781    '''
782    def __init__(self,parent,label,loc,key):
783        wx.CheckBox.__init__(self,parent,id=wx.ID_ANY,label=label)
784        self.loc = loc
785        self.key = key
786        self.SetValue(self.loc[self.key]==True)
787        self.Bind(wx.EVT_CHECKBOX, self._OnCheckBox)
788    def _OnCheckBox(self,event):
789        self.loc[self.key] = self.GetValue()
790################################################################################
791class G2ChoiceButton(wx.Choice):
792    '''A customized version of a wx.Choice that automatically initializes
793    the control to match a supplied value and saves the choice directly
794    into an array or list. Optionally a function can be called each time a
795    choice is selected.
796
797    :param wx.Panel parent: name of panel or frame that will be
798      the parent to the widget. Can be None.
799    :param list choiceList: a list or tuple of choices to offer the user.
800    :param dict/list indLoc: a dict or list with the initial value to be
801      placed in the Choice button.
802    :param int/str indKey: the dict key or the list index for the value to be
803      edited by the Choice button. The ``indLoc[indKey]`` element must exist.
804      The value for this should be None or an integer in range(len(choiceList)).
805      The Choice button will be initialized to the choice corresponding to the
806      value in this element if not None.
807    :param dict/list strLoc: a dict or list with the string value corresponding to
808      indLoc/indKey. Default (None) means that this is not used.
809    :param int/str strKey: the dict key or the list index for the string value
810      The ``strLoc[strKey]`` element must exist or strLoc must be None (default).
811    :param function onChoice: name of a   
812    '''
813    def __init__(self,parent,choiceList,indLoc,indKey,strLoc=None,strKey=None,
814                 onChoice=None,**kwargs):
815        wx.Choice.__init__(self,parent,choices=choiceList,id=wx.ID_ANY,**kwargs)
816        self.choiceList = choiceList
817        self.indLoc = indLoc
818        self.indKey = indKey
819        self.strLoc = strLoc
820        self.strKey = strKey
821        self.onChoice = None
822        if self.indLoc[self.indKey] is not None:
823            self.SetSelection(self.indLoc[self.indKey])
824        else:
825            self.SetSelection(wx.NOT_FOUND)
826        self.Bind(wx.EVT_CHOICE, self._OnChoice)
827        if self.strLoc is not None: # make sure strLoc gets initialized
828            self._OnChoice(None) # note that onChoice will not be called
829        self.onChoice = onChoice
830    def _OnChoice(self,event):
831        self.indLoc[self.indKey] = self.GetSelection()
832        if self.strLoc is not None:
833            self.strLoc[self.strKey] = self.GetStringSelection()
834        if self.onChoice:
835            self.onChoice()
836################################################################################   
837def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[],
838                 title='Edit items',header='',size=(300,250),
839                             CopyButton=False, **kw):
840    '''Shell routine to call a ScrolledMultiEditor dialog. See
841    :class:`ScrolledMultiEditor` for parameter definitions.
842
843    :returns: True if the OK button is pressed; False if the window is closed
844      with the system menu or the Cancel button.
845
846    '''
847    dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl,
848                              title,header,size,
849                              CopyButton, **kw)
850    if dlg.ShowModal() == wx.ID_OK:
851        dlg.Destroy()
852        return True
853    else:
854        dlg.Destroy()
855        return False
856
857class ScrolledMultiEditor(wx.Dialog):
858    '''Define a window for editing a potentially large number of dict- or
859    list-contained values with validation for each item. Edited values are
860    automatically placed in their source location. If invalid entries
861    are provided, the TextCtrl is turned yellow and the OK button is disabled.
862
863    The type for each TextCtrl validation is determined by the
864    initial value of the entry (int, float or string).
865    Float values can be entered in the TextCtrl as numbers or also
866    as algebraic expressions using operators + - / \* () and \*\*,
867    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
868    as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq().
869
870    :param wx.Frame parent: name of parent window, or may be None
871
872    :param tuple dictlst: a list of dicts or lists containing values to edit
873
874    :param tuple elemlst: a list of keys for each item in a dictlst. Must have the
875      same length as dictlst.
876
877    :param wx.Frame parent: name of parent window, or may be None
878   
879    :param tuple prelbl: a list of labels placed before the TextCtrl for each
880      item (optional)
881   
882    :param tuple postlbl: a list of labels placed after the TextCtrl for each
883      item (optional)
884
885    :param str title: a title to place in the frame of the dialog
886
887    :param str header: text to place at the top of the window. May contain
888      new line characters.
889
890    :param wx.Size size: a size parameter that dictates the
891      size for the scrolled region of the dialog. The default is
892      (300,250).
893
894    :param bool CopyButton: if True adds a small button that copies the
895      value for the current row to all fields below (default is False)
896     
897    :param list minvals: optional list of minimum values for validation
898      of float or int values. Ignored if value is None.
899    :param list maxvals: optional list of maximum values for validation
900      of float or int values. Ignored if value is None.
901    :param list sizevals: optional list of wx.Size values for each input
902      widget. Ignored if value is None.
903     
904    :param tuple checkdictlst: an optional list of dicts or lists containing bool
905      values (similar to dictlst).
906    :param tuple checkelemlst: an optional list of dicts or lists containing bool
907      key values (similar to elemlst). Must be used with checkdictlst.
908    :param string checklabel: a string to use for each checkbutton
909     
910    :returns: the wx.Dialog created here. Use method .ShowModal() to display it.
911   
912    *Example for use of ScrolledMultiEditor:*
913
914    ::
915
916        dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl,
917                                        header=header)
918        if dlg.ShowModal() == wx.ID_OK:
919             for d,k in zip(dictlst,elemlst):
920                 print d[k]
921
922    *Example definitions for dictlst and elemlst:*
923
924    ::
925     
926          dictlst = (dict1,list1,dict1,list1)
927          elemlst = ('a', 1, 2, 3)
928
929      This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited.
930   
931    Note that these items must have int, float or str values assigned to
932    them. The dialog will force these types to be retained. String values
933    that are blank are marked as invalid.
934    '''
935   
936    def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[],
937                 title='Edit items',header='',size=(300,250),
938                 CopyButton=False,
939                 minvals=[],maxvals=[],sizevals=[],
940                 checkdictlst=[], checkelemlst=[], checklabel=""):
941        if len(dictlst) != len(elemlst):
942            raise Exception,"ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))
943        if len(checkdictlst) != len(checkelemlst):
944            raise Exception,"ScrolledMultiEditor error: len(checkdictlst) != len(checkelemlst) "+str(len(checkdictlst))+" != "+str(len(checkelemlst))
945        wx.Dialog.__init__( # create dialog & sizer
946            self,parent,wx.ID_ANY,title,
947            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
948        mainSizer = wx.BoxSizer(wx.VERTICAL)
949        self.orig = []
950        self.dictlst = dictlst
951        self.elemlst = elemlst
952        self.checkdictlst = checkdictlst
953        self.checkelemlst = checkelemlst
954        self.StartCheckValues = [checkdictlst[i][checkelemlst[i]] for i in range(len(checkdictlst))]
955        self.ButtonIndex = {}
956        for d,i in zip(dictlst,elemlst):
957            self.orig.append(d[i])
958        # add a header if supplied
959        if header:
960            subSizer = wx.BoxSizer(wx.HORIZONTAL)
961            subSizer.Add((-1,-1),1,wx.EXPAND)
962            subSizer.Add(wx.StaticText(self,wx.ID_ANY,header))
963            subSizer.Add((-1,-1),1,wx.EXPAND)
964            mainSizer.Add(subSizer,0,wx.EXPAND,0)
965        # make OK button now, because we will need it for validation
966        self.OKbtn = wx.Button(self, wx.ID_OK)
967        self.OKbtn.SetDefault()
968        # create scrolled panel and sizer
969        panel = wxscroll.ScrolledPanel(
970            self, wx.ID_ANY,
971            size=size,
972            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
973        cols = 4
974        if CopyButton: cols += 1
975        subSizer = wx.FlexGridSizer(cols=cols,hgap=2,vgap=2)
976        self.ValidatedControlsList = [] # make list of TextCtrls
977        self.CheckControlsList = [] # make list of CheckBoxes
978        for i,(d,k) in enumerate(zip(dictlst,elemlst)):
979            if i >= len(prelbl): # label before TextCtrl, or put in a blank
980                subSizer.Add((-1,-1)) 
981            else:
982                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i])))
983            kargs = {}
984            if i < len(minvals):
985                if minvals[i] is not None: kargs['min']=minvals[i]
986            if i < len(maxvals):
987                if maxvals[i] is not None: kargs['max']=maxvals[i]
988            if i < len(sizevals):
989                if sizevals[i]: kargs['size']=sizevals[i]
990            if CopyButton:
991                import wx.lib.colourselect as wscs
992                but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP
993                                        # is there a way to test?
994                                        parent=panel,
995                                        colour=(255,255,200),
996                                        size=wx.Size(30,23),
997                                        style=wx.RAISED_BORDER)
998                but.Bind(wx.EVT_BUTTON, self._OnCopyButton)
999                but.SetToolTipString('Press to copy adjacent value to all rows below')
1000                self.ButtonIndex[but] = i
1001                subSizer.Add(but)
1002            # create the validated TextCrtl, store it and add it to the sizer
1003            ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,
1004                                    **kargs)
1005            self.ValidatedControlsList.append(ctrl)
1006            subSizer.Add(ctrl)
1007            if i < len(postlbl): # label after TextCtrl, or put in a blank
1008                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i])))
1009            else:
1010                subSizer.Add((-1,-1))
1011            if i < len(checkdictlst):
1012                ch = G2CheckBox(panel,checklabel,checkdictlst[i],checkelemlst[i])
1013                self.CheckControlsList.append(ch)
1014                subSizer.Add(ch)                   
1015            else:
1016                subSizer.Add((-1,-1))
1017        # finish up ScrolledPanel
1018        panel.SetSizer(subSizer)
1019        panel.SetAutoLayout(1)
1020        panel.SetupScrolling()
1021        # patch for wx 2.9 on Mac
1022        i,j= wx.__version__.split('.')[0:2]
1023        if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
1024            panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1]))       
1025        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
1026
1027        # Sizer for OK/Close buttons. N.B. on Close changes are discarded
1028        # by restoring the initial values
1029        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
1030        btnsizer.Add(self.OKbtn)
1031        btn = wx.Button(self, wx.ID_CLOSE,"Cancel") 
1032        btn.Bind(wx.EVT_BUTTON,self._onClose)
1033        btnsizer.Add(btn)
1034        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1035        # size out the window. Set it to be enlarged but not made smaller
1036        self.SetSizer(mainSizer)
1037        mainSizer.Fit(self)
1038        self.SetMinSize(self.GetSize())
1039
1040    def _OnCopyButton(self,event):
1041        'Implements the copy down functionality'
1042        but = event.GetEventObject()
1043        n = self.ButtonIndex.get(but)
1044        if n is None: return
1045        for i,(d,k,ctrl) in enumerate(zip(self.dictlst,self.elemlst,self.ValidatedControlsList)):
1046            if i < n: continue
1047            if i == n:
1048                val = d[k]
1049                continue
1050            d[k] = val
1051            ctrl.SetValue(val)
1052        for i in range(len(self.checkdictlst)):
1053            if i < n: continue
1054            self.checkdictlst[i][self.checkelemlst[i]] = self.checkdictlst[n][self.checkelemlst[n]]
1055            self.CheckControlsList[i].SetValue(self.checkdictlst[i][self.checkelemlst[i]])
1056    def _onClose(self,event):
1057        'Used on Cancel: Restore original values & close the window'
1058        for d,i,v in zip(self.dictlst,self.elemlst,self.orig):
1059            d[i] = v
1060        for i in range(len(self.checkdictlst)):
1061            self.checkdictlst[i][self.checkelemlst[i]] = self.StartCheckValues[i]
1062        self.EndModal(wx.ID_CANCEL)
1063       
1064    def ControlOKButton(self,setvalue):
1065        '''Enable or Disable the OK button for the dialog. Note that this is
1066        passed into the ValidatedTxtCtrl for use by validators.
1067
1068        :param bool setvalue: if True, all entries in the dialog are
1069          checked for validity. if False then the OK button is disabled.
1070
1071        '''
1072        if setvalue: # turn button on, do only if all controls show as valid
1073            for ctrl in self.ValidatedControlsList:
1074                if ctrl.invalid:
1075                    self.OKbtn.Disable()
1076                    return
1077            else:
1078                self.OKbtn.Enable()
1079        else:
1080            self.OKbtn.Disable()
1081
1082################################################################################
1083class SymOpDialog(wx.Dialog):
1084    '''Class to select a symmetry operator
1085    '''
1086    def __init__(self,parent,SGData,New=True,ForceUnit=False):
1087        wx.Dialog.__init__(self,parent,-1,'Select symmetry operator',
1088            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1089        panel = wx.Panel(self)
1090        self.SGData = SGData
1091        self.New = New
1092        self.Force = ForceUnit
1093        self.OpSelected = [0,0,0,[0,0,0],False,False]
1094        mainSizer = wx.BoxSizer(wx.VERTICAL)
1095        if ForceUnit:
1096            choice = ['No','Yes']
1097            self.force = wx.RadioBox(panel,-1,'Force to unit cell?',choices=choice)
1098            self.force.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1099            mainSizer.Add(self.force,0,WACV)
1100        mainSizer.Add((5,5),0)
1101        if SGData['SGInv']:
1102            choice = ['No','Yes']
1103            self.inv = wx.RadioBox(panel,-1,'Choose inversion?',choices=choice)
1104            self.inv.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1105            mainSizer.Add(self.inv,0,WACV)
1106        mainSizer.Add((5,5),0)
1107        if SGData['SGLatt'] != 'P':
1108            LattOp = G2spc.Latt2text(SGData['SGLatt']).split(';')
1109            self.latt = wx.RadioBox(panel,-1,'Choose cell centering?',choices=LattOp)
1110            self.latt.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1111            mainSizer.Add(self.latt,0,WACV)
1112        mainSizer.Add((5,5),0)
1113        if SGData['SGLaue'] in ['-1','2/m','mmm','4/m','4/mmm']:
1114            Ncol = 2
1115        else:
1116            Ncol = 3
1117        OpList = []
1118        for M,T in SGData['SGOps']:
1119            OpList.append(G2spc.MT2text(M,T))
1120        self.oprs = wx.RadioBox(panel,-1,'Choose space group operator?',choices=OpList,
1121            majorDimension=Ncol)
1122        self.oprs.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1123        mainSizer.Add(self.oprs,0,WACV)
1124        mainSizer.Add((5,5),0)
1125        mainSizer.Add(wx.StaticText(panel,-1,"   Choose unit cell?"),0,WACV)
1126        mainSizer.Add((5,5),0)
1127        cellSizer = wx.BoxSizer(wx.HORIZONTAL)
1128        cellSizer.Add((5,0),0)
1129        cellName = ['X','Y','Z']
1130        self.cell = []
1131        for i in range(3):
1132            self.cell.append(wx.SpinCtrl(panel,-1,cellName[i],size=wx.Size(50,20)))
1133            self.cell[-1].SetRange(-3,3)
1134            self.cell[-1].SetValue(0)
1135            self.cell[-1].Bind(wx.EVT_SPINCTRL, self.OnOpSelect)
1136            cellSizer.Add(self.cell[-1],0,WACV)
1137        mainSizer.Add(cellSizer,0,)
1138        if self.New:
1139            choice = ['No','Yes']
1140            self.new = wx.RadioBox(panel,-1,'Generate new positions?',choices=choice)
1141            self.new.Bind(wx.EVT_RADIOBOX, self.OnOpSelect)
1142            mainSizer.Add(self.new,0,WACV)
1143        mainSizer.Add((5,5),0)
1144
1145        OkBtn = wx.Button(panel,-1,"Ok")
1146        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1147        cancelBtn = wx.Button(panel,-1,"Cancel")
1148        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1149        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1150        btnSizer.Add((20,20),1)
1151        btnSizer.Add(OkBtn)
1152        btnSizer.Add((20,20),1)
1153        btnSizer.Add(cancelBtn)
1154        btnSizer.Add((20,20),1)
1155
1156        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1157        panel.SetSizer(mainSizer)
1158        panel.Fit()
1159        self.Fit()
1160
1161    def OnOpSelect(self,event):
1162        if self.SGData['SGInv']:
1163            self.OpSelected[0] = self.inv.GetSelection()
1164        if self.SGData['SGLatt'] != 'P':
1165            self.OpSelected[1] = self.latt.GetSelection()
1166        self.OpSelected[2] = self.oprs.GetSelection()
1167        for i in range(3):
1168            self.OpSelected[3][i] = float(self.cell[i].GetValue())
1169        if self.New:
1170            self.OpSelected[4] = self.new.GetSelection()
1171        if self.Force:
1172            self.OpSelected[5] = self.force.GetSelection()
1173
1174    def GetSelection(self):
1175        return self.OpSelected
1176
1177    def OnOk(self,event):
1178        parent = self.GetParent()
1179        parent.Raise()
1180        self.EndModal(wx.ID_OK)
1181
1182    def OnCancel(self,event):
1183        parent = self.GetParent()
1184        parent.Raise()
1185        self.EndModal(wx.ID_CANCEL)
1186
1187class DisAglDialog(wx.Dialog):
1188    '''Distance/Angle Controls input dialog. After
1189    :meth:`ShowModal` returns, the results are found in
1190    dict :attr:`self.data`, which is accessed using :meth:`GetData`.
1191
1192    :param wx.Frame parent: reference to parent frame (or None)
1193    :param dict data: a dict containing the current
1194      search ranges or an empty dict, which causes default values
1195      to be used.
1196      Will be used to set element `DisAglCtls` in
1197      :ref:`Phase Tree Item <Phase_table>`
1198    :param dict default:  A dict containing the default
1199      search ranges for each element.
1200    '''
1201    def __init__(self,parent,data,default):
1202        wx.Dialog.__init__(self,parent,wx.ID_ANY,
1203                           'Distance Angle Controls', 
1204            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1205        self.default = default
1206        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1207        self._default(data,self.default)
1208        self.Draw(self.data)
1209               
1210    def _default(self,data,default):
1211        '''Set starting values for the search values, either from
1212        the input array or from defaults, if input is null
1213        '''
1214        if data:
1215            self.data = copy.deepcopy(data) # don't mess with originals
1216        else:
1217            self.data = {}
1218            self.data['Name'] = default['Name']
1219            self.data['Factors'] = [0.85,0.85]
1220            self.data['AtomTypes'] = default['AtomTypes']
1221            self.data['BondRadii'] = default['BondRadii'][:]
1222            self.data['AngleRadii'] = default['AngleRadii'][:]
1223
1224    def Draw(self,data):
1225        '''Creates the contents of the dialog. Normally called
1226        by :meth:`__init__`.
1227        '''
1228        self.panel.Destroy()
1229        self.panel = wx.Panel(self)
1230        mainSizer = wx.BoxSizer(wx.VERTICAL)
1231        mainSizer.Add(wx.StaticText(self.panel,-1,'Controls for phase '+data['Name']),
1232            0,WACV|wx.LEFT,10)
1233        mainSizer.Add((10,10),1)
1234       
1235        radiiSizer = wx.FlexGridSizer(0,3,5,5)
1236        radiiSizer.Add(wx.StaticText(self.panel,-1,' Type'),0,WACV)
1237        radiiSizer.Add(wx.StaticText(self.panel,-1,'Bond radii'),0,WACV)
1238        radiiSizer.Add(wx.StaticText(self.panel,-1,'Angle radii'),0,WACV)
1239        self.objList = {}
1240        for id,item in enumerate(self.data['AtomTypes']):
1241            radiiSizer.Add(wx.StaticText(self.panel,-1,' '+item),0,WACV)
1242            bRadii = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['BondRadii'][id]),style=wx.TE_PROCESS_ENTER)
1243            self.objList[bRadii.GetId()] = ['BondRadii',id]
1244            bRadii.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1245            bRadii.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1246            radiiSizer.Add(bRadii,0,WACV)
1247            aRadii = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['AngleRadii'][id]),style=wx.TE_PROCESS_ENTER)
1248            self.objList[aRadii.GetId()] = ['AngleRadii',id]
1249            aRadii.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1250            aRadii.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1251            radiiSizer.Add(aRadii,0,WACV)
1252        mainSizer.Add(radiiSizer,0,wx.EXPAND)
1253        factorSizer = wx.FlexGridSizer(0,2,5,5)
1254        Names = ['Bond','Angle']
1255        for i,name in enumerate(Names):
1256            factorSizer.Add(wx.StaticText(self.panel,-1,name+' search factor'),0,WACV)
1257            bondFact = wx.TextCtrl(self.panel,-1,value='%.3f'%(data['Factors'][i]),style=wx.TE_PROCESS_ENTER)
1258            self.objList[bondFact.GetId()] = ['Factors',i]
1259            bondFact.Bind(wx.EVT_TEXT_ENTER,self.OnRadiiVal)
1260            bondFact.Bind(wx.EVT_KILL_FOCUS,self.OnRadiiVal)
1261            factorSizer.Add(bondFact)
1262        mainSizer.Add(factorSizer,0,wx.EXPAND)
1263       
1264        OkBtn = wx.Button(self.panel,-1,"Ok")
1265        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1266        ResetBtn = wx.Button(self.panel,-1,'Reset')
1267        ResetBtn.Bind(wx.EVT_BUTTON, self.OnReset)
1268        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1269        btnSizer.Add((20,20),1)
1270        btnSizer.Add(OkBtn)
1271        btnSizer.Add(ResetBtn)
1272        btnSizer.Add((20,20),1)
1273        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1274        self.panel.SetSizer(mainSizer)
1275        self.panel.Fit()
1276        self.Fit()
1277   
1278    def OnRadiiVal(self,event):
1279        Obj = event.GetEventObject()
1280        item = self.objList[Obj.GetId()]
1281        try:
1282            self.data[item[0]][item[1]] = float(Obj.GetValue())
1283        except ValueError:
1284            pass
1285        Obj.SetValue("%.3f"%(self.data[item[0]][item[1]]))          #reset in case of error
1286       
1287    def GetData(self):
1288        'Returns the values from the dialog'
1289        return self.data
1290       
1291    def OnOk(self,event):
1292        'Called when the OK button is pressed'
1293        parent = self.GetParent()
1294        parent.Raise()
1295        self.EndModal(wx.ID_OK)             
1296       
1297    def OnReset(self,event):
1298        'Called when the Reset button is pressed'
1299        data = {}
1300        self._default(data,self.default)
1301        self.Draw(self.data)
1302       
1303class PickTwoDialog(wx.Dialog):
1304    '''This does not seem to be in use
1305    '''
1306    def __init__(self,parent,title,prompt,names,choices):
1307        wx.Dialog.__init__(self,parent,-1,title, 
1308            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1309        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1310        self.prompt = prompt
1311        self.choices = choices
1312        self.names = names
1313        self.Draw()
1314
1315    def Draw(self):
1316        Indx = {}
1317       
1318        def OnSelection(event):
1319            Obj = event.GetEventObject()
1320            id = Indx[Obj.GetId()]
1321            self.choices[id] = Obj.GetValue().encode()  #to avoid Unicode versions
1322            self.Draw()
1323           
1324        self.panel.DestroyChildren()
1325        self.panel.Destroy()
1326        self.panel = wx.Panel(self)
1327        mainSizer = wx.BoxSizer(wx.VERTICAL)
1328        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1329        for isel,name in enumerate(self.choices):
1330            lineSizer = wx.BoxSizer(wx.HORIZONTAL)
1331            lineSizer.Add(wx.StaticText(self.panel,-1,'Reference atom '+str(isel+1)),0,wx.ALIGN_CENTER)
1332            nameList = self.names[:]
1333            if isel:
1334                if self.choices[0] in nameList:
1335                    nameList.remove(self.choices[0])
1336            choice = wx.ComboBox(self.panel,-1,value=name,choices=nameList,
1337                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1338            Indx[choice.GetId()] = isel
1339            choice.Bind(wx.EVT_COMBOBOX, OnSelection)
1340            lineSizer.Add(choice,0,WACV)
1341            mainSizer.Add(lineSizer)
1342        OkBtn = wx.Button(self.panel,-1,"Ok")
1343        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1344        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1345        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1346        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1347        btnSizer.Add((20,20),1)
1348        btnSizer.Add(OkBtn)
1349        btnSizer.Add(CancelBtn)
1350        btnSizer.Add((20,20),1)
1351        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1352        self.panel.SetSizer(mainSizer)
1353        self.panel.Fit()
1354        self.Fit()
1355       
1356    def GetSelection(self):
1357        return self.choices
1358
1359    def OnOk(self,event):
1360        parent = self.GetParent()
1361        parent.Raise()
1362        self.EndModal(wx.ID_OK)             
1363       
1364    def OnCancel(self,event):
1365        parent = self.GetParent()
1366        parent.Raise()
1367        self.EndModal(wx.ID_CANCEL)
1368       
1369class SingleFloatDialog(wx.Dialog):
1370    'Dialog to obtain a single float value from user'
1371    def __init__(self,parent,title,prompt,value,limits=[0.,1.],format='%.5g'):
1372        wx.Dialog.__init__(self,parent,-1,title, 
1373            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1374        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
1375        self.limits = limits
1376        self.value = value
1377        self.prompt = prompt
1378        self.format = format
1379        self.Draw()
1380       
1381    def Draw(self):
1382       
1383        def OnValItem(event):
1384            try:
1385                val = float(valItem.GetValue())
1386                if val < self.limits[0] or val > self.limits[1]:
1387                    raise ValueError
1388            except ValueError:
1389                val = self.value
1390            self.value = val
1391            valItem.SetValue(self.format%(self.value))
1392           
1393        self.panel.Destroy()
1394        self.panel = wx.Panel(self)
1395        mainSizer = wx.BoxSizer(wx.VERTICAL)
1396        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1397        valItem = wx.TextCtrl(self.panel,-1,value=self.format%(self.value),style=wx.TE_PROCESS_ENTER)
1398        mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
1399        valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
1400        valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
1401        OkBtn = wx.Button(self.panel,-1,"Ok")
1402        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1403        CancelBtn = wx.Button(self.panel,-1,'Cancel')
1404        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1405        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1406        btnSizer.Add((20,20),1)
1407        btnSizer.Add(OkBtn)
1408        btnSizer.Add(CancelBtn)
1409        btnSizer.Add((20,20),1)
1410        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1411        self.panel.SetSizer(mainSizer)
1412        self.panel.Fit()
1413        self.Fit()
1414
1415    def GetValue(self):
1416        return self.value
1417       
1418    def OnOk(self,event):
1419        parent = self.GetParent()
1420        parent.Raise()
1421        self.EndModal(wx.ID_OK)             
1422       
1423    def OnCancel(self,event):
1424        parent = self.GetParent()
1425        parent.Raise()
1426        self.EndModal(wx.ID_CANCEL)
1427
1428################################################################################
1429class SingleStringDialog(wx.Dialog):
1430    '''Dialog to obtain a single string value from user
1431   
1432    :param wx.Frame parent: name of parent frame
1433    :param str title: title string for dialog
1434    :param str prompt: string to tell use what they are inputting
1435    :param str value: default input value, if any
1436    '''
1437    def __init__(self,parent,title,prompt,value='',size=(200,-1)):
1438        wx.Dialog.__init__(self,parent,wx.ID_ANY,title, 
1439                           pos=wx.DefaultPosition,
1440                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1441        self.value = value
1442        self.prompt = prompt
1443        self.CenterOnParent()
1444        self.panel = wx.Panel(self)
1445        mainSizer = wx.BoxSizer(wx.VERTICAL)
1446        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
1447        self.valItem = wx.TextCtrl(self.panel,-1,value=self.value,size=size)
1448        mainSizer.Add(self.valItem,0,wx.ALIGN_CENTER)
1449        btnsizer = wx.StdDialogButtonSizer()
1450        OKbtn = wx.Button(self.panel, wx.ID_OK)
1451        OKbtn.SetDefault()
1452        btnsizer.AddButton(OKbtn)
1453        btn = wx.Button(self.panel, wx.ID_CANCEL)
1454        btnsizer.AddButton(btn)
1455        btnsizer.Realize()
1456        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
1457        self.panel.SetSizer(mainSizer)
1458        self.panel.Fit()
1459        self.Fit()
1460
1461    def Show(self):
1462        '''Use this method after creating the dialog to post it
1463        :returns: True if the user pressed OK; False if the User pressed Cancel
1464        '''
1465        if self.ShowModal() == wx.ID_OK:
1466            self.value = self.valItem.GetValue()
1467            return True
1468        else:
1469            return False
1470
1471    def GetValue(self):
1472        '''Use this method to get the value entered by the user
1473        :returns: string entered by user
1474        '''
1475        return self.value
1476
1477################################################################################
1478class MultiStringDialog(wx.Dialog):
1479    '''Dialog to obtain a multi string values from user
1480   
1481    :param wx.Frame parent: name of parent frame
1482    :param str title: title string for dialog
1483    :param str prompts: strings to tell use what they are inputting
1484    :param str values: default input values, if any
1485    '''
1486    def __init__(self,parent,title,prompts,values=[]):      #,size=(200,-1)?
1487       
1488        wx.Dialog.__init__(self,parent,wx.ID_ANY,title, 
1489                           pos=wx.DefaultPosition,
1490                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1491        self.values = values
1492        self.prompts = prompts
1493        self.CenterOnParent()
1494        self.panel = wx.Panel(self)
1495        mainSizer = wx.BoxSizer(wx.VERTICAL)
1496        promptSizer = wx.FlexGridSizer(0,2,5,5)
1497        self.Indx = {}
1498        for prompt,value in zip(prompts,values):
1499            promptSizer.Add(wx.StaticText(self.panel,-1,prompt),0,WACV)
1500            valItem = wx.TextCtrl(self.panel,-1,value=value,style=wx.TE_PROCESS_ENTER)
1501            self.Indx[valItem.GetId()] = prompt
1502            valItem.Bind(wx.EVT_TEXT,self.newValue)
1503            promptSizer.Add(valItem,0,WACV)
1504        mainSizer.Add(promptSizer,0)
1505        btnsizer = wx.StdDialogButtonSizer()
1506        OKbtn = wx.Button(self.panel, wx.ID_OK)
1507        OKbtn.SetDefault()
1508        btnsizer.AddButton(OKbtn)
1509        btn = wx.Button(self.panel, wx.ID_CANCEL)
1510        btnsizer.AddButton(btn)
1511        btnsizer.Realize()
1512        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
1513        self.panel.SetSizer(mainSizer)
1514        self.panel.Fit()
1515        self.Fit()
1516       
1517    def newValue(self,event):
1518        Obj = event.GetEventObject()
1519        item = self.Indx[Obj.GetId()]
1520        id = self.prompts.index(item)
1521        self.values[id] = Obj.GetValue()
1522
1523    def Show(self):
1524        '''Use this method after creating the dialog to post it
1525        :returns: True if the user pressed OK; False if the User pressed Cancel
1526        '''
1527        if self.ShowModal() == wx.ID_OK:
1528            return True
1529        else:
1530            return False
1531
1532    def GetValues(self):
1533        '''Use this method to get the value entered by the user
1534        :returns: string entered by user
1535        '''
1536        return self.values
1537
1538################################################################################
1539
1540class G2MultiChoiceDialog(wx.Dialog):
1541    '''A dialog similar to MultiChoiceDialog except that buttons are
1542    added to set all choices and to toggle all choices.
1543
1544    :param wx.Frame ParentFrame: reference to parent frame
1545    :param str title: heading above list of choices
1546    :param str header: Title to place on window frame
1547    :param list ChoiceList: a list of choices where one will be selected
1548    :param bool toggle: If True (default) the toggle and select all buttons
1549      are displayed
1550    :param bool monoFont: If False (default), use a variable-spaced font;
1551      if True use a equally-spaced font.
1552    :param bool filterBox: If True (default) an input widget is placed on
1553      the window and only entries matching the entered text are shown.
1554    :param kw: optional keyword parameters for the wx.Dialog may
1555      be included such as size [which defaults to `(320,310)`] and
1556      style (which defaults to `wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL`);
1557      note that `wx.OK` and `wx.CANCEL` controls
1558      the presence of the eponymous buttons in the dialog.
1559    :returns: the name of the created dialog 
1560    '''
1561    def __init__(self,parent, title, header, ChoiceList, toggle=True,
1562                 monoFont=False, filterBox=True, **kw):
1563        # process keyword parameters, notably style
1564        options = {'size':(320,310), # default Frame keywords
1565                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
1566                   }
1567        options.update(kw)
1568        self.ChoiceList = ChoiceList # list of choices (list of str values)
1569        self.Selections = len(self.ChoiceList) * [False,] # selection status for each choice (list of bools)
1570        self.filterlist = range(len(self.ChoiceList)) # list of the choice numbers that have been filtered (list of int indices)
1571        if options['style'] & wx.OK:
1572            useOK = True
1573            options['style'] ^= wx.OK
1574        else:
1575            useOK = False
1576        if options['style'] & wx.CANCEL:
1577            useCANCEL = True
1578            options['style'] ^= wx.CANCEL
1579        else:
1580            useCANCEL = False       
1581        # create the dialog frame
1582        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
1583        # fill the dialog
1584        Sizer = wx.BoxSizer(wx.VERTICAL)
1585        topSizer = wx.BoxSizer(wx.HORIZONTAL)
1586        topSizer.Add(
1587            wx.StaticText(self,wx.ID_ANY,title,size=(-1,35)),
1588            1,wx.ALL|wx.EXPAND|WACV,1)
1589        if filterBox:
1590            self.timer = wx.Timer()
1591            self.timer.Bind(wx.EVT_TIMER,self.Filter)
1592            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Name \nFilter: '),0,wx.ALL|WACV,1)
1593            self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),style=wx.TE_PROCESS_ENTER)
1594            self.filterBox.Bind(wx.EVT_CHAR,self.onChar)
1595            self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
1596            topSizer.Add(self.filterBox,0,wx.ALL|WACV,0)
1597        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
1598        self.clb = wx.CheckListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
1599        if monoFont:
1600            font1 = wx.Font(self.clb.GetFont().GetPointSize(),
1601                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
1602            self.clb.SetFont(font1)
1603        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
1604        Sizer.Add((-1,10))
1605        # set/toggle buttons
1606        if toggle:
1607            bSizer = wx.BoxSizer(wx.VERTICAL)
1608            setBut = wx.Button(self,wx.ID_ANY,'Set All')
1609            setBut.Bind(wx.EVT_BUTTON,self._SetAll)
1610            bSizer.Add(setBut,0,wx.ALIGN_CENTER)
1611            bSizer.Add((-1,5))
1612            togBut = wx.Button(self,wx.ID_ANY,'Toggle All')
1613            togBut.Bind(wx.EVT_BUTTON,self._ToggleAll)
1614            bSizer.Add(togBut,0,wx.ALIGN_CENTER)
1615            Sizer.Add(bSizer,0,wx.LEFT,12)
1616        # OK/Cancel buttons
1617        btnsizer = wx.StdDialogButtonSizer()
1618        if useOK:
1619            self.OKbtn = wx.Button(self, wx.ID_OK)
1620            self.OKbtn.SetDefault()
1621            btnsizer.AddButton(self.OKbtn)
1622        if useCANCEL:
1623            btn = wx.Button(self, wx.ID_CANCEL)
1624            btnsizer.AddButton(btn)
1625        btnsizer.Realize()
1626        Sizer.Add((-1,5))
1627        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
1628        Sizer.Add((-1,20))
1629        # OK done, let's get outa here
1630        self.SetSizer(Sizer)
1631        self.CenterOnParent()
1632    def GetSelections(self):
1633        'Returns a list of the indices for the selected choices'
1634        # update self.Selections with settings for displayed items
1635        for i in range(len(self.filterlist)):
1636            self.Selections[self.filterlist[i]] = self.clb.IsChecked(i)
1637        # return all selections, shown or hidden
1638        return [i for i in range(len(self.Selections)) if self.Selections[i]]
1639    def SetSelections(self,selList):
1640        '''Sets the selection indices in selList as selected. Resets any previous
1641        selections for compatibility with wx.MultiChoiceDialog. Note that
1642        the state for only the filtered items is shown.
1643
1644        :param list selList: indices of items to be selected. These indices
1645          are referenced to the order in self.ChoiceList
1646        '''
1647        self.Selections = len(self.ChoiceList) * [False,] # reset selections
1648        for sel in selList:
1649            self.Selections[sel] = True
1650        self._ShowSelections()
1651
1652    def _ShowSelections(self):
1653        'Show the selection state for displayed items'
1654        self.clb.SetChecked(
1655            [i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
1656            ) # Note anything previously checked will be cleared.
1657    def _SetAll(self,event):
1658        'Set all viewed choices on'
1659        self.clb.SetChecked(range(len(self.filterlist)))
1660    def _ToggleAll(self,event):
1661        'flip the state of all viewed choices'
1662        for i in range(len(self.filterlist)):
1663            self.clb.Check(i,not self.clb.IsChecked(i))
1664    def onChar(self,event):
1665        self.OKbtn.Enable(False)
1666        if self.timer.IsRunning():
1667            self.timer.Stop()
1668        self.timer.Start(1000,oneShot=True)
1669        event.Skip()
1670    def Filter(self,event):
1671        if self.timer.IsRunning():
1672            self.timer.Stop()
1673        self.GetSelections() # record current selections
1674        txt = self.filterBox.GetValue()
1675        self.clb.Clear()
1676        self.Update()
1677        self.filterlist = []
1678        if txt:
1679            txt = txt.lower()
1680            ChoiceList = []
1681            for i,item in enumerate(self.ChoiceList):
1682                if item.lower().find(txt) != -1:
1683                    ChoiceList.append(item)
1684                    self.filterlist.append(i)
1685        else:
1686            self.filterlist = range(len(self.ChoiceList))
1687            ChoiceList = self.ChoiceList
1688        self.clb.AppendItems(ChoiceList)
1689        self._ShowSelections()
1690        self.OKbtn.Enable(True)
1691
1692################################################################################
1693
1694class G2SingleChoiceDialog(wx.Dialog):
1695    '''A dialog similar to wx.SingleChoiceDialog except that a filter can be
1696    added.
1697
1698    :param wx.Frame ParentFrame: reference to parent frame
1699    :param str title: heading above list of choices
1700    :param str header: Title to place on window frame
1701    :param list ChoiceList: a list of choices where one will be selected
1702    :param bool monoFont: If False (default), use a variable-spaced font;
1703      if True use a equally-spaced font.
1704    :param bool filterBox: If True (default) an input widget is placed on
1705      the window and only entries matching the entered text are shown.
1706    :param kw: optional keyword parameters for the wx.Dialog may
1707      be included such as size [which defaults to `(320,310)`] and
1708      style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
1709      note that ``wx.OK`` and ``wx.CANCEL`` controls
1710      the presence of the eponymous buttons in the dialog.
1711    :returns: the name of the created dialog
1712    '''
1713    def __init__(self,parent, title, header, ChoiceList, 
1714                 monoFont=False, filterBox=True, **kw):
1715        # process keyword parameters, notably style
1716        options = {'size':(320,310), # default Frame keywords
1717                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
1718                   }
1719        options.update(kw)
1720        self.ChoiceList = ChoiceList
1721        self.filterlist = range(len(self.ChoiceList))
1722        if options['style'] & wx.OK:
1723            useOK = True
1724            options['style'] ^= wx.OK
1725        else:
1726            useOK = False
1727        if options['style'] & wx.CANCEL:
1728            useCANCEL = True
1729            options['style'] ^= wx.CANCEL
1730        else:
1731            useCANCEL = False       
1732        # create the dialog frame
1733        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
1734        # fill the dialog
1735        Sizer = wx.BoxSizer(wx.VERTICAL)
1736        topSizer = wx.BoxSizer(wx.HORIZONTAL)
1737        topSizer.Add(
1738            wx.StaticText(self,wx.ID_ANY,title,size=(-1,35)),
1739            1,wx.ALL|wx.EXPAND|WACV,1)
1740        if filterBox:
1741            self.timer = wx.Timer()
1742            self.timer.Bind(wx.EVT_TIMER,self.Filter)
1743            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Filter: '),0,wx.ALL,1)
1744            self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),
1745                                         style=wx.TE_PROCESS_ENTER)
1746            self.filterBox.Bind(wx.EVT_CHAR,self.onChar)
1747            self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
1748        topSizer.Add(self.filterBox,0,wx.ALL,0)
1749        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
1750        self.clb = wx.ListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
1751        self.clb.Bind(wx.EVT_LEFT_DCLICK,self.onDoubleClick)
1752        if monoFont:
1753            font1 = wx.Font(self.clb.GetFont().GetPointSize(),
1754                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
1755            self.clb.SetFont(font1)
1756        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
1757        Sizer.Add((-1,10))
1758        # OK/Cancel buttons
1759        btnsizer = wx.StdDialogButtonSizer()
1760        if useOK:
1761            self.OKbtn = wx.Button(self, wx.ID_OK)
1762            self.OKbtn.SetDefault()
1763            btnsizer.AddButton(self.OKbtn)
1764        if useCANCEL:
1765            btn = wx.Button(self, wx.ID_CANCEL)
1766            btnsizer.AddButton(btn)
1767        btnsizer.Realize()
1768        Sizer.Add((-1,5))
1769        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
1770        Sizer.Add((-1,20))
1771        # OK done, let's get outa here
1772        self.SetSizer(Sizer)
1773    def GetSelection(self):
1774        'Returns the index of the selected choice'
1775        i = self.clb.GetSelection()
1776        if i < 0 or i >= len(self.filterlist):
1777            return wx.NOT_FOUND
1778        return self.filterlist[i]
1779    def onChar(self,event):
1780        self.OKbtn.Enable(False)
1781        if self.timer.IsRunning():
1782            self.timer.Stop()
1783        self.timer.Start(1000,oneShot=True)
1784        event.Skip()
1785    def Filter(self,event):
1786        if self.timer.IsRunning():
1787            self.timer.Stop()
1788        txt = self.filterBox.GetValue()
1789        self.clb.Clear()
1790        self.Update()
1791        self.filterlist = []
1792        if txt:
1793            txt = txt.lower()
1794            ChoiceList = []
1795            for i,item in enumerate(self.ChoiceList):
1796                if item.lower().find(txt) != -1:
1797                    ChoiceList.append(item)
1798                    self.filterlist.append(i)
1799        else:
1800            self.filterlist = range(len(self.ChoiceList))
1801            ChoiceList = self.ChoiceList
1802        self.clb.AppendItems(ChoiceList)
1803        self.OKbtn.Enable(True)
1804    def onDoubleClick(self,event):
1805        self.EndModal(wx.ID_OK)
1806
1807################################################################################
1808
1809def ItemSelector(ChoiceList, ParentFrame=None,
1810                 title='Select an item',
1811                 size=None, header='Item Selector',
1812                 useCancel=True,multiple=False):
1813    ''' Provide a wx dialog to select a single item or multiple items from list of choices
1814
1815    :param list ChoiceList: a list of choices where one will be selected
1816    :param wx.Frame ParentFrame: Name of parent frame (default None)
1817    :param str title: heading above list of choices (default 'Select an item')
1818    :param wx.Size size: Size for dialog to be created (default None -- size as needed)
1819    :param str header: Title to place on window frame (default 'Item Selector')
1820    :param bool useCancel: If True (default) both the OK and Cancel buttons are offered
1821    :param bool multiple: If True then multiple items can be selected (default False)
1822   
1823    :returns: the selection index or None or a selection list if multiple is true
1824    '''
1825    if multiple:
1826        if useCancel:
1827            dlg = G2MultiChoiceDialog(
1828                ParentFrame,title, header, ChoiceList)
1829        else:
1830            dlg = G2MultiChoiceDialog(
1831                ParentFrame,title, header, ChoiceList,
1832                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1833    else:
1834        if useCancel:
1835            dlg = wx.SingleChoiceDialog(
1836                ParentFrame,title, header, ChoiceList)
1837        else:
1838            dlg = wx.SingleChoiceDialog(
1839                ParentFrame,title, header,ChoiceList,
1840                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1841    if size: dlg.SetSize(size)
1842    if dlg.ShowModal() == wx.ID_OK:
1843        if multiple:
1844            dlg.Destroy()
1845            return dlg.GetSelections()
1846        else:
1847            dlg.Destroy()
1848            return dlg.GetSelection()
1849    else:
1850        dlg.Destroy()
1851        return None
1852    dlg.Destroy()
1853
1854################################################################################
1855class GridFractionEditor(wg.PyGridCellEditor):
1856    '''A grid cell editor class that allows entry of values as fractions as well
1857    as sine and cosine values [as s() and c()]
1858    '''
1859    def __init__(self,grid):
1860        wg.PyGridCellEditor.__init__(self)
1861
1862    def Create(self, parent, id, evtHandler):
1863        self._tc = wx.TextCtrl(parent, id, "")
1864        self._tc.SetInsertionPoint(0)
1865        self.SetControl(self._tc)
1866
1867        if evtHandler:
1868            self._tc.PushEventHandler(evtHandler)
1869
1870        self._tc.Bind(wx.EVT_CHAR, self.OnChar)
1871
1872    def SetSize(self, rect):
1873        self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2,
1874                               wx.SIZE_ALLOW_MINUS_ONE)
1875
1876    def BeginEdit(self, row, col, grid):
1877        self.startValue = grid.GetTable().GetValue(row, col)
1878        self._tc.SetValue(str(self.startValue))
1879        self._tc.SetInsertionPointEnd()
1880        self._tc.SetFocus()
1881        self._tc.SetSelection(0, self._tc.GetLastPosition())
1882
1883    def EndEdit(self, row, col, grid):
1884        changed = False
1885
1886        val = self._tc.GetValue().lower()
1887       
1888        if val != self.startValue:
1889            changed = True
1890            neg = False
1891            if '-' in val:
1892                neg = True
1893            if '/' in val and '.' not in val:
1894                val += '.'
1895            elif 's' in val and not 'sind(' in val:
1896                if neg:
1897                    val = '-sind('+val.strip('-s')+')'
1898                else:
1899                    val = 'sind('+val.strip('s')+')'
1900            elif 'c' in val and not 'cosd(' in val:
1901                if neg:
1902                    val = '-cosd('+val.strip('-c')+')'
1903                else:
1904                    val = 'cosd('+val.strip('c')+')'
1905            try:
1906                val = float(eval(val))
1907            except (SyntaxError,NameError):
1908                val = self.startValue
1909            grid.GetTable().SetValue(row, col, val) # update the table
1910
1911        self.startValue = ''
1912        self._tc.SetValue('')
1913        return changed
1914
1915    def Reset(self):
1916        self._tc.SetValue(self.startValue)
1917        self._tc.SetInsertionPointEnd()
1918
1919    def Clone(self):
1920        return GridFractionEditor(grid)
1921
1922    def StartingKey(self, evt):
1923        self.OnChar(evt)
1924        if evt.GetSkipped():
1925            self._tc.EmulateKeyPress(evt)
1926
1927    def OnChar(self, evt):
1928        key = evt.GetKeyCode()
1929        if key == 15:
1930            return
1931        if key > 255:
1932            evt.Skip()
1933            return
1934        char = chr(key)
1935        if char in '.+-/0123456789cosind()':
1936            self._tc.WriteText(char)
1937        else:
1938            evt.Skip()
1939
1940################################################################################
1941def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem):
1942    '''Select a variable from a list, then edit it and select histograms
1943    to copy it to.
1944
1945    :param wx.Frame G2frame: main GSAS-II frame
1946    :param dict array: the array (dict or list) where values to be edited are kept
1947    :param list labelLst: labels for each data item
1948    :param list elemKeysLst: a list of lists of keys needed to be applied (see below)
1949      to obtain the value of each parameter
1950    :param list dspLst: list list of digits to be displayed (10,4) is 10 digits
1951      with 4 decimal places. Can be None.
1952    :param list refFlgElem: a list of lists of keys needed to be applied (see below)
1953      to obtain the refine flag for each parameter or None if the parameter
1954      does not have refine flag.
1955
1956    Example::
1957      array = data
1958      labelLst = ['v1','v2']
1959      elemKeysLst = [['v1'], ['v2',0]]
1960      refFlgElem = [None, ['v2',1]]
1961
1962     * The value for v1 will be in data['v1'] and this cannot be refined while,
1963     * The value for v2 will be in data['v2'][0] and its refinement flag is data['v2'][1]
1964    '''
1965    def unkey(dct,keylist):
1966        '''dive into a nested set of dicts/lists applying keys in keylist
1967        consecutively
1968        '''
1969        d = dct
1970        for k in keylist:
1971            d = d[k]
1972        return d
1973
1974    def OnChoice(event):
1975        'Respond when a parameter is selected in the Choice box'
1976        valSizer.DeleteWindows()
1977        lbl = event.GetString()
1978        copyopts['currentsel'] = lbl
1979        i = labelLst.index(lbl)
1980        OKbtn.Enable(True)
1981        ch.SetLabel(lbl)
1982        args = {}
1983        if dspLst[i]:
1984            args = {'nDig':dspLst[i]}
1985        Val = ValidatedTxtCtrl(
1986            dlg,
1987            unkey(array,elemKeysLst[i][:-1]),
1988            elemKeysLst[i][-1],
1989            **args)
1990        copyopts['startvalue'] = unkey(array,elemKeysLst[i])
1991        #unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] =
1992        valSizer.Add(Val,0,wx.LEFT,5)
1993        dlg.SendSizeEvent()
1994       
1995    # SelectEdit1Var execution begins here
1996    saveArray = copy.deepcopy(array) # keep original values
1997    TreeItemType = G2frame.PatternTree.GetItemText(G2frame.PickId)
1998    copyopts = {'InTable':False,"startvalue":None,'currentsel':None}       
1999    hst = G2frame.PatternTree.GetItemText(G2frame.PatternId)
2000    histList = G2pdG.GetHistsLikeSelected(G2frame)
2001    if not histList:
2002        G2frame.ErrorDialog('No match','No histograms match '+hst,G2frame.dataFrame)
2003        return
2004    dlg = wx.Dialog(G2frame.dataDisplay,wx.ID_ANY,'Set a parameter value',
2005        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2006    mainSizer = wx.BoxSizer(wx.VERTICAL)
2007    mainSizer.Add((5,5))
2008    subSizer = wx.BoxSizer(wx.HORIZONTAL)
2009    subSizer.Add((-1,-1),1,wx.EXPAND)
2010    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Select a parameter and set a new value'))
2011    subSizer.Add((-1,-1),1,wx.EXPAND)
2012    mainSizer.Add(subSizer,0,wx.EXPAND,0)
2013    mainSizer.Add((0,10))
2014
2015    subSizer = wx.FlexGridSizer(0,2,5,0)
2016    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Parameter: '))
2017    ch = wx.Choice(dlg, wx.ID_ANY, choices = sorted(labelLst))
2018    ch.SetSelection(-1)
2019    ch.Bind(wx.EVT_CHOICE, OnChoice)
2020    subSizer.Add(ch)
2021    subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Value: '))
2022    valSizer = wx.BoxSizer(wx.HORIZONTAL)
2023    subSizer.Add(valSizer)
2024    mainSizer.Add(subSizer)
2025
2026    mainSizer.Add((-1,20))
2027    subSizer = wx.BoxSizer(wx.HORIZONTAL)
2028    subSizer.Add(G2CheckBox(dlg, 'Edit in table ', copyopts, 'InTable'))
2029    mainSizer.Add(subSizer)
2030
2031    btnsizer = wx.StdDialogButtonSizer()
2032    OKbtn = wx.Button(dlg, wx.ID_OK,'Continue')
2033    OKbtn.Enable(False)
2034    OKbtn.SetDefault()
2035    OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
2036    btnsizer.AddButton(OKbtn)
2037    btn = wx.Button(dlg, wx.ID_CANCEL)
2038    btnsizer.AddButton(btn)
2039    btnsizer.Realize()
2040    mainSizer.Add((-1,5),1,wx.EXPAND,1)
2041    mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
2042    mainSizer.Add((-1,10))
2043
2044    dlg.SetSizer(mainSizer)
2045    dlg.CenterOnParent()
2046    if dlg.ShowModal() != wx.ID_OK:
2047        array.update(saveArray)
2048        dlg.Destroy()
2049        return
2050    dlg.Destroy()
2051
2052    copyList = []
2053    lbl = copyopts['currentsel']
2054    dlg = G2MultiChoiceDialog(
2055        G2frame.dataFrame, 
2056        'Copy parameter '+lbl+' from\n'+hst,
2057        'Copy parameters', histList)
2058    dlg.CenterOnParent()
2059    try:
2060        if dlg.ShowModal() == wx.ID_OK:
2061            for i in dlg.GetSelections(): 
2062                copyList.append(histList[i])
2063        else:
2064            # reset the parameter since cancel was pressed
2065            array.update(saveArray)
2066            return
2067    finally:
2068        dlg.Destroy()
2069
2070    prelbl = [hst]
2071    i = labelLst.index(lbl)
2072    keyLst = elemKeysLst[i]
2073    refkeys = refFlgElem[i]
2074    dictlst = [unkey(array,keyLst[:-1])]
2075    if refkeys is not None:
2076        refdictlst = [unkey(array,refkeys[:-1])]
2077    else:
2078        refdictlst = None
2079    Id = GetPatternTreeItemId(G2frame,G2frame.root,hst)
2080    hstData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Instrument Parameters'))[0]
2081    for h in copyList:
2082        Id = GetPatternTreeItemId(G2frame,G2frame.root,h)
2083        instData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Instrument Parameters'))[0]
2084        if len(hstData) != len(instData) or hstData['Type'][0] != instData['Type'][0]:  #don't mix data types or lam & lam1/lam2 parms!
2085            print h+' not copied - instrument parameters not commensurate'
2086            continue
2087        hData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,TreeItemType))
2088        if TreeItemType == 'Instrument Parameters':
2089            hData = hData[0]
2090        #copy the value if it is changed or we will not edit in a table
2091        valNow = unkey(array,keyLst)
2092        if copyopts['startvalue'] != valNow or not copyopts['InTable']:
2093            unkey(hData,keyLst[:-1])[keyLst[-1]] = valNow
2094        prelbl += [h]
2095        dictlst += [unkey(hData,keyLst[:-1])]
2096        if refdictlst is not None:
2097            refdictlst += [unkey(hData,refkeys[:-1])]
2098    if refdictlst is None:
2099        args = {}
2100    else:
2101        args = {'checkdictlst':refdictlst,
2102                'checkelemlst':len(dictlst)*[refkeys[-1]],
2103                'checklabel':'Refine?'}
2104    if copyopts['InTable']:
2105        dlg = ScrolledMultiEditor(
2106            G2frame.dataDisplay,dictlst,
2107            len(dictlst)*[keyLst[-1]],prelbl,
2108            header='Editing parameter '+lbl,
2109            CopyButton=True,**args)
2110        dlg.CenterOnParent()
2111        if dlg.ShowModal() != wx.ID_OK:
2112            array.update(saveArray)
2113        dlg.Destroy()
2114
2115################################################################################
2116class ShowLSParms(wx.Dialog):
2117    '''Create frame to show least-squares parameters
2118    '''
2119    def __init__(self,parent,title,parmDict,varyList,fullVaryList,
2120                 size=(300,430)):
2121        wx.Dialog.__init__(self,parent,wx.ID_ANY,title,size=size,
2122                           style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2123        mainSizer = wx.BoxSizer(wx.VERTICAL)
2124
2125        panel = wxscroll.ScrolledPanel(
2126            self, wx.ID_ANY,
2127            #size=size,
2128            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
2129        num = len(varyList)
2130        mainSizer.Add(wx.StaticText(self,wx.ID_ANY,'Number of refined variables: '+str(num)))
2131        if len(varyList) != len(fullVaryList):
2132            num = len(fullVaryList) - len(varyList)
2133            mainSizer.Add(wx.StaticText(self,wx.ID_ANY,' + '+str(num)+' parameters are varied via constraints'))
2134        subSizer = wx.FlexGridSizer(cols=4,hgap=2,vgap=2)
2135        parmNames = parmDict.keys()
2136        parmNames.sort()
2137        subSizer.Add((-1,-1))
2138        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'Parameter name  '))
2139        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'refine?'))
2140        subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'value'),0,wx.ALIGN_RIGHT)
2141        explainRefine = False
2142        for name in parmNames:
2143            # skip entries without numerical values
2144            if isinstance(parmDict[name],basestring): continue
2145            try:
2146                value = G2py3.FormatSigFigs(parmDict[name])
2147            except TypeError:
2148                value = str(parmDict[name])+' -?' # unexpected
2149                #continue
2150            v = G2obj.getVarDescr(name)
2151            if v is None or v[-1] is None:
2152                subSizer.Add((-1,-1))
2153            else:               
2154                ch = HelpButton(panel,G2obj.fmtVarDescr(name))
2155                subSizer.Add(ch,0,wx.LEFT|wx.RIGHT|WACV|wx.ALIGN_CENTER,1)
2156            subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(name)))
2157            if name in varyList:
2158                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'R'))
2159            elif name in fullVaryList:
2160                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,'C'))
2161                explainRefine = True
2162            else:
2163                subSizer.Add((-1,-1))
2164            subSizer.Add(wx.StaticText(panel,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
2165
2166        # finish up ScrolledPanel
2167        panel.SetSizer(subSizer)
2168        panel.SetAutoLayout(1)
2169        panel.SetupScrolling()
2170        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
2171
2172        if explainRefine:
2173            mainSizer.Add(
2174                wx.StaticText(self,wx.ID_ANY,
2175                          '"R" indicates a refined variable\n'+
2176                          '"C" indicates generated from a constraint'
2177                          ),
2178                0, wx.ALL,0)
2179        # make OK button
2180        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2181        btn = wx.Button(self, wx.ID_CLOSE,"Close") 
2182        btn.Bind(wx.EVT_BUTTON,self._onClose)
2183        btnsizer.Add(btn)
2184        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2185        # Allow window to be enlarged but not made smaller
2186        self.SetSizer(mainSizer)
2187        self.SetMinSize(self.GetSize())
2188
2189    def _onClose(self,event):
2190        self.EndModal(wx.ID_CANCEL)
2191 
2192################################################################################
2193class downdate(wx.Dialog):
2194    '''Dialog to allow a user to select a version of GSAS-II to install
2195    '''
2196    def __init__(self,parent=None):
2197        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
2198        wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style)
2199        pnl = wx.Panel(self)
2200        sizer = wx.BoxSizer(wx.VERTICAL)
2201        insver = GSASIIpath.svnGetRev(local=True)
2202        curver = int(GSASIIpath.svnGetRev(local=False))
2203        label = wx.StaticText(
2204            pnl,  wx.ID_ANY,
2205            'Select a specific GSAS-II version to install'
2206            )
2207        sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
2208        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
2209        sizer1.Add(
2210            wx.StaticText(pnl,  wx.ID_ANY,
2211                          'Currently installed version: '+str(insver)),
2212            0, wx.ALIGN_CENTRE|wx.ALL, 5)
2213        sizer.Add(sizer1)
2214        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
2215        sizer1.Add(
2216            wx.StaticText(pnl,  wx.ID_ANY,
2217                          'Select GSAS-II version to install: '),
2218            0, wx.ALIGN_CENTRE|wx.ALL, 5)
2219        self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1))
2220        self.spin.SetRange(1, curver)
2221        self.spin.SetValue(curver)
2222        self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
2223        self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
2224        sizer1.Add(self.spin)
2225        sizer.Add(sizer1)
2226
2227        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
2228        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2229
2230        self.text = wx.StaticText(pnl,  wx.ID_ANY, "")
2231        sizer.Add(self.text, 0, wx.ALIGN_LEFT|wx.EXPAND|wx.ALL, 5)
2232
2233        line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
2234        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2235        sizer.Add(
2236            wx.StaticText(
2237                pnl,  wx.ID_ANY,
2238                'If "Install" is pressed, your project will be saved;\n'
2239                'GSAS-II will exit; The specified version will be loaded\n'
2240                'and GSAS-II will restart. Press "Cancel" to abort.'),
2241            0, wx.EXPAND|wx.ALL, 10)
2242        btnsizer = wx.StdDialogButtonSizer()
2243        btn = wx.Button(pnl, wx.ID_OK, "Install")
2244        btn.SetDefault()
2245        btnsizer.AddButton(btn)
2246        btn = wx.Button(pnl, wx.ID_CANCEL)
2247        btnsizer.AddButton(btn)
2248        btnsizer.Realize()
2249        sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2250        pnl.SetSizer(sizer)
2251        sizer.Fit(self)
2252        self.topsizer=sizer
2253        self.CenterOnParent()
2254        self._onSpin(None)
2255
2256    def _onSpin(self,event):
2257        'Called to load info about the selected version in the dialog'
2258        ver = self.spin.GetValue()
2259        d = GSASIIpath.svnGetLog(version=ver)
2260        date = d.get('date','?').split('T')[0]
2261        s = '(Version '+str(ver)+' created '+date
2262        s += ' by '+d.get('author','?')+')'
2263        msg = d.get('msg')
2264        if msg: s += '\n\nComment: '+msg
2265        self.text.SetLabel(s)
2266        self.topsizer.Fit(self)
2267
2268    def getVersion(self):
2269        'Get the version number in the dialog'
2270        return self.spin.GetValue()
2271
2272################################################################################
2273class MyHelp(wx.Menu):
2274    '''
2275    A class that creates the contents of a help menu.
2276    The menu will start with two entries:
2277
2278    * 'Help on <helpType>': where helpType is a reference to an HTML page to
2279      be opened
2280    * About: opens an About dialog using OnHelpAbout. N.B. on the Mac this
2281      gets moved to the App menu to be consistent with Apple style.
2282
2283    NOTE: for this to work properly with respect to system menus, the title
2284    for the menu must be &Help, or it will not be processed properly:
2285
2286    ::
2287
2288       menu.Append(menu=MyHelp(self,...),title="&Help")
2289
2290    '''
2291    def __init__(self,frame,helpType=None,helpLbl=None,morehelpitems=[],title=''):
2292        wx.Menu.__init__(self,title)
2293        self.HelpById = {}
2294        self.frame = frame
2295        self.Append(help='', id=wx.ID_ABOUT, kind=wx.ITEM_NORMAL,
2296            text='&About GSAS-II')
2297        frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT)
2298        if GSASIIpath.whichsvn():
2299            helpobj = self.Append(
2300                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
2301                text='&Check for updates')
2302            frame.Bind(wx.EVT_MENU, self.OnCheckUpdates, helpobj)
2303            helpobj = self.Append(
2304                help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
2305                text='&Regress to an old GSAS-II version')
2306            frame.Bind(wx.EVT_MENU, self.OnSelectVersion, helpobj)
2307        for lbl,indx in morehelpitems:
2308            helpobj = self.Append(text=lbl,
2309                id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
2310            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
2311            self.HelpById[helpobj.GetId()] = indx
2312        # add a help item only when helpType is specified
2313        if helpType is not None:
2314            self.AppendSeparator()
2315            if helpLbl is None: helpLbl = helpType
2316            helpobj = self.Append(text='Help on '+helpLbl,
2317                                  id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
2318            frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
2319            self.HelpById[helpobj.GetId()] = helpType
2320       
2321    def OnHelpById(self,event):
2322        '''Called when Help on... is pressed in a menu. Brings up
2323        a web page for documentation.
2324        '''
2325        helpType = self.HelpById.get(event.GetId())
2326        if helpType is None:
2327            print 'Error: help lookup failed!',event.GetEventObject()
2328            print 'id=',event.GetId()
2329        else:
2330            if helpType == 'Tutorials':
2331                self.frame.Tutorials = True 
2332            ShowHelp(helpType,self.frame)
2333
2334    def OnHelpAbout(self, event):
2335        "Display an 'About GSAS-II' box"
2336        global __version__
2337        info = wx.AboutDialogInfo()
2338        info.Name = 'GSAS-II'
2339        ver = GSASIIpath.svnGetRev()
2340        if ver: 
2341            info.Version = 'Revision '+str(ver)+' (svn), version '+__version__
2342        else:
2343            info.Version = 'Revision '+str(GSASIIpath.GetVersionNumber())+' (.py files), version '+__version__
2344        #info.Developers = ['Robert B. Von Dreele','Brian H. Toby']
2345        info.Copyright = ('(c) ' + time.strftime('%Y') +
2346''' Argonne National Laboratory
2347This product includes software developed
2348by the UChicago Argonne, LLC, as
2349Operator of Argonne National Laboratory.''')
2350        info.Description = '''General Structure Analysis System-II (GSAS-II)
2351Robert B. Von Dreele and Brian H. Toby
2352
2353Please cite as:
2354B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013) '''
2355
2356        info.WebSite = ("https://subversion.xray.aps.anl.gov/trac/pyGSAS","GSAS-II home page")
2357        wx.AboutBox(info)
2358
2359    def OnCheckUpdates(self,event):
2360        '''Check if the GSAS-II repository has an update for the current source files
2361        and perform that update if requested.
2362        '''
2363        if not GSASIIpath.whichsvn():
2364            dlg = wx.MessageDialog(self.frame,
2365                                   'No Subversion','Cannot update GSAS-II because subversion (svn) was not found.',
2366                                   wx.OK)
2367            dlg.ShowModal()
2368            dlg.Destroy()
2369            return
2370        wx.BeginBusyCursor()
2371        local = GSASIIpath.svnGetRev()
2372        if local is None: 
2373            wx.EndBusyCursor()
2374            dlg = wx.MessageDialog(self.frame,
2375                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
2376                                   'Subversion error',
2377                                   wx.OK)
2378            dlg.ShowModal()
2379            dlg.Destroy()
2380            return
2381        print 'Installed GSAS-II version: '+local
2382        repos = GSASIIpath.svnGetRev(local=False)
2383        wx.EndBusyCursor()
2384        if repos is None: 
2385            dlg = wx.MessageDialog(self.frame,
2386                                   'Unable to access the GSAS-II server. Is this computer on the internet?',
2387                                   'Server unavailable',
2388                                   wx.OK)
2389            dlg.ShowModal()
2390            dlg.Destroy()
2391            return
2392        print 'GSAS-II version on server: '+repos
2393        if local == repos:
2394            dlg = wx.MessageDialog(self.frame,
2395                                   'GSAS-II is up-to-date. Version '+local+' is already loaded.',
2396                                   'GSAS-II Up-to-date',
2397                                   wx.OK)
2398            dlg.ShowModal()
2399            dlg.Destroy()
2400            return
2401        mods = GSASIIpath.svnFindLocalChanges()
2402        if mods:
2403            dlg = wx.MessageDialog(self.frame,
2404                                   'You have version '+local+
2405                                   ' of GSAS-II installed, but the current version is '+repos+
2406                                   '. However, '+str(len(mods))+
2407                                   ' file(s) on your local computer have been modified.'
2408                                   ' Updating will attempt to merge your local changes with '
2409                                   'the latest GSAS-II version, but if '
2410                                   'conflicts arise, local changes will be '
2411                                   'discarded. It is also possible that the '
2412                                   'local changes my prevent GSAS-II from running. '
2413                                   'Press OK to start an update if this is acceptable:',
2414                                   'Local GSAS-II Mods',
2415                                   wx.OK|wx.CANCEL)
2416            if dlg.ShowModal() != wx.ID_OK:
2417                dlg.Destroy()
2418                return
2419            else:
2420                dlg.Destroy()
2421        else:
2422            dlg = wx.MessageDialog(self.frame,
2423                                   'You have version '+local+
2424                                   ' of GSAS-II installed, but the current version is '+repos+
2425                                   '. Press OK to start an update:',
2426                                   'GSAS-II Updates',
2427                                   wx.OK|wx.CANCEL)
2428            if dlg.ShowModal() != wx.ID_OK:
2429                dlg.Destroy()
2430                return
2431            dlg.Destroy()
2432        print 'start updates'
2433        dlg = wx.MessageDialog(self.frame,
2434                               'Your project will now be saved, GSAS-II will exit and an update '
2435                               'will be performed and GSAS-II will restart. Press Cancel to '
2436                               'abort the update',
2437                               'Start update?',
2438                               wx.OK|wx.CANCEL)
2439        if dlg.ShowModal() != wx.ID_OK:
2440            dlg.Destroy()
2441            return
2442        dlg.Destroy()
2443        self.frame.OnFileSave(event)
2444        GSASIIpath.svnUpdateProcess(projectfile=self.frame.GSASprojectfile)
2445        return
2446
2447    def OnSelectVersion(self,event):
2448        '''Allow the user to select a specific version of GSAS-II
2449        '''
2450        if not GSASIIpath.whichsvn():
2451            dlg = wx.MessageDialog(self,'No Subversion','Cannot update GSAS-II because subversion (svn) '+
2452                                   'was not found.'
2453                                   ,wx.OK)
2454            dlg.ShowModal()
2455            return
2456        local = GSASIIpath.svnGetRev()
2457        if local is None: 
2458            dlg = wx.MessageDialog(self.frame,
2459                                   'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
2460                                   'Subversion error',
2461                                   wx.OK)
2462            dlg.ShowModal()
2463            return
2464        mods = GSASIIpath.svnFindLocalChanges()
2465        if mods:
2466            dlg = wx.MessageDialog(self.frame,
2467                                   'You have version '+local+
2468                                   ' of GSAS-II installed'
2469                                   '. However, '+str(len(mods))+
2470                                   ' file(s) on your local computer have been modified.'
2471                                   ' Downdating will attempt to merge your local changes with '
2472                                   'the selected GSAS-II version. '
2473                                   'Downdating is not encouraged because '
2474                                   'if merging is not possible, your local changes will be '
2475                                   'discarded. It is also possible that the '
2476                                   'local changes my prevent GSAS-II from running. '
2477                                   'Press OK to continue anyway.',
2478                                   'Local GSAS-II Mods',
2479                                   wx.OK|wx.CANCEL)
2480            if dlg.ShowModal() != wx.ID_OK:
2481                dlg.Destroy()
2482                return
2483            dlg.Destroy()
2484        dlg = downdate(parent=self.frame)
2485        if dlg.ShowModal() == wx.ID_OK:
2486            ver = dlg.getVersion()
2487        else:
2488            dlg.Destroy()
2489            return
2490        dlg.Destroy()
2491        print('start regress to '+str(ver))
2492        GSASIIpath.svnUpdateProcess(
2493            projectfile=self.frame.GSASprojectfile,
2494            version=str(ver)
2495            )
2496        self.frame.OnFileSave(event)
2497        return
2498
2499################################################################################
2500class AddHelp(wx.Menu):
2501    '''For the Mac: creates an entry to the help menu of type
2502    'Help on <helpType>': where helpType is a reference to an HTML page to
2503    be opened.
2504
2505    NOTE: when appending this menu (menu.Append) be sure to set the title to
2506    '&Help' so that wx handles it correctly.
2507    '''
2508    def __init__(self,frame,helpType,helpLbl=None,title=''):
2509        wx.Menu.__init__(self,title)
2510        self.frame = frame
2511        if helpLbl is None: helpLbl = helpType
2512        # add a help item only when helpType is specified
2513        helpobj = self.Append(text='Help on '+helpLbl,
2514                              id=wx.ID_ANY, kind=wx.ITEM_NORMAL)
2515        frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
2516        self.HelpById = helpType
2517       
2518    def OnHelpById(self,event):
2519        '''Called when Help on... is pressed in a menu. Brings up
2520        a web page for documentation.
2521        '''
2522        ShowHelp(self.HelpById,self.frame)
2523
2524################################################################################
2525class HelpButton(wx.Button):
2526    '''Create a help button that displays help information.
2527    The text is displayed in a modal message window.
2528
2529    TODO: it might be nice if it were non-modal: e.g. it stays around until
2530    the parent is deleted or the user closes it, but this did not work for
2531    me.
2532
2533    :param parent: the panel which will be the parent of the button
2534    :param str msg: the help text to be displayed
2535    '''
2536    def __init__(self,parent,msg):
2537        if sys.platform == "darwin": 
2538            wx.Button.__init__(self,parent,wx.ID_HELP)
2539        else:
2540            wx.Button.__init__(self,parent,wx.ID_ANY,'?',style=wx.BU_EXACTFIT)
2541        self.Bind(wx.EVT_BUTTON,self._onPress)
2542        self.msg=msg
2543        self.parent = parent
2544    def _onClose(self,event):
2545        self.dlg.EndModal(wx.ID_CANCEL)
2546    def _onPress(self,event):
2547        'Respond to a button press by displaying the requested text'
2548        #dlg = wx.MessageDialog(self.parent,self.msg,'Help info',wx.OK)
2549        self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', 
2550                        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2551        #self.dlg.SetBackgroundColour(wx.WHITE)
2552        mainSizer = wx.BoxSizer(wx.VERTICAL)
2553        txt = wx.StaticText(self.dlg,wx.ID_ANY,self.msg)
2554        mainSizer.Add(txt,1,wx.ALL|wx.EXPAND,10)
2555        txt.SetBackgroundColour(wx.WHITE)
2556
2557        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2558        btn = wx.Button(self.dlg, wx.ID_CLOSE) 
2559        btn.Bind(wx.EVT_BUTTON,self._onClose)
2560        btnsizer.Add(btn)
2561        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2562        self.dlg.SetSizer(mainSizer)
2563        mainSizer.Fit(self.dlg)
2564        self.dlg.ShowModal()
2565        self.dlg.Destroy()
2566################################################################################
2567class MyHtmlPanel(wx.Panel):
2568    '''Defines a panel to display HTML help information, as an alternative to
2569    displaying help information in a web browser.
2570    '''
2571    def __init__(self, frame, id):
2572        self.frame = frame
2573        wx.Panel.__init__(self, frame, id)
2574        sizer = wx.BoxSizer(wx.VERTICAL)
2575        back = wx.Button(self, -1, "Back")
2576        back.Bind(wx.EVT_BUTTON, self.OnBack)
2577        self.htmlwin = G2HtmlWindow(self, id, size=(750,450))
2578        sizer.Add(self.htmlwin, 1,wx.EXPAND)
2579        sizer.Add(back, 0, wx.ALIGN_LEFT, 0)
2580        self.SetSizer(sizer)
2581        sizer.Fit(frame)       
2582        self.Bind(wx.EVT_SIZE,self.OnHelpSize)
2583    def OnHelpSize(self,event):         #does the job but weirdly!!
2584        anchor = self.htmlwin.GetOpenedAnchor()
2585        if anchor:           
2586            self.htmlwin.ScrollToAnchor(anchor)
2587            wx.CallAfter(self.htmlwin.ScrollToAnchor,anchor)
2588            event.Skip()
2589    def OnBack(self, event):
2590        self.htmlwin.HistoryBack()
2591    def LoadFile(self,file):
2592        pos = file.rfind('#')
2593        if pos != -1:
2594            helpfile = file[:pos]
2595            helpanchor = file[pos+1:]
2596        else:
2597            helpfile = file
2598            helpanchor = None
2599        self.htmlwin.LoadPage(helpfile)
2600        if helpanchor is not None:
2601            self.htmlwin.ScrollToAnchor(helpanchor)
2602            xs,ys = self.htmlwin.GetViewStart()
2603            self.htmlwin.Scroll(xs,ys-1)
2604
2605class G2HtmlWindow(wx.html.HtmlWindow):
2606    '''Displays help information in a primitive HTML browser type window
2607    '''
2608    def __init__(self, parent, *args, **kwargs):
2609        self.parent = parent
2610        wx.html.HtmlWindow.__init__(self, parent, *args, **kwargs)
2611    def LoadPage(self, *args, **kwargs):
2612        wx.html.HtmlWindow.LoadPage(self, *args, **kwargs)
2613        self.TitlePage()
2614    def OnLinkClicked(self, *args, **kwargs):
2615        wx.html.HtmlWindow.OnLinkClicked(self, *args, **kwargs)
2616        xs,ys = self.GetViewStart()
2617        self.Scroll(xs,ys-1)
2618        self.TitlePage()
2619    def HistoryBack(self, *args, **kwargs):
2620        wx.html.HtmlWindow.HistoryBack(self, *args, **kwargs)
2621        self.TitlePage()
2622    def TitlePage(self):
2623        self.parent.frame.SetTitle(self.GetOpenedPage() + ' -- ' + 
2624            self.GetOpenedPageTitle())
2625
2626################################################################################
2627class DataFrame(wx.Frame):
2628    '''Create the data item window and all the entries in menus used in
2629    that window. For Linux and windows, the menu entries are created for the
2630    current data item window, but in the Mac the menu is accessed from all
2631    windows. This means that a different menu is posted depending on which
2632    data item is posted. On the Mac, all the menus contain the data tree menu
2633    items, but additional menus are added specific to the data item.
2634
2635    Note that while the menus are created here,
2636    the binding for the menus is done later in various GSASII*GUI modules,
2637    where the functions to be called are defined.
2638    '''
2639    def Bind(self,*args,**kwargs):
2640        '''Override the Bind() function: on the Mac the binding is to
2641        the main window, so that menus operate with any window on top.
2642        For other platforms, call the default wx.Frame Bind()
2643        '''
2644        if sys.platform == "darwin": # mac
2645            self.G2frame.Bind(*args,**kwargs)
2646        else:
2647            wx.Frame.Bind(self,*args,**kwargs)     
2648       
2649    def PrefillDataMenu(self,menu,helpType,helpLbl=None,empty=False):
2650        '''Create the "standard" part of data frame menus. Note that on Linux and
2651        Windows nothing happens here. On Mac, this menu duplicates the
2652        tree menu, but adds an extra help command for the data item and a separator.
2653        '''
2654        self.datamenu = menu
2655        self.helpType = helpType
2656        self.helpLbl = helpLbl
2657        if sys.platform == "darwin": # mac                         
2658            self.G2frame.FillMainMenu(menu) # add the data tree menu items
2659            if not empty:
2660                menu.Append(wx.Menu(title=''),title='|') # add a separator
2661       
2662    def PostfillDataMenu(self,empty=False):
2663        '''Create the "standard" part of data frame menus. Note that on Linux and
2664        Windows, this is the standard help Menu. On Mac, this menu duplicates the
2665        tree menu, but adds an extra help command for the data item and a separator.
2666        '''
2667        menu = self.datamenu
2668        helpType = self.helpType
2669        helpLbl = self.helpLbl
2670        if sys.platform == "darwin": # mac
2671            if not empty:
2672                menu.Append(wx.Menu(title=''),title='|') # add another separator
2673            menu.Append(AddHelp(self.G2frame,helpType=helpType, helpLbl=helpLbl),
2674                        title='&Help')
2675        else: # other
2676            menu.Append(menu=MyHelp(self,helpType=helpType, helpLbl=helpLbl),
2677                        title='&Help')
2678
2679    def _init_menus(self):
2680        'define all GSAS-II data frame menus'
2681
2682        # for use where no menu or data frame help is provided
2683        self.BlankMenu = wx.MenuBar()
2684       
2685        # Controls
2686        self.ControlsMenu = wx.MenuBar()
2687        self.PrefillDataMenu(self.ControlsMenu,helpType='Controls',empty=True)
2688        self.PostfillDataMenu(empty=True)
2689       
2690        # Notebook
2691        self.DataNotebookMenu = wx.MenuBar() 
2692        self.PrefillDataMenu(self.DataNotebookMenu,helpType='Notebook',empty=True)
2693        self.PostfillDataMenu(empty=True)
2694       
2695        # Comments
2696        self.DataCommentsMenu = wx.MenuBar()
2697        self.PrefillDataMenu(self.DataCommentsMenu,helpType='Comments',empty=True)
2698        self.PostfillDataMenu(empty=True)
2699       
2700        # Constraints - something amiss here - get weird wx C++ error after refine!
2701        self.ConstraintMenu = wx.MenuBar()
2702        self.PrefillDataMenu(self.ConstraintMenu,helpType='Constraints')
2703        self.ConstraintTab = wx.Menu(title='')
2704        self.ConstraintMenu.Append(menu=self.ConstraintTab, title='Select tab')
2705        for id,txt in (
2706            (wxID_CONSPHASE,'Phase'),
2707            (wxID_CONSHAP,'Histogram/Phase'),
2708            (wxID_CONSHIST,'Histogram'),
2709            (wxID_CONSGLOBAL,'Global')):
2710            self.ConstraintTab.Append(
2711                id=id, kind=wx.ITEM_NORMAL,text=txt,
2712                help='Select '+txt+' constraint editing tab')
2713        self.ConstraintEdit = wx.Menu(title='')
2714        self.ConstraintMenu.Append(menu=self.ConstraintEdit, title='Edit')
2715        self.ConstraintEdit.Append(id=wxID_HOLDADD, kind=wx.ITEM_NORMAL,text='Add hold',
2716            help='Add hold on a parameter value')
2717        self.ConstraintEdit.Append(id=wxID_EQUIVADD, kind=wx.ITEM_NORMAL,text='Add equivalence',
2718            help='Add equivalence between parameter values')
2719        self.ConstraintEdit.Append(id=wxID_CONSTRAINTADD, kind=wx.ITEM_NORMAL,text='Add constraint',
2720            help='Add constraint on parameter values')
2721        self.ConstraintEdit.Append(id=wxID_FUNCTADD, kind=wx.ITEM_NORMAL,text='Add New Var',
2722            help='Add variable composed of existing parameter')
2723
2724        # item = self.ConstraintEdit.Append(id=wx.ID_ANY,kind=wx.ITEM_NORMAL,text='Update GUI')
2725        # def UpdateGSASIIconstrGUI(event):
2726        #     import GSASIIconstrGUI
2727        #     reload(GSASIIconstrGUI)
2728        #     import GSASIIobj
2729        #     reload(GSASIIobj)
2730        # self.Bind(wx.EVT_MENU,UpdateGSASIIconstrGUI,id=item.GetId())
2731
2732        self.PostfillDataMenu()
2733       
2734        # Rigid bodies
2735        self.VectorRBEdit = wx.Menu(title='')
2736        self.VectorRBEdit.Append(id=wxID_RIGIDBODYADD, kind=wx.ITEM_NORMAL,text='Add rigid body',
2737            help='Add vector rigid body')
2738        self.ResidueRBMenu = wx.Menu(title='')
2739        self.ResidueRBMenu.Append(id=wxID_RIGIDBODYIMPORT, kind=wx.ITEM_NORMAL,text='Import XYZ',
2740            help='Import rigid body XYZ from file')
2741        self.ResidueRBMenu.Append(id=wxID_RESIDUETORSSEQ, kind=wx.ITEM_NORMAL,text='Define sequence',
2742            help='Define torsion sequence')
2743        self.ResidueRBMenu.Append(id=wxID_RIGIDBODYADD, kind=wx.ITEM_NORMAL,text='Import residues',
2744            help='Import residue rigid bodies from macro file')
2745           
2746        self.RigidBodyMenu = wx.MenuBar()
2747        self.PrefillDataMenu(self.RigidBodyMenu,helpType='Rigid bodies')
2748        self.RigidBodyMenu.Append(menu=self.VectorRBEdit, title='Edit')       
2749        self.PostfillDataMenu()
2750           
2751        # Restraints
2752        self.RestraintTab = wx.Menu(title='')
2753        self.RestraintEdit = wx.Menu(title='')
2754        self.RestraintEdit.Append(id=wxID_RESTSELPHASE, kind=wx.ITEM_NORMAL,text='Select phase',
2755            help='Select phase')
2756        self.RestraintEdit.Append(id=wxID_RESTRAINTADD, kind=wx.ITEM_NORMAL,text='Add restraints',
2757            help='Add restraints')
2758        self.RestraintEdit.Enable(wxID_RESTRAINTADD,True)    #gets disenabled if macromolecule phase
2759        self.RestraintEdit.Append(id=wxID_AARESTRAINTADD, kind=wx.ITEM_NORMAL,text='Add residue restraints',
2760            help='Add residue based restraints for macromolecules from macro file')
2761        self.RestraintEdit.Enable(wxID_AARESTRAINTADD,False)    #gets enabled if macromolecule phase
2762        self.RestraintEdit.Append(id=wxID_AARESTRAINTPLOT, kind=wx.ITEM_NORMAL,text='Plot residue restraints',
2763            help='Plot selected residue based restraints for macromolecules from macro file')
2764        self.RestraintEdit.Enable(wxID_AARESTRAINTPLOT,False)    #gets enabled if macromolecule phase
2765        self.RestraintEdit.Append(id=wxID_RESRCHANGEVAL, kind=wx.ITEM_NORMAL,text='Change value',
2766            help='Change observed value')
2767        self.RestraintEdit.Append(id=wxID_RESTCHANGEESD, kind=wx.ITEM_NORMAL,text='Change esd',
2768            help='Change esd in observed value')
2769        self.RestraintEdit.Append(id=wxID_RESTDELETE, kind=wx.ITEM_NORMAL,text='Delete restraints',
2770            help='Delete selected restraints')
2771
2772        self.RestraintMenu = wx.MenuBar()
2773        self.PrefillDataMenu(self.RestraintMenu,helpType='Restraints')
2774        self.RestraintMenu.Append(menu=self.RestraintTab, title='Select tab')
2775        self.RestraintMenu.Append(menu=self.RestraintEdit, title='Edit')
2776        self.PostfillDataMenu()
2777           
2778        # Sequential results
2779        self.SequentialMenu = wx.MenuBar()
2780        self.PrefillDataMenu(self.SequentialMenu,helpType='Sequential',helpLbl='Sequential Refinement')
2781        self.SequentialFile = wx.Menu(title='')
2782        self.SequentialMenu.Append(menu=self.SequentialFile, title='Selected Cols')
2783        self.SequentialFile.Append(id=wxID_RENAMESEQSEL, kind=wx.ITEM_NORMAL,text='Rename',
2784            help='Rename selected sequential refinement columns')
2785        self.SequentialFile.Append(id=wxID_SAVESEQSEL, kind=wx.ITEM_NORMAL,text='Save as text',
2786            help='Save selected sequential refinement results as a text file')
2787        self.SequentialFile.Append(id=wxID_SAVESEQSELCSV, kind=wx.ITEM_NORMAL,text='Save as CSV',
2788            help='Save selected sequential refinement results as a CSV spreadsheet file')
2789        self.SequentialFile.Append(id=wxID_PLOTSEQSEL, kind=wx.ITEM_NORMAL,text='Plot selected',
2790            help='Plot selected sequential refinement results')
2791        self.SequentialPvars = wx.Menu(title='')
2792        self.SequentialMenu.Append(menu=self.SequentialPvars, title='Pseudo Vars')
2793        self.SequentialPvars.Append(
2794            id=wxADDSEQVAR, kind=wx.ITEM_NORMAL,text='Add',
2795            help='Add a new pseudo-variable')
2796        self.SequentialPvars.Append(
2797            id=wxDELSEQVAR, kind=wx.ITEM_NORMAL,text='Delete',
2798            help='Delete an existing pseudo-variable')
2799        self.SequentialPvars.Append(
2800            id=wxEDITSEQVAR, kind=wx.ITEM_NORMAL,text='Edit',
2801            help='Edit an existing pseudo-variable')
2802
2803        self.SequentialPfit = wx.Menu(title='')
2804        self.SequentialMenu.Append(menu=self.SequentialPfit, title='Parametric Fit')
2805        self.SequentialPfit.Append(
2806            id=wxADDPARFIT, kind=wx.ITEM_NORMAL,text='Add equation',
2807            help='Add a new equation to minimize')
2808        self.SequentialPfit.Append(
2809            id=wxCOPYPARFIT, kind=wx.ITEM_NORMAL,text='Copy equation',
2810            help='Copy an equation to minimize - edit it next')
2811        self.SequentialPfit.Append(
2812            id=wxDELPARFIT, kind=wx.ITEM_NORMAL,text='Delete equation',
2813            help='Delete an equation for parametric minimization')
2814        self.SequentialPfit.Append(
2815            id=wxEDITPARFIT, kind=wx.ITEM_NORMAL,text='Edit equation',
2816            help='Edit an existing parametric minimization equation')
2817        self.SequentialPfit.Append(
2818            id=wxDOPARFIT, kind=wx.ITEM_NORMAL,text='Fit to equation(s)',
2819            help='Perform a parametric minimization')
2820        self.PostfillDataMenu()
2821           
2822        # PWDR & SASD
2823        self.PWDRMenu = wx.MenuBar()
2824        self.PrefillDataMenu(self.PWDRMenu,helpType='PWDR Analysis',helpLbl='Powder Fit Error Analysis')
2825        self.ErrorAnal = wx.Menu(title='')
2826        self.PWDRMenu.Append(menu=self.ErrorAnal,title='Commands')
2827        self.ErrorAnal.Append(id=wxID_PWDANALYSIS,kind=wx.ITEM_NORMAL,text='Error Analysis',
2828            help='Error analysis on powder pattern')
2829        self.ErrorAnal.Append(id=wxID_PWDCOPY,kind=wx.ITEM_NORMAL,text='Copy params',
2830            help='Copy of PWDR parameters')
2831        self.PostfillDataMenu()
2832           
2833        # HKLF
2834        self.HKLFMenu = wx.MenuBar()
2835        self.PrefillDataMenu(self.HKLFMenu,helpType='HKLF Analysis',helpLbl='HKLF Fit Error Analysis')
2836        self.ErrorAnal = wx.Menu(title='')
2837        self.HKLFMenu.Append(menu=self.ErrorAnal,title='Commands')
2838        self.ErrorAnal.Append(id=wxID_PWDANALYSIS,kind=wx.ITEM_NORMAL,text='Error Analysis',
2839            help='Error analysis on single crystal data')
2840        self.ErrorAnal.Append(id=wxID_PWD3DHKLPLOT,kind=wx.ITEM_NORMAL,text='Plot 3D HKLs',
2841            help='Plot HKLs from single crystal data in 3D')
2842        self.ErrorAnal.Append(id=wxID_PWDCOPY,kind=wx.ITEM_NORMAL,text='Copy params',
2843            help='Copy of HKLF parameters')
2844        self.PostfillDataMenu()
2845           
2846        # PDR / Limits
2847        self.LimitMenu = wx.MenuBar()
2848        self.PrefillDataMenu(self.LimitMenu,helpType='Limits')
2849        self.LimitEdit = wx.Menu(title='')
2850        self.LimitMenu.Append(menu=self.LimitEdit, title='Edit')
2851        self.LimitEdit.Append(id=wxID_LIMITCOPY, kind=wx.ITEM_NORMAL,text='Copy',
2852            help='Copy limits to other histograms')
2853        self.LimitEdit.Append(id=wxID_ADDEXCLREGION, kind=wx.ITEM_NORMAL,text='Add exclude',
2854            help='Add excluded region - select a point on plot; drag to adjust')           
2855        self.PostfillDataMenu()
2856           
2857        # PDR / Background
2858        self.BackMenu = wx.MenuBar()
2859        self.PrefillDataMenu(self.BackMenu,helpType='Background')
2860        self.BackEdit = wx.Menu(title='')
2861        self.BackMenu.Append(menu=self.BackEdit, title='File')
2862        self.BackEdit.Append(id=wxID_BACKCOPY, kind=wx.ITEM_NORMAL,text='Copy',
2863            help='Copy background parameters to other histograms')
2864        self.BackEdit.Append(id=wxID_BACKFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2865            help='Copy background refinement flags to other histograms')
2866        self.PostfillDataMenu()
2867           
2868        # PDR / Instrument Parameters
2869        self.InstMenu = wx.MenuBar()
2870        self.PrefillDataMenu(self.InstMenu,helpType='Instrument Parameters')
2871        self.InstEdit = wx.Menu(title='')
2872        self.InstMenu.Append(menu=self.InstEdit, title='Operations')
2873        self.InstEdit.Append(help='Calibrate from indexed peaks', 
2874            id=wxID_INSTCALIB, kind=wx.ITEM_NORMAL,text='Calibrate')
2875        self.InstEdit.Append(help='Reset instrument profile parameters to default', 
2876            id=wxID_INSTLOAD, kind=wx.ITEM_NORMAL,text='Load profile...')
2877        self.InstEdit.Append(help='Load instrument profile parameters from file', 
2878            id=wxID_INSTSAVE, kind=wx.ITEM_NORMAL,text='Save profile...')
2879        self.InstEdit.Append(help='Save instrument profile parameters to file', 
2880            id=wxID_INSTPRMRESET, kind=wx.ITEM_NORMAL,text='Reset profile')
2881        self.InstEdit.Append(help='Copy instrument profile parameters to other histograms', 
2882            id=wxID_INSTCOPY, kind=wx.ITEM_NORMAL,text='Copy')
2883        self.InstEdit.Append(id=wxID_INSTFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2884            help='Copy instrument parameter refinement flags to other histograms')
2885#        self.InstEdit.Append(help='Change radiation type (Ka12 - synch)',
2886#            id=wxID_CHANGEWAVETYPE, kind=wx.ITEM_NORMAL,text='Change radiation')
2887        self.InstEdit.Append(id=wxID_INST1VAL, kind=wx.ITEM_NORMAL,text='Set one value',
2888            help='Set one instrument parameter value across multiple histograms')
2889
2890        self.PostfillDataMenu()
2891       
2892        # PDR / Sample Parameters
2893        self.SampleMenu = wx.MenuBar()
2894        self.PrefillDataMenu(self.SampleMenu,helpType='Sample Parameters')
2895        self.SampleEdit = wx.Menu(title='')
2896        self.SampleMenu.Append(menu=self.SampleEdit, title='Command')
2897        self.SetScale = self.SampleEdit.Append(id=wxID_SETSCALE, kind=wx.ITEM_NORMAL,text='Set scale',
2898            help='Set scale by matching to another histogram')
2899        self.SampleEdit.Append(id=wxID_SAMPLELOAD, kind=wx.ITEM_NORMAL,text='Load',
2900            help='Load sample parameters from file')
2901        self.SampleEdit.Append(id=wxID_SAMPLESAVE, kind=wx.ITEM_NORMAL,text='Save',
2902            help='Save sample parameters to file')
2903        self.SampleEdit.Append(id=wxID_SAMPLECOPY, kind=wx.ITEM_NORMAL,text='Copy',
2904            help='Copy refinable and most other sample parameters to other histograms')
2905        self.SampleEdit.Append(id=wxID_SAMPLECOPYSOME, kind=wx.ITEM_NORMAL,text='Copy selected...',
2906            help='Copy selected sample parameters to other histograms')
2907        self.SampleEdit.Append(id=wxID_SAMPLEFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2908            help='Copy sample parameter refinement flags to other histograms')
2909        self.SampleEdit.Append(id=wxID_SAMPLE1VAL, kind=wx.ITEM_NORMAL,text='Set one value',
2910            help='Set one sample parameter value across multiple histograms')
2911
2912        self.PostfillDataMenu()
2913        self.SetScale.Enable(False)
2914
2915        # PDR / Peak List
2916        self.PeakMenu = wx.MenuBar()
2917        self.PrefillDataMenu(self.PeakMenu,helpType='Peak List')
2918        self.PeakEdit = wx.Menu(title='')
2919        self.PeakMenu.Append(menu=self.PeakEdit, title='Peak Fitting')
2920        self.AutoSearch = self.PeakEdit.Append(help='Automatic peak search', 
2921            id=wxID_AUTOSEARCH, kind=wx.ITEM_NORMAL,text='Auto search')
2922        self.UnDo = self.PeakEdit.Append(help='Undo last least squares refinement', 
2923            id=wxID_UNDO, kind=wx.ITEM_NORMAL,text='UnDo')
2924        self.PeakFit = self.PeakEdit.Append(id=wxID_LSQPEAKFIT, kind=wx.ITEM_NORMAL,text='LSQ PeakFit', 
2925            help='Peak fitting via least-squares' )
2926        self.PFOneCycle = self.PeakEdit.Append(id=wxID_LSQONECYCLE, kind=wx.ITEM_NORMAL,text='LSQ one cycle', 
2927            help='One cycle of Peak fitting via least-squares' )
2928        self.PeakEdit.Append(id=wxID_RESETSIGGAM, kind=wx.ITEM_NORMAL, 
2929            text='Reset sig and gam',help='Reset sigma and gamma to global fit' )
2930        self.PeakEdit.Append(id=wxID_CLEARPEAKS, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2931            help='Clear the peak list' )
2932        self.PostfillDataMenu()
2933        self.UnDo.Enable(False)
2934        self.PeakFit.Enable(False)
2935        self.PFOneCycle.Enable(False)
2936       
2937        # PDR / Index Peak List
2938        self.IndPeaksMenu = wx.MenuBar()
2939        self.PrefillDataMenu(self.IndPeaksMenu,helpType='Index Peak List')
2940        self.IndPeaksEdit = wx.Menu(title='')
2941        self.IndPeaksMenu.Append(menu=self.IndPeaksEdit,title='Operations')
2942        self.IndPeaksEdit.Append(help='Load/Reload index peaks from peak list',id=wxID_INDXRELOAD, 
2943            kind=wx.ITEM_NORMAL,text='Load/Reload')
2944        self.PostfillDataMenu()
2945       
2946        # PDR / Unit Cells List
2947        self.IndexMenu = wx.MenuBar()
2948        self.PrefillDataMenu(self.IndexMenu,helpType='Unit Cells List')
2949        self.IndexEdit = wx.Menu(title='')
2950        self.IndexMenu.Append(menu=self.IndexEdit, title='Cell Index/Refine')
2951        self.IndexPeaks = self.IndexEdit.Append(help='', id=wxID_INDEXPEAKS, kind=wx.ITEM_NORMAL,
2952            text='Index Cell')
2953        self.CopyCell = self.IndexEdit.Append( id=wxID_COPYCELL, kind=wx.ITEM_NORMAL,text='Copy Cell', 
2954            help='Copy selected unit cell from indexing to cell refinement fields')
2955        self.RefineCell = self.IndexEdit.Append( id=wxID_REFINECELL, kind=wx.ITEM_NORMAL, 
2956            text='Refine Cell',help='Refine unit cell parameters from indexed peaks')
2957        self.MakeNewPhase = self.IndexEdit.Append( id=wxID_MAKENEWPHASE, kind=wx.ITEM_NORMAL,
2958            text='Make new phase',help='Make new phase from selected unit cell')
2959        self.PostfillDataMenu()
2960        self.IndexPeaks.Enable(False)
2961        self.CopyCell.Enable(False)
2962        self.RefineCell.Enable(False)
2963        self.MakeNewPhase.Enable(False)
2964       
2965        # PDR / Reflection Lists
2966        self.ReflMenu = wx.MenuBar()
2967        self.PrefillDataMenu(self.ReflMenu,helpType='Reflection List')
2968        self.ReflEdit = wx.Menu(title='')
2969        self.ReflMenu.Append(menu=self.ReflEdit, title='Reflection List')
2970        self.SelectPhase = self.ReflEdit.Append(help='Select phase for reflection list',id=wxID_SELECTPHASE, 
2971            kind=wx.ITEM_NORMAL,text='Select phase')
2972        self.ReflEdit.Append(id=wxID_PWDHKLPLOT,kind=wx.ITEM_NORMAL,text='Plot HKLs',
2973            help='Plot HKLs from powder pattern')
2974        self.ReflEdit.Append(id=wxID_PWD3DHKLPLOT,kind=wx.ITEM_NORMAL,text='Plot 3D HKLs',
2975            help='Plot HKLs from powder pattern in 3D')
2976        self.PostfillDataMenu()
2977       
2978        #SASD & REFL/ Substance editor
2979        self.SubstanceMenu = wx.MenuBar()
2980        self.PrefillDataMenu(self.SubstanceMenu,helpType='Substances')
2981        self.SubstanceEdit = wx.Menu(title='')
2982        self.SubstanceMenu.Append(menu=self.SubstanceEdit, title='Edit')
2983        self.SubstanceEdit.Append(id=wxID_LOADSUBSTANCE, kind=wx.ITEM_NORMAL,text='Load substance',
2984            help='Load substance from file')
2985        self.SubstanceEdit.Append(id=wxID_ADDSUBSTANCE, kind=wx.ITEM_NORMAL,text='Add substance',
2986            help='Add new substance to list')
2987        self.SubstanceEdit.Append(id=wxID_COPYSUBSTANCE, kind=wx.ITEM_NORMAL,text='Copy substances',
2988            help='Copy substances')
2989        self.SubstanceEdit.Append(id=wxID_DELETESUBSTANCE, kind=wx.ITEM_NORMAL,text='Delete substance',
2990            help='Delete substance from list')           
2991        self.SubstanceEdit.Append(id=wxID_ELEMENTADD, kind=wx.ITEM_NORMAL,text='Add elements',
2992            help='Add elements to substance')
2993        self.SubstanceEdit.Append(id=wxID_ELEMENTDELETE, kind=wx.ITEM_NORMAL,text='Delete elements',
2994            help='Delete elements from substance')
2995        self.PostfillDataMenu()
2996       
2997        # SASD/ Models
2998        self.ModelMenu = wx.MenuBar()
2999        self.PrefillDataMenu(self.ModelMenu,helpType='Models')
3000        self.ModelEdit = wx.Menu(title='')
3001        self.ModelMenu.Append(menu=self.ModelEdit, title='Models')
3002        self.ModelEdit.Append(id=wxID_MODELADD,kind=wx.ITEM_NORMAL,text='Add',
3003            help='Add new term to model')
3004        self.ModelEdit.Append(id=wxID_MODELFIT, kind=wx.ITEM_NORMAL,text='Fit',
3005            help='Fit model parameters to data')
3006        self.SasdUndo = self.ModelEdit.Append(id=wxID_MODELUNDO, kind=wx.ITEM_NORMAL,text='Undo',
3007            help='Undo model fit')
3008        self.SasdUndo.Enable(False)           
3009        self.ModelEdit.Append(id=wxID_MODELFITALL, kind=wx.ITEM_NORMAL,text='Sequential fit',
3010            help='Sequential fit of model parameters to all SASD data')
3011        self.ModelEdit.Append(id=wxID_MODELCOPY, kind=wx.ITEM_NORMAL,text='Copy',
3012            help='Copy model parameters to other histograms')
3013        self.ModelEdit.Append(id=wxID_MODELCOPYFLAGS, kind=wx.ITEM_NORMAL,text='Copy flags',
3014            help='Copy model refinement flags to other histograms')
3015        self.PostfillDataMenu()
3016       
3017        # IMG / Image Controls
3018        self.ImageMenu = wx.MenuBar()
3019        self.PrefillDataMenu(self.ImageMenu,helpType='Image Controls')
3020        self.ImageEdit = wx.Menu(title='')
3021        self.ImageMenu.Append(menu=self.ImageEdit, title='Operations')
3022        self.ImageEdit.Append(help='Calibrate detector by fitting to calibrant lines', 
3023            id=wxID_IMCALIBRATE, kind=wx.ITEM_NORMAL,text='Calibrate')
3024        self.ImageEdit.Append(help='Recalibrate detector by fitting to calibrant lines', 
3025            id=wxID_IMRECALIBRATE, kind=wx.ITEM_NORMAL,text='Recalibrate')
3026        self.ImageEdit.Append(help='Clear calibration data points and rings',id=wxID_IMCLEARCALIB, 
3027            kind=wx.ITEM_NORMAL,text='Clear calibration')
3028        self.ImageEdit.Append(help='Integrate selected image',id=wxID_IMINTEGRATE, 
3029            kind=wx.ITEM_NORMAL,text='Integrate')
3030        self.ImageEdit.Append(help='Integrate all images selected from list',id=wxID_INTEGRATEALL,
3031            kind=wx.ITEM_NORMAL,text='Integrate all')
3032        self.ImageEdit.Append(help='Copy image controls to other images', 
3033            id=wxID_IMCOPYCONTROLS, kind=wx.ITEM_NORMAL,text='Copy Controls')
3034        self.ImageEdit.Append(help='Save image controls to file', 
3035            id=wxID_IMSAVECONTROLS, kind=wx.ITEM_NORMAL,text='Save Controls')
3036        self.ImageEdit.Append(help='Load image controls from file', 
3037            id=wxID_IMLOADCONTROLS, kind=wx.ITEM_NORMAL,text='Load Controls')
3038        self.PostfillDataMenu()
3039           
3040        # IMG / Masks
3041        self.MaskMenu = wx.MenuBar()
3042        self.PrefillDataMenu(self.MaskMenu,helpType='Image Masks')
3043        self.MaskEdit = wx.Menu(title='')
3044        self.MaskMenu.Append(menu=self.MaskEdit, title='Operations')
3045        submenu = wx.Menu()
3046        self.MaskEdit.AppendMenu(
3047            wx.ID_ANY,'Create new', submenu,
3048            help=''
3049            )
3050        self.MaskEdit.Append(help='Copy mask to other images', 
3051            id=wxID_MASKCOPY, kind=wx.ITEM_NORMAL,text='Copy mask')
3052        self.MaskEdit.Append(help='Save mask to file', 
3053            id=wxID_MASKSAVE, kind=wx.ITEM_NORMAL,text='Save mask')
3054        self.MaskEdit.Append(help='Load mask from file', 
3055            id=wxID_MASKLOAD, kind=wx.ITEM_NORMAL,text='Load mask')
3056        submenu.Append(help='Create an arc mask with mouse input', 
3057            id=wxID_NEWMASKARC, kind=wx.ITEM_NORMAL,text='Arc mask')
3058        submenu.Append(help='Create a frame mask with mouse input', 
3059            id=wxID_NEWMASKFRAME, kind=wx.ITEM_NORMAL,text='Frame mask')
3060        submenu.Append(help='Create a polygon mask with mouse input', 
3061            id=wxID_NEWMASKPOLY, kind=wx.ITEM_NORMAL,text='Polygon mask')
3062        submenu.Append(help='Create a ring mask with mouse input', 
3063            id=wxID_NEWMASKRING, kind=wx.ITEM_NORMAL,text='Ring mask')
3064        submenu.Append(help='Create a spot mask with mouse input', 
3065            id=wxID_NEWMASKSPOT, kind=wx.ITEM_NORMAL,text='Spot mask')
3066        self.PostfillDataMenu()
3067           
3068        # IMG / Stress/Strain
3069        self.StrStaMenu = wx.MenuBar()
3070        self.PrefillDataMenu(self.StrStaMenu,helpType='Stress/Strain')
3071        self.StrStaEdit = wx.Menu(title='')
3072        self.StrStaMenu.Append(menu=self.StrStaEdit, title='Operations')
3073        self.StrStaEdit.Append(help='Append d-zero for one ring', 
3074            id=wxID_APPENDDZERO, kind=wx.ITEM_NORMAL,text='Append d-zero')
3075        self.StrStaEdit.Append(help='Fit stress/strain data', 
3076            id=wxID_STRSTAFIT, kind=wx.ITEM_NORMAL,text='Fit stress/strain')
3077        self.StrStaEdit.Append(help='Update d-zero from ave d-zero',
3078            id=wxID_UPDATEDZERO, kind=wx.ITEM_NORMAL,text='Update d-zero')       
3079        self.StrStaEdit.Append(help='Fit stress/strain data for all images', 
3080            id=wxID_STRSTAALLFIT, kind=wx.ITEM_NORMAL,text='All image fit')
3081        self.StrStaEdit.Append(help='Copy stress/strain data to other images', 
3082            id=wxID_STRSTACOPY, kind=wx.ITEM_NORMAL,text='Copy stress/strain')
3083        self.StrStaEdit.Append(help='Save stress/strain data to file', 
3084            id=wxID_STRSTASAVE, kind=wx.ITEM_NORMAL,text='Save stress/strain')
3085        self.StrStaEdit.Append(help='Load stress/strain data from file', 
3086            id=wxID_STRSTALOAD, kind=wx.ITEM_NORMAL,text='Load stress/strain')
3087        self.PostfillDataMenu()
3088           
3089        # PDF / PDF Controls
3090        self.PDFMenu = wx.MenuBar()
3091        self.PrefillDataMenu(self.PDFMenu,helpType='PDF Controls')
3092        self.PDFEdit = wx.Menu(title='')
3093        self.PDFMenu.Append(menu=self.PDFEdit, title='PDF Controls')
3094        self.PDFEdit.Append(help='Add element to sample composition',id=wxID_PDFADDELEMENT, kind=wx.ITEM_NORMAL,
3095            text='Add element')
3096        self.PDFEdit.Append(help='Delete element from sample composition',id=wxID_PDFDELELEMENT, kind=wx.ITEM_NORMAL,
3097            text='Delete element')
3098        self.PDFEdit.Append(help='Copy PDF controls', id=wxID_PDFCOPYCONTROLS, kind=wx.ITEM_NORMAL,
3099            text='Copy controls')
3100        #        self.PDFEdit.Append(help='Load PDF controls from file',id=wxID_PDFLOADCONTROLS, kind=wx.ITEM_NORMAL,
3101        #            text='Load Controls')
3102        #        self.PDFEdit.Append(help='Save PDF controls to file', id=wxID_PDFSAVECONTROLS, kind=wx.ITEM_NORMAL,
3103        #            text='Save controls')
3104        self.PDFEdit.Append(help='Compute PDF', id=wxID_PDFCOMPUTE, kind=wx.ITEM_NORMAL,
3105            text='Compute PDF')
3106        self.PDFEdit.Append(help='Compute all PDFs', id=wxID_PDFCOMPUTEALL, kind=wx.ITEM_NORMAL,
3107            text='Compute all PDFs')
3108        self.PostfillDataMenu()
3109       
3110        # Phase / General tab
3111        self.DataGeneral = wx.MenuBar()
3112        self.PrefillDataMenu(self.DataGeneral,helpType='General', helpLbl='Phase/General')
3113        self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab')
3114        self.GeneralCalc = wx.Menu(title='')
3115        self.DataGeneral.Append(menu=self.GeneralCalc,title='Compute')
3116        self.GeneralCalc.Append(help='Compute Fourier map',id=wxID_FOURCALC, kind=wx.ITEM_NORMAL,
3117            text='Fourier map')
3118        self.GeneralCalc.Append(help='Search Fourier map',id=wxID_FOURSEARCH, kind=wx.ITEM_NORMAL,
3119            text='Search map')
3120        self.GeneralCalc.Append(help='Run charge flipping',id=wxID_CHARGEFLIP, kind=wx.ITEM_NORMAL,
3121            text='Charge flipping')
3122        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
3123            text='Clear map')
3124        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
3125            text='MC/SA')
3126        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
3127            text='Multi MC/SA')            #currently not useful
3128        self.PostfillDataMenu()
3129       
3130        # Phase / Data tab
3131        self.DataMenu = wx.MenuBar()
3132        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
3133        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3134        self.DataEdit = wx.Menu(title='')
3135        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
3136        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
3137            help='Select new powder histograms to be used for this phase')
3138        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
3139            help='Select new single crystal histograms to be used for this phase')
3140        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Remove histograms',
3141            help='Remove histograms from use for this phase')
3142        self.PostfillDataMenu()
3143           
3144        # Phase / Atoms tab
3145        self.AtomsMenu = wx.MenuBar()
3146        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
3147        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3148        self.AtomEdit = wx.Menu(title='')
3149        self.AtomCompute = wx.Menu(title='')
3150        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
3151        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
3152        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
3153            help='Appended as an H atom')
3154        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
3155            help='Appended as an H atom')
3156        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
3157            help='Select atom row to insert before; inserted as an H atom')
3158        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
3159            help='Select atom row to insert before; inserted as an H atom')
3160        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
3161            help='Select single atom to move')
3162        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
3163            help='Select atoms to delete first')
3164        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
3165            help='Select atoms to refine first')
3166        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
3167            help='Select atoms to modify first')
3168        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
3169            help='Select atoms to transform first')
3170        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
3171            help='Reload atom drawing list')
3172        submenu = wx.Menu()
3173        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
3174            help='Reimport atoms from file; sequence must match')
3175        # setup a cascade menu for the formats that have been defined
3176        self.ReImportMenuId = {}  # points to readers for each menu entry
3177        for reader in self.G2frame.ImportPhaseReaderlist:
3178            item = submenu.Append(
3179                wx.ID_ANY,help=reader.longFormatName,
3180                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
3181            self.ReImportMenuId[item.GetId()] = reader
3182        item = submenu.Append(
3183            wx.ID_ANY,
3184            help='Reimport coordinates, try to determine format from file',
3185            kind=wx.ITEM_NORMAL,
3186            text='guess format from file')
3187        self.ReImportMenuId[item.GetId()] = None # try all readers
3188
3189        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Show Distances && Angles',
3190            help='Compute distances & angles for selected atoms')
3191        self.AtomCompute.Append(id=wxID_ATOMSPDISAGL, kind=wx.ITEM_NORMAL,text='Save Distances && Angles',
3192            help='Compute distances & angles for selected atoms')
3193        self.AtomCompute.ISOcalc = self.AtomCompute.Append(
3194            id=wxID_ISODISP, kind=wx.ITEM_NORMAL,
3195            text='Compute ISODISTORT mode values',
3196            help='Compute values of ISODISTORT modes from atom parameters')
3197        self.PostfillDataMenu()
3198                 
3199        # Phase / Draw Options tab
3200        self.DataDrawOptions = wx.MenuBar()
3201        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options')
3202        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
3203        self.PostfillDataMenu()
3204       
3205        # Phase / Draw Atoms tab
3206        self.DrawAtomsMenu = wx.MenuBar()
3207        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
3208        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3209        self.DrawAtomEdit = wx.Menu(title='')
3210        self.DrawAtomCompute = wx.Menu(title='')
3211        self.DrawAtomRestraint = wx.Menu(title='')
3212        self.DrawAtomRigidBody = wx.Menu(title='')
3213        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
3214        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
3215        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
3216        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
3217        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
3218            help='Select atoms first')
3219        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
3220            help='Select atoms first')
3221        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
3222            help='Select atoms first')
3223        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
3224            help='Resets all atom colors to defaults')
3225        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
3226            help='View point is 1st atom selected')
3227        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
3228            help='Add symmetry & cell equivalents to drawing set from selected atoms')
3229        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
3230            help='Transform selected atoms by symmetry & cell translations')
3231        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
3232            help='Fill coordination sphere for selected atoms')           
3233        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
3234            help='Fill unit cell with selected atoms')
3235        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
3236            help='Delete atoms from drawing set')
3237        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
3238            help='Compute distance of selected atoms from view point')   
3239        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
3240            help='Compute distance, angle or torsion for 2-4 selected atoms')   
3241        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
3242            help='Compute best plane for 4+ selected atoms')   
3243        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
3244            help='Add bond restraint for selected atoms (2)')
3245        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
3246            help='Add angle restraint for selected atoms (3: one end 1st)')
3247        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
3248            help='Add plane restraint for selected atoms (4+)')
3249        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
3250            help='Add chiral restraint for selected atoms (4: center atom 1st)')
3251        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
3252            help='Define rigid body with selected atoms')
3253        self.PostfillDataMenu()
3254
3255        # Phase / MCSA tab
3256        self.MCSAMenu = wx.MenuBar()
3257        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
3258        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3259        self.MCSAEdit = wx.Menu(title='')
3260        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
3261        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
3262            help='Add single atom to MC/SA model')
3263        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
3264            help='Add rigid body to MC/SA model' )
3265        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
3266            help='Clear all atoms & rigid bodies from MC/SA model' )
3267        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
3268            help='Move MC/SA solution to atom list' )
3269        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
3270            help='Clear table of MC/SA results' )
3271        self.PostfillDataMenu()
3272           
3273        # Phase / Texture tab
3274        self.TextureMenu = wx.MenuBar()
3275        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
3276        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3277        self.TextureEdit = wx.Menu(title='')
3278        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
3279        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture', 
3280            help='Refine the texture coefficients from sequential Pawley results')
3281        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
3282            help='Clear the texture coefficients' )
3283        self.PostfillDataMenu()
3284           
3285        # Phase / Pawley tab
3286        self.PawleyMenu = wx.MenuBar()
3287        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
3288        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3289        self.PawleyEdit = wx.Menu(title='')
3290        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
3291        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
3292            help='Initialize Pawley reflection list')
3293        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
3294            help='Estimate initial Pawley intensities')
3295        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
3296            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
3297        self.PostfillDataMenu()
3298           
3299        # Phase / Map peaks tab
3300        self.MapPeaksMenu = wx.MenuBar()
3301        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
3302        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3303        self.MapPeaksEdit = wx.Menu(title='')
3304        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
3305        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
3306            help='Move selected peaks to atom list')
3307        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
3308            help='View point is 1st peak selected')
3309        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
3310            help='Compute distance of selected peaks from view point')   
3311        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
3312            help='Hide or show bonds between peak positions')   
3313        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
3314            help='Calculate distance or angle for selection')
3315        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
3316            help='Find equivalent peaks')
3317        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
3318            help='Select unique set')
3319        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
3320            help='Delete selected peaks')
3321        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
3322            help='Clear the map peak list')
3323        self.PostfillDataMenu()
3324
3325        # Phase / Rigid bodies tab
3326        self.RigidBodiesMenu = wx.MenuBar()
3327        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
3328        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3329        self.RigidBodiesEdit = wx.Menu(title='')
3330        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
3331        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
3332            help='Select & position rigid body in structure of existing atoms')
3333        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
3334            help='Auto find of residue RBs in macromolecule')
3335        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
3336            help='Copy rigid body location & TLS parameters')
3337        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
3338            help='Global setting of residue thermal motion models')
3339        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
3340            help='Global setting of residue RB refinement flags')
3341        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
3342            help='Remove all rigid body assignment for atoms')
3343        self.PostfillDataMenu()
3344    # end of GSAS-II menu definitions
3345       
3346    def _init_ctrls(self, parent,name=None,size=None,pos=None):
3347        wx.Frame.__init__(
3348            self,parent=parent,
3349            #style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
3350            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX,
3351            size=size,pos=pos,title='GSAS-II data display')
3352        self._init_menus()
3353        if name:
3354            self.SetLabel(name)
3355        self.Show()
3356       
3357    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
3358        self.G2frame = frame
3359        self._init_ctrls(parent,name,size,pos)
3360        self.data = data
3361        clientSize = wx.ClientDisplayRect()
3362        Size = self.GetSize()
3363        xPos = clientSize[2]-Size[0]
3364        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
3365        self.AtomGrid = []
3366        self.selectedRow = 0
3367       
3368    def setSizePosLeft(self,Width):
3369        clientSize = wx.ClientDisplayRect()
3370        Width[1] = min(Width[1],clientSize[2]-300)
3371        Width[0] = max(Width[0],300)
3372        self.SetSize(Width)
3373#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
3374       
3375    def Clear(self):
3376        self.ClearBackground()
3377        self.DestroyChildren()
3378                   
3379################################################################################
3380#####  GSNotebook
3381################################################################################           
3382       
3383class GSNoteBook(wx.aui.AuiNotebook):
3384    '''Notebook used in various locations; implemented with wx.aui extension
3385    '''
3386    def __init__(self, parent, name='',size = None):
3387        wx.aui.AuiNotebook.__init__(self, parent, -1,
3388                                    style=wx.aui.AUI_NB_TOP |
3389                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
3390        if size: self.SetSize(size)
3391                                                     
3392    def Clear(self):       
3393        GSNoteBook.DeleteAllPages(self)
3394       
3395    def FindPage(self,name):
3396        numPage = self.GetPageCount()
3397        for page in range(numPage):
3398            if self.GetPageText(page) == name:
3399                return page
3400
3401    def ChangeSelection(self,page):
3402        # in wx.Notebook ChangeSelection is like SetSelection, but it
3403        # does not invoke the event related to pressing the tab button
3404        # I don't see a way to do that in aui.
3405        oldPage = self.GetSelection()
3406        self.SetSelection(page)
3407        return oldPage
3408
3409    # def __getattribute__(self,name):
3410    #     '''This method provides a way to print out a message every time
3411    #     that a method in a class is called -- to see what all the calls
3412    #     might be, or where they might be coming from.
3413    #     Cute trick for debugging!
3414    #     '''
3415    #     attr = object.__getattribute__(self, name)
3416    #     if hasattr(attr, '__call__'):
3417    #         def newfunc(*args, **kwargs):
3418    #             print('GSauiNoteBook calling %s' %attr.__name__)
3419    #             result = attr(*args, **kwargs)
3420    #             return result
3421    #         return newfunc
3422    #     else:
3423    #         return attr
3424           
3425################################################################################
3426#####  GSGrid
3427################################################################################           
3428       
3429class GSGrid(wg.Grid):
3430    '''Basic wx.Grid implementation
3431    '''
3432    def __init__(self, parent, name=''):
3433        wg.Grid.__init__(self,parent,-1,name=name)                   
3434        #self.SetSize(parent.GetClientSize())
3435        # above removed to speed drawing of initial grid
3436        # does not appear to be needed
3437           
3438    def Clear(self):
3439        wg.Grid.ClearGrid(self)
3440       
3441    def SetCellReadOnly(self,r,c,readonly=True):
3442        self.SetReadOnly(r,c,isReadOnly=readonly)
3443       
3444    def SetCellStyle(self,r,c,color="white",readonly=True):
3445        self.SetCellBackgroundColour(r,c,color)
3446        self.SetReadOnly(r,c,isReadOnly=readonly)
3447       
3448    def GetSelection(self):
3449        #this is to satisfy structure drawing stuff in G2plt when focus changes
3450        return None
3451
3452    def InstallGridToolTip(self, rowcolhintcallback,
3453                           colLblCallback=None,rowLblCallback=None):
3454        '''code to display a tooltip for each item on a grid
3455        from http://wiki.wxpython.org/wxGrid%20ToolTips (buggy!), expanded to
3456        column and row labels using hints from
3457        https://groups.google.com/forum/#!topic/wxPython-users/bm8OARRVDCs
3458
3459        :param function rowcolhintcallback: a routine that returns a text
3460          string depending on the selected row and column, to be used in
3461          explaining grid entries.
3462        :param function colLblCallback: a routine that returns a text
3463          string depending on the selected column, to be used in
3464          explaining grid columns (if None, the default), column labels
3465          do not get a tooltip.
3466        :param function rowLblCallback: a routine that returns a text
3467          string depending on the selected row, to be used in
3468          explaining grid rows (if None, the default), row labels
3469          do not get a tooltip.
3470        '''
3471        prev_rowcol = [None,None,None]
3472        def OnMouseMotion(event):
3473            # event.GetRow() and event.GetCol() would be nice to have here,
3474            # but as this is a mouse event, not a grid event, they are not
3475            # available and we need to compute them by hand.
3476            x, y = self.CalcUnscrolledPosition(event.GetPosition())
3477            row = self.YToRow(y)
3478            col = self.XToCol(x)
3479            hinttext = ''
3480            win = event.GetEventObject()
3481            if [row,col,win] == prev_rowcol: # no change from last position
3482                event.Skip()
3483                return
3484            if win == self.GetGridWindow() and row >= 0 and col >= 0:
3485                hinttext = rowcolhintcallback(row, col)
3486            elif win == self.GetGridColLabelWindow() and col >= 0:
3487                if colLblCallback: hinttext = colLblCallback(col)
3488            elif win == self.GetGridRowLabelWindow() and row >= 0:
3489                if rowLblCallback: hinttext = rowLblCallback(row)
3490            else: # this should be the upper left corner, which is empty
3491                event.Skip()
3492                return
3493            if hinttext is None: hinttext = ''
3494            win.SetToolTipString(hinttext)
3495            prev_rowcol[:] = [row,col,win]
3496            event.Skip()
3497
3498        wx.EVT_MOTION(self.GetGridWindow(), OnMouseMotion)
3499        if colLblCallback: wx.EVT_MOTION(self.GetGridColLabelWindow(), OnMouseMotion)
3500        if rowLblCallback: wx.EVT_MOTION(self.GetGridRowLabelWindow(), OnMouseMotion)
3501                                                   
3502################################################################################
3503#####  Table
3504################################################################################           
3505       
3506class Table(wg.PyGridTableBase):
3507    '''Basic data table for use with GSgrid
3508    '''
3509    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
3510        wg.PyGridTableBase.__init__(self)
3511        self.colLabels = colLabels
3512        self.rowLabels = rowLabels
3513        self.dataTypes = types
3514        self.data = data
3515       
3516    def AppendRows(self, numRows=1):
3517        self.data.append([])
3518        return True
3519       
3520    def CanGetValueAs(self, row, col, typeName):
3521        if self.dataTypes:
3522            colType = self.dataTypes[col].split(':')[0]
3523            if typeName == colType:
3524                return True
3525            else:
3526                return False
3527        else:
3528            return False
3529
3530    def CanSetValueAs(self, row, col, typeName):
3531        return self.CanGetValueAs(row, col, typeName)
3532
3533    def DeleteRow(self,pos):
3534        data = self.GetData()
3535        self.SetData([])
3536        new = []
3537        for irow,row in enumerate(data):
3538            if irow <> pos:
3539                new.append(row)
3540        self.SetData(new)
3541       
3542    def GetColLabelValue(self, col):
3543        if self.colLabels:
3544            return self.colLabels[col]
3545           
3546    def GetData(self):
3547        data = []
3548        for row in range(self.GetNumberRows()):
3549            data.append(self.GetRowValues(row))
3550        return data
3551       
3552    def GetNumberCols(self):
3553        try:
3554            return len(self.colLabels)
3555        except TypeError:
3556            return None
3557       
3558    def GetNumberRows(self):
3559        return len(self.data)
3560       
3561    def GetRowLabelValue(self, row):
3562        if self.rowLabels:
3563            return self.rowLabels[row]
3564       
3565    def GetColValues(self, col):
3566        data = []
3567        for row in range(self.GetNumberRows()):
3568            data.append(self.GetValue(row, col))
3569        return data
3570       
3571    def GetRowValues(self, row):
3572        data = []
3573        for col in range(self.GetNumberCols()):
3574            data.append(self.GetValue(row, col))
3575        return data
3576       
3577    def GetTypeName(self, row, col):
3578        try:
3579            return self.dataTypes[col]
3580        except TypeError:
3581            return None
3582
3583    def GetValue(self, row, col):
3584        try:
3585            return self.data[row][col]
3586        except IndexError:
3587            return None
3588           
3589    def InsertRows(self, pos, rows):
3590        for row in range(rows):
3591            self.data.insert(pos,[])
3592            pos += 1
3593       
3594    def IsEmptyCell(self,row,col):
3595        try:
3596            return not self.data[row][col]
3597        except IndexError:
3598            return True
3599       
3600    def OnKeyPress(self, event):
3601        dellist = self.GetSelectedRows()
3602        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
3603            grid = self.GetView()
3604            for i in dellist: grid.DeleteRow(i)
3605               
3606    def SetColLabelValue(self, col, label):
3607        numcols = self.GetNumberCols()
3608        if col > numcols-1:
3609            self.colLabels.append(label)
3610        else:
3611            self.colLabels[col]=label
3612       
3613    def SetData(self,data):
3614        for row in range(len(data)):
3615            self.SetRowValues(row,data[row])
3616               
3617    def SetRowLabelValue(self, row, label):
3618        self.rowLabels[row]=label
3619           
3620    def SetRowValues(self,row,data):
3621        self.data[row] = data
3622           
3623    def SetValue(self, row, col, value):
3624        def innerSetValue(row, col, value):
3625            try:
3626                self.data[row][col] = value
3627            except TypeError:
3628                return
3629            except IndexError:
3630                print row,col,value
3631                # add a new row
3632                if row > self.GetNumberRows():
3633                    self.data.append([''] * self.GetNumberCols())
3634                elif col > self.GetNumberCols():
3635                    for row in range(self.GetNumberRows):
3636                        self.data[row].append('')
3637                print self.data
3638                self.data[row][col] = value
3639        innerSetValue(row, col, value)
3640       
3641################################################################################
3642#### Help
3643################################################################################
3644
3645def ShowHelp(helpType,frame):
3646    '''Called to bring up a web page for documentation.'''
3647    global htmlFirstUse
3648    # look up a definition for help info from dict
3649    helplink = helpLocDict.get(helpType)
3650    if helplink is None:
3651        # no defined link to use, create a default based on key
3652        helplink = 'gsasII.html#'+helpType.replace(' ','_')
3653    helplink = os.path.join(path2GSAS2,'help',helplink)
3654    if helpMode == 'internal':
3655        try:
3656            htmlPanel.LoadFile(helplink)
3657            htmlFrame.Raise()
3658        except:
3659            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
3660            htmlFrame.Show(True)
3661            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
3662            htmlPanel = MyHtmlPanel(htmlFrame,-1)
3663            htmlPanel.LoadFile(helplink)
3664    else:
3665        pfx = "file://"
3666        if sys.platform.lower().startswith('win'):
3667            pfx = ''
3668        if htmlFirstUse:
3669            webbrowser.open_new(pfx+helplink)
3670            htmlFirstUse = False
3671        else:
3672            webbrowser.open(pfx+helplink, new=0, autoraise=True)
3673
3674################################################################################
3675#####  Notebook
3676################################################################################           
3677       
3678def UpdateNotebook(G2frame,data):
3679    '''Called when the data tree notebook entry is selected. Allows for
3680    editing of the text in that tree entry
3681    '''
3682    def OnNoteBook(event):
3683        data = G2frame.dataDisplay.GetValue().split('\n')
3684        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
3685        if 'nt' not in os.name:
3686            G2frame.dataDisplay.AppendText('\n')
3687                   
3688    if G2frame.dataDisplay:
3689        G2frame.dataDisplay.Destroy()
3690    G2frame.dataFrame.SetLabel('Notebook')
3691    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3692        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
3693    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
3694    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
3695    for line in data:
3696        G2frame.dataDisplay.AppendText(line+"\n")
3697    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
3698    G2frame.dataFrame.setSizePosLeft([400,250])
3699           
3700################################################################################
3701#####  Controls
3702################################################################################           
3703       
3704def UpdateControls(G2frame,data):
3705    '''Edit overall GSAS-II controls in main Controls data tree entry
3706    '''
3707    #patch
3708    if 'deriv type' not in data:
3709        data = {}
3710        data['deriv type'] = 'analytic Hessian'
3711        data['min dM/M'] = 0.0001
3712        data['shift factor'] = 1.
3713        data['max cyc'] = 3       
3714        data['F**2'] = True
3715        data['minF/sig'] = 0
3716    if 'shift factor' not in data:
3717        data['shift factor'] = 1.
3718    if 'max cyc' not in data:
3719        data['max cyc'] = 3
3720    if 'F**2' not in data:
3721        data['F**2'] = True
3722        data['minF/sig'] = 0
3723    if 'Author' not in data:
3724        data['Author'] = 'no name'
3725    if 'FreePrm1' not in data:
3726        data['FreePrm1'] = 'Sample humidity (%)'
3727    if 'FreePrm2' not in data:
3728        data['FreePrm2'] = 'Sample voltage (V)'
3729    if 'FreePrm3' not in data:
3730        data['FreePrm3'] = 'Applied load (MN)'
3731    if 'Copy2Next' not in data:
3732        data['Copy2Next'] = False
3733    if 'Reverse Seq' not in data:
3734        data['Reverse Seq'] = False   
3735     
3736   
3737    #end patch
3738
3739    def SeqSizer():
3740       
3741        def OnSelectData(event):
3742            choices = GetPatternTreeDataNames(G2frame,['PWDR',])
3743            sel = []
3744            if 'Seq Data' in data:
3745                for item in data['Seq Data']:
3746                    sel.append(choices.index(item))
3747                sel = [choices.index(item) for item in data['Seq Data']]
3748            dlg = G2MultiChoiceDialog(G2frame.dataFrame, 'Sequential refinement',
3749                                      'Select dataset to include',
3750                                      choices)
3751            dlg.SetSelections(sel)
3752            names = []
3753            if dlg.ShowModal() == wx.ID_OK:
3754                for sel in dlg.GetSelections():
3755                    names.append(choices[sel])
3756                data['Seq Data'] = names               
3757                G2frame.EnableSeqRefineMenu()
3758            dlg.Destroy()
3759            wx.CallAfter(UpdateControls,G2frame,data)
3760           
3761        def OnReverse(event):
3762            data['Reverse Seq'] = reverseSel.GetValue()
3763           
3764        def OnCopySel(event):
3765            data['Copy2Next'] = copySel.GetValue() 
3766                   
3767        seqSizer = wx.BoxSizer(wx.VERTICAL)
3768        dataSizer = wx.BoxSizer(wx.HORIZONTAL)
3769        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement: '),0,WACV)
3770        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3771        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3772        dataSizer.Add(selSeqData,0,WACV)
3773        SeqData = data.get('Seq Data',[])
3774        if not SeqData:
3775            lbl = ' (no powder data selected)'
3776        else:
3777            lbl = ' ('+str(len(SeqData))+' dataset(s) selected)'
3778
3779        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=lbl),0,WACV)
3780        seqSizer.Add(dataSizer,0)
3781        if SeqData:
3782            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3783            reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3784            reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3785            reverseSel.SetValue(data['Reverse Seq'])
3786            selSizer.Add(reverseSel,0,WACV)
3787            copySel =  wx.CheckBox(G2frame.dataDisplay,-1,label=' Copy results to next histogram?')
3788            copySel.Bind(wx.EVT_CHECKBOX,OnCopySel)
3789            copySel.SetValue(data['Copy2Next'])
3790            selSizer.Add(copySel,0,WACV)
3791            seqSizer.Add(selSizer,0)
3792        return seqSizer
3793       
3794    def LSSizer():       
3795       
3796        def OnDerivType(event):
3797            data['deriv type'] = derivSel.GetValue()
3798            derivSel.SetValue(data['deriv type'])
3799            wx.CallAfter(UpdateControls,G2frame,data)
3800           
3801        def OnConvergence(event):
3802            try:
3803                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3804            except ValueError:
3805                value = 0.0001
3806            data['min dM/M'] = value
3807            Cnvrg.SetValue('%.2g'%(value))
3808           
3809        def OnMaxCycles(event):
3810            data['max cyc'] = int(maxCyc.GetValue())
3811            maxCyc.SetValue(str(data['max cyc']))
3812                       
3813        def OnFactor(event):
3814            try:
3815                value = min(max(float(Factr.GetValue()),0.00001),100.)
3816            except ValueError:
3817                value = 1.0
3818            data['shift factor'] = value
3819            Factr.SetValue('%.5f'%(value))
3820           
3821        def OnFsqRef(event):
3822            data['F**2'] = fsqRef.GetValue()
3823       
3824        def OnMinSig(event):
3825            try:
3826                value = min(max(float(minSig.GetValue()),0.),5.)
3827            except ValueError:
3828                value = 1.0
3829            data['minF/sig'] = value
3830            minSig.SetValue('%.2f'%(value))
3831
3832        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3833        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,WACV)
3834        Choice=['analytic Jacobian','numeric','analytic Hessian']
3835        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3836            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3837        derivSel.SetValue(data['deriv type'])
3838        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3839           
3840        LSSizer.Add(derivSel,0,WACV)
3841        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,WACV)
3842        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3843        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3844        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3845        LSSizer.Add(Cnvrg,0,WACV)
3846        if 'Hessian' in data['deriv type']:
3847            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,WACV)
3848            Choice = ['0','1','2','3','5','10','15','20']
3849            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3850                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3851            maxCyc.SetValue(str(data['max cyc']))
3852            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3853            LSSizer.Add(maxCyc,0,WACV)
3854        else:
3855            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,WACV)
3856            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3857            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3858            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3859            LSSizer.Add(Factr,0,WACV)
3860        if G2frame.Sngl:
3861            LSSizer.Add((1,0),)
3862            LSSizer.Add((1,0),)
3863            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3864            fsqRef.SetValue(data['F**2'])
3865            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3866            LSSizer.Add(fsqRef,0,WACV)
3867            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,WACV)
3868            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3869            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3870            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3871            LSSizer.Add(minSig,0,WACV)
3872        return LSSizer
3873       
3874    def AuthSizer():
3875
3876        def OnAuthor(event):
3877            data['Author'] = auth.GetValue()
3878
3879        Author = data['Author']
3880        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3881        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,WACV)
3882        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3883        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3884        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3885        authSizer.Add(auth,0,WACV)
3886        return authSizer
3887       
3888       
3889    if G2frame.dataDisplay:
3890        G2frame.dataDisplay.Destroy()
3891    if not G2frame.dataFrame.GetStatusBar():
3892        Status = G2frame.dataFrame.CreateStatusBar()
3893        Status.SetStatusText('')
3894    G2frame.dataFrame.SetLabel('Controls')
3895    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3896    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3897    mainSizer = wx.BoxSizer(wx.VERTICAL)
3898    mainSizer.Add((5,5),0)
3899    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,WACV)   
3900    mainSizer.Add(LSSizer())
3901    mainSizer.Add((5,5),0)
3902    mainSizer.Add(SeqSizer())
3903    mainSizer.Add((5,5),0)
3904    mainSizer.Add(AuthSizer())
3905    mainSizer.Add((5,5),0)
3906       
3907    mainSizer.Layout()   
3908    G2frame.dataDisplay.SetSizer(mainSizer)
3909    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3910    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3911     
3912################################################################################
3913#####  Comments
3914################################################################################           
3915       
3916def UpdateComments(G2frame,data):                   
3917
3918    if G2frame.dataDisplay:
3919        G2frame.dataDisplay.Destroy()
3920    G2frame.dataFrame.SetLabel('Comments')
3921    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3922        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3923    for line in data:
3924        G2frame.dataDisplay.AppendText(line+'\n')
3925    G2frame.dataFrame.setSizePosLeft([400,250])
3926           
3927################################################################################
3928#####  Display of Sequential Results
3929################################################################################           
3930       
3931def UpdateSeqResults(G2frame,data,prevSize=None):
3932    """
3933    Called when the Sequential Results data tree entry is selected
3934    to show results from a sequential refinement.
3935   
3936    :param wx.Frame G2frame: main GSAS-II data tree windows
3937
3938    :param dict data: a dictionary containing the following items: 
3939
3940            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3941            * 'varyList' - list of variables - identical over all refinements in sequence
3942              note that this is the original list of variables, prior to processing
3943              constraints.
3944            * 'variableLabels' -- a dict of labels to be applied to each parameter
3945              (this is created as an empty dict if not present in data).
3946            * keyed by histName - dictionaries for all data sets processed, which contains:
3947
3948              * 'variables'- result[0] from leastsq call
3949              * 'varyList' - list of variables passed to leastsq call (not same as above)
3950              * 'sig' - esds for variables
3951              * 'covMatrix' - covariance matrix from individual refinement
3952              * 'title' - histogram name; same as dict item name
3953              * 'newAtomDict' - new atom parameters after shifts applied
3954              * 'newCellDict' - refined cell parameters after shifts to A0-A5 from Dij terms applied'
3955    """
3956
3957    def GetSampleParms():
3958        '''Make a dictionary of the sample parameters are not the same over the
3959        refinement series.
3960        '''
3961        if 'IMG' in histNames[0]:
3962            sampleParmDict = {'Sample load':[],}
3963        else:
3964            sampleParmDict = {'Temperature':[],'Pressure':[],
3965                              'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],}
3966        Controls = G2frame.PatternTree.GetItemPyData(
3967            GetPatternTreeItemId(G2frame,G2frame.root, 'Controls'))
3968        sampleParm = {}
3969        for name in histNames:
3970            if 'IMG' in name:
3971                for item in sampleParmDict:
3972                    sampleParmDict[item].append(data[name]['parmDict'][item])
3973            else:
3974                Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3975                sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3976                for item in sampleParmDict:
3977                    sampleParmDict[item].append(sampleData[item])
3978        for item in sampleParmDict:
3979            frstValue = sampleParmDict[item][0]
3980            if np.any(np.array(sampleParmDict[item])-frstValue):
3981                if item.startswith('FreePrm'):
3982                    sampleParm[Controls[item]] = sampleParmDict[item]
3983                else:
3984                    sampleParm[item] = sampleParmDict[item]
3985        return sampleParm
3986
3987    def GetColumnInfo(col):
3988        '''returns column label, lists of values and errors (or None) for each column in the table
3989        for plotting. The column label is reformatted from Unicode to MatPlotLib encoding
3990        '''
3991        colName = G2frame.SeqTable.GetColLabelValue(col)
3992        plotName = variableLabels.get(colName,colName)
3993        plotName = plotSpCharFix(plotName)
3994        return plotName,colList[col],colSigs[col]
3995           
3996    def PlotSelect(event):
3997        'Plots a row (covariance) or column on double-click'
3998        cols = G2frame.dataDisplay.GetSelectedCols()
3999        rows = G2frame.dataDisplay.GetSelectedRows()
4000        if cols:
4001            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4002        elif rows:
4003            name = histNames[rows[0]]       #only does 1st one selected
4004            G2plt.PlotCovariance(G2frame,data[name])
4005        else:
4006            G2frame.ErrorDialog(
4007                'Select row or columns',
4008                'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.'
4009                )
4010           
4011    def OnPlotSelSeq(event):
4012        'plot the selected columns or row from menu command'
4013        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4014        rows = G2frame.dataDisplay.GetSelectedRows()
4015        if cols:
4016            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4017        elif rows:
4018            name = histNames[rows[0]]       #only does 1st one selected
4019            G2plt.PlotCovariance(G2frame,data[name])
4020        else:
4021            G2frame.ErrorDialog(
4022                'Select columns',
4023                'No columns or rows selected in table. Click on row or column labels to select fields for plotting.'
4024                )
4025               
4026    def OnRenameSelSeq(event):
4027        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4028        colNames = [G2frame.SeqTable.GetColLabelValue(c) for c in cols]
4029        newNames = colNames[:]
4030        for i,name in enumerate(colNames):
4031            if name in variableLabels:
4032                newNames[i] = variableLabels[name]
4033        if not cols:
4034            G2frame.ErrorDialog('Select columns',
4035                'No columns selected in table. Click on column labels to select fields for rename.')
4036            return
4037        dlg = MultiStringDialog(G2frame.dataDisplay,'Set column names',colNames,newNames)
4038        if dlg.Show():
4039            newNames = dlg.GetValues()           
4040            variableLabels.update(dict(zip(colNames,newNames)))
4041        data['variableLabels'] = variableLabels
4042        dlg.Destroy()
4043        UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4044        G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4045           
4046    def OnSaveSelSeqCSV(event):
4047        'export the selected columns to a .csv file from menu command'
4048        OnSaveSelSeq(event,csv=True)
4049       
4050    def OnSaveSelSeq(event,csv=False):
4051        'export the selected columns to a .txt file from menu command'
4052        def WriteCSV():
4053            def WriteList(headerItems):
4054                line = ''
4055                for lbl in headerItems:
4056                    if line: line += ','
4057                    line += '"'+lbl+'"'
4058                return line
4059            head = ['name']
4060            for col in cols:
4061                item = G2frame.SeqTable.GetColLabelValue(col)
4062                if col in havesig:
4063                    head += [item,'esd-'+item]
4064                else:
4065                    head += [item]
4066            SeqFile.write(WriteList(head)+'\n')
4067            for row,name in enumerate(saveNames):
4068                line = '"'+saveNames[row]+'"'
4069                for col in cols:
4070                    if col in havesig:
4071                        line += ','+str(saveData[col][row])+','+str(saveSigs[col][row])
4072                    else:
4073                        line += ','+str(saveData[col][row])
4074                SeqFile.write(line+'\n')
4075        def WriteSeq():
4076            lenName = len(saveNames[0])
4077            line = %s  '%('name'.center(lenName))
4078            for col in cols:
4079                item = G2frame.SeqTable.GetColLabelValue(col)
4080                if col in havesig:
4081                    line += ' %12s %12s '%(item.center(12),'esd'.center(12))
4082                else:
4083                    line += ' %12s '%(item.center(12))
4084            SeqFile.write(line+'\n')
4085            for row,name in enumerate(saveNames):
4086                line = " '%s' "%(saveNames[row])
4087                for col in cols:
4088                    if col in havesig:
4089                        line += ' %12.6f %12.6f '%(saveData[col][row],saveSigs[col][row])
4090                    else:
4091                        line += ' %12.6f '%saveData[col][row]
4092                SeqFile.write(line+'\n')
4093
4094        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4095        nrows = G2frame.SeqTable.GetNumberRows()
4096        if not cols:
4097            G2frame.ErrorDialog('Select columns',
4098                             'No columns selected in table. Click on column labels to select fields for output.')
4099            return
4100        saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(nrows)]
4101        saveData = {}
4102        saveSigs = {}
4103        havesig = []
4104        for col in cols:
4105            name,vals,sigs = GetColumnInfo(col)
4106            saveData[col] = vals
4107            if sigs:
4108                havesig.append(col)
4109                saveSigs[col] = sigs
4110        if csv:
4111            wild = 'CSV output file (*.csv)|*.csv'
4112        else:
4113            wild = 'Text output file (*.txt)|*.txt'
4114        dlg = wx.FileDialog(
4115            G2frame,
4116            'Choose text output file for your selection', '.', '', 
4117            wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
4118        try:
4119            if dlg.ShowModal() == wx.ID_OK:
4120                SeqTextFile = dlg.GetPath()
4121                SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) 
4122                SeqFile = open(SeqTextFile,'w')
4123                if csv:
4124                    WriteCSV()
4125                else:
4126                    WriteSeq()
4127                SeqFile.close()
4128        finally:
4129            dlg.Destroy()
4130               
4131    def striphist(var,insChar=''):
4132        'strip a histogram number from a var name'
4133        sv = var.split(':')
4134        if len(sv) <= 1: return var
4135        sv[1] = insChar
4136        return ':'.join(sv)
4137       
4138    def plotSpCharFix(lbl):
4139        'Change selected unicode characters to their matplotlib equivalent'
4140        for u,p in [
4141            (u'\u03B1',r'$\alpha$'),
4142            (u'\u03B2',r'$\beta$'),
4143            (u'\u03B3',r'$\gamma$'),
4144            (u'\u0394\u03C7',r'$\Delta\chi$'),
4145            ]:
4146            lbl = lbl.replace(u,p)
4147        return lbl
4148   
4149    def SelectXaxis():
4150        'returns a selected column number (or None) as the X-axis selection'
4151        ncols = G2frame.SeqTable.GetNumberCols()
4152        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
4153        dlg = G2SingleChoiceDialog(
4154            G2frame.dataDisplay,
4155            'Select x-axis parameter for plot or Cancel for sequence number',
4156            'Select X-axis',
4157            colNames)
4158        try:
4159            if dlg.ShowModal() == wx.ID_OK:
4160                col = dlg.GetSelection()
4161            else:
4162                col = None
4163        finally:
4164            dlg.Destroy()
4165        return col
4166   
4167    def EnablePseudoVarMenus():
4168        'Enables or disables the PseudoVar menu items that require existing defs'
4169        if Controls['SeqPseudoVars']:
4170            val = True
4171        else:
4172            val = False
4173        G2frame.dataFrame.SequentialPvars.Enable(wxDELSEQVAR,val)
4174        G2frame.dataFrame.SequentialPvars.Enable(wxEDITSEQVAR,val)
4175
4176    def DelPseudoVar(event):
4177        'Ask the user to select a pseudo var expression to delete'
4178        choices = Controls['SeqPseudoVars'].keys()
4179        selected = ItemSelector(
4180            choices,G2frame.dataFrame,
4181            multiple=True,
4182            title='Select expressions to remove',
4183            header='Delete expression')
4184        if selected is None: return
4185        for item in selected:
4186            del Controls['SeqPseudoVars'][choices[item]]
4187        if selected:
4188            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4189
4190    def EditPseudoVar(event):
4191        'Edit an existing pseudo var expression'
4192        choices = Controls['SeqPseudoVars'].keys()
4193        if len(choices) == 1:
4194            selected = 0
4195        else:
4196            selected = ItemSelector(
4197                choices,G2frame.dataFrame,
4198                multiple=False,
4199                title='Select an expression to edit',
4200                header='Edit expression')
4201        if selected is not None:
4202            dlg = G2exG.ExpressionDialog(
4203                G2frame.dataDisplay,PSvarDict,
4204                Controls['SeqPseudoVars'][choices[selected]],
4205                header="Edit the PseudoVar expression",
4206                VarLabel="PseudoVar #"+str(selected+1),
4207                fit=False)
4208            newobj = dlg.Show(True)
4209            if newobj:
4210                calcobj = G2obj.ExpressionCalcObj(newobj)
4211                del Controls['SeqPseudoVars'][choices[selected]]
4212                Controls['SeqPseudoVars'][calcobj.eObj.expression] = newobj
4213                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4214       
4215    def AddNewPseudoVar(event):
4216        'Create a new pseudo var expression'
4217        dlg = G2exG.ExpressionDialog(
4218            G2frame.dataDisplay,PSvarDict,
4219            header='Enter an expression for a PseudoVar here',
4220            VarLabel = "New PseudoVar",
4221            fit=False)
4222        obj = dlg.Show(True)
4223        dlg.Destroy()
4224        if obj:
4225            calcobj = G2obj.ExpressionCalcObj(obj)
4226            Controls['SeqPseudoVars'][calcobj.eObj.expression] = obj
4227            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4228
4229    # PATCH: this routine can go away eventually
4230    def CreatePSvarDict(seqnum,name):
4231        '''Create a parameter dict (parmDict) for everything that might be used
4232        in a PseudoVar.
4233        Also creates a list of revised labels (modVaryList) for the covariance matrix to
4234        match the items in the parameter dict and a matching list of ESDs (ESDvaryList).
4235       
4236        :param int seqnum: the sequence number of the histogram in the sequential
4237          refinement
4238        :param str name: the name of the histogram in the data tree
4239
4240        :returns: parmDict,modVaryList,ESDvaryList
4241        '''
4242        parmDict = {}
4243        modVaryList = []
4244        for i,(key,val) in enumerate(zip(data[name]['varyList'],data[name]['variables'])):
4245            skey = striphist(key)
4246            if skey in data[name].get('newAtomDict',{}):
4247                # replace coordinate shifts with equivalents from lookup table
4248                repkey,repval = data[name]['newAtomDict'][skey]
4249                parmDict[repkey] = repval
4250                modVaryList.append(repkey)
4251            elif skey in data[name].get('newCellDict',{}):
4252                # replace recip. cell term shifts with equivalents from lookup table       
4253                repkey,repval = data[name]['newCellDict'][skey]
4254                parmDict[repkey] = repval
4255                modVaryList.append(repkey)
4256            else:
4257                parmDict[key] = val
4258                modVaryList.append(key)
4259        # create a cell parm dict, override initial settings with values in parmDict
4260        for phase in Phases:
4261            phasedict = Phases[phase]
4262            pId = phasedict['pId']
4263            cell = Rcelldict.copy()
4264            cell.update(
4265                {lbl:parmDict[lbl] for lbl in RcellLbls[pId] if lbl in parmDict}
4266                )
4267            pfx = str(pId)+'::' # prefix for A values from phase
4268            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cell,zeroDict[pId])
4269            parmDict.update({pfx+cellUlbl[i]:val for i,val in
4270                             enumerate(G2lat.A2cell(A))
4271                             if i in uniqCellIndx[pId]
4272                             })
4273            parmDict[pfx+"vol"] = G2lat.calc_V(A)
4274        # now add misc terms to dict
4275        parmDict['Rwp'] = data[name]['Rvals']['Rwp']
4276        parmDict[u'\u0394\u03C7\u00B2 (%)'] = 100.*data[name]['Rvals'].get('DelChi2',-1)
4277        for key in sampleParms:
4278            parmDict[key] = sampleParms[key][seqnum]
4279        return parmDict,modVaryList,data[name]['sig']
4280
4281    def UpdateParmDict(parmDict):
4282        '''generate the atom positions and the direct & reciprocal cell values,
4283        because they might be needed to evaluate the pseudovar
4284        '''
4285        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
4286                         ['A'+str(i) for i in range(6)])
4287                     )
4288        delList = []
4289        phaselist = []
4290        for item in parmDict: 
4291            if ':' not in item: continue
4292            key = item.split(':')
4293            if len(key) < 3: continue
4294            # remove the dA[xyz] terms, they would only bring confusion
4295            if key[2].startswith('dA'):
4296                delList.append(item)
4297            # compute and update the corrected reciprocal cell terms using the Dij values
4298            elif key[2] in Ddict:
4299                if key[0] not in phaselist: phaselist.append(key[0])
4300                akey = key[0]+'::'+Ddict[key[2]]
4301                parmDict[akey] += parmDict[item]
4302                delList.append(item)
4303        for item in delList:
4304            del parmDict[item]               
4305        for i in phaselist:
4306            pId = int(i)
4307            # apply cell symmetry
4308            A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
4309            # convert to direct cell & add the unique terms to the dictionary
4310            for i,val in enumerate(G2lat.A2cell(A)):
4311                if i in uniqCellIndx[pId]:
4312                    lbl = str(pId)+'::'+cellUlbl[i]
4313                    parmDict[lbl] = val
4314            lbl = str(pId)+'::'+'vol'
4315            parmDict[lbl] = G2lat.calc_V(A)
4316        return parmDict
4317
4318    def EvalPSvarDeriv(calcobj,parmDict,var,ESD):
4319        '''Evaluate an expression derivative with respect to a
4320        GSAS-II variable name.
4321
4322        Note this likely could be faster if the loop over calcobjs were done
4323        inside after the Dict was created.
4324        '''
4325        step = ESD/10
4326        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
4327                         ['A'+str(i) for i in range(6)])
4328                     )
4329        results = []
4330        phaselist = []
4331        for incr in step,-step:
4332            VparmDict = parmDict.copy()           
4333            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
4334            # values are not zeroed: fix that!
4335            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
4336            VparmDict[var] += incr
4337            G2mv.Dict2Map(VparmDict,[]) # apply constraints
4338            # generate the atom positions and the direct & reciprocal cell values now, because they might
4339            # needed to evaluate the pseudovar
4340            for item in VparmDict: 
4341                if ':' not in item: continue
4342                key = item.split(':')
4343                if len(key) < 3: continue
4344                # apply any new shifts to atom positions
4345                if key[2].startswith('dA'):
4346                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
4347                    VparmDict[item] = 0.0
4348                # compute and update the corrected reciprocal cell terms using the Dij values
4349                if key[2] in Ddict:
4350                    if key[0] not in phaselist: phaselist.append(key[0])
4351                    akey = key[0]+'::'+Ddict[key[2]]
4352                    VparmDict[akey] += VparmDict[item]
4353            for i in phaselist:
4354                pId = int(i)
4355                # apply cell symmetry
4356                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
4357                # convert to direct cell & add the unique terms to the dictionary
4358                for i,val in enumerate(G2lat.A2cell(A)):
4359                    if i in uniqCellIndx[pId]:
4360                        lbl = str(pId)+'::'+cellUlbl[i]
4361                        VparmDict[lbl] = val
4362                lbl = str(pId)+'::'+'vol'
4363                VparmDict[lbl] = G2lat.calc_V(A)
4364            # dict should be fully updated, use it & calculate
4365            calcobj.SetupCalc(VparmDict)
4366            results.append(calcobj.EvalExpression())
4367        return (results[0] - results[1]) / (2.*step)
4368       
4369    def EnableParFitEqMenus():
4370        'Enables or disables the Parametric Fit menu items that require existing defs'
4371        if Controls['SeqParFitEqList']:
4372            val = True
4373        else:
4374            val = False
4375        G2frame.dataFrame.SequentialPfit.Enable(wxDELPARFIT,val)
4376        G2frame.dataFrame.SequentialPfit.Enable(wxEDITPARFIT,val)
4377        G2frame.dataFrame.SequentialPfit.Enable(wxDOPARFIT,val)
4378
4379    def ParEqEval(Values,calcObjList,varyList):
4380        '''Evaluate the parametric expression(s)
4381        :param list Values: a list of values for each variable parameter
4382        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
4383          expression objects to evaluate
4384        :param list varyList: a list of variable names for each value in Values
4385        '''
4386        result = []
4387        for calcobj in calcObjList:
4388            calcobj.UpdateVars(varyList,Values)
4389            result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
4390        return result
4391
4392    def DoParEqFit(event,eqObj=None):
4393        'Parametric fit minimizer'
4394        varyValueDict = {} # dict of variables and their initial values
4395        calcObjList = [] # expression objects, ready to go for each data point
4396        if eqObj is not None:
4397            eqObjList = [eqObj,]
4398        else:
4399            eqObjList = Controls['SeqParFitEqList']
4400        UseFlags = G2frame.SeqTable.GetColValues(0)         
4401        for obj in eqObjList:
4402            expr = obj.expression
4403            # assemble refined vars for this equation
4404            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
4405            # lookup dependent var position
4406            depVar = obj.GetDepVar()
4407            if depVar in colLabels:
4408                indx = colLabels.index(depVar)
4409            else:
4410                raise Exception('Dependent variable '+depVar+' not found')
4411            # assemble a list of the independent variables
4412            indepVars = obj.GetIndependentVars()
4413            # loop over each datapoint
4414            for j,row in enumerate(zip(*colList)):
4415                if not UseFlags[j]: continue
4416                # assemble equations to fit
4417                calcobj = G2obj.ExpressionCalcObj(obj)
4418                # prepare a dict of needed independent vars for this expression
4419                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4420                calcobj.SetupCalc(indepVarDict)               
4421                # values and sigs for current value of dependent var
4422                calcobj.depVal = row[indx]
4423                calcobj.depSig = colSigs[indx][j]
4424                calcObjList.append(calcobj)
4425        # varied parameters
4426        varyList = varyValueDict.keys()
4427        values = varyValues = [varyValueDict[key] for key in varyList]
4428        if not varyList:
4429            print 'no variables to refine!'
4430            return
4431        try:
4432            result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
4433                                args=(calcObjList,varyList)
4434                                )
4435            values = result[0]
4436            covar = result[1]
4437            if covar is None:
4438                raise Exception
4439            esdDict = {}
4440            for i,avar in enumerate(varyList):
4441                esdDict[avar] = np.sqrt(covar[i,i])
4442        except:
4443            print('====> Fit failed')
4444            return
4445        print('==== Fit Results ====')
4446        for obj in eqObjList:
4447            obj.UpdateVariedVars(varyList,values)
4448            ind = '      '
4449            print('  '+obj.GetDepVar()+' = '+obj.expression)
4450            for var in obj.assgnVars:
4451                print(ind+var+' = '+obj.assgnVars[var])
4452            for var in obj.freeVars:
4453                avar = "::"+obj.freeVars[var][0]
4454                val = obj.freeVars[var][1]
4455                if obj.freeVars[var][2]:
4456                    print(ind+var+' = '+avar + " = " + G2mth.ValEsd(val,esdDict[avar]))
4457                else:
4458                    print(ind+var+' = '+avar + " =" + G2mth.ValEsd(val,0))
4459        # create a plot for each parametric variable
4460        for fitnum,obj in enumerate(eqObjList):
4461            calcobj = G2obj.ExpressionCalcObj(obj)
4462            # lookup dependent var position
4463            indx = colLabels.index(obj.GetDepVar())
4464            # assemble a list of the independent variables
4465            indepVars = obj.GetIndependentVars()           
4466            # loop over each datapoint
4467            fitvals = []
4468            for j,row in enumerate(zip(*colList)):
4469                calcobj.SetupCalc(
4470                    {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4471                    )
4472                fitvals.append(calcobj.EvalExpression())
4473            G2plt.PlotSelectedSequence(
4474                G2frame,[indx],GetColumnInfo,SelectXaxis,
4475                fitnum,fitvals)
4476
4477    def SingleParEqFit(eqObj):
4478        DoParEqFit(None,eqObj)
4479
4480    def DelParFitEq(event):
4481        'Ask the user to select function to delete'
4482        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4483        selected = ItemSelector(
4484            txtlst,G2frame.dataFrame,
4485            multiple=True,
4486            title='Select a parametric equation(s) to remove',
4487            header='Delete equation')
4488        if selected is None: return
4489        Controls['SeqParFitEqList'] = [obj for i,obj in enumerate(Controls['SeqParFitEqList']) if i not in selected]
4490        EnableParFitEqMenus()
4491        if Controls['SeqParFitEqList']: DoParEqFit(event)
4492       
4493    def EditParFitEq(event):
4494        'Edit an existing parametric equation'
4495        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4496        if len(txtlst) == 1:
4497            selected = 0
4498        else:
4499            selected = ItemSelector(
4500                txtlst,G2frame.dataFrame,
4501                multiple=False,
4502                title='Select a parametric equation to edit',
4503                header='Edit equation')
4504        if selected is not None:
4505            dlg = G2exG.ExpressionDialog(
4506                G2frame.dataDisplay,indepVarDict,
4507                Controls['SeqParFitEqList'][selected],
4508                depVarDict=depVarDict,
4509                header="Edit the formula for this minimization function",
4510                ExtraButton=['Fit',SingleParEqFit])
4511            newobj = dlg.Show(True)
4512            if newobj:
4513                calcobj = G2obj.ExpressionCalcObj(newobj)
4514                Controls['SeqParFitEqList'][selected] = newobj
4515                EnableParFitEqMenus()
4516            if Controls['SeqParFitEqList']: DoParEqFit(event)
4517
4518    def AddNewParFitEq(event):
4519        'Create a new parametric equation to be fit to sequential results'
4520
4521        # compile the variable names used in previous freevars to avoid accidental name collisions
4522        usedvarlist = []
4523        for obj in Controls['SeqParFitEqList']:
4524            for var in obj.freeVars:
4525                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
4526
4527        dlg = G2exG.ExpressionDialog(
4528            G2frame.dataDisplay,indepVarDict,
4529            depVarDict=depVarDict,
4530            header='Define an equation to minimize in the parametric fit',
4531            ExtraButton=['Fit',SingleParEqFit],
4532            usedVars=usedvarlist)
4533        obj = dlg.Show(True)
4534        dlg.Destroy()
4535        if obj:
4536            Controls['SeqParFitEqList'].append(obj)
4537            EnableParFitEqMenus()
4538            if Controls['SeqParFitEqList']: DoParEqFit(event)
4539               
4540    def CopyParFitEq(event):
4541        'Copy an existing parametric equation to be fit to sequential results'
4542        # compile the variable names used in previous freevars to avoid accidental name collisions
4543        usedvarlist = []
4544        for obj in Controls['SeqParFitEqList']:
4545            for var in obj.freeVars:
4546                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
4547        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4548        if len(txtlst) == 1:
4549            selected = 0
4550        else:
4551            selected = ItemSelector(
4552                txtlst,G2frame.dataFrame,
4553                multiple=False,
4554                title='Select a parametric equation to copy',
4555                header='Copy equation')
4556        if selected is not None:
4557            newEqn = copy.deepcopy(Controls['SeqParFitEqList'][selected])
4558            for var in newEqn.freeVars:
4559                newEqn.freeVars[var][0] = G2obj.MakeUniqueLabel(newEqn.freeVars[var][0],usedvarlist)
4560            dlg = G2exG.ExpressionDialog(
4561                G2frame.dataDisplay,indepVarDict,
4562                newEqn,
4563                depVarDict=depVarDict,
4564                header="Edit the formula for this minimization function",
4565                ExtraButton=['Fit',SingleParEqFit])
4566            newobj = dlg.Show(True)
4567            if newobj:
4568                calcobj = G2obj.ExpressionCalcObj(newobj)
4569                Controls['SeqParFitEqList'].append(newobj)
4570                EnableParFitEqMenus()
4571            if Controls['SeqParFitEqList']: DoParEqFit(event)
4572                                           
4573    def GridSetToolTip(row,col):
4574        '''Routine to show standard uncertainties for each element in table
4575        as a tooltip
4576        '''
4577        if colSigs[col]:
4578            return u'\u03c3 = '+str(colSigs[col][row])
4579        return ''
4580       
4581    def GridColLblToolTip(col):
4582        '''Define a tooltip for a column. This will be the user-entered value
4583        (from data['variableLabels']) or the default name
4584        '''
4585        if col < 0 or col > len(colLabels):
4586            print 'Illegal column #',col
4587            return
4588        var = colLabels[col]
4589        return variableLabels.get(var,G2obj.fmtVarDescr(var))
4590       
4591    def SetLabelString(event):
4592        '''Define or edit the label for a column in the table, to be used
4593        as a tooltip and for plotting
4594        '''
4595        col = event.GetCol()
4596        if col < 0 or col > len(colLabels):
4597            return
4598        var = colLabels[col]
4599        lbl = variableLabels.get(var,G2obj.fmtVarDescr(var))
4600        dlg = SingleStringDialog(G2frame.dataFrame,'Set variable label',
4601                                 'Set a name for variable '+var,lbl,size=(400,-1))
4602        if dlg.Show():
4603            variableLabels[var] = dlg.GetValue()
4604        dlg.Destroy()
4605       
4606    #def GridRowLblToolTip(row): return 'Row ='+str(row)
4607   
4608    # lookup table for unique cell parameters by symmetry
4609    cellGUIlist = [
4610        [['m3','m3m'],(0,)],
4611        [['3R','3mR'],(0,3)],
4612        [['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],(0,2)],
4613        [['mmm'],(0,1,2)],
4614        [['2/m'+'a'],(0,1,2,3)],
4615        [['2/m'+'b'],(0,1,2,4)],
4616        [['2/m'+'c'],(0,1,2,5)],
4617        [['-1'],(0,1,2,3,4,5)],
4618        ]
4619    # cell labels
4620    cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') # unicode a,b,c,alpha,beta,gamma
4621
4622    #======================================================================
4623    # start processing sequential results here (UpdateSeqResults)
4624    #======================================================================
4625    if not data:
4626        print 'No sequential refinement results'
4627        return
4628    variableLabels = data.get('variableLabels',{})
4629    data['variableLabels'] = variableLabels
4630    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
4631    Controls = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Controls'))
4632    # create a place to store Pseudo Vars & Parametric Fit functions, if needed
4633    if 'SeqPseudoVars' not in Controls: Controls['SeqPseudoVars'] = {}
4634    if 'SeqParFitEqList' not in Controls: Controls['SeqParFitEqList'] = []
4635    histNames = data['histNames']
4636    if G2frame.dataDisplay:
4637        G2frame.dataDisplay.Destroy()
4638    if not G2frame.dataFrame.GetStatusBar():
4639        Status = G2frame.dataFrame.CreateStatusBar()
4640        Status.SetStatusText("Select column to export; Double click on column to plot data; on row for Covariance")
4641    sampleParms = GetSampleParms()
4642
4643    # make dict of varied atom coords keyed by absolute position
4644    newAtomDict = data[histNames[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
4645    # Possible error: the next might need to be data[histNames[0]]['varyList']
4646    # error will arise if there constraints on coordinates?
4647    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in data['varyList']}
4648   
4649    # make dict of varied cell parameters equivalents
4650    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
4651    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
4652    # N.B. These Dij vars are missing a histogram #
4653    newCellDict = data[histNames[0]].get('newCellDict',{})
4654    for item in newCellDict:
4655        if item in data['varyList']:
4656            ESDlookup[newCellDict[item][0]] = item
4657            Dlookup[item] = newCellDict[item][0]
4658    # add coordinate equivalents to lookup table
4659    for parm in atomLookup:
4660        Dlookup[atomLookup[parm]] = parm
4661        ESDlookup[parm] = atomLookup[parm]
4662
4663    # get unit cell & symmetry for all phases & initial stuff for later use
4664    RecpCellTerms = {}
4665    SGdata = {}
4666    uniqCellIndx = {}
4667    initialCell = {}
4668    RcellLbls = {}
4669    zeroDict = {}
4670    Rcelldict = {}
4671    for phase in Phases:
4672        phasedict = Phases[phase]
4673        pId = phasedict['pId']
4674        pfx = str(pId)+'::' # prefix for A values from phase
4675        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
4676        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
4677        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
4678        SGdata[pId] = phasedict['General']['SGData']
4679        Rcelldict.update({lbl:val for lbl,val in zip(RcellLbls[pId],RecpCellTerms[pId])})
4680        laue = SGdata[pId]['SGLaue']
4681        if laue == '2/m':
4682            laue += SGdata[pId]['SGUniq']
4683        for symlist,celllist in cellGUIlist:
4684            if laue in symlist:
4685                uniqCellIndx[pId] = celllist
4686                break
4687        else: # should not happen
4688            uniqCellIndx[pId] = range(6)
4689        for i in uniqCellIndx[pId]:
4690            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
4691
4692    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
4693    G2frame.dataFrame.SetLabel('Sequential refinement results')
4694    if not G2frame.dataFrame.GetStatusBar():
4695        Status = G2frame.dataFrame.CreateStatusBar()
4696        Status.SetStatusText('')
4697    G2frame.dataFrame.Bind(wx.EVT_MENU, OnRenameSelSeq, id=wxID_RENAMESEQSEL)
4698    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
4699    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=wxID_SAVESEQSELCSV)
4700    G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=wxID_PLOTSEQSEL)
4701    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=wxADDSEQVAR)
4702    G2frame.dataFrame.Bind(wx.EVT_MENU, DelPseudoVar, id=wxDELSEQVAR)
4703    G2frame.dataFrame.Bind(wx.EVT_MENU, EditPseudoVar, id=wxEDITSEQVAR)
4704    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewParFitEq, id=wxADDPARFIT)
4705    G2frame.dataFrame.Bind(wx.EVT_MENU, CopyParFitEq, id=wxCOPYPARFIT)
4706    G2frame.dataFrame.Bind(wx.EVT_MENU, DelParFitEq, id=wxDELPARFIT)
4707    G2frame.dataFrame.Bind(wx.EVT_MENU, EditParFitEq, id=wxEDITPARFIT)
4708    G2frame.dataFrame.Bind(wx.EVT_MENU, DoParEqFit, id=wxDOPARFIT)
4709    EnablePseudoVarMenus()
4710    EnableParFitEqMenus()
4711
4712    #-----------------------------------------------------------------------------------
4713    # build up the data table by columns -----------------------------------------------
4714    nRows = len(histNames)
4715    colList = [nRows*[True]]
4716    colSigs = [None]
4717    colLabels = ['Use']
4718    Types = [wg.GRID_VALUE_BOOL]
4719    # start with Rwp values
4720    if 'IMG ' not in histNames[0][:4]:
4721        colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
4722        colSigs += [None]
4723        colLabels += ['Rwp']
4724        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
4725    # add % change in Chi^2 in last cycle
4726    if histNames[0][:4] not in ['SASD','IMG ']:
4727        colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
4728        colSigs += [None]
4729        colLabels += [u'\u0394\u03C7\u00B2 (%)']
4730        Types += [wg.GRID_VALUE_FLOAT,]
4731    deltaChiCol = len(colLabels)-1
4732    # add changing sample parameters to table
4733    for key in sampleParms:
4734        colList += [sampleParms[key]]
4735        colSigs += [None]
4736        colLabels += [key]
4737        Types += [wg.GRID_VALUE_FLOAT,]
4738    # add unique cell parameters
4739    for pId in sorted(RecpCellTerms):
4740        pfx = str(pId)+'::' # prefix for A values from phase
4741        cells = []
4742        cellESDs = []
4743        colLabels += [pfx+cellUlbl[i] for i in uniqCellIndx[pId]]
4744        colLabels += [pfx+'Vol']
4745        Types += (1+len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT,]
4746        for name in histNames:
4747            covData = {
4748                'varyList': [Dlookup.get(striphist(v),v) for v in data[name]['varyList']],
4749                'covMatrix': data[name]['covMatrix']
4750                }
4751            A = RecpCellTerms[pId][:] # make copy of starting A values
4752            # update with refined values
4753            for i in range(6):
4754                var = str(pId)+'::A'+str(i)
4755                if var in ESDlookup:
4756                    val = data[name]['newCellDict'][ESDlookup[var]][1] # get refined value
4757                    A[i] = val # override with updated value
4758            # apply symmetry
4759            Albls = [pfx+'A'+str(i) for i in range(6)]
4760            cellDict = dict(zip(Albls,A))
4761            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
4762            # convert to direct cell & add only unique values to table
4763            c = G2lat.A2cell(A)
4764            vol = G2lat.calc_V(A)
4765            cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
4766            cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
4767            cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
4768        colList += zip(*cells)
4769        colSigs += zip(*cellESDs)
4770    # add the variables that were refined; change from rows to columns
4771    colList += zip(*[data[name]['variables'] for name in histNames])
4772    colLabels += data[histNames[0]]['varyList']
4773    Types += len(data[histNames[0]]['varyList'])*[wg.GRID_VALUE_FLOAT]
4774    colSigs += zip(*[data[name]['sig'] for name in histNames])
4775
4776    # tabulate constrained variables, removing histogram numbers if needed
4777    # from parameter label
4778    depValDict = {}
4779    depSigDict = {}
4780    for name in histNames:
4781        for var in data[name].get('depParmDict',{}):
4782            val,sig = data[name]['depParmDict'][var]
4783            svar = striphist(var,'*')
4784            if svar not in depValDict:
4785               depValDict[svar] = [val]
4786               depSigDict[svar] = [sig]
4787            else:
4788               depValDict[svar].append(val)
4789               depSigDict[svar].append(sig)
4790    # add the dependent constrained variables to the table
4791    for var in sorted(depValDict):
4792        if len(depValDict[var]) != len(histNames): continue
4793        colLabels.append(var)
4794        Types += [wg.GRID_VALUE_FLOAT,]
4795        colSigs += [depSigDict[var]]
4796        colList += [depValDict[var]]
4797
4798    # add atom parameters to table
4799    colLabels += atomLookup.keys()
4800    Types += len(atomLookup)*[wg.GRID_VALUE_FLOAT]
4801    for parm in sorted(atomLookup):
4802        colList += [[data[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
4803        if atomLookup[parm] in data[histNames[0]]['varyList']:
4804            col = data[histNames[0]]['varyList'].index(atomLookup[parm])
4805            colSigs += [[data[name]['sig'][col] for name in histNames]]
4806        else:
4807            colSigs += [None] # should not happen
4808    # evaluate Pseudovars, their ESDs and add them to grid
4809    for expr in Controls['SeqPseudoVars']:
4810        obj = Controls['S