source: branch/logging/GSASIIgrid.py @ 1477

Last change on this file since 1477 was 1477, checked in by toby, 7 years ago

variable change logging finally works

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