source: trunk/GSASIIgrid.py @ 1403

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

allow renaming of multiple columns in seq refinement results
allow changing of plot titles for seq plots.
new dialog - MultiStringDialog?

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