source: trunk/GSASIIgrid.py @ 1498

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

try to implement drag/drop of tree items - commented out as it doesn't work
add 'ShowCell?' to Controls to avoid cell errors for sequential single peak fitting when normal sequential refinement has been done
fix a neg. peak width error

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