source: trunk/GSASIIgrid.py @ 1322

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

rework pseudovars and parametric fitting; remove step size for derivatives from expression objects

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