source: trunk/GSASIIgrid.py @ 1398

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

add event.Skip to focus event handler, as per recommendation from Robin

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