source: trunk/GSASIIgrid.py @ 1390

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

revise start, sort out phase fraction vs scale factor label, add configurable label for columns in sequential refinements. Note: the dict (variableLabels) is not saved yet when a seq. refinement is repeated.

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