source: trunk/GSASIIgrid.py @ 1402

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

make pseudovariables work for strain seq. results
fix Pos2dsp for TOF
allow plot of TOF vs d-spacing for indexed peaks - might be useful for getting TOF to d-space coefficients - uses PlotXY

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