source: trunk/GSASIIgrid.py @ 1496

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

Add sequential peak fitting.
Change Back:n to Back;n for background parameters
Also DebyeA, etc.

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