source: trunk/GSASIIgrid.py @ 1396

Last change on this file since 1396 was 1396, checked in by toby, 8 years ago

fix bugs in multieq seq fits; print results better

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