source: trunk/GSASIIgrid.py @ 1445

Last change on this file since 1445 was 1445, checked in by vondreele, 7 years ago

add error bars to calibration plot
disable Autosearch if peaks already picked
implement cell refine for TOF data - isn't quite right yet
implement tool tip on calibration plot to show position for each peak

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 242.2 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2014-07-30 15:29:52 +0000 (Wed, 30 Jul 2014) $
5# $Author: vondreele $
6# $Revision: 1445 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1445 2014-07-30 15:29:52Z 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: 1445 $")
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        self.AutoSearch.Enable(True)
2937       
2938        # PDR / Index Peak List
2939        self.IndPeaksMenu = wx.MenuBar()
2940        self.PrefillDataMenu(self.IndPeaksMenu,helpType='Index Peak List')
2941        self.IndPeaksEdit = wx.Menu(title='')
2942        self.IndPeaksMenu.Append(menu=self.IndPeaksEdit,title='Operations')
2943        self.IndPeaksEdit.Append(help='Load/Reload index peaks from peak list',id=wxID_INDXRELOAD, 
2944            kind=wx.ITEM_NORMAL,text='Load/Reload')
2945        self.PostfillDataMenu()
2946       
2947        # PDR / Unit Cells List
2948        self.IndexMenu = wx.MenuBar()
2949        self.PrefillDataMenu(self.IndexMenu,helpType='Unit Cells List')
2950        self.IndexEdit = wx.Menu(title='')
2951        self.IndexMenu.Append(menu=self.IndexEdit, title='Cell Index/Refine')
2952        self.IndexPeaks = self.IndexEdit.Append(help='', id=wxID_INDEXPEAKS, kind=wx.ITEM_NORMAL,
2953            text='Index Cell')
2954        self.CopyCell = self.IndexEdit.Append( id=wxID_COPYCELL, kind=wx.ITEM_NORMAL,text='Copy Cell', 
2955            help='Copy selected unit cell from indexing to cell refinement fields')
2956        self.RefineCell = self.IndexEdit.Append( id=wxID_REFINECELL, kind=wx.ITEM_NORMAL, 
2957            text='Refine Cell',help='Refine unit cell parameters from indexed peaks')
2958        self.MakeNewPhase = self.IndexEdit.Append( id=wxID_MAKENEWPHASE, kind=wx.ITEM_NORMAL,
2959            text='Make new phase',help='Make new phase from selected unit cell')
2960        self.PostfillDataMenu()
2961        self.IndexPeaks.Enable(False)
2962        self.CopyCell.Enable(False)
2963        self.RefineCell.Enable(False)
2964        self.MakeNewPhase.Enable(False)
2965       
2966        # PDR / Reflection Lists
2967        self.ReflMenu = wx.MenuBar()
2968        self.PrefillDataMenu(self.ReflMenu,helpType='Reflection List')
2969        self.ReflEdit = wx.Menu(title='')
2970        self.ReflMenu.Append(menu=self.ReflEdit, title='Reflection List')
2971        self.SelectPhase = self.ReflEdit.Append(help='Select phase for reflection list',id=wxID_SELECTPHASE, 
2972            kind=wx.ITEM_NORMAL,text='Select phase')
2973        self.ReflEdit.Append(id=wxID_PWDHKLPLOT,kind=wx.ITEM_NORMAL,text='Plot HKLs',
2974            help='Plot HKLs from powder pattern')
2975        self.ReflEdit.Append(id=wxID_PWD3DHKLPLOT,kind=wx.ITEM_NORMAL,text='Plot 3D HKLs',
2976            help='Plot HKLs from powder pattern in 3D')
2977        self.PostfillDataMenu()
2978       
2979        #SASD & REFL/ Substance editor
2980        self.SubstanceMenu = wx.MenuBar()
2981        self.PrefillDataMenu(self.SubstanceMenu,helpType='Substances')
2982        self.SubstanceEdit = wx.Menu(title='')
2983        self.SubstanceMenu.Append(menu=self.SubstanceEdit, title='Edit')
2984        self.SubstanceEdit.Append(id=wxID_LOADSUBSTANCE, kind=wx.ITEM_NORMAL,text='Load substance',
2985            help='Load substance from file')
2986        self.SubstanceEdit.Append(id=wxID_ADDSUBSTANCE, kind=wx.ITEM_NORMAL,text='Add substance',
2987            help='Add new substance to list')
2988        self.SubstanceEdit.Append(id=wxID_COPYSUBSTANCE, kind=wx.ITEM_NORMAL,text='Copy substances',
2989            help='Copy substances')
2990        self.SubstanceEdit.Append(id=wxID_DELETESUBSTANCE, kind=wx.ITEM_NORMAL,text='Delete substance',
2991            help='Delete substance from list')           
2992        self.SubstanceEdit.Append(id=wxID_ELEMENTADD, kind=wx.ITEM_NORMAL,text='Add elements',
2993            help='Add elements to substance')
2994        self.SubstanceEdit.Append(id=wxID_ELEMENTDELETE, kind=wx.ITEM_NORMAL,text='Delete elements',
2995            help='Delete elements from substance')
2996        self.PostfillDataMenu()
2997       
2998        # SASD/ Models
2999        self.ModelMenu = wx.MenuBar()
3000        self.PrefillDataMenu(self.ModelMenu,helpType='Models')
3001        self.ModelEdit = wx.Menu(title='')
3002        self.ModelMenu.Append(menu=self.ModelEdit, title='Models')
3003        self.ModelEdit.Append(id=wxID_MODELADD,kind=wx.ITEM_NORMAL,text='Add',
3004            help='Add new term to model')
3005        self.ModelEdit.Append(id=wxID_MODELFIT, kind=wx.ITEM_NORMAL,text='Fit',
3006            help='Fit model parameters to data')
3007        self.SasdUndo = self.ModelEdit.Append(id=wxID_MODELUNDO, kind=wx.ITEM_NORMAL,text='Undo',
3008            help='Undo model fit')
3009        self.SasdUndo.Enable(False)           
3010        self.ModelEdit.Append(id=wxID_MODELFITALL, kind=wx.ITEM_NORMAL,text='Sequential fit',
3011            help='Sequential fit of model parameters to all SASD data')
3012        self.ModelEdit.Append(id=wxID_MODELCOPY, kind=wx.ITEM_NORMAL,text='Copy',
3013            help='Copy model parameters to other histograms')
3014        self.ModelEdit.Append(id=wxID_MODELCOPYFLAGS, kind=wx.ITEM_NORMAL,text='Copy flags',
3015            help='Copy model refinement flags to other histograms')
3016        self.PostfillDataMenu()
3017       
3018        # IMG / Image Controls
3019        self.ImageMenu = wx.MenuBar()
3020        self.PrefillDataMenu(self.ImageMenu,helpType='Image Controls')
3021        self.ImageEdit = wx.Menu(title='')
3022        self.ImageMenu.Append(menu=self.ImageEdit, title='Operations')
3023        self.ImageEdit.Append(help='Calibrate detector by fitting to calibrant lines', 
3024            id=wxID_IMCALIBRATE, kind=wx.ITEM_NORMAL,text='Calibrate')
3025        self.ImageEdit.Append(help='Recalibrate detector by fitting to calibrant lines', 
3026            id=wxID_IMRECALIBRATE, kind=wx.ITEM_NORMAL,text='Recalibrate')
3027        self.ImageEdit.Append(help='Clear calibration data points and rings',id=wxID_IMCLEARCALIB, 
3028            kind=wx.ITEM_NORMAL,text='Clear calibration')
3029        self.ImageEdit.Append(help='Integrate selected image',id=wxID_IMINTEGRATE, 
3030            kind=wx.ITEM_NORMAL,text='Integrate')
3031        self.ImageEdit.Append(help='Integrate all images selected from list',id=wxID_INTEGRATEALL,
3032            kind=wx.ITEM_NORMAL,text='Integrate all')
3033        self.ImageEdit.Append(help='Copy image controls to other images', 
3034            id=wxID_IMCOPYCONTROLS, kind=wx.ITEM_NORMAL,text='Copy Controls')
3035        self.ImageEdit.Append(help='Save image controls to file', 
3036            id=wxID_IMSAVECONTROLS, kind=wx.ITEM_NORMAL,text='Save Controls')
3037        self.ImageEdit.Append(help='Load image controls from file', 
3038            id=wxID_IMLOADCONTROLS, kind=wx.ITEM_NORMAL,text='Load Controls')
3039        self.PostfillDataMenu()
3040           
3041        # IMG / Masks
3042        self.MaskMenu = wx.MenuBar()
3043        self.PrefillDataMenu(self.MaskMenu,helpType='Image Masks')
3044        self.MaskEdit = wx.Menu(title='')
3045        self.MaskMenu.Append(menu=self.MaskEdit, title='Operations')
3046        submenu = wx.Menu()
3047        self.MaskEdit.AppendMenu(
3048            wx.ID_ANY,'Create new', submenu,
3049            help=''
3050            )
3051        self.MaskEdit.Append(help='Copy mask to other images', 
3052            id=wxID_MASKCOPY, kind=wx.ITEM_NORMAL,text='Copy mask')
3053        self.MaskEdit.Append(help='Save mask to file', 
3054            id=wxID_MASKSAVE, kind=wx.ITEM_NORMAL,text='Save mask')
3055        self.MaskEdit.Append(help='Load mask from file', 
3056            id=wxID_MASKLOAD, kind=wx.ITEM_NORMAL,text='Load mask')
3057        submenu.Append(help='Create an arc mask with mouse input', 
3058            id=wxID_NEWMASKARC, kind=wx.ITEM_NORMAL,text='Arc mask')
3059        submenu.Append(help='Create a frame mask with mouse input', 
3060            id=wxID_NEWMASKFRAME, kind=wx.ITEM_NORMAL,text='Frame mask')
3061        submenu.Append(help='Create a polygon mask with mouse input', 
3062            id=wxID_NEWMASKPOLY, kind=wx.ITEM_NORMAL,text='Polygon mask')
3063        submenu.Append(help='Create a ring mask with mouse input', 
3064            id=wxID_NEWMASKRING, kind=wx.ITEM_NORMAL,text='Ring mask')
3065        submenu.Append(help='Create a spot mask with mouse input', 
3066            id=wxID_NEWMASKSPOT, kind=wx.ITEM_NORMAL,text='Spot mask')
3067        self.PostfillDataMenu()
3068           
3069        # IMG / Stress/Strain
3070        self.StrStaMenu = wx.MenuBar()
3071        self.PrefillDataMenu(self.StrStaMenu,helpType='Stress/Strain')
3072        self.StrStaEdit = wx.Menu(title='')
3073        self.StrStaMenu.Append(menu=self.StrStaEdit, title='Operations')
3074        self.StrStaEdit.Append(help='Append d-zero for one ring', 
3075            id=wxID_APPENDDZERO, kind=wx.ITEM_NORMAL,text='Append d-zero')
3076        self.StrStaEdit.Append(help='Fit stress/strain data', 
3077            id=wxID_STRSTAFIT, kind=wx.ITEM_NORMAL,text='Fit stress/strain')
3078        self.StrStaEdit.Append(help='Update d-zero from ave d-zero',
3079            id=wxID_UPDATEDZERO, kind=wx.ITEM_NORMAL,text='Update d-zero')       
3080        self.StrStaEdit.Append(help='Fit stress/strain data for all images', 
3081            id=wxID_STRSTAALLFIT, kind=wx.ITEM_NORMAL,text='All image fit')
3082        self.StrStaEdit.Append(help='Copy stress/strain data to other images', 
3083            id=wxID_STRSTACOPY, kind=wx.ITEM_NORMAL,text='Copy stress/strain')
3084        self.StrStaEdit.Append(help='Save stress/strain data to file', 
3085            id=wxID_STRSTASAVE, kind=wx.ITEM_NORMAL,text='Save stress/strain')
3086        self.StrStaEdit.Append(help='Load stress/strain data from file', 
3087            id=wxID_STRSTALOAD, kind=wx.ITEM_NORMAL,text='Load stress/strain')
3088        self.PostfillDataMenu()
3089           
3090        # PDF / PDF Controls
3091        self.PDFMenu = wx.MenuBar()
3092        self.PrefillDataMenu(self.PDFMenu,helpType='PDF Controls')
3093        self.PDFEdit = wx.Menu(title='')
3094        self.PDFMenu.Append(menu=self.PDFEdit, title='PDF Controls')
3095        self.PDFEdit.Append(help='Add element to sample composition',id=wxID_PDFADDELEMENT, kind=wx.ITEM_NORMAL,
3096            text='Add element')
3097        self.PDFEdit.Append(help='Delete element from sample composition',id=wxID_PDFDELELEMENT, kind=wx.ITEM_NORMAL,
3098            text='Delete element')
3099        self.PDFEdit.Append(help='Copy PDF controls', id=wxID_PDFCOPYCONTROLS, kind=wx.ITEM_NORMAL,
3100            text='Copy controls')
3101        #        self.PDFEdit.Append(help='Load PDF controls from file',id=wxID_PDFLOADCONTROLS, kind=wx.ITEM_NORMAL,
3102        #            text='Load Controls')
3103        #        self.PDFEdit.Append(help='Save PDF controls to file', id=wxID_PDFSAVECONTROLS, kind=wx.ITEM_NORMAL,
3104        #            text='Save controls')
3105        self.PDFEdit.Append(help='Compute PDF', id=wxID_PDFCOMPUTE, kind=wx.ITEM_NORMAL,
3106            text='Compute PDF')
3107        self.PDFEdit.Append(help='Compute all PDFs', id=wxID_PDFCOMPUTEALL, kind=wx.ITEM_NORMAL,
3108            text='Compute all PDFs')
3109        self.PostfillDataMenu()
3110       
3111        # Phase / General tab
3112        self.DataGeneral = wx.MenuBar()
3113        self.PrefillDataMenu(self.DataGeneral,helpType='General', helpLbl='Phase/General')
3114        self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab')
3115        self.GeneralCalc = wx.Menu(title='')
3116        self.DataGeneral.Append(menu=self.GeneralCalc,title='Compute')
3117        self.GeneralCalc.Append(help='Compute Fourier map',id=wxID_FOURCALC, kind=wx.ITEM_NORMAL,
3118            text='Fourier map')
3119        self.GeneralCalc.Append(help='Search Fourier map',id=wxID_FOURSEARCH, kind=wx.ITEM_NORMAL,
3120            text='Search map')
3121        self.GeneralCalc.Append(help='Run charge flipping',id=wxID_CHARGEFLIP, kind=wx.ITEM_NORMAL,
3122            text='Charge flipping')
3123        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
3124            text='Clear map')
3125        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
3126            text='MC/SA')
3127        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
3128            text='Multi MC/SA')            #currently not useful
3129        self.PostfillDataMenu()
3130       
3131        # Phase / Data tab
3132        self.DataMenu = wx.MenuBar()
3133        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
3134        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3135        self.DataEdit = wx.Menu(title='')
3136        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
3137        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
3138            help='Select new powder histograms to be used for this phase')
3139        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
3140            help='Select new single crystal histograms to be used for this phase')
3141        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Remove histograms',
3142            help='Remove histograms from use for this phase')
3143        self.PostfillDataMenu()
3144           
3145        # Phase / Atoms tab
3146        self.AtomsMenu = wx.MenuBar()
3147        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
3148        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3149        self.AtomEdit = wx.Menu(title='')
3150        self.AtomCompute = wx.Menu(title='')
3151        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
3152        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
3153        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
3154            help='Appended as an H atom')
3155        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
3156            help='Appended as an H atom')
3157        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
3158            help='Select atom row to insert before; inserted as an H atom')
3159        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
3160            help='Select atom row to insert before; inserted as an H atom')
3161        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
3162            help='Select single atom to move')
3163        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
3164            help='Select atoms to delete first')
3165        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
3166            help='Select atoms to refine first')
3167        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
3168            help='Select atoms to modify first')
3169        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
3170            help='Select atoms to transform first')
3171        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
3172            help='Reload atom drawing list')
3173        submenu = wx.Menu()
3174        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
3175            help='Reimport atoms from file; sequence must match')
3176        # setup a cascade menu for the formats that have been defined
3177        self.ReImportMenuId = {}  # points to readers for each menu entry
3178        for reader in self.G2frame.ImportPhaseReaderlist:
3179            item = submenu.Append(
3180                wx.ID_ANY,help=reader.longFormatName,
3181                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
3182            self.ReImportMenuId[item.GetId()] = reader
3183        item = submenu.Append(
3184            wx.ID_ANY,
3185            help='Reimport coordinates, try to determine format from file',
3186            kind=wx.ITEM_NORMAL,
3187            text='guess format from file')
3188        self.ReImportMenuId[item.GetId()] = None # try all readers
3189
3190        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Show Distances && Angles',
3191            help='Compute distances & angles for selected atoms')
3192        self.AtomCompute.Append(id=wxID_ATOMSPDISAGL, kind=wx.ITEM_NORMAL,text='Save Distances && Angles',
3193            help='Compute distances & angles for selected atoms')
3194        self.AtomCompute.ISOcalc = self.AtomCompute.Append(
3195            id=wxID_ISODISP, kind=wx.ITEM_NORMAL,
3196            text='Compute ISODISTORT mode values',
3197            help='Compute values of ISODISTORT modes from atom parameters')
3198        self.PostfillDataMenu()
3199                 
3200        # Phase / Draw Options tab
3201        self.DataDrawOptions = wx.MenuBar()
3202        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options')
3203        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
3204        self.PostfillDataMenu()
3205       
3206        # Phase / Draw Atoms tab
3207        self.DrawAtomsMenu = wx.MenuBar()
3208        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
3209        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3210        self.DrawAtomEdit = wx.Menu(title='')
3211        self.DrawAtomCompute = wx.Menu(title='')
3212        self.DrawAtomRestraint = wx.Menu(title='')
3213        self.DrawAtomRigidBody = wx.Menu(title='')
3214        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
3215        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
3216        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
3217        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
3218        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
3219            help='Select atoms first')
3220        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
3221            help='Select atoms first')
3222        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
3223            help='Select atoms first')
3224        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
3225            help='Resets all atom colors to defaults')
3226        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
3227            help='View point is 1st atom selected')
3228        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
3229            help='Add symmetry & cell equivalents to drawing set from selected atoms')
3230        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
3231            help='Transform selected atoms by symmetry & cell translations')
3232        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
3233            help='Fill coordination sphere for selected atoms')           
3234        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
3235            help='Fill unit cell with selected atoms')
3236        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
3237            help='Delete atoms from drawing set')
3238        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
3239            help='Compute distance of selected atoms from view point')   
3240        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
3241            help='Compute distance, angle or torsion for 2-4 selected atoms')   
3242        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
3243            help='Compute best plane for 4+ selected atoms')   
3244        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
3245            help='Add bond restraint for selected atoms (2)')
3246        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
3247            help='Add angle restraint for selected atoms (3: one end 1st)')
3248        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
3249            help='Add plane restraint for selected atoms (4+)')
3250        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
3251            help='Add chiral restraint for selected atoms (4: center atom 1st)')
3252        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
3253            help='Define rigid body with selected atoms')
3254        self.PostfillDataMenu()
3255
3256        # Phase / MCSA tab
3257        self.MCSAMenu = wx.MenuBar()
3258        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
3259        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3260        self.MCSAEdit = wx.Menu(title='')
3261        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
3262        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
3263            help='Add single atom to MC/SA model')
3264        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
3265            help='Add rigid body to MC/SA model' )
3266        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
3267            help='Clear all atoms & rigid bodies from MC/SA model' )
3268        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
3269            help='Move MC/SA solution to atom list' )
3270        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
3271            help='Clear table of MC/SA results' )
3272        self.PostfillDataMenu()
3273           
3274        # Phase / Texture tab
3275        self.TextureMenu = wx.MenuBar()
3276        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
3277        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3278        self.TextureEdit = wx.Menu(title='')
3279        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
3280        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture', 
3281            help='Refine the texture coefficients from sequential Pawley results')
3282        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
3283            help='Clear the texture coefficients' )
3284        self.PostfillDataMenu()
3285           
3286        # Phase / Pawley tab
3287        self.PawleyMenu = wx.MenuBar()
3288        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
3289        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3290        self.PawleyEdit = wx.Menu(title='')
3291        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
3292        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
3293            help='Initialize Pawley reflection list')
3294        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
3295            help='Estimate initial Pawley intensities')
3296        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
3297            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
3298        self.PostfillDataMenu()
3299           
3300        # Phase / Map peaks tab
3301        self.MapPeaksMenu = wx.MenuBar()
3302        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
3303        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3304        self.MapPeaksEdit = wx.Menu(title='')
3305        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
3306        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
3307            help='Move selected peaks to atom list')
3308        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
3309            help='View point is 1st peak selected')
3310        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
3311            help='Compute distance of selected peaks from view point')   
3312        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
3313            help='Hide or show bonds between peak positions')   
3314        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
3315            help='Calculate distance or angle for selection')
3316        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
3317            help='Find equivalent peaks')
3318        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
3319            help='Select unique set')
3320        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
3321            help='Delete selected peaks')
3322        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
3323            help='Clear the map peak list')
3324        self.PostfillDataMenu()
3325
3326        # Phase / Rigid bodies tab
3327        self.RigidBodiesMenu = wx.MenuBar()
3328        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
3329        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
3330        self.RigidBodiesEdit = wx.Menu(title='')
3331        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
3332        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
3333            help='Select & position rigid body in structure of existing atoms')
3334        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
3335            help='Auto find of residue RBs in macromolecule')
3336        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
3337            help='Copy rigid body location & TLS parameters')
3338        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
3339            help='Global setting of residue thermal motion models')
3340        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
3341            help='Global setting of residue RB refinement flags')
3342        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
3343            help='Remove all rigid body assignment for atoms')
3344        self.PostfillDataMenu()
3345    # end of GSAS-II menu definitions
3346       
3347    def _init_ctrls(self, parent,name=None,size=None,pos=None):
3348        wx.Frame.__init__(
3349            self,parent=parent,
3350            #style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
3351            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX,
3352            size=size,pos=pos,title='GSAS-II data display')
3353        self._init_menus()
3354        if name:
3355            self.SetLabel(name)
3356        self.Show()
3357       
3358    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
3359        self.G2frame = frame
3360        self._init_ctrls(parent,name,size,pos)
3361        self.data = data
3362        clientSize = wx.ClientDisplayRect()
3363        Size = self.GetSize()
3364        xPos = clientSize[2]-Size[0]
3365        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
3366        self.AtomGrid = []
3367        self.selectedRow = 0
3368       
3369    def setSizePosLeft(self,Width):
3370        clientSize = wx.ClientDisplayRect()
3371        Width[1] = min(Width[1],clientSize[2]-300)
3372        Width[0] = max(Width[0],300)
3373        self.SetSize(Width)
3374#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
3375       
3376    def Clear(self):
3377        self.ClearBackground()
3378        self.DestroyChildren()
3379                   
3380################################################################################
3381#####  GSNotebook
3382################################################################################           
3383       
3384class GSNoteBook(wx.aui.AuiNotebook):
3385    '''Notebook used in various locations; implemented with wx.aui extension
3386    '''
3387    def __init__(self, parent, name='',size = None):
3388        wx.aui.AuiNotebook.__init__(self, parent, -1,
3389                                    style=wx.aui.AUI_NB_TOP |
3390                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
3391        if size: self.SetSize(size)
3392                                                     
3393    def Clear(self):       
3394        GSNoteBook.DeleteAllPages(self)
3395       
3396    def FindPage(self,name):
3397        numPage = self.GetPageCount()
3398        for page in range(numPage):
3399            if self.GetPageText(page) == name:
3400                return page
3401
3402    def ChangeSelection(self,page):
3403        # in wx.Notebook ChangeSelection is like SetSelection, but it
3404        # does not invoke the event related to pressing the tab button
3405        # I don't see a way to do that in aui.
3406        oldPage = self.GetSelection()
3407        self.SetSelection(page)
3408        return oldPage
3409
3410    # def __getattribute__(self,name):
3411    #     '''This method provides a way to print out a message every time
3412    #     that a method in a class is called -- to see what all the calls
3413    #     might be, or where they might be coming from.
3414    #     Cute trick for debugging!
3415    #     '''
3416    #     attr = object.__getattribute__(self, name)
3417    #     if hasattr(attr, '__call__'):
3418    #         def newfunc(*args, **kwargs):
3419    #             print('GSauiNoteBook calling %s' %attr.__name__)
3420    #             result = attr(*args, **kwargs)
3421    #             return result
3422    #         return newfunc
3423    #     else:
3424    #         return attr
3425           
3426################################################################################
3427#####  GSGrid
3428################################################################################           
3429       
3430class GSGrid(wg.Grid):
3431    '''Basic wx.Grid implementation
3432    '''
3433    def __init__(self, parent, name=''):
3434        wg.Grid.__init__(self,parent,-1,name=name)                   
3435        #self.SetSize(parent.GetClientSize())
3436        # above removed to speed drawing of initial grid
3437        # does not appear to be needed
3438           
3439    def Clear(self):
3440        wg.Grid.ClearGrid(self)
3441       
3442    def SetCellReadOnly(self,r,c,readonly=True):
3443        self.SetReadOnly(r,c,isReadOnly=readonly)
3444       
3445    def SetCellStyle(self,r,c,color="white",readonly=True):
3446        self.SetCellBackgroundColour(r,c,color)
3447        self.SetReadOnly(r,c,isReadOnly=readonly)
3448       
3449    def GetSelection(self):
3450        #this is to satisfy structure drawing stuff in G2plt when focus changes
3451        return None
3452
3453    def InstallGridToolTip(self, rowcolhintcallback,
3454                           colLblCallback=None,rowLblCallback=None):
3455        '''code to display a tooltip for each item on a grid
3456        from http://wiki.wxpython.org/wxGrid%20ToolTips (buggy!), expanded to
3457        column and row labels using hints from
3458        https://groups.google.com/forum/#!topic/wxPython-users/bm8OARRVDCs
3459
3460        :param function rowcolhintcallback: a routine that returns a text
3461          string depending on the selected row and column, to be used in
3462          explaining grid entries.
3463        :param function colLblCallback: a routine that returns a text
3464          string depending on the selected column, to be used in
3465          explaining grid columns (if None, the default), column labels
3466          do not get a tooltip.
3467        :param function rowLblCallback: a routine that returns a text
3468          string depending on the selected row, to be used in
3469          explaining grid rows (if None, the default), row labels
3470          do not get a tooltip.
3471        '''
3472        prev_rowcol = [None,None,None]
3473        def OnMouseMotion(event):
3474            # event.GetRow() and event.GetCol() would be nice to have here,
3475            # but as this is a mouse event, not a grid event, they are not
3476            # available and we need to compute them by hand.
3477            x, y = self.CalcUnscrolledPosition(event.GetPosition())
3478            row = self.YToRow(y)
3479            col = self.XToCol(x)
3480            hinttext = ''
3481            win = event.GetEventObject()
3482            if [row,col,win] == prev_rowcol: # no change from last position
3483                event.Skip()
3484                return
3485            if win == self.GetGridWindow() and row >= 0 and col >= 0:
3486                hinttext = rowcolhintcallback(row, col)
3487            elif win == self.GetGridColLabelWindow() and col >= 0:
3488                if colLblCallback: hinttext = colLblCallback(col)
3489            elif win == self.GetGridRowLabelWindow() and row >= 0:
3490                if rowLblCallback: hinttext = rowLblCallback(row)
3491            else: # this should be the upper left corner, which is empty
3492                event.Skip()
3493                return
3494            if hinttext is None: hinttext = ''
3495            win.SetToolTipString(hinttext)
3496            prev_rowcol[:] = [row,col,win]
3497            event.Skip()
3498
3499        wx.EVT_MOTION(self.GetGridWindow(), OnMouseMotion)
3500        if colLblCallback: wx.EVT_MOTION(self.GetGridColLabelWindow(), OnMouseMotion)
3501        if rowLblCallback: wx.EVT_MOTION(self.GetGridRowLabelWindow(), OnMouseMotion)
3502                                                   
3503################################################################################
3504#####  Table
3505################################################################################           
3506       
3507class Table(wg.PyGridTableBase):
3508    '''Basic data table for use with GSgrid
3509    '''
3510    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
3511        wg.PyGridTableBase.__init__(self)
3512        self.colLabels = colLabels
3513        self.rowLabels = rowLabels
3514        self.dataTypes = types
3515        self.data = data
3516       
3517    def AppendRows(self, numRows=1):
3518        self.data.append([])
3519        return True
3520       
3521    def CanGetValueAs(self, row, col, typeName):
3522        if self.dataTypes:
3523            colType = self.dataTypes[col].split(':')[0]
3524            if typeName == colType:
3525                return True
3526            else:
3527                return False
3528        else:
3529            return False
3530
3531    def CanSetValueAs(self, row, col, typeName):
3532        return self.CanGetValueAs(row, col, typeName)
3533
3534    def DeleteRow(self,pos):
3535        data = self.GetData()
3536        self.SetData([])
3537        new = []
3538        for irow,row in enumerate(data):
3539            if irow <> pos:
3540                new.append(row)
3541        self.SetData(new)
3542       
3543    def GetColLabelValue(self, col):
3544        if self.colLabels:
3545            return self.colLabels[col]
3546           
3547    def GetData(self):
3548        data = []
3549        for row in range(self.GetNumberRows()):
3550            data.append(self.GetRowValues(row))
3551        return data
3552       
3553    def GetNumberCols(self):
3554        try:
3555            return len(self.colLabels)
3556        except TypeError:
3557            return None
3558       
3559    def GetNumberRows(self):
3560        return len(self.data)
3561       
3562    def GetRowLabelValue(self, row):
3563        if self.rowLabels:
3564            return self.rowLabels[row]
3565       
3566    def GetColValues(self, col):
3567        data = []
3568        for row in range(self.GetNumberRows()):
3569            data.append(self.GetValue(row, col))
3570        return data
3571       
3572    def GetRowValues(self, row):
3573        data = []
3574        for col in range(self.GetNumberCols()):
3575            data.append(self.GetValue(row, col))
3576        return data
3577       
3578    def GetTypeName(self, row, col):
3579        try:
3580            return self.dataTypes[col]
3581        except TypeError:
3582            return None
3583
3584    def GetValue(self, row, col):
3585        try:
3586            return self.data[row][col]
3587        except IndexError:
3588            return None
3589           
3590    def InsertRows(self, pos, rows):
3591        for row in range(rows):
3592            self.data.insert(pos,[])
3593            pos += 1
3594       
3595    def IsEmptyCell(self,row,col):
3596        try:
3597            return not self.data[row][col]
3598        except IndexError:
3599            return True
3600       
3601    def OnKeyPress(self, event):
3602        dellist = self.GetSelectedRows()
3603        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
3604            grid = self.GetView()
3605            for i in dellist: grid.DeleteRow(i)
3606               
3607    def SetColLabelValue(self, col, label):
3608        numcols = self.GetNumberCols()
3609        if col > numcols-1:
3610            self.colLabels.append(label)
3611        else:
3612            self.colLabels[col]=label
3613       
3614    def SetData(self,data):
3615        for row in range(len(data)):
3616            self.SetRowValues(row,data[row])
3617               
3618    def SetRowLabelValue(self, row, label):
3619        self.rowLabels[row]=label
3620           
3621    def SetRowValues(self,row,data):
3622        self.data[row] = data
3623           
3624    def SetValue(self, row, col, value):
3625        def innerSetValue(row, col, value):
3626            try:
3627                self.data[row][col] = value
3628            except TypeError:
3629                return
3630            except IndexError:
3631                print row,col,value
3632                # add a new row
3633                if row > self.GetNumberRows():
3634                    self.data.append([''] * self.GetNumberCols())
3635                elif col > self.GetNumberCols():
3636                    for row in range(self.GetNumberRows):
3637                        self.data[row].append('')
3638                print self.data
3639                self.data[row][col] = value
3640        innerSetValue(row, col, value)
3641       
3642################################################################################
3643#### Help
3644################################################################################
3645
3646def ShowHelp(helpType,frame):
3647    '''Called to bring up a web page for documentation.'''
3648    global htmlFirstUse
3649    # look up a definition for help info from dict
3650    helplink = helpLocDict.get(helpType)
3651    if helplink is None:
3652        # no defined link to use, create a default based on key
3653        helplink = 'gsasII.html#'+helpType.replace(' ','_')
3654    helplink = os.path.join(path2GSAS2,'help',helplink)
3655    if helpMode == 'internal':
3656        try:
3657            htmlPanel.LoadFile(helplink)
3658            htmlFrame.Raise()
3659        except:
3660            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
3661            htmlFrame.Show(True)
3662            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
3663            htmlPanel = MyHtmlPanel(htmlFrame,-1)
3664            htmlPanel.LoadFile(helplink)
3665    else:
3666        pfx = "file://"
3667        if sys.platform.lower().startswith('win'):
3668            pfx = ''
3669        if htmlFirstUse:
3670            webbrowser.open_new(pfx+helplink)
3671            htmlFirstUse = False
3672        else:
3673            webbrowser.open(pfx+helplink, new=0, autoraise=True)
3674
3675################################################################################
3676#####  Notebook
3677################################################################################           
3678       
3679def UpdateNotebook(G2frame,data):
3680    '''Called when the data tree notebook entry is selected. Allows for
3681    editing of the text in that tree entry
3682    '''
3683    def OnNoteBook(event):
3684        data = G2frame.dataDisplay.GetValue().split('\n')
3685        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
3686        if 'nt' not in os.name:
3687            G2frame.dataDisplay.AppendText('\n')
3688                   
3689    if G2frame.dataDisplay:
3690        G2frame.dataDisplay.Destroy()
3691    G2frame.dataFrame.SetLabel('Notebook')
3692    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3693        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
3694    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
3695    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
3696    for line in data:
3697        G2frame.dataDisplay.AppendText(line+"\n")
3698    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
3699    G2frame.dataFrame.setSizePosLeft([400,250])
3700           
3701################################################################################
3702#####  Controls
3703################################################################################           
3704       
3705def UpdateControls(G2frame,data):
3706    '''Edit overall GSAS-II controls in main Controls data tree entry
3707    '''
3708    #patch
3709    if 'deriv type' not in data:
3710        data = {}
3711        data['deriv type'] = 'analytic Hessian'
3712        data['min dM/M'] = 0.0001
3713        data['shift factor'] = 1.
3714        data['max cyc'] = 3       
3715        data['F**2'] = True
3716        data['minF/sig'] = 0
3717    if 'shift factor' not in data:
3718        data['shift factor'] = 1.
3719    if 'max cyc' not in data:
3720        data['max cyc'] = 3
3721    if 'F**2' not in data:
3722        data['F**2'] = True
3723        data['minF/sig'] = 0
3724    if 'Author' not in data:
3725        data['Author'] = 'no name'
3726    if 'FreePrm1' not in data:
3727        data['FreePrm1'] = 'Sample humidity (%)'
3728    if 'FreePrm2' not in data:
3729        data['FreePrm2'] = 'Sample voltage (V)'
3730    if 'FreePrm3' not in data:
3731        data['FreePrm3'] = 'Applied load (MN)'
3732    if 'Copy2Next' not in data:
3733        data['Copy2Next'] = False
3734    if 'Reverse Seq' not in data:
3735        data['Reverse Seq'] = False   
3736     
3737   
3738    #end patch
3739
3740    def SeqSizer():
3741       
3742        def OnSelectData(event):
3743            choices = GetPatternTreeDataNames(G2frame,['PWDR',])
3744            sel = []
3745            if 'Seq Data' in data:
3746                for item in data['Seq Data']:
3747                    sel.append(choices.index(item))
3748                sel = [choices.index(item) for item in data['Seq Data']]
3749            dlg = G2MultiChoiceDialog(G2frame.dataFrame, 'Sequential refinement',
3750                                      'Select dataset to include',
3751                                      choices)
3752            dlg.SetSelections(sel)
3753            names = []
3754            if dlg.ShowModal() == wx.ID_OK:
3755                for sel in dlg.GetSelections():
3756                    names.append(choices[sel])
3757                data['Seq Data'] = names               
3758                G2frame.EnableSeqRefineMenu()
3759            dlg.Destroy()
3760            wx.CallAfter(UpdateControls,G2frame,data)
3761           
3762        def OnReverse(event):
3763            data['Reverse Seq'] = reverseSel.GetValue()
3764           
3765        def OnCopySel(event):
3766            data['Copy2Next'] = copySel.GetValue() 
3767                   
3768        seqSizer = wx.BoxSizer(wx.VERTICAL)
3769        dataSizer = wx.BoxSizer(wx.HORIZONTAL)
3770        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement: '),0,WACV)
3771        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3772        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3773        dataSizer.Add(selSeqData,0,WACV)
3774        SeqData = data.get('Seq Data',[])
3775        if not SeqData:
3776            lbl = ' (no powder data selected)'
3777        else:
3778            lbl = ' ('+str(len(SeqData))+' dataset(s) selected)'
3779
3780        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=lbl),0,WACV)
3781        seqSizer.Add(dataSizer,0)
3782        if SeqData:
3783            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3784            reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3785            reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3786            reverseSel.SetValue(data['Reverse Seq'])
3787            selSizer.Add(reverseSel,0,WACV)
3788            copySel =  wx.CheckBox(G2frame.dataDisplay,-1,label=' Copy results to next histogram?')
3789            copySel.Bind(wx.EVT_CHECKBOX,OnCopySel)
3790            copySel.SetValue(data['Copy2Next'])
3791            selSizer.Add(copySel,0,WACV)
3792            seqSizer.Add(selSizer,0)
3793        return seqSizer
3794       
3795    def LSSizer():       
3796       
3797        def OnDerivType(event):
3798            data['deriv type'] = derivSel.GetValue()
3799            derivSel.SetValue(data['deriv type'])
3800            wx.CallAfter(UpdateControls,G2frame,data)
3801           
3802        def OnConvergence(event):
3803            try:
3804                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3805            except ValueError:
3806                value = 0.0001
3807            data['min dM/M'] = value
3808            Cnvrg.SetValue('%.2g'%(value))
3809           
3810        def OnMaxCycles(event):
3811            data['max cyc'] = int(maxCyc.GetValue())
3812            maxCyc.SetValue(str(data['max cyc']))
3813                       
3814        def OnFactor(event):
3815            try:
3816                value = min(max(float(Factr.GetValue()),0.00001),100.)
3817            except ValueError:
3818                value = 1.0
3819            data['shift factor'] = value
3820            Factr.SetValue('%.5f'%(value))
3821           
3822        def OnFsqRef(event):
3823            data['F**2'] = fsqRef.GetValue()
3824       
3825        def OnMinSig(event):
3826            try:
3827                value = min(max(float(minSig.GetValue()),0.),5.)
3828            except ValueError:
3829                value = 1.0
3830            data['minF/sig'] = value
3831            minSig.SetValue('%.2f'%(value))
3832
3833        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3834        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,WACV)
3835        Choice=['analytic Jacobian','numeric','analytic Hessian']
3836        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3837            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3838        derivSel.SetValue(data['deriv type'])
3839        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3840           
3841        LSSizer.Add(derivSel,0,WACV)
3842        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,WACV)
3843        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3844        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3845        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3846        LSSizer.Add(Cnvrg,0,WACV)
3847        if 'Hessian' in data['deriv type']:
3848            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,WACV)
3849            Choice = ['0','1','2','3','5','10','15','20']
3850            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3851                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3852            maxCyc.SetValue(str(data['max cyc']))
3853            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3854            LSSizer.Add(maxCyc,0,WACV)
3855        else:
3856            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,WACV)
3857            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3858            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3859            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3860            LSSizer.Add(Factr,0,WACV)
3861        if G2frame.Sngl:
3862            LSSizer.Add((1,0),)
3863            LSSizer.Add((1,0),)
3864            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3865            fsqRef.SetValue(data['F**2'])
3866            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3867            LSSizer.Add(fsqRef,0,WACV)
3868            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,WACV)
3869            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3870            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3871            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3872            LSSizer.Add(minSig,0,WACV)
3873        return LSSizer
3874       
3875    def AuthSizer():
3876
3877        def OnAuthor(event):
3878            data['Author'] = auth.GetValue()
3879
3880        Author = data['Author']
3881        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3882        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,WACV)
3883        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3884        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3885        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3886        authSizer.Add(auth,0,WACV)
3887        return authSizer
3888       
3889       
3890    if G2frame.dataDisplay:
3891        G2frame.dataDisplay.Destroy()
3892    if not G2frame.dataFrame.GetStatusBar():
3893        Status = G2frame.dataFrame.CreateStatusBar()
3894        Status.SetStatusText('')
3895    G2frame.dataFrame.SetLabel('Controls')
3896    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3897    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3898    mainSizer = wx.BoxSizer(wx.VERTICAL)
3899    mainSizer.Add((5,5),0)
3900    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,WACV)   
3901    mainSizer.Add(LSSizer())
3902    mainSizer.Add((5,5),0)
3903    mainSizer.Add(SeqSizer())
3904    mainSizer.Add((5,5),0)
3905    mainSizer.Add(AuthSizer())
3906    mainSizer.Add((5,5),0)
3907       
3908    mainSizer.Layout()   
3909    G2frame.dataDisplay.SetSizer(mainSizer)
3910    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3911    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3912     
3913################################################################################
3914#####  Comments
3915################################################################################           
3916       
3917def UpdateComments(G2frame,data):                   
3918
3919    if G2frame.dataDisplay:
3920        G2frame.dataDisplay.Destroy()
3921    G2frame.dataFrame.SetLabel('Comments')
3922    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3923        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3924    for line in data:
3925        G2frame.dataDisplay.AppendText(line+'\n')
3926    G2frame.dataFrame.setSizePosLeft([400,250])
3927           
3928################################################################################
3929#####  Display of Sequential Results
3930################################################################################           
3931       
3932def UpdateSeqResults(G2frame,data,prevSize=None):
3933    """
3934    Called when the Sequential Results data tree entry is selected
3935    to show results from a sequential refinement.
3936   
3937    :param wx.Frame G2frame: main GSAS-II data tree windows
3938
3939    :param dict data: a dictionary containing the following items: 
3940
3941            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3942            * 'varyList' - list of variables - identical over all refinements in sequence
3943              note that this is the original list of variables, prior to processing
3944              constraints.
3945            * 'variableLabels' -- a dict of labels to be applied to each parameter
3946              (this is created as an empty dict if not present in data).
3947            * keyed by histName - dictionaries for all data sets processed, which contains:
3948
3949              * 'variables'- result[0] from leastsq call
3950              * 'varyList' - list of variables passed to leastsq call (not same as above)
3951              * 'sig' - esds for variables
3952              * 'covMatrix' - covariance matrix from individual refinement
3953              * 'title' - histogram name; same as dict item name
3954              * 'newAtomDict' - new atom parameters after shifts applied
3955              * 'newCellDict' - refined cell parameters after shifts to A0-A5 from Dij terms applied'
3956    """
3957
3958    def GetSampleParms():
3959        '''Make a dictionary of the sample parameters are not the same over the
3960        refinement series.
3961        '''
3962        if 'IMG' in histNames[0]:
3963            sampleParmDict = {'Sample load':[],}
3964        else:
3965            sampleParmDict = {'Temperature':[],'Pressure':[],
3966                              'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],}
3967        Controls = G2frame.PatternTree.GetItemPyData(
3968            GetPatternTreeItemId(G2frame,G2frame.root, 'Controls'))
3969        sampleParm = {}
3970        for name in histNames:
3971            if 'IMG' in name:
3972                for item in sampleParmDict:
3973                    sampleParmDict[item].append(data[name]['parmDict'][item])
3974            else:
3975                Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3976                sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3977                for item in sampleParmDict:
3978                    sampleParmDict[item].append(sampleData[item])
3979        for item in sampleParmDict:
3980            frstValue = sampleParmDict[item][0]
3981            if np.any(np.array(sampleParmDict[item])-frstValue):
3982                if item.startswith('FreePrm'):
3983                    sampleParm[Controls[item]] = sampleParmDict[item]
3984                else:
3985                    sampleParm[item] = sampleParmDict[item]
3986        return sampleParm
3987
3988    def GetColumnInfo(col):
3989        '''returns column label, lists of values and errors (or None) for each column in the table
3990        for plotting. The column label is reformatted from Unicode to MatPlotLib encoding
3991        '''
3992        colName = G2frame.SeqTable.GetColLabelValue(col)
3993        plotName = variableLabels.get(colName,colName)
3994        plotName = plotSpCharFix(plotName)
3995        return plotName,colList[col],colSigs[col]
3996           
3997    def PlotSelect(event):
3998        'Plots a row (covariance) or column on double-click'
3999        cols = G2frame.dataDisplay.GetSelectedCols()
4000        rows = G2frame.dataDisplay.GetSelectedRows()
4001        if cols:
4002            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4003        elif rows:
4004            name = histNames[rows[0]]       #only does 1st one selected
4005            G2plt.PlotCovariance(G2frame,data[name])
4006        else:
4007            G2frame.ErrorDialog(
4008                'Select row or columns',
4009                'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.'
4010                )
4011           
4012    def OnPlotSelSeq(event):
4013        'plot the selected columns or row from menu command'
4014        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4015        rows = G2frame.dataDisplay.GetSelectedRows()
4016        if cols:
4017            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4018        elif rows:
4019            name = histNames[rows[0]]       #only does 1st one selected
4020            G2plt.PlotCovariance(G2frame,data[name])
4021        else:
4022            G2frame.ErrorDialog(
4023                'Select columns',
4024                'No columns or rows selected in table. Click on row or column labels to select fields for plotting.'
4025                )
4026               
4027    def OnRenameSelSeq(event):
4028        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4029        colNames = [G2frame.SeqTable.GetColLabelValue(c) for c in cols]
4030        newNames = colNames[:]
4031        for i,name in enumerate(colNames):
4032            if name in variableLabels:
4033                newNames[i] = variableLabels[name]
4034        if not cols:
4035            G2frame.ErrorDialog('Select columns',
4036                'No columns selected in table. Click on column labels to select fields for rename.')
4037            return
4038        dlg = MultiStringDialog(G2frame.dataDisplay,'Set column names',colNames,newNames)
4039        if dlg.Show():
4040            newNames = dlg.GetValues()           
4041            variableLabels.update(dict(zip(colNames,newNames)))
4042        data['variableLabels'] = variableLabels
4043        dlg.Destroy()
4044        UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4045        G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
4046           
4047    def OnSaveSelSeqCSV(event):
4048        'export the selected columns to a .csv file from menu command'
4049        OnSaveSelSeq(event,csv=True)
4050       
4051    def OnSaveSelSeq(event,csv=False):
4052        'export the selected columns to a .txt file from menu command'
4053        def WriteCSV():
4054            def WriteList(headerItems):
4055                line = ''
4056                for lbl in headerItems:
4057                    if line: line += ','
4058                    line += '"'+lbl+'"'
4059                return line
4060            head = ['name']
4061            for col in cols:
4062                item = G2frame.SeqTable.GetColLabelValue(col)
4063                if col in havesig:
4064                    head += [item,'esd-'+item]
4065                else:
4066                    head += [item]
4067            SeqFile.write(WriteList(head)+'\n')
4068            for row,name in enumerate(saveNames):
4069                line = '"'+saveNames[row]+'"'
4070                for col in cols:
4071                    if col in havesig:
4072                        line += ','+str(saveData[col][row])+','+str(saveSigs[col][row])
4073                    else:
4074                        line += ','+str(saveData[col][row])
4075                SeqFile.write(line+'\n')
4076        def WriteSeq():
4077            lenName = len(saveNames[0])
4078            line = %s  '%('name'.center(lenName))
4079            for col in cols:
4080                item = G2frame.SeqTable.GetColLabelValue(col)
4081                if col in havesig:
4082                    line += ' %12s %12s '%(item.center(12),'esd'.center(12))
4083                else:
4084                    line += ' %12s '%(item.center(12))
4085            SeqFile.write(line+'\n')
4086            for row,name in enumerate(saveNames):
4087                line = " '%s' "%(saveNames[row])
4088                for col in cols:
4089                    if col in havesig:
4090                        line += ' %12.6f %12.6f '%(saveData[col][row],saveSigs[col][row])
4091                    else:
4092                        line += ' %12.6f '%saveData[col][row]
4093                SeqFile.write(line+'\n')
4094
4095        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
4096        nrows = G2frame.SeqTable.GetNumberRows()
4097        if not cols:
4098            G2frame.ErrorDialog('Select columns',
4099                             'No columns selected in table. Click on column labels to select fields for output.')
4100            return
4101        saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(nrows)]
4102        saveData = {}
4103        saveSigs = {}
4104        havesig = []
4105        for col in cols:
4106            name,vals,sigs = GetColumnInfo(col)
4107            saveData[col] = vals
4108            if sigs:
4109                havesig.append(col)
4110                saveSigs[col] = sigs
4111        if csv:
4112            wild = 'CSV output file (*.csv)|*.csv'
4113        else:
4114            wild = 'Text output file (*.txt)|*.txt'
4115        dlg = wx.FileDialog(
4116            G2frame,
4117            'Choose text output file for your selection', '.', '', 
4118            wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
4119        try:
4120            if dlg.ShowModal() == wx.ID_OK:
4121                SeqTextFile = dlg.GetPath()
4122                SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) 
4123                SeqFile = open(SeqTextFile,'w')
4124                if csv:
4125                    WriteCSV()
4126                else:
4127                    WriteSeq()
4128                SeqFile.close()
4129        finally:
4130            dlg.Destroy()
4131               
4132    def striphist(var,insChar=''):
4133        'strip a histogram number from a var name'
4134        sv = var.split(':')
4135        if len(sv) <= 1: return var
4136        sv[1] = insChar
4137        return ':'.join(sv)
4138       
4139    def plotSpCharFix(lbl):
4140        'Change selected unicode characters to their matplotlib equivalent'
4141        for u,p in [
4142            (u'\u03B1',r'$\alpha$'),
4143            (u'\u03B2',r'$\beta$'),
4144            (u'\u03B3',r'$\gamma$'),
4145            (u'\u0394\u03C7',r'$\Delta\chi$'),
4146            ]:
4147            lbl = lbl.replace(u,p)
4148        return lbl
4149   
4150    def SelectXaxis():
4151        'returns a selected column number (or None) as the X-axis selection'
4152        ncols = G2frame.SeqTable.GetNumberCols()
4153        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
4154        dlg = G2SingleChoiceDialog(
4155            G2frame.dataDisplay,
4156            'Select x-axis parameter for plot or Cancel for sequence number',
4157            'Select X-axis',
4158            colNames)
4159        try:
4160            if dlg.ShowModal() == wx.ID_OK:
4161                col = dlg.GetSelection()
4162            else:
4163                col = None
4164        finally:
4165            dlg.Destroy()
4166        return col
4167   
4168    def EnablePseudoVarMenus():
4169        'Enables or disables the PseudoVar menu items that require existing defs'
4170        if Controls['SeqPseudoVars']:
4171            val = True
4172        else:
4173            val = False
4174        G2frame.dataFrame.SequentialPvars.Enable(wxDELSEQVAR,val)
4175        G2frame.dataFrame.SequentialPvars.Enable(wxEDITSEQVAR,val)
4176
4177    def DelPseudoVar(event):
4178        'Ask the user to select a pseudo var expression to delete'
4179        choices = Controls['SeqPseudoVars'].keys()
4180        selected = ItemSelector(
4181            choices,G2frame.dataFrame,
4182            multiple=True,
4183            title='Select expressions to remove',
4184            header='Delete expression')
4185        if selected is None: return
4186        for item in selected:
4187            del Controls['SeqPseudoVars'][choices[item]]
4188        if selected:
4189            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4190
4191    def EditPseudoVar(event):
4192        'Edit an existing pseudo var expression'
4193        choices = Controls['SeqPseudoVars'].keys()
4194        if len(choices) == 1:
4195            selected = 0
4196        else:
4197            selected = ItemSelector(
4198                choices,G2frame.dataFrame,
4199                multiple=False,
4200                title='Select an expression to edit',
4201                header='Edit expression')
4202        if selected is not None:
4203            dlg = G2exG.ExpressionDialog(
4204                G2frame.dataDisplay,PSvarDict,
4205                Controls['SeqPseudoVars'][choices[selected]],
4206                header="Edit the PseudoVar expression",
4207                VarLabel="PseudoVar #"+str(selected+1),
4208                fit=False)
4209            newobj = dlg.Show(True)
4210            if newobj:
4211                calcobj = G2obj.ExpressionCalcObj(newobj)
4212                del Controls['SeqPseudoVars'][choices[selected]]
4213                Controls['SeqPseudoVars'][calcobj.eObj.expression] = newobj
4214                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4215       
4216    def AddNewPseudoVar(event):
4217        'Create a new pseudo var expression'
4218        dlg = G2exG.ExpressionDialog(
4219            G2frame.dataDisplay,PSvarDict,
4220            header='Enter an expression for a PseudoVar here',
4221            VarLabel = "New PseudoVar",
4222            fit=False)
4223        obj = dlg.Show(True)
4224        dlg.Destroy()
4225        if obj:
4226            calcobj = G2obj.ExpressionCalcObj(obj)
4227            Controls['SeqPseudoVars'][calcobj.eObj.expression] = obj
4228            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
4229
4230    # PATCH: this routine can go away eventually
4231    def CreatePSvarDict(seqnum,name):
4232        '''Create a parameter dict (parmDict) for everything that might be used
4233        in a PseudoVar.
4234        Also creates a list of revised labels (modVaryList) for the covariance matrix to
4235        match the items in the parameter dict and a matching list of ESDs (ESDvaryList).
4236       
4237        :param int seqnum: the sequence number of the histogram in the sequential
4238          refinement
4239        :param str name: the name of the histogram in the data tree
4240
4241        :returns: parmDict,modVaryList,ESDvaryList
4242        '''
4243        parmDict = {}
4244        modVaryList = []
4245        for i,(key,val) in enumerate(zip(data[name]['varyList'],data[name]['variables'])):
4246            skey = striphist(key)
4247            if skey in data[name].get('newAtomDict',{}):
4248                # replace coordinate shifts with equivalents from lookup table
4249                repkey,repval = data[name]['newAtomDict'][skey]
4250                parmDict[repkey] = repval
4251                modVaryList.append(repkey)
4252            elif skey in data[name].get('newCellDict',{}):
4253                # replace recip. cell term shifts with equivalents from lookup table       
4254                repkey,repval = data[name]['newCellDict'][skey]
4255                parmDict[repkey] = repval
4256                modVaryList.append(repkey)
4257            else:
4258                parmDict[key] = val
4259                modVaryList.append(key)
4260        # create a cell parm dict, override initial settings with values in parmDict
4261        for phase in Phases:
4262            phasedict = Phases[phase]
4263            pId = phasedict['pId']
4264            cell = Rcelldict.copy()
4265            cell.update(
4266                {lbl:parmDict[lbl] for lbl in RcellLbls[pId] if lbl in parmDict}
4267                )
4268            pfx = str(pId)+'::' # prefix for A values from phase
4269            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cell,zeroDict[pId])
4270            parmDict.update({pfx+cellUlbl[i]:val for i,val in
4271                             enumerate(G2lat.A2cell(A))
4272                             if i in uniqCellIndx[pId]
4273                             })
4274            parmDict[pfx+"vol"] = G2lat.calc_V(A)
4275        # now add misc terms to dict
4276        parmDict['Rwp'] = data[name]['Rvals']['Rwp']
4277        parmDict[u'\u0394\u03C7\u00B2 (%)'] = 100.*data[name]['Rvals'].get('DelChi2',-1)
4278        for key in sampleParms:
4279            parmDict[key] = sampleParms[key][seqnum]
4280        return parmDict,modVaryList,data[name]['sig']
4281
4282    def UpdateParmDict(parmDict):
4283        '''generate the atom positions and the direct & reciprocal cell values,
4284        because they might be needed to evaluate the pseudovar
4285        '''
4286        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
4287                         ['A'+str(i) for i in range(6)])
4288                     )
4289        delList = []
4290        phaselist = []
4291        for item in parmDict: 
4292            if ':' not in item: continue
4293            key = item.split(':')
4294            if len(key) < 3: continue
4295            # remove the dA[xyz] terms, they would only bring confusion
4296            if key[2].startswith('dA'):
4297                delList.append(item)
4298            # compute and update the corrected reciprocal cell terms using the Dij values
4299            elif key[2] in Ddict:
4300                if key[0] not in phaselist: phaselist.append(key[0])
4301                akey = key[0]+'::'+Ddict[key[2]]
4302                parmDict[akey] += parmDict[item]
4303                delList.append(item)
4304        for item in delList:
4305            del parmDict[item]               
4306        for i in phaselist:
4307            pId = int(i)
4308            # apply cell symmetry
4309            A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
4310            # convert to direct cell & add the unique terms to the dictionary
4311            for i,val in enumerate(G2lat.A2cell(A)):
4312                if i in uniqCellIndx[pId]:
4313                    lbl = str(pId)+'::'+cellUlbl[i]
4314                    parmDict[lbl] = val
4315            lbl = str(pId)+'::'+'vol'
4316            parmDict[lbl] = G2lat.calc_V(A)
4317        return parmDict
4318
4319    def EvalPSvarDeriv(calcobj,parmDict,var,ESD):
4320        '''Evaluate an expression derivative with respect to a
4321        GSAS-II variable name.
4322
4323        Note this likely could be faster if the loop over calcobjs were done
4324        inside after the Dict was created.
4325        '''
4326        step = ESD/10
4327        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
4328                         ['A'+str(i) for i in range(6)])
4329                     )
4330        results = []
4331        phaselist = []
4332        for incr in step,-step:
4333            VparmDict = parmDict.copy()           
4334            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
4335            # values are not zeroed: fix that!
4336            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
4337            VparmDict[var] += incr
4338            G2mv.Dict2Map(VparmDict,[]) # apply constraints
4339            # generate the atom positions and the direct & reciprocal cell values now, because they might
4340            # needed to evaluate the pseudovar
4341            for item in VparmDict: 
4342                if ':' not in item: continue
4343                key = item.split(':')
4344                if len(key) < 3: continue
4345                # apply any new shifts to atom positions
4346                if key[2].startswith('dA'):
4347                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
4348                    VparmDict[item] = 0.0
4349                # compute and update the corrected reciprocal cell terms using the Dij values
4350                if key[2] in Ddict:
4351                    if key[0] not in phaselist: phaselist.append(key[0])
4352                    akey = key[0]+'::'+Ddict[key[2]]
4353                    VparmDict[akey] += VparmDict[item]
4354            for i in phaselist:
4355                pId = int(i)
4356                # apply cell symmetry
4357                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
4358                # convert to direct cell & add the unique terms to the dictionary
4359                for i,val in enumerate(G2lat.A2cell(A)):
4360                    if i in uniqCellIndx[pId]:
4361                        lbl = str(pId)+'::'+cellUlbl[i]
4362                        VparmDict[lbl] = val
4363                lbl = str(pId)+'::'+'vol'
4364                VparmDict[lbl] = G2lat.calc_V(A)
4365            # dict should be fully updated, use it & calculate
4366            calcobj.SetupCalc(VparmDict)
4367            results.append(calcobj.EvalExpression())
4368        return (results[0] - results[1]) / (2.*step)
4369       
4370    def EnableParFitEqMenus():
4371        'Enables or disables the Parametric Fit menu items that require existing defs'
4372        if Controls['SeqParFitEqList']:
4373            val = True
4374        else:
4375            val = False
4376        G2frame.dataFrame.SequentialPfit.Enable(wxDELPARFIT,val)
4377        G2frame.dataFrame.SequentialPfit.Enable(wxEDITPARFIT,val)
4378        G2frame.dataFrame.SequentialPfit.Enable(wxDOPARFIT,val)
4379
4380    def ParEqEval(Values,calcObjList,varyList):
4381        '''Evaluate the parametric expression(s)
4382        :param list Values: a list of values for each variable parameter
4383        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
4384          expression objects to evaluate
4385        :param list varyList: a list of variable names for each value in Values
4386        '''
4387        result = []
4388        for calcobj in calcObjList:
4389            calcobj.UpdateVars(varyList,Values)
4390            result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
4391        return result
4392
4393    def DoParEqFit(event,eqObj=None):
4394        'Parametric fit minimizer'
4395        varyValueDict = {} # dict of variables and their initial values
4396        calcObjList = [] # expression objects, ready to go for each data point
4397        if eqObj is not None:
4398            eqObjList = [eqObj,]
4399        else:
4400            eqObjList = Controls['SeqParFitEqList']
4401        UseFlags = G2frame.SeqTable.GetColValues(0)         
4402        for obj in eqObjList:
4403            expr = obj.expression
4404            # assemble refined vars for this equation
4405            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
4406            # lookup dependent var position
4407            depVar = obj.GetDepVar()
4408            if depVar in colLabels:
4409                indx = colLabels.index(depVar)
4410            else:
4411                raise Exception('Dependent variable '+depVar+' not found')
4412            # assemble a list of the independent variables
4413            indepVars = obj.GetIndependentVars()
4414            # loop over each datapoint
4415            for j,row in enumerate(zip(*colList)):
4416                if not UseFlags[j]: continue
4417                # assemble equations to fit
4418                calcobj = G2obj.ExpressionCalcObj(obj)
4419                # prepare a dict of needed independent vars for this expression
4420                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4421                calcobj.SetupCalc(indepVarDict)               
4422                # values and sigs for current value of dependent var
4423                calcobj.depVal = row[indx]
4424                calcobj.depSig = colSigs[indx][j]
4425                calcObjList.append(calcobj)
4426        # varied parameters
4427        varyList = varyValueDict.keys()
4428        values = varyValues = [varyValueDict[key] for key in varyList]
4429        if not varyList:
4430            print 'no variables to refine!'
4431            return
4432        try:
4433            result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
4434                                args=(calcObjList,varyList)
4435                                )
4436            values = result[0]
4437            covar = result[1]
4438            if covar is None:
4439                raise Exception
4440            esdDict = {}
4441            for i,avar in enumerate(varyList):
4442                esdDict[avar] = np.sqrt(covar[i,i])
4443        except:
4444            print('====> Fit failed')
4445            return
4446        print('==== Fit Results ====')
4447        for obj in eqObjList:
4448            obj.UpdateVariedVars(varyList,values)
4449            ind = '      '
4450            print('  '+obj.GetDepVar()+' = '+obj.expression)
4451            for var in obj.assgnVars:
4452                print(ind+var+' = '+obj.assgnVars[var])
4453            for var in obj.freeVars:
4454                avar = "::"+obj.freeVars[var][0]
4455                val = obj.freeVars[var][1]
4456                if obj.freeVars[var][2]:
4457                    print(ind+var+' = '+avar + " = " + G2mth.ValEsd(val,esdDict[avar]))
4458                else:
4459                    print(ind+var+' = '+avar + " =" + G2mth.ValEsd(val,0))
4460        # create a plot for each parametric variable
4461        for fitnum,obj in enumerate(eqObjList):
4462            calcobj = G2obj.ExpressionCalcObj(obj)
4463            # lookup dependent var position
4464            indx = colLabels.index(obj.GetDepVar())
4465            # assemble a list of the independent variables
4466            indepVars = obj.GetIndependentVars()           
4467            # loop over each datapoint
4468            fitvals = []
4469            for j,row in enumerate(zip(*colList)):
4470                calcobj.SetupCalc(
4471                    {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4472                    )
4473                fitvals.append(calcobj.EvalExpression())
4474            G2plt.PlotSelectedSequence(
4475                G2frame,[indx],GetColumnInfo,SelectXaxis,
4476                fitnum,fitvals)
4477
4478    def SingleParEqFit(eqObj):
4479        DoParEqFit(None,eqObj)
4480
4481    def DelParFitEq(event):
4482        'Ask the user to select function to delete'
4483        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4484        selected = ItemSelector(
4485            txtlst,G2frame.dataFrame,
4486            multiple=True,
4487            title='Select a parametric equation(s) to remove',
4488            header='Delete equation')
4489        if selected is None: return
4490        Controls['SeqParFitEqList'] = [obj for i,obj in enumerate(Controls['SeqParFitEqList']) if i not in selected]
4491        EnableParFitEqMenus()
4492        if Controls['SeqParFitEqList']: DoParEqFit(event)
4493       
4494    def EditParFitEq(event):
4495        'Edit an existing parametric equation'
4496        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4497        if len(txtlst) == 1:
4498            selected = 0
4499        else:
4500            selected = ItemSelector(
4501                txtlst,G2frame.dataFrame,
4502                multiple=False,
4503                title='Select a parametric equation to edit',
4504                header='Edit equation')
4505        if selected is not None:
4506            dlg = G2exG.ExpressionDialog(
4507                G2frame.dataDisplay,indepVarDict,
4508                Controls['SeqParFitEqList'][selected],
4509                depVarDict=depVarDict,
4510                header="Edit the formula for this minimization function",
4511                ExtraButton=['Fit',SingleParEqFit])
4512            newobj = dlg.Show(True)
4513            if newobj:
4514                calcobj = G2obj.ExpressionCalcObj(newobj)
4515                Controls['SeqParFitEqList'][selected] = newobj
4516                EnableParFitEqMenus()
4517            if Controls['SeqParFitEqList']: DoParEqFit(event)
4518
4519    def AddNewParFitEq(event):
4520        'Create a new parametric equation to be fit to sequential results'
4521
4522        # compile the variable names used in previous freevars to avoid accidental name collisions
4523        usedvarlist = []
4524        for obj in Controls['SeqParFitEqList']:
4525            for var in obj.freeVars:
4526                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
4527
4528        dlg = G2exG.ExpressionDialog(
4529            G2frame.dataDisplay,indepVarDict,
4530            depVarDict=depVarDict,
4531            header='Define an equation to minimize in the parametric fit',
4532            ExtraButton=['Fit',SingleParEqFit],
4533            usedVars=usedvarlist)
4534        obj = dlg.Show(True)
4535        dlg.Destroy()
4536        if obj:
4537            Controls['SeqParFitEqList'].append(obj)
4538            EnableParFitEqMenus()
4539            if Controls['SeqParFitEqList']: DoParEqFit(event)
4540               
4541    def CopyParFitEq(event):
4542        'Copy an existing parametric equation to be fit to sequential results'
4543        # compile the variable names used in previous freevars to avoid accidental name collisions
4544        usedvarlist = []
4545        for obj in Controls['SeqParFitEqList']:
4546            for var in obj.freeVars:
4547                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
4548        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
4549        if len(txtlst) == 1:
4550            selected = 0
4551        else:
4552            selected = ItemSelector(
4553                txtlst,G2frame.dataFrame,
4554                multiple=False,
4555                title='Select a parametric equation to copy',
4556                header='Copy equation')
4557        if selected is not None:
4558            newEqn = copy.deepcopy(Controls['SeqParFitEqList'][selected])
4559            for var in newEqn.freeVars:
4560                newEqn.freeVars[var][0] = G2obj.MakeUniqueLabel(newEqn.freeVars[var][0],usedvarlist)
4561            dlg = G2exG.ExpressionDialog(
4562                G2frame.dataDisplay,indepVarDict,
4563                newEqn,
4564                depVarDict=depVarDict,
4565                header="Edit the formula for this minimization function",
4566                ExtraButton=['Fit',SingleParEqFit])
4567            newobj = dlg.Show(True)
4568            if newobj:
4569                calcobj = G2obj.ExpressionCalcObj(newobj)
4570                Controls['SeqParFitEqList'].append(newobj)
4571                EnableParFitEqMenus()
4572            if Controls['SeqParFitEqList']: DoParEqFit(event)
4573                                           
4574    def GridSetToolTip(row,col):
4575        '''Routine to show standard uncertainties for each element in table
4576        as a tooltip
4577        '''
4578        if colSigs[col]:
4579            return u'\u03c3 = '+str(colSigs[col][row])
4580        return ''
4581       
4582    def GridColLblToolTip(col):
4583        '''Define a tooltip for a column. This will be the user-entered value
4584        (from data['variableLabels']) or the default name
4585        '''
4586        if col < 0 or col > len(colLabels):
4587            print 'Illegal column #',col
4588            return
4589        var = colLabels[col]
4590        return variableLabels.get(var,G2obj.fmtVarDescr(var))
4591       
4592    def SetLabelString(event):
4593        '''Define or edit the label for a column in the table, to be used
4594        as a tooltip and for plotting
4595        '''
4596        col = event.GetCol()
4597        if col < 0 or col > len(colLabels):
4598            return
4599        var = colLabels[col]
4600        lbl = variableLabels.get(var,G2obj.fmtVarDescr(var))
4601        dlg = SingleStringDialog(G2frame.dataFrame,'Set variable label',
4602                                 'Set a name for variable '+var,lbl,size=(400,-1))
4603        if dlg.Show():
4604            variableLabels[var] = dlg.GetValue()
4605        dlg.Destroy()
4606       
4607    #def GridRowLblToolTip(row): return 'Row ='+str(row)
4608   
4609    # lookup table for unique cell parameters by symmetry
4610    cellGUIlist = [
4611        [['m3','m3m'],(0,)],
4612        [['3R','3mR'],(0,3)],
4613        [['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],(0,2)],
4614        [['mmm'],(0,1,2)],
4615        [['2/m'+'a'],(0,1,2,3)],
4616        [['2/m'+'b'],(0,1,2,4)],
4617        [['2/m'+'c'],(0,1,2,5)],
4618        [['-1'],(0,1,2,3,4,5)],
4619        ]
4620    # cell labels
4621    cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') # unicode a,b,c,alpha,beta,gamma
4622
4623    #======================================================================
4624    # start processing sequential results here (UpdateSeqResults)
4625    #======================================================================
4626    if not data:
4627        print 'No sequential refinement results'
4628        return
4629    variableLabels = data.get('variableLabels',{})
4630    data['variableLabels'] = variableLabels
4631    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
4632    Controls = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Controls'))
4633    # create a place to store Pseudo Vars & Parametric Fit functions, if needed
4634    if 'SeqPseudoVars' not in Controls: Controls['SeqPseudoVars'] = {}
4635    if 'SeqParFitEqList' not in Controls: Controls['SeqParFitEqList'] = []
4636    histNames = data['histNames']
4637    if G2frame.dataDisplay:
4638        G2frame.dataDisplay.Destroy()
4639    if not G2frame.dataFrame.GetStatusBar():
4640        Status = G2frame.dataFrame.CreateStatusBar()
4641        Status.SetStatusText("Select column to export; Double click on column to plot data; on row for Covariance")
4642    sampleParms = GetSampleParms()
4643
4644    # make dict of varied atom coords keyed by absolute position
4645    newAtomDict = data[histNames[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
4646    # Possible error: the next might need to be data[histNames[0]]['varyList']
4647    # error will arise if there constraints on coordinates?
4648    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in data['varyList']}
4649   
4650    # make dict of varied cell parameters equivalents
4651    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
4652    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
4653    # N.B. These Dij vars are missing a histogram #
4654    newCellDict = data[histNames[0]].get('newCellDict',{})
4655    for item in newCellDict:
4656        if item in data['varyList']:
4657            ESDlookup[newCellDict[item][0]] = item
4658            Dlookup[item] = newCellDict[item][0]
4659    # add coordinate equivalents to lookup table
4660    for parm in atomLookup:
4661        Dlookup[atomLookup[parm]] = parm
4662        ESDlookup[parm] = atomLookup[parm]
4663
4664    # get unit cell & symmetry for all phases & initial stuff for later use
4665    RecpCellTerms = {}
4666    SGdata = {}
4667    uniqCellIndx = {}
4668    initialCell = {}
4669    RcellLbls = {}
4670    zeroDict = {}
4671    Rcelldict = {}
4672    for phase in Phases:
4673        phasedict = Phases[phase]
4674        pId = phasedict['pId']
4675        pfx = str(pId)+'::' # prefix for A values from phase
4676        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
4677        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
4678        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
4679        SGdata[pId] = phasedict['General']['SGData']
4680        Rcelldict.update({lbl:val for lbl,val in zip(RcellLbls[pId],RecpCellTerms[pId])})
4681        laue = SGdata[pId]['SGLaue']
4682        if laue == '2/m':
4683            laue += SGdata[pId]['SGUniq']
4684        for symlist,celllist in cellGUIlist:
4685            if laue in symlist:
4686                uniqCellIndx[pId] = celllist
4687                break
4688        else: # should not happen
4689            uniqCellIndx[pId] = range(6)
4690        for i in uniqCellIndx[pId]:
4691            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
4692
4693    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
4694    G2frame.dataFrame.SetLabel('Sequential refinement results')
4695    if not G2frame.dataFrame.GetStatusBar():
4696        Status = G2frame.dataFrame.CreateStatusBar()
4697        Status.SetStatusText('')
4698    G2frame.dataFrame.Bind(wx.EVT_MENU, OnRenameSelSeq, id=wxID_RENAMESEQSEL)
4699    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
4700    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=wxID_SAVESEQSELCSV)
4701    G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=wxID_PLOTSEQSEL)
4702    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=wxADDSEQVAR)
4703    G2frame.dataFrame.Bind(wx.EVT_MENU, DelPseudoVar, id=wxDELSEQVAR)
4704    G2frame.dataFrame.Bind(wx.EVT_MENU, EditPseudoVar, id=wxEDITSEQVAR)
4705    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewParFitEq, id=wxADDPARFIT)
4706    G2frame.dataFrame.Bind(wx.EVT_MENU, CopyParFitEq, id=wxCOPYPARFIT)
4707    G2frame.dataFrame.Bind(wx.EVT_MENU, DelParFitEq, id=wxDELPARFIT)
4708    G2frame.dataFrame.Bind(wx.EVT_MENU, EditParFitEq, id=wxEDITPARFIT)
4709    G2frame.dataFrame.Bind(wx.EVT_MENU, DoParEqFit, id=wxDOPARFIT)
4710    EnablePseudoVarMenus()
4711    EnableParFitEqMenus()
4712
4713    #-----------------------------------------------------------------------------------
4714    # build up the data table by columns -----------------------------------------------
4715    nRows = len(histNames)
4716    colList = [nRows*[True]]
4717    colSigs = [None]
4718    colLabels = ['Use']
4719    Types = [wg.GRID_VALUE_BOOL]
4720    # start with Rwp values
4721    if 'IMG ' not in histNames[0][:4]:
4722        colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
4723        colSigs += [None]
4724        colLabels += ['Rwp']
4725        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
4726    # add % change in Chi^2 in last cycle
4727    if histNames[0][:4] not in ['SASD','IMG ']:
4728        colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
4729        colSigs += [None]
4730        colLabels += [u'\u0394\u03C7\u00B2 (%)']
4731        Types += [wg.GRID_VALUE_FLOAT,]
4732    deltaChiCol = len(colLabels)-1
4733    # add changing sample parameters to table
4734    for key in sampleParms:
4735        colList += [sampleParms[key]]
4736        colSigs += [None]
4737        colLabels += [key]
4738        Types += [wg.GRID_VALUE_FLOAT,]
4739    # add unique cell parameters
4740    for pId in sorted(RecpCellTerms):
4741        pfx = str(pId)+'::' # prefix for A values from phase
4742        cells = []
4743        cellESDs = []
4744        colLabels += [pfx+cellUlbl[i] for i in uniqCellIndx[pId]]
4745        colLabels += [pfx+'Vol']
4746        Types += (1+len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT,]
4747        for name in histNames:
4748            covData = {
4749                'varyList': [Dlookup.get(striphist(v),v) for v in data[name]['varyList']],
4750                'covMatrix': data[name]['covMatrix']
4751                }
4752            A = RecpCellTerms[pId][:] # make copy of starting A values
4753            # update with refined values
4754            for i in range(6):
4755                var = str(pId)+'::A'+str(i)
4756                if var in ESDlookup:
4757                    val = data[name]['newCellDict'][ESDlookup[var]][1] # get refined value
4758                    A[i] = val # override with updated value
4759            # apply symmetry
4760            Albls = [pfx+'A'+str(i) for i in range(6)]
4761            cellDict = dict(zip(Albls,A))
4762            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
4763            # convert to direct cell & add only unique values to table
4764            c = G2lat.A2cell(A)
4765            vol = G2lat.calc_V(A)
4766            cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
4767            cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
4768            cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
4769        colList += zip(*cells)
4770        colSigs += zip(*cellESDs)
4771    # add the variables that were refined; change from rows to columns
4772    colList += zip(*[data[name]['variables'] for name in histNames])
4773    colLabels += data[histNames[0]]['varyList']
4774    Types += len(data[histNames[0]]['varyList'])*[wg.GRID_VALUE_FLOAT]
4775    colSigs += zip(*[data[name]['sig'] for name in histNames])
4776
4777    # tabulate constrained variables, removing histogram numbers if needed
4778    # from parameter label
4779    depValDict = {}
4780    depSigDict = {}
4781    for name in histNames:
4782        for var in data[name].get('depParmDict',{}):
4783            val,sig = data[name]['depParmDict'][var]
4784            svar = striphist(var,'*')
4785            if svar not in depValDict:
4786               depValDict[svar] = [val]
4787               depSigDict[svar] = [sig]
4788            else:
4789               depValDict[svar].append(val)
4790               depSigDict[svar].append(sig)
4791    # add the dependent constrained variables to the table
4792    for var in sorted(depValDict):
4793        if len(depValDict[var]) != len(histNames): continue
4794        colLabels.append(var)
4795        Types += [wg.GRID_VALUE_FLOAT,]
4796        colSigs += [depSigDict[var]]
4797        colList += [depValDict[var]]
4798
4799    # add atom parameters to table
4800    colLabels += atomLookup.keys()
4801    Types += len(atomLookup)*[wg.GRID_VALUE_FLOAT]
4802    for parm in sorted(atomLookup):
4803        colList += [[data[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
4804        if atomLookup[parm] in data[histNames[0]]['varyList']:
4805            col = data[histNames[0]]['varyList'].index(atomLookup[parm])
4806            colSigs += [[data[name]['sig'][col] for name in histNames]]
4807        else:
4808            colSigs += [None] # should not happen
4809    # evaluate Pseudovars, their ESDs and add them to grid</