source: branch/logging/GSASIIgrid.py @ 1497

Last change on this file since 1497 was 1497, checked in by toby, 9 years ago

logging more complete

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