source: trunk/GSASIIgrid.py @ 1335

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

Redo pseudovar computation to handle constraint-generated vars

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 219.4 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2014-05-07 19:38:30 +0000 (Wed, 07 May 2014) $
5# $Author: toby $
6# $Revision: 1335 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1335 2014-05-07 19:38:30Z 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: 1335 $")
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(rows=len(dictlst),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(2,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(2,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=str(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(rows=len(parmDict)+1,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 function',
2532            help='Add a new function to minimize')
2533        self.SequentialPfit.Append(
2534            id=wxDELPARFIT, kind=wx.ITEM_NORMAL,text='Delete function',
2535            help='Delete a function for parametric minimization')
2536        self.SequentialPfit.Append(
2537            id=wxEDITPARFIT, kind=wx.ITEM_NORMAL,text='Edit function',
2538            help='Edit an existing parametric minimization function')
2539        self.SequentialPfit.Append(
2540            id=wxDOPARFIT, kind=wx.ITEM_NORMAL,text='Fit to function(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        '''code to display a tooltip for each item on a grid
3150        from http://wiki.wxpython.org/wxGrid%20ToolTips
3151
3152        :param function rowcolhintcallback: a routine that returns a text
3153          string depending on the selected row and column
3154        '''
3155        prev_rowcol = [None,None]
3156        def OnMouseMotion(evt):
3157            # evt.GetRow() and evt.GetCol() would be nice to have here,
3158            # but as this is a mouse event, not a grid event, they are not
3159            # available and we need to compute them by hand.
3160            x, y = self.CalcUnscrolledPosition(evt.GetPosition())
3161            row = self.YToRow(y)
3162            col = self.XToCol(x)
3163
3164            if (row,col) != prev_rowcol and row >= 0 and col >= 0:
3165                prev_rowcol[:] = [row,col]
3166                hinttext = rowcolhintcallback(row, col)
3167                if hinttext is None:
3168                    hinttext = ''
3169                self.GetGridWindow().SetToolTipString(hinttext)
3170            evt.Skip()
3171
3172        wx.EVT_MOTION(self.GetGridWindow(), OnMouseMotion)
3173                                               
3174################################################################################
3175#####  Table
3176################################################################################           
3177       
3178class Table(wg.PyGridTableBase):
3179    '''Basic data table for use with GSgrid
3180    '''
3181    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
3182        wg.PyGridTableBase.__init__(self)
3183        self.colLabels = colLabels
3184        self.rowLabels = rowLabels
3185        self.dataTypes = types
3186        self.data = data
3187       
3188    def AppendRows(self, numRows=1):
3189        self.data.append([])
3190        return True
3191       
3192    def CanGetValueAs(self, row, col, typeName):
3193        if self.dataTypes:
3194            colType = self.dataTypes[col].split(':')[0]
3195            if typeName == colType:
3196                return True
3197            else:
3198                return False
3199        else:
3200            return False
3201
3202    def CanSetValueAs(self, row, col, typeName):
3203        return self.CanGetValueAs(row, col, typeName)
3204
3205    def DeleteRow(self,pos):
3206        data = self.GetData()
3207        self.SetData([])
3208        new = []
3209        for irow,row in enumerate(data):
3210            if irow <> pos:
3211                new.append(row)
3212        self.SetData(new)
3213       
3214    def GetColLabelValue(self, col):
3215        if self.colLabels:
3216            return self.colLabels[col]
3217           
3218    def GetData(self):
3219        data = []
3220        for row in range(self.GetNumberRows()):
3221            data.append(self.GetRowValues(row))
3222        return data
3223       
3224    def GetNumberCols(self):
3225        try:
3226            return len(self.colLabels)
3227        except TypeError:
3228            return None
3229       
3230    def GetNumberRows(self):
3231        return len(self.data)
3232       
3233    def GetRowLabelValue(self, row):
3234        if self.rowLabels:
3235            return self.rowLabels[row]
3236       
3237    def GetColValues(self, col):
3238        data = []
3239        for row in range(self.GetNumberRows()):
3240            data.append(self.GetValue(row, col))
3241        return data
3242       
3243    def GetRowValues(self, row):
3244        data = []
3245        for col in range(self.GetNumberCols()):
3246            data.append(self.GetValue(row, col))
3247        return data
3248       
3249    def GetTypeName(self, row, col):
3250        try:
3251            return self.dataTypes[col]
3252        except TypeError:
3253            return None
3254
3255    def GetValue(self, row, col):
3256        try:
3257            return self.data[row][col]
3258        except IndexError:
3259            return None
3260           
3261    def InsertRows(self, pos, rows):
3262        for row in range(rows):
3263            self.data.insert(pos,[])
3264            pos += 1
3265       
3266    def IsEmptyCell(self,row,col):
3267        try:
3268            return not self.data[row][col]
3269        except IndexError:
3270            return True
3271       
3272    def OnKeyPress(self, event):
3273        dellist = self.GetSelectedRows()
3274        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
3275            grid = self.GetView()
3276            for i in dellist: grid.DeleteRow(i)
3277               
3278    def SetColLabelValue(self, col, label):
3279        numcols = self.GetNumberCols()
3280        if col > numcols-1:
3281            self.colLabels.append(label)
3282        else:
3283            self.colLabels[col]=label
3284       
3285    def SetData(self,data):
3286        for row in range(len(data)):
3287            self.SetRowValues(row,data[row])
3288               
3289    def SetRowLabelValue(self, row, label):
3290        self.rowLabels[row]=label
3291           
3292    def SetRowValues(self,row,data):
3293        self.data[row] = data
3294           
3295    def SetValue(self, row, col, value):
3296        def innerSetValue(row, col, value):
3297            try:
3298                self.data[row][col] = value
3299            except TypeError:
3300                return
3301            except IndexError:
3302                print row,col,value
3303                # add a new row
3304                if row > self.GetNumberRows():
3305                    self.data.append([''] * self.GetNumberCols())
3306                elif col > self.GetNumberCols():
3307                    for row in range(self.GetNumberRows):
3308                        self.data[row].append('')
3309                print self.data
3310                self.data[row][col] = value
3311        innerSetValue(row, col, value)
3312       
3313################################################################################
3314#### Help
3315################################################################################
3316
3317def ShowHelp(helpType,frame):
3318    '''Called to bring up a web page for documentation.'''
3319    global htmlFirstUse
3320    # look up a definition for help info from dict
3321    helplink = helpLocDict.get(helpType)
3322    if helplink is None:
3323        # no defined link to use, create a default based on key
3324        helplink = 'gsasII.html#'+helpType.replace(' ','_')
3325    helplink = os.path.join(path2GSAS2,'help',helplink)
3326    if helpMode == 'internal':
3327        try:
3328            htmlPanel.LoadFile(helplink)
3329            htmlFrame.Raise()
3330        except:
3331            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
3332            htmlFrame.Show(True)
3333            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
3334            htmlPanel = MyHtmlPanel(htmlFrame,-1)
3335            htmlPanel.LoadFile(helplink)
3336    else:
3337        pfx = "file://"
3338        if sys.platform.lower().startswith('win'):
3339            pfx = ''
3340        if htmlFirstUse:
3341            webbrowser.open_new(pfx+helplink)
3342            htmlFirstUse = False
3343        else:
3344            webbrowser.open(pfx+helplink, new=0, autoraise=True)
3345
3346################################################################################
3347#####  Notebook
3348################################################################################           
3349       
3350def UpdateNotebook(G2frame,data):
3351    '''Called when the data tree notebook entry is selected. Allows for
3352    editing of the text in that tree entry
3353    '''
3354    def OnNoteBook(event):
3355        data = G2frame.dataDisplay.GetValue().split('\n')
3356        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
3357        if 'nt' not in os.name:
3358            G2frame.dataDisplay.AppendText('\n')
3359                   
3360    if G2frame.dataDisplay:
3361        G2frame.dataDisplay.Destroy()
3362    G2frame.dataFrame.SetLabel('Notebook')
3363    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3364        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
3365    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
3366    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
3367    for line in data:
3368        G2frame.dataDisplay.AppendText(line+"\n")
3369    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
3370    G2frame.dataFrame.setSizePosLeft([400,250])
3371           
3372################################################################################
3373#####  Controls
3374################################################################################           
3375       
3376def UpdateControls(G2frame,data):
3377    '''Edit overall GSAS-II controls in main Controls data tree entry
3378    '''
3379    #patch
3380    if 'deriv type' not in data:
3381        data = {}
3382        data['deriv type'] = 'analytic Hessian'
3383        data['min dM/M'] = 0.0001
3384        data['shift factor'] = 1.
3385        data['max cyc'] = 3       
3386        data['F**2'] = True
3387        data['minF/sig'] = 0
3388    if 'shift factor' not in data:
3389        data['shift factor'] = 1.
3390    if 'max cyc' not in data:
3391        data['max cyc'] = 3
3392    if 'F**2' not in data:
3393        data['F**2'] = True
3394        data['minF/sig'] = 0
3395    if 'Author' not in data:
3396        data['Author'] = 'no name'
3397    if 'FreePrm1' not in data:
3398        data['FreePrm1'] = 'Sample humidity (%)'
3399    if 'FreePrm2' not in data:
3400        data['FreePrm2'] = 'Sample voltage (V)'
3401    if 'FreePrm3' not in data:
3402        data['FreePrm3'] = 'Applied load (MN)'
3403    if 'Copy2Next' not in data:
3404        data['Copy2Next'] = False
3405    if 'Reverse Seq' not in data:
3406        data['Reverse Seq'] = False   
3407     
3408   
3409    #end patch
3410
3411    def SeqSizer():
3412       
3413        def OnSelectData(event):
3414            choices = GetPatternTreeDataNames(G2frame,['PWDR',])
3415            sel = []
3416            if 'Seq Data' in data:
3417                for item in data['Seq Data']:
3418                    sel.append(choices.index(item))
3419                sel = [choices.index(item) for item in data['Seq Data']]
3420            dlg = G2MultiChoiceDialog(G2frame.dataFrame, 'Sequential refinement',
3421                                      'Select dataset to include',
3422                                      choices)
3423            dlg.SetSelections(sel)
3424            names = []
3425            if dlg.ShowModal() == wx.ID_OK:
3426                for sel in dlg.GetSelections():
3427                    names.append(choices[sel])
3428                data['Seq Data'] = names               
3429                G2frame.EnableSeqRefineMenu()
3430            dlg.Destroy()
3431            wx.CallAfter(UpdateControls,G2frame,data)
3432           
3433        def OnReverse(event):
3434            data['Reverse Seq'] = reverseSel.GetValue()
3435           
3436        def OnCopySel(event):
3437            data['Copy2Next'] = copySel.GetValue() 
3438                   
3439        seqSizer = wx.BoxSizer(wx.VERTICAL)
3440        dataSizer = wx.BoxSizer(wx.HORIZONTAL)
3441        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement: '),0,WACV)
3442        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3443        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3444        dataSizer.Add(selSeqData,0,WACV)
3445        SeqData = data.get('Seq Data',[])
3446        if not SeqData:
3447            lbl = ' (no powder data selected)'
3448        else:
3449            lbl = ' ('+str(len(SeqData))+' dataset(s) selected)'
3450
3451        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=lbl),0,WACV)
3452        seqSizer.Add(dataSizer,0)
3453        if SeqData:
3454            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3455            reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3456            reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3457            reverseSel.SetValue(data['Reverse Seq'])
3458            selSizer.Add(reverseSel,0,WACV)
3459            copySel =  wx.CheckBox(G2frame.dataDisplay,-1,label=' Copy results to next histogram?')
3460            copySel.Bind(wx.EVT_CHECKBOX,OnCopySel)
3461            copySel.SetValue(data['Copy2Next'])
3462            selSizer.Add(copySel,0,WACV)
3463            seqSizer.Add(selSizer,0)
3464        return seqSizer
3465       
3466    def LSSizer():       
3467       
3468        def OnDerivType(event):
3469            data['deriv type'] = derivSel.GetValue()
3470            derivSel.SetValue(data['deriv type'])
3471            wx.CallAfter(UpdateControls,G2frame,data)
3472           
3473        def OnConvergence(event):
3474            try:
3475                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3476            except ValueError:
3477                value = 0.0001
3478            data['min dM/M'] = value
3479            Cnvrg.SetValue('%.2g'%(value))
3480           
3481        def OnMaxCycles(event):
3482            data['max cyc'] = int(maxCyc.GetValue())
3483            maxCyc.SetValue(str(data['max cyc']))
3484                       
3485        def OnFactor(event):
3486            try:
3487                value = min(max(float(Factr.GetValue()),0.00001),100.)
3488            except ValueError:
3489                value = 1.0
3490            data['shift factor'] = value
3491            Factr.SetValue('%.5f'%(value))
3492           
3493        def OnFsqRef(event):
3494            data['F**2'] = fsqRef.GetValue()
3495       
3496        def OnMinSig(event):
3497            try:
3498                value = min(max(float(minSig.GetValue()),0.),5.)
3499            except ValueError:
3500                value = 1.0
3501            data['minF/sig'] = value
3502            minSig.SetValue('%.2f'%(value))
3503
3504        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3505        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,WACV)
3506        Choice=['analytic Jacobian','numeric','analytic Hessian']
3507        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3508            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3509        derivSel.SetValue(data['deriv type'])
3510        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3511           
3512        LSSizer.Add(derivSel,0,WACV)
3513        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,WACV)
3514        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3515        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3516        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3517        LSSizer.Add(Cnvrg,0,WACV)
3518        if 'Hessian' in data['deriv type']:
3519            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,WACV)
3520            Choice = ['0','1','2','3','5','10','15','20']
3521            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3522                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3523            maxCyc.SetValue(str(data['max cyc']))
3524            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3525            LSSizer.Add(maxCyc,0,WACV)
3526        else:
3527            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,WACV)
3528            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3529            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3530            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3531            LSSizer.Add(Factr,0,WACV)
3532        if G2frame.Sngl:
3533            LSSizer.Add((1,0),)
3534            LSSizer.Add((1,0),)
3535            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3536            fsqRef.SetValue(data['F**2'])
3537            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3538            LSSizer.Add(fsqRef,0,WACV)
3539            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,WACV)
3540            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3541            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3542            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3543            LSSizer.Add(minSig,0,WACV)
3544        return LSSizer
3545       
3546    def AuthSizer():
3547
3548        def OnAuthor(event):
3549            data['Author'] = auth.GetValue()
3550
3551        Author = data['Author']
3552        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3553        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,WACV)
3554        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3555        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3556        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3557        authSizer.Add(auth,0,WACV)
3558        return authSizer
3559       
3560       
3561    if G2frame.dataDisplay:
3562        G2frame.dataDisplay.Destroy()
3563    if not G2frame.dataFrame.GetStatusBar():
3564        Status = G2frame.dataFrame.CreateStatusBar()
3565        Status.SetStatusText('')
3566    G2frame.dataFrame.SetLabel('Controls')
3567    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3568    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3569    mainSizer = wx.BoxSizer(wx.VERTICAL)
3570    mainSizer.Add((5,5),0)
3571    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,WACV)   
3572    mainSizer.Add(LSSizer())
3573    mainSizer.Add((5,5),0)
3574    mainSizer.Add(SeqSizer())
3575    mainSizer.Add((5,5),0)
3576    mainSizer.Add(AuthSizer())
3577    mainSizer.Add((5,5),0)
3578       
3579    mainSizer.Layout()   
3580    G2frame.dataDisplay.SetSizer(mainSizer)
3581    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3582    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3583     
3584################################################################################
3585#####  Comments
3586################################################################################           
3587       
3588def UpdateComments(G2frame,data):                   
3589
3590    if G2frame.dataDisplay:
3591        G2frame.dataDisplay.Destroy()
3592    G2frame.dataFrame.SetLabel('Comments')
3593    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3594        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3595    for line in data:
3596        G2frame.dataDisplay.AppendText(line+'\n')
3597    G2frame.dataFrame.setSizePosLeft([400,250])
3598           
3599################################################################################
3600#####  Display of Sequential Results
3601################################################################################           
3602       
3603def UpdateSeqResults(G2frame,data,prevSize=None):
3604    """
3605    Called when the Sequential Results data tree entry is selected
3606    to show results from a sequential refinement.
3607   
3608    :param wx.Frame G2frame: main GSAS-II data tree windows
3609
3610    :param dict data: a dictionary containing the following items: 
3611
3612            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3613            * 'varyList' - list of variables - identical over all refinements in sequence
3614              note that this is the original list of variables, prior to processing
3615              constraints.
3616            * keyed by histName - dictionaries for all data sets processed, which contains:
3617
3618              * 'variables'- result[0] from leastsq call
3619              * 'varyList' - list of variables passed to leastsq call (not same as above)
3620              * 'sig' - esds for variables
3621              * 'covMatrix' - covariance matrix from individual refinement
3622              * 'title' - histogram name; same as dict item name
3623              * 'newAtomDict' - new atom parameters after shifts applied
3624              * 'newCellDict' - refined cell parameters after shifts to A0-A5 from Dij terms applied'
3625    """
3626
3627    def GetSampleParms():
3628        '''Make a dictionary of the sample parameters are not the same over the
3629        refinement series.
3630        '''
3631        sampleParmDict = {'Temperature':[],'Pressure':[],
3632                          'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],}
3633        Controls = G2frame.PatternTree.GetItemPyData(
3634            GetPatternTreeItemId(G2frame,G2frame.root, 'Controls'))
3635        sampleParm = {}
3636        for name in histNames:
3637            Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3638            sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3639            for item in sampleParmDict:
3640                sampleParmDict[item].append(sampleData[item])
3641        for item in sampleParmDict:
3642            frstValue = sampleParmDict[item][0]
3643            if np.any(np.array(sampleParmDict[item])-frstValue):
3644                if item.startswith('FreePrm'):
3645                    sampleParm[Controls[item]] = sampleParmDict[item]
3646                else:
3647                    sampleParm[item] = sampleParmDict[item]
3648        return sampleParm
3649
3650    def GetColumnInfo(col):
3651        '''returns column label, lists of values and errors (or None) for each column in the table
3652        for plotting. The column label is reformatted from Unicode to MatPlotLib encoding
3653        '''
3654        plotName = plotSpCharFix(G2frame.SeqTable.GetColLabelValue(col))
3655        return plotName,colList[col],colSigs[col]
3656           
3657    def PlotSelect(event):
3658        'Plots a row (covariance) or column on double-click'
3659        cols = G2frame.dataDisplay.GetSelectedCols()
3660        rows = G2frame.dataDisplay.GetSelectedRows()
3661        if cols:
3662            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
3663        elif rows:
3664            name = histNames[rows[0]]       #only does 1st one selected
3665            G2plt.PlotCovariance(G2frame,data[name])
3666        else:
3667            G2frame.ErrorDialog(
3668                'Select row or columns',
3669                'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.'
3670                )
3671           
3672    def OnPlotSelSeq(event):
3673        'plot the selected columns or row from menu command'
3674        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
3675        rows = G2frame.dataDisplay.GetSelectedRows()
3676        if cols:
3677            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
3678        elif rows:
3679            name = histNames[rows[0]]       #only does 1st one selected
3680            G2plt.PlotCovariance(G2frame,data[name])
3681        else:
3682            G2frame.ErrorDialog(
3683                'Select columns',
3684                'No columns or rows selected in table. Click on row or column labels to select fields for plotting.'
3685                )
3686           
3687    def OnSaveSelSeqCSV(event):
3688        'export the selected columns to a .csv file from menu command'
3689        OnSaveSelSeq(event,csv=True)
3690       
3691    def OnSaveSelSeq(event,csv=False):
3692        'export the selected columns to a .txt file from menu command'
3693        def WriteCSV():
3694            def WriteList(headerItems):
3695                line = ''
3696                for lbl in headerItems:
3697                    if line: line += ','
3698                    line += '"'+lbl+'"'
3699                return line
3700            head = ['name']
3701            for col in cols:
3702                item = G2frame.SeqTable.GetColLabelValue(col)
3703                if col in havesig:
3704                    head += [item,'esd-'+item]
3705                else:
3706                    head += [item]
3707            SeqFile.write(WriteList(head)+'\n')
3708            for row,name in enumerate(saveNames):
3709                line = '"'+saveNames[row]+'"'
3710                for col in cols:
3711                    if col in havesig:
3712                        line += ','+str(saveData[col][row])+','+str(saveSigs[col][row])
3713                    else:
3714                        line += ','+str(saveData[col][row])
3715                SeqFile.write(line+'\n')
3716        def WriteSeq():
3717            lenName = len(saveNames[0])
3718            line = %s  '%('name'.center(lenName))
3719            for col in cols:
3720                item = G2frame.SeqTable.GetColLabelValue(col)
3721                if col in havesig:
3722                    line += ' %12s %12s '%(item.center(12),'esd'.center(12))
3723                else:
3724                    line += ' %12s '%(item.center(12))
3725            SeqFile.write(line+'\n')
3726            for row,name in enumerate(saveNames):
3727                line = " '%s' "%(saveNames[row])
3728                for col in cols:
3729                    if col in havesig:
3730                        line += ' %12.6f %12.6f '%(saveData[col][row],saveSigs[col][row])
3731                    else:
3732                        line += ' %12.6f '%saveData[col][row]
3733                SeqFile.write(line+'\n')
3734
3735        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
3736        nrows = G2frame.SeqTable.GetNumberRows()
3737        if not cols:
3738            G2frame.ErrorDialog('Select columns',
3739                             'No columns selected in table. Click on column labels to select fields for output.')
3740            return
3741        saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(nrows)]
3742        saveData = {}
3743        saveSigs = {}
3744        havesig = []
3745        for col in cols:
3746            name,vals,sigs = GetColumnInfo(col)
3747            saveData[col] = vals
3748            if sigs:
3749                havesig.append(col)
3750                saveSigs[col] = sigs
3751        if csv:
3752            wild = 'CSV output file (*.csv)|*.csv'
3753        else:
3754            wild = 'Text output file (*.txt)|*.txt'
3755        dlg = wx.FileDialog(
3756            G2frame,
3757            'Choose text output file for your selection', '.', '', 
3758            wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
3759        try:
3760            if dlg.ShowModal() == wx.ID_OK:
3761                SeqTextFile = dlg.GetPath()
3762                SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) 
3763                SeqFile = open(SeqTextFile,'w')
3764                if csv:
3765                    WriteCSV()
3766                else:
3767                    WriteSeq()
3768                SeqFile.close()
3769        finally:
3770            dlg.Destroy()
3771               
3772    def striphist(var,insChar=''):
3773        'strip a histogram number from a var name'
3774        sv = var.split(':')
3775        if len(sv) <= 1: return var
3776        sv[1] = insChar
3777        return ':'.join(sv)
3778       
3779    def plotSpCharFix(lbl):
3780        'Change selected unicode characters to their matplotlib equivalent'
3781        for u,p in [
3782            (u'\u03B1',r'$\alpha$'),
3783            (u'\u03B2',r'$\beta$'),
3784            (u'\u03B3',r'$\gamma$'),
3785            (u'\u0394\u03C7',r'$\Delta\chi$'),
3786            ]:
3787            lbl = lbl.replace(u,p)
3788        return lbl
3789   
3790    def SelectXaxis():
3791        'returns a selected column number (or None) as the X-axis selection'
3792        ncols = G2frame.SeqTable.GetNumberCols()
3793        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
3794        dlg = G2SingleChoiceDialog(
3795            G2frame.dataDisplay,
3796            'Select x-axis parameter for plot or Cancel for sequence number',
3797            'Select X-axis',
3798            colNames)
3799        try:
3800            if dlg.ShowModal() == wx.ID_OK:
3801                col = dlg.GetSelection()
3802            else:
3803                col = None
3804        finally:
3805            dlg.Destroy()
3806        return col
3807   
3808    def EnablePseudoVarMenus():
3809        'Enables or disables the PseudoVar menu items that require existing defs'
3810        if Controls['SeqPseudoVars']:
3811            val = True
3812        else:
3813            val = False
3814        G2frame.dataFrame.SequentialPvars.Enable(wxDELSEQVAR,val)
3815        G2frame.dataFrame.SequentialPvars.Enable(wxEDITSEQVAR,val)
3816
3817    def DelPseudoVar(event):
3818        'Ask the user to select a pseudo var expression to delete'
3819        choices = Controls['SeqPseudoVars'].keys()
3820        selected = ItemSelector(
3821            choices,G2frame.dataFrame,
3822            multiple=True,
3823            title='Select expressions to remove',
3824            header='Delete expression')
3825        if selected is None: return
3826        for item in selected:
3827            del Controls['SeqPseudoVars'][choices[item]]
3828        if selected:
3829            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3830
3831    def EditPseudoVar(event):
3832        'Edit an existing pseudo var expression'
3833        choices = Controls['SeqPseudoVars'].keys()
3834        if len(choices) == 1:
3835            selected = 0
3836        else:
3837            selected = ItemSelector(
3838                choices,G2frame.dataFrame,
3839                multiple=False,
3840                title='Select an expression to edit',
3841                header='Edit expression')
3842        if selected is not None:
3843            dlg = G2exG.ExpressionDialog(
3844                G2frame.dataDisplay,PSvarDict,
3845                Controls['SeqPseudoVars'][choices[selected]],
3846                header="Edit the PseudoVar expression",
3847                VarLabel="PseudoVar #"+str(selected+1),
3848                fit=False)
3849            newobj = dlg.Show(True)
3850            if newobj:
3851                calcobj = G2obj.ExpressionCalcObj(newobj)
3852                del Controls['SeqPseudoVars'][choices[selected]]
3853                Controls['SeqPseudoVars'][calcobj.eObj.expression] = newobj
3854                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3855       
3856    def AddNewPseudoVar(event):
3857        'Create a new pseudo var expression'
3858        dlg = G2exG.ExpressionDialog(
3859            G2frame.dataDisplay,PSvarDict,
3860            header='Enter an expression for a PseudoVar here',
3861            VarLabel = "New PseudoVar",
3862            fit=False)
3863        obj = dlg.Show(True)
3864        dlg.Destroy()
3865        if obj:
3866            calcobj = G2obj.ExpressionCalcObj(obj)
3867            Controls['SeqPseudoVars'][calcobj.eObj.expression] = obj
3868            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3869
3870    def CreatePSvarDict(seqnum,name):
3871        '''Create a parameter dict (parmDict) for everything that might be used
3872        in a PseudoVar.
3873        Also creates a list of revised labels (modVaryList) for the covariance matrix to
3874        match the items in the parameter dict and a matching list of ESDs (ESDvaryList).
3875       
3876        :param int seqnum: the sequence number of the histogram in the sequential
3877          refinement
3878        :param str name: the name of the histogram in the data tree
3879
3880        :returns: parmDict,modVaryList,ESDvaryList
3881        '''
3882        parmDict = {}
3883        modVaryList = []
3884        for i,(key,val) in enumerate(zip(data[name]['varyList'],data[name]['variables'])):
3885            skey = striphist(key)
3886            if skey in data[name].get('newAtomDict',{}):
3887                # replace coordinate shifts with equivalents from lookup table
3888                repkey,repval = data[name]['newAtomDict'][skey]
3889                parmDict[repkey] = repval
3890                modVaryList.append(repkey)
3891            elif skey in data[name].get('newCellDict',{}):
3892                # replace recip. cell term shifts with equivalents from lookup table       
3893                repkey,repval = data[name]['newCellDict'][skey]
3894                parmDict[repkey] = repval
3895                modVaryList.append(repkey)
3896            else:
3897                parmDict[key] = val
3898                modVaryList.append(key)
3899        # create a cell parm dict, override initial settings with values in parmDict
3900        for phase in Phases:
3901            phasedict = Phases[phase]
3902            pId = phasedict['pId']
3903            cell = Rcelldict.copy()
3904            cell.update(
3905                {lbl:parmDict[lbl] for lbl in RcellLbls[pId] if lbl in parmDict}
3906                )
3907            pfx = str(pId)+'::' # prefix for A values from phase
3908            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cell,zeroDict[pId])
3909            parmDict.update({pfx+cellUlbl[i]:val for i,val in
3910                             enumerate(G2lat.A2cell(A))
3911                             if i in uniqCellIndx[pId]
3912                             })
3913            parmDict[pfx+"vol"] = G2lat.calc_V(A)
3914        # now add misc terms to dict
3915        parmDict['Rwp'] = data[name]['Rvals']['Rwp']
3916        parmDict[u'\u0394\u03C7\u00B2 (%)'] = 100.*data[name]['Rvals'].get('DelChi2',-1)
3917        for key in sampleParms:
3918            parmDict[key] = sampleParms[key][seqnum]
3919        return parmDict,modVaryList,data[name]['sig']
3920
3921    def UpdateParmDict(parmDict):
3922        '''generate the atom positions and the direct & reciprocal cell values,
3923        because they might be needed to evaluate the pseudovar
3924        '''
3925        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3926                         ['A'+str(i) for i in range(6)])
3927                     )
3928        delList = []
3929        phaselist = []
3930        for item in parmDict: 
3931            if ':' not in item: continue
3932            key = item.split(':')
3933            if len(key) < 3: continue
3934            # remove the dA[xyz] terms, they would only bring confusion
3935            if key[2].startswith('dA'):
3936                delList.append(item)
3937            # compute and update the corrected reciprocal cell terms using the Dij values
3938            elif key[2] in Ddict:
3939                if key[0] not in phaselist: phaselist.append(key[0])
3940                akey = key[0]+'::'+Ddict[key[2]]
3941                parmDict[akey] += parmDict[item]
3942                delList.append(item)
3943        for item in delList:
3944            del parmDict[item]               
3945        for i in phaselist:
3946            pId = int(i)
3947            # apply cell symmetry
3948            A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
3949            # convert to direct cell & add the unique terms to the dictionary
3950            for i,val in enumerate(G2lat.A2cell(A)):
3951                if i in uniqCellIndx[pId]:
3952                    lbl = str(pId)+'::'+cellUlbl[i]
3953                    parmDict[lbl] = val
3954            lbl = str(pId)+'::'+'vol'
3955            parmDict[lbl] = G2lat.calc_V(A)
3956        return parmDict
3957
3958    def EvalPSvarDeriv(calcobj,parmDict,var,ESD):
3959        '''Evaluate an expression derivative with respect to a
3960        GSAS-II variable name.
3961
3962        Note this likely could be faster if the loop over calcobjs were done
3963        inside after the Dict was created.
3964        '''
3965        step = ESD/10
3966        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3967                         ['A'+str(i) for i in range(6)])
3968                     )
3969        results = []
3970        phaselist = []
3971        for incr in step,-step:
3972            VparmDict = parmDict.copy()           
3973            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
3974            # values are not zeroed: fix that!
3975            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
3976            VparmDict[var] += incr
3977            G2mv.Dict2Map(VparmDict,[]) # apply constraints
3978            # generate the atom positions and the direct & reciprocal cell values now, because they might
3979            # needed to evaluate the pseudovar
3980            for item in VparmDict: 
3981                if ':' not in item: continue
3982                key = item.split(':')
3983                if len(key) < 3: continue
3984                # apply any new shifts to atom positions
3985                if key[2].startswith('dA'):
3986                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
3987                    VparmDict[item] = 0.0
3988                # compute and update the corrected reciprocal cell terms using the Dij values
3989                if key[2] in Ddict:
3990                    if key[0] not in phaselist: phaselist.append(key[0])
3991                    akey = key[0]+'::'+Ddict[key[2]]
3992                    VparmDict[akey] += VparmDict[item]
3993            for i in phaselist:
3994                pId = int(i)
3995                # apply cell symmetry
3996                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
3997                # convert to direct cell & add the unique terms to the dictionary
3998                for i,val in enumerate(G2lat.A2cell(A)):
3999                    if i in uniqCellIndx[pId]:
4000                        lbl = str(pId)+'::'+cellUlbl[i]
4001                        VparmDict[lbl] = val
4002                lbl = str(pId)+'::'+'vol'
4003                VparmDict[lbl] = G2lat.calc_V(A)
4004            # dict should be fully updated, use it & calculate
4005            calcobj.SetupCalc(VparmDict)
4006            results.append(calcobj.EvalExpression())
4007        return (results[0] - results[1]) / (2.*step)
4008       
4009    def EnableParFitEqMenus():
4010        'Enables or disables the Parametric Fit menu items that require existing defs'
4011        if Controls['SeqParFitEqs']:
4012            val = True
4013        else:
4014            val = False
4015        G2frame.dataFrame.SequentialPfit.Enable(wxDELPARFIT,val)
4016        G2frame.dataFrame.SequentialPfit.Enable(wxEDITPARFIT,val)
4017        G2frame.dataFrame.SequentialPfit.Enable(wxDOPARFIT,val)
4018
4019    def DelParFitEq(event):
4020        'Ask the user to select function to delete'
4021        choices = Controls['SeqParFitEqs'].keys()
4022        selected = ItemSelector(
4023            choices,G2frame.dataFrame,
4024            multiple=True,
4025            title='Select functions to remove',
4026            header='Delete function')
4027        if selected is None: return
4028        for item in selected:
4029            del Controls['SeqParFitEqs'][choices[item]]
4030        EnableParFitEqMenus()
4031        if Controls['SeqParFitEqs']: DoParEqFit(event)
4032       
4033    def EditParFitEq(event):
4034        'Edit an existing parametric equation'
4035        choices = Controls['SeqParFitEqs'].keys()
4036        if len(choices) == 1:
4037            selected = 0
4038        else:
4039            selected = ItemSelector(
4040                choices,G2frame.dataFrame,
4041                multiple=False,
4042                title='Select a function to edit',
4043                header='Edit function')
4044        if selected is not None:
4045            dlg = G2exG.ExpressionDialog(
4046                G2frame.dataDisplay,indepVarDict,
4047                Controls['SeqParFitEqs'][choices[selected]],
4048                depVarDict=depVarDict,
4049                header="Edit this minimization function's formula")
4050            newobj = dlg.Show(True)
4051            if newobj:
4052                calcobj = G2obj.ExpressionCalcObj(newobj)
4053                del Controls['SeqParFitEqs'][choices[selected]]
4054                Controls['SeqParFitEqs'][calcobj.eObj.expression] = newobj
4055            EnableParFitEqMenus()
4056        if Controls['SeqParFitEqs']: DoParEqFit(event)
4057
4058    def AddNewParFitEq(event):
4059        'Create a new parametric equation to be fit to sequential results'
4060        dlg = G2exG.ExpressionDialog(
4061            G2frame.dataDisplay,indepVarDict,
4062            depVarDict=depVarDict,
4063            header='Enter a function to minimize in the parametric fit')
4064        obj = dlg.Show(True)
4065        dlg.Destroy()
4066        if obj:
4067            calcobj = G2obj.ExpressionCalcObj(obj)
4068            Controls['SeqParFitEqs'][calcobj.eObj.expression] = obj
4069            EnableParFitEqMenus()
4070        if Controls['SeqParFitEqs']: DoParEqFit(event)
4071           
4072    def ParEqEval(Values,calcObjList,varyList):
4073        '''Evaluate the parametric expression(s)
4074        :param list Values: a list of values for each variable parameter
4075        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
4076          expression objects to evaluate
4077        :param list varyList: a list of variable names for each value in Values
4078        '''
4079        result = []
4080        for calcobj in calcObjList:
4081            calcobj.UpdateVars(varyList,Values)
4082            result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
4083        return result
4084
4085    def DoParEqFit(event):
4086        'Parametric fit minimizer'
4087        varyValueDict = {} # dict of variables and their initial values
4088        calcObjList = [] # expression objects, ready to go for each data point
4089        for expr in Controls['SeqParFitEqs']:
4090            obj = Controls['SeqParFitEqs'][expr]
4091            # assemble refined vars for this equation
4092            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
4093            # lookup dependent var position
4094            depVar = obj.GetDepVar()
4095            if depVar in colLabels:
4096                indx = colLabels.index(depVar)
4097            else:
4098                raise Exception('Dependent variable '+depVar+' not found')
4099            # assemble a list of the independent variables
4100            indepVars = obj.GetIndependentVars()
4101            # loop over each datapoint
4102            for j,row in enumerate(zip(*colList)):
4103                # assemble equations to fit
4104                calcobj = G2obj.ExpressionCalcObj(obj)
4105                # prepare a dict of needed independent vars for this expression
4106                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4107                calcobj.SetupCalc(indepVarDict)               
4108                # values and sigs for current value of dependent var
4109                calcobj.depVal = row[indx]
4110                calcobj.depSig = colSigs[indx][j]
4111                calcObjList.append(calcobj)
4112        # varied parameters
4113        varyList = varyValueDict.keys()
4114        varyValues = [varyValueDict[key] for key in varyList]
4115        result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
4116                            args=(calcObjList,varyList)
4117                            )
4118        # create a plot for each parametric variable
4119        values = result[0]
4120        covar = result[1]
4121        print 'Fit Results'
4122        for i,(var,val) in enumerate(zip(varyList,values)):
4123            print '  ',var,' =',G2mth.ValEsd(val,np.sqrt(covar[i,i]))
4124        for fitnum,expr in enumerate(Controls['SeqParFitEqs']):
4125            obj = Controls['SeqParFitEqs'][expr]
4126            obj.UpdateVariedVars(varyList,values)
4127            calcobj = G2obj.ExpressionCalcObj(obj)
4128            # lookup dependent var position
4129            indx = colLabels.index(obj.GetDepVar())
4130            # assemble a list of the independent variables
4131            indepVars = obj.GetIndependentVars()           
4132            # loop over each datapoint
4133            fitvals = []
4134            for j,row in enumerate(zip(*colList)):
4135                calcobj.SetupCalc(
4136                    {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
4137                    )
4138                fitvals.append(calcobj.EvalExpression())
4139            G2plt.PlotSelectedSequence(G2frame,[indx],GetColumnInfo,SelectXaxis,
4140                                           fitnum,fitvals)
4141                               
4142    def GridSetToolTip(row,col):
4143        '''Routine to show standard uncertainties for each element in table
4144        as a tooltip
4145        '''
4146        if colSigs[col]:
4147            return u'\u03c3 = '+str(colSigs[col][row])
4148        return ''
4149   
4150    # lookup table for unique cell parameters by symmetry
4151    cellGUIlist = [
4152        [['m3','m3m'],(0,)],
4153        [['3R','3mR'],(0,3)],
4154        [['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],(0,2)],
4155        [['mmm'],(0,1,2)],
4156        [['2/m'+'a'],(0,1,2,3)],
4157        [['2/m'+'b'],(0,1,2,4)],
4158        [['2/m'+'c'],(0,1,2,5)],
4159        [['-1'],(0,1,2,3,4,5)],
4160        ]
4161    # cell labels
4162    cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') # unicode a,b,c,alpha,beta,gamma
4163
4164    #======================================================================
4165    # start processing sequential results here (UpdateSeqResults)
4166    #======================================================================
4167    if not data:
4168        print 'No sequential refinement results'
4169        return
4170    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
4171    Controls = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Controls'))
4172    # create a place to store Pseudo Vars & Parametric Fit functions, if needed
4173    if 'SeqPseudoVars' not in Controls: Controls['SeqPseudoVars'] = {}
4174    if 'SeqParFitEqs' not in Controls: Controls['SeqParFitEqs'] = {}
4175    histNames = data['histNames']
4176    if G2frame.dataDisplay:
4177        G2frame.dataDisplay.Destroy()
4178    if not G2frame.dataFrame.GetStatusBar():
4179        Status = G2frame.dataFrame.CreateStatusBar()
4180        Status.SetStatusText("Select column to export; Double click on column to plot data; on row for Covariance")
4181    sampleParms = GetSampleParms()
4182
4183    # make dict of varied atom coords keyed by absolute position
4184    newAtomDict = data[histNames[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
4185    # Possible error: the next might need to be data[histNames[0]]['varyList']
4186    # error will arise if there constraints on coordinates?
4187    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in data['varyList']}
4188   
4189    # make dict of varied cell parameters equivalents
4190    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
4191    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
4192    # N.B. These Dij vars are missing a histogram #
4193    newCellDict = data[histNames[0]].get('newCellDict',{})
4194    for item in newCellDict:
4195        if item in data['varyList']:
4196            ESDlookup[newCellDict[item][0]] = item
4197            Dlookup[item] = newCellDict[item][0]
4198    # add coordinate equivalents to lookup table
4199    for parm in atomLookup:
4200        Dlookup[atomLookup[parm]] = parm
4201        ESDlookup[parm] = atomLookup[parm]
4202
4203    # get unit cell & symmetry for all phases & initial stuff for later use
4204    RecpCellTerms = {}
4205    SGdata = {}
4206    uniqCellIndx = {}
4207    initialCell = {}
4208    RcellLbls = {}
4209    zeroDict = {}
4210    Rcelldict = {}
4211    for phase in Phases:
4212        phasedict = Phases[phase]
4213        pId = phasedict['pId']
4214        pfx = str(pId)+'::' # prefix for A values from phase
4215        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
4216        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
4217        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
4218        SGdata[pId] = phasedict['General']['SGData']
4219        Rcelldict.update({lbl:val for lbl,val in zip(RcellLbls[pId],RecpCellTerms[pId])})
4220        laue = SGdata[pId]['SGLaue']
4221        if laue == '2/m':
4222            laue += SGdata[pId]['SGUniq']
4223        for symlist,celllist in cellGUIlist:
4224            if laue in symlist:
4225                uniqCellIndx[pId] = celllist
4226                break
4227        else: # should not happen
4228            uniqCellIndx[pId] = range(6)
4229        for i in uniqCellIndx[pId]:
4230            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
4231
4232    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
4233    G2frame.dataFrame.SetLabel('Sequential refinement results')
4234    if not G2frame.dataFrame.GetStatusBar():
4235        Status = G2frame.dataFrame.CreateStatusBar()
4236        Status.SetStatusText('')
4237    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
4238    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=wxID_SAVESEQSELCSV)
4239    G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=wxID_PLOTSEQSEL)
4240    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=wxADDSEQVAR)
4241    G2frame.dataFrame.Bind(wx.EVT_MENU, DelPseudoVar, id=wxDELSEQVAR)
4242    G2frame.dataFrame.Bind(wx.EVT_MENU, EditPseudoVar, id=wxEDITSEQVAR)
4243    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewParFitEq, id=wxADDPARFIT)
4244    G2frame.dataFrame.Bind(wx.EVT_MENU, DelParFitEq, id=wxDELPARFIT)
4245    G2frame.dataFrame.Bind(wx.EVT_MENU, EditParFitEq, id=wxEDITPARFIT)
4246    G2frame.dataFrame.Bind(wx.EVT_MENU, DoParEqFit, id=wxDOPARFIT)
4247    EnablePseudoVarMenus()
4248    EnableParFitEqMenus()
4249
4250    #-----------------------------------------------------------------------------------
4251    # build up the data table by columns -----------------------------------------------
4252    colList = []
4253    colSigs = []
4254    colLabels = []
4255    Types = []
4256    nRows = len(histNames)
4257    # start with Rwp values
4258    colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
4259    colSigs += [None]
4260    colLabels += ['Rwp']
4261    Types += [wg.GRID_VALUE_FLOAT+':10,3',]
4262    # add % change in Chi^2 in last cycle
4263    if 'SASD' not in histNames[0]:
4264        colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
4265        colSigs += [None]
4266        colLabels += [u'\u0394\u03C7\u00B2 (%)']
4267        Types += [wg.GRID_VALUE_FLOAT,]
4268    deltaChiCol = len(colLabels)-1
4269    # add changing sample parameters to table
4270    for key in sampleParms:
4271        colList += [sampleParms[key]]
4272        colSigs += [None]
4273        colLabels += [key]
4274        Types += [wg.GRID_VALUE_FLOAT,]
4275    # add unique cell parameters
4276    for pId in sorted(RecpCellTerms):
4277        pfx = str(pId)+'::' # prefix for A values from phase
4278        cells = []
4279        cellESDs = []
4280        colLabels += [pfx+cellUlbl[i] for i in uniqCellIndx[pId]]
4281        colLabels += [pfx+'Vol']
4282        Types += (1+len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT,]
4283        for name in histNames:
4284            covData = {
4285                'varyList': [Dlookup.get(striphist(v),v) for v in data[name]['varyList']],
4286                'covMatrix': data[name]['covMatrix']
4287                }
4288            A = RecpCellTerms[pId][:] # make copy of starting A values
4289            # update with refined values
4290            for i in range(6):
4291                var = str(pId)+'::A'+str(i)
4292                if var in ESDlookup:
4293                    val = data[name]['newCellDict'][ESDlookup[var]][1] # get refined value
4294                    A[i] = val # override with updated value
4295            # apply symmetry
4296            Albls = [pfx+'A'+str(i) for i in range(6)]
4297            cellDict = dict(zip(Albls,A))
4298            A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
4299            # convert to direct cell & add only unique values to table
4300            c = G2lat.A2cell(A)
4301            vol = G2lat.calc_V(A)
4302            cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
4303            cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
4304            cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
4305        colList += zip(*cells)
4306        colSigs += zip(*cellESDs)
4307    # add the variables that were refined; change from rows to columns
4308    colList += zip(*[data[name]['variables'] for name in histNames])
4309    colLabels += data[histNames[0]]['varyList']
4310    Types += len(data[histNames[0]]['varyList'])*[wg.GRID_VALUE_FLOAT]
4311    colSigs += zip(*[data[name]['sig'] for name in histNames])
4312
4313    # tabulate constrained variables, removing histogram numbers if needed
4314    # from parameter label
4315    depValDict = {}
4316    depSigDict = {}
4317    for name in histNames:
4318        for var in data[name].get('depParmDict',{}):
4319            val,sig = data[name]['depParmDict'][var]
4320            svar = striphist(var,'*')
4321            if svar not in depValDict:
4322               depValDict[svar] = [val]
4323               depSigDict[svar] = [sig]
4324            else:
4325               depValDict[svar].append(val)
4326               depSigDict[svar].append(sig)
4327    # add the dependent constrained variables to the table
4328    for var in sorted(depValDict):
4329        if len(depValDict[var]) != len(histNames): continue
4330        colLabels.append(var)
4331        Types += [wg.GRID_VALUE_FLOAT,]
4332        colSigs += [depSigDict[var]]
4333        colList += [depValDict[var]]
4334
4335    # add atom parameters to table
4336    colLabels += atomLookup.keys()
4337    Types += len(atomLookup)*[wg.GRID_VALUE_FLOAT]
4338    for parm in sorted(atomLookup):
4339        colList += [[data[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
4340        if atomLookup[parm] in data[histNames[0]]['varyList']:
4341            col = data[histNames[0]]['varyList'].index(atomLookup[parm])
4342            colSigs += [[data[name]['sig'][col] for name in histNames]]
4343        else:
4344            colSigs += [None] # should not happen
4345    # evaluate Pseudovars, their ESDs and add them to grid
4346    for expr in Controls['SeqPseudoVars']:
4347        obj = Controls['SeqPseudoVars'][expr]
4348        calcobj = G2obj.ExpressionCalcObj(obj)
4349        valList = []
4350        esdList = []
4351        for seqnum,name in enumerate(histNames):
4352            sigs = data[name]['sig']
4353            G2mv.InitVars()
4354            parmDict = data[name].get('parmDict')
4355            if parmDict:
4356                constraintInfo = data[name].get('constraintInfo')
4357                groups,parmlist,constrDict,fixedList,ihst = constraintInfo
4358                varyList = data[name]['varyList']
4359                parmDict = data[name]['parmDict']
4360                G2mv.GenerateConstraints(groups,parmlist,varyList,constrDict,fixedList,parmDict,SeqHist=ihst)
4361                derivs = np.array(
4362                    [EvalPSvarDeriv(calcobj,parmDict.copy(),var,ESD)
4363                     for var,ESD in zip(varyList,sigs)]
4364                    )
4365                esdList.append(np.sqrt(
4366                    np.inner(derivs,np.inner(data[name]['covMatrix'],derivs.T))
4367                    ))
4368                PSvarDict = parmDict.copy()
4369                UpdateParmDict(PSvarDict)
4370                #calcobj.SetupCalc(PSvarDict)
4371                calcobj.UpdateDict(PSvarDict)
4372            else:
4373                PSvarDict,unused,unused = CreatePSvarDict(0,histNames[0])
4374                calcobj.SetupCalc(PSvarDict)
4375            valList.append(calcobj.EvalExpression())
4376        if not esdList:
4377            esdList = None
4378        colList += [valList]
4379        colSigs += [esdList]
4380        colLabels += [expr]
4381        Types += [wg.GRID_VALUE_FLOAT,]
4382    #---- table build done -------------------------------------------------------------
4383
4384    # Make dict needed for creating & editing pseudovars (PSvarDict).
4385    name = histNames[0]
4386    parmDict = data[name].get('parmDict')
4387    if parmDict:
4388        PSvarDict = parmDict.copy()
4389        UpdateParmDict(PSvarDict)
4390    else:
4391        print 'Sequential refinement needs to be rerun to obtain ESDs for PseudoVariables'
4392        PSvarDict,unused,unused = CreatePSvarDict(0,histNames[0])
4393    # Also dicts of dependent (depVarDict) & independent vars (indepVarDict)
4394    # for Parametric fitting from the data table
4395    parmDict = dict(zip(colLabels,zip(*colList)[0])) # scratch dict w/all values in table
4396    parmDict.update(
4397        {var:val for var,val in data[name].get('newCellDict',{}).values()} #  add varied reciprocal cell terms
4398    )
4399    name = histNames[0]
4400    indepVarDict = {     #  values in table w/o ESDs
4401        var:colList[i][0] for i,var in enumerate(colLabels) if colSigs[i] is None
4402        }
4403    # make dict of dependent vars (w/ESDs) that are not converted (Dij to Ak or dAx to Ax)
4404    depVarDict = {
4405        var:colList[i][0] for i,var in enumerate(colLabels)
4406        if colSigs[i] is not None and striphist(var) not in Dlookup
4407        }
4408    # add recip cell coeff. values
4409    depVarDict.update({var:val for var,val in data[name].get('newCellDict',{}).values()})
4410   
4411    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
4412    G2frame.SeqTable = Table(
4413        [c for c in zip(*colList)],     # convert from columns to rows
4414        colLabels=colLabels,rowLabels=histNames,types=Types)
4415    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
4416    G2frame.dataDisplay.EnableEditing(False)
4417    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, PlotSelect)
4418    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
4419    G2frame.dataDisplay.SetMargins(0,0)
4420    G2frame.dataDisplay.AutoSizeColumns(True)
4421    if prevSize:
4422        G2frame.dataDisplay.SetSize(prevSize)
4423    else:
4424        G2frame.dataFrame.setSizePosLeft([700,350])
4425    # highlight unconverged shifts
4426    if 'SASD' not in histNames[0]:
4427        for row,name in enumerate(histNames):
4428            deltaChi = G2frame.SeqTable.GetValue(row,deltaChiCol)
4429            if deltaChi > 10.:
4430                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Color(255,0,0))
4431            elif deltaChi > 1.0:
4432                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Color(255,255,0))
4433    G2frame.dataDisplay.InstallGridToolTip(GridSetToolTip)
4434    #======================================================================
4435    # end UpdateSeqResults; done processing sequential results
4436    #======================================================================
4437
4438    # make dict with vars for use in PseudoVars
4439    # name = histNames[0]
4440    # parmDict = dict(zip(colLabels,zip(*colList)[0]))
4441    # # create a dict with the refined Ax values for all phases (only)
4442    # refCellDict = {var:val for var,val in data[name]['newCellDict'].values()}
4443    # # compute the Ai values for each phase, updated with refined values and
4444    # # with symmetry constraints applied
4445    # for pId in Alist:     # loop over phases
4446    #     Albls = [str(pId)+'::A'+str(i) for i in range(6)]
4447    #     cellDict = {var:refCellDict.get(var,val) for var,val in zip(Albls,Alist[pId])}
4448    #     zeroDict = {var:0.0 for var in Albls}
4449    #     A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],cellDict,zeroDict)
4450    #     parmDict.update(dict(zip(Albls,A)))
4451
4452#    print data[name]['varyList']
4453#           
4454    # print 'vars for PVar dict'
4455    # for i,(var,val) in enumerate(zip(colLabels,zip(*colList)[0])):
4456    #     if var in data[name]['varyList']:
4457    #         print var,Dlookup.get(striphist(var),var),val,parmDict.get(Dlookup.get(striphist(var),var))
4458    #         pass
4459    #     #else:
4460    #     #    print i,var,colSigs[i]
4461    # print 'vars for PVar dict again'
4462   
4463################################################################################
4464#####  Main PWDR panel
4465################################################################################           
4466       
4467def UpdatePWHKPlot(G2frame,kind,item):
4468    '''Called when the histogram main tree entry is called. Displays the
4469    histogram weight factor, refinement statistics for the histogram
4470    and the range of data for a simulation.
4471
4472    Also invokes a plot of the histogram.
4473    '''
4474    def onEditSimRange(event):
4475        'Edit simulation range'
4476        inp = [
4477            min(data[1][0]),
4478            max(data[1][0]),
4479            None
4480            ]
4481        inp[2] = (inp[1] - inp[0])/(len(data[1][0])-1.)
4482        names = ('start angle', 'end angle', 'step size')
4483        dictlst = [inp] * len(inp)
4484        elemlst = range(len(inp))
4485        dlg = ScrolledMultiEditor(
4486            G2frame,[inp] * len(inp), range(len(inp)), names,
4487            header='Edit simulation range',
4488            minvals=(0.001,0.001,0.0001),
4489            maxvals=(180.,180.,.1),
4490            )
4491        dlg.CenterOnParent()
4492        val = dlg.ShowModal()
4493        dlg.Destroy()
4494        if val != wx.ID_OK: return
4495        if inp[0] > inp[1]:
4496            end,start,step = inp
4497        else:               
4498            start,end,step = inp
4499        step = abs(step)
4500        N = int((end-start)/step)+1
4501        newdata = np.linspace(start,end,N,True)
4502        if len(newdata) < 2: return # too small a range - reject
4503        data[1] = [newdata,np.zeros_like(newdata),np.ones_like(newdata),
4504            np.zeros_like(newdata),np.zeros_like(newdata),np.zeros_like(newdata)]
4505        Tmin = newdata[0]
4506        Tmax = newdata[-1]
4507        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,item,'Limits'),
4508            [(Tmin,Tmax),[Tmin,Tmax]])
4509        UpdatePWHKPlot(G2frame,kind,item) # redisplay data screen
4510
4511    def OnErrorAnalysis(event):
4512        G2plt.PlotDeltSig(G2frame,kind)
4513       
4514    def OnWtFactor(event):
4515        try:
4516            val = float(wtval.GetValue())
4517        except ValueError:
4518            val = data[0]['wtFactor']
4519        data[0]['wtFactor'] = val
4520        wtval.SetValue('%.3f'%(val))
4521
4522    def onCopySelectedItems(event):
4523        '''Respond to menu item to copy multiple sections from a histogram.
4524        Need this here to pass on the G2frame object.
4525        '''
4526        G2pdG.CopySelectedHistItems(G2frame)
4527           
4528    data = G2frame.PatternTree.GetItemPyData(item)
4529#patches
4530    if 'wtFactor' not in data[0]:
4531        data[0] = {'wtFactor':1.0}
4532    if isinstance(data[1],list) and kind == 'HKLF':
4533        RefData = {'RefList':[],'FF':[]}
4534        for ref in data[1]:
4535            RefData['RefList'].append(ref[:11]+[ref[13],])
4536            RefData['FF'].append(ref[14])
4537        data[1] = RefData
4538        G2frame.PatternTree.SetItemPyData(item,data)
4539#end patches
4540    if G2frame.dataDisplay:
4541        G2frame.dataDisplay.Destroy()
4542    SetDataMenuBar(G2frame,G2frame.dataFrame.HistMenu)
4543    G2frame.dataFrame.Bind(wx.EVT_MENU, OnErrorAnalysis, id=wxID_PWDANALYSIS)
4544    G2frame.dataFrame.Bind(wx.EVT_MENU, onCopySelectedItems, id=wxID_PWDCOPY)
4545    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
4546   
4547    mainSizer = wx.BoxSizer(wx.VERTICAL)
4548    mainSizer.Add((5,5),)
4549    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
4550    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,WACV)
4551    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
4552    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
4553    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
4554    wtSizer.Add(wtval,0,WACV)
4555    mainSizer.Add(wtSizer)
4556    if data[0].get('Dummy'):
4557        simSizer = wx.BoxSizer(wx.HORIZONTAL)
4558        Tmin = min(data[1][0])
4559        Tmax = max(data[1][0])
4560        num = len(data[1][0])
4561        step = (Tmax - Tmin)/(num-1)
4562        t = u'2\u03b8' # 2theta
4563        lbl =  u'Simulation range: {:.2f} to {:.2f} {:s}\nwith {:.4f} steps ({:d} points)'
4564        lbl += u'\n(Edit range resets observed intensities).'
4565        lbl = lbl.format(Tmin,Tmax,t,step,num)
4566        simSizer.Add(wx.StaticText(G2frame.dataDisplay,wx.ID_ANY,lbl),
4567                    0,WACV)
4568        but = wx.Button(G2frame.dataDisplay,wx.ID_ANY,"Edit range")
4569        but.Bind(wx.EVT_BUTTON,onEditSimRange)
4570        simSizer.Add(but,0,WACV)
4571        mainSizer.Add(simSizer)
4572    if 'Nobs' in data[0]:
4573        mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4574            ' Data residual wR: %.3f%% on %d observations'%(data[0]['wR'],data[0]['Nobs'])))
4575        for value in data[0]:
4576            if 'Nref' in value:
4577                mainSizer.Add((5,5),)
4578                pfx = value.split('Nref')[0]
4579                name = data[0].get(pfx.split(':')[0]+'::Name','?')
4580                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' For phase '+name+':'))
4581                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4582                    u' Unweighted phase residuals RF\u00b2: %.3f%%, RF: %.3f%% on %d reflections  '% \
4583                    (data[0][pfx+'Rf^2'],data[0][pfx+'Rf'],data[0][value])))
4584    mainSizer.Add((5,5),)
4585    mainSizer.Layout()   
4586    G2frame.dataDisplay.SetSizer(mainSizer)
4587    Size = mainSizer.Fit(G2frame.dataFrame)
4588    Size[1] += 10
4589    G2frame.dataFrame.setSizePosLeft(Size)
4590    G2frame.PatternTree.SetItemPyData(item,data)
4591    if kind == 'PWDR':
4592        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
4593    elif kind == 'SASD':
4594        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
4595    elif kind == 'HKLF':
4596        refList = data[1]['RefList']
4597        FoMax = np.max(refList.T[5])
4598        controls = {'Type' : 'Fo','ifFc' : True,     
4599            'HKLmax' : [int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))],
4600            'HKLmin' : [int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))],
4601            'FoMax' : FoMax,'Zone' : '001','Layer' : 0,'Scale' : 1.0,}
4602        G2plt.PlotSngl(G2frame,newPlot=True,Data=controls,hklRef=refList)
4603                 
4604################################################################################
4605#####  Pattern tree routines
4606################################################################################           
4607       
4608def GetPatternTreeDataNames(G2frame,dataTypes):
4609    '''Needs a doc string
4610    '''
4611    names = []
4612    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
4613    while item:
4614        name = G2frame.PatternTree.GetItemText(item)
4615        if name[:4] in dataTypes:
4616            names.append(name)
4617        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
4618    return names
4619                         
4620def GetPatternTreeItemId(G2frame, parentId, itemText):
4621    '''Needs a doc string
4622    '''
4623    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
4624    while item:
4625        if G2frame.PatternTree.GetItemText(item) == itemText:
4626            return item
4627        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
4628    return 0               
4629
4630def MovePatternTreeToGrid(G2frame,item):
4631    '''Called from GSASII.OnPatternTreeSelChanged when a item is selected on the tree
4632    '''
4633   
4634#    print G2frame.PatternTree.GetItemText(item)
4635   
4636    oldPage = None # will be set later if already on a Phase item
4637    if G2frame.dataFrame:
4638        SetDataMenuBar(G2frame)
4639        if G2frame.dataFrame.GetLabel() == 'Comments':
4640            try:
4641                data = [G2frame.dataDisplay.GetValue()]
4642                G2frame.dataDisplay.Clear() 
4643                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
4644                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4645            except:     #clumsy but avoids dead window problem when opening another project
4646                pass
4647        elif G2frame.dataFrame.GetLabel() == 'Notebook':
4648            try:
4649                data = [G2frame.dataDisplay.GetValue()]
4650                G2frame.dataDisplay.Clear() 
4651                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
4652                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4653            except:     #clumsy but avoids dead window problem when opening another project
4654                pass
4655        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
4656            if G2frame.dataDisplay: 
4657                oldPage = G2frame.dataDisplay.GetSelection()
4658        G2frame.dataFrame.Clear()
4659        G2frame.dataFrame.SetLabel('')
4660    else:
4661        #create the frame for the data item window
4662        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
4663        G2frame.dataFrame.PhaseUserSize = None
4664       
4665    G2frame.dataFrame.Raise()           
4666    G2frame.PickId = 0
4667    parentID = G2frame.root
4668    #for i in G2frame.ExportPattern: i.Enable(False)
4669    defWid = [250,150]
4670    if item != G2frame.root:
4671        parentID = G2frame.PatternTree.GetItemParent(item)
4672    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
4673        G2frame.PatternId = item
4674        G2frame.PickId = item
4675        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
4676            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
4677            G2frame.PatternId = 0
4678            #for i in G2frame.ExportPattern: i.Enable(False)
4679            data = G2frame.PatternTree.GetItemPyData(item)
4680            UpdateNotebook(G2frame,data)
4681        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
4682            G2frame.PatternId = 0
4683            #for i in G2frame.ExportPattern: i.Enable(False)
4684            data = G2frame.PatternTree.GetItemPyData(item)
4685            if not data:           #fill in defaults
4686                data = copy.copy(G2obj.DefaultControls)    #least squares controls
4687                G2frame.PatternTree.SetItemPyData(item,data)                             
4688            for i in G2frame.Refine: i.Enable(True)
4689            G2frame.EnableSeqRefineMenu()
4690            UpdateControls(G2frame,data)
4691        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
4692            data = G2frame.PatternTree.GetItemPyData(item)
4693            UpdateSeqResults(G2frame,data)
4694        elif G2frame.PatternTree.GetItemText(item) == 'Small Angle Sequential results':
4695            data = G2frame.PatternTree.GetItemPyData(item)
4696            UpdateSASDSeqResults(G2frame,data)
4697        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
4698            data = G2frame.PatternTree.GetItemPyData(item)
4699            G2frame.dataFrame.setSizePosLeft(defWid)
4700            text = ''
4701            if 'Rvals' in data:
4702                Nvars = len(data['varyList'])
4703                Rvals = data['Rvals']
4704                text = '\nFinal residuals: \nwR = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
4705                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
4706                if 'lamMax' in Rvals:
4707                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
4708            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4709                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
4710            G2plt.PlotCovariance(G2frame,data)
4711        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
4712            data = G2frame.PatternTree.GetItemPyData(item)
4713            G2cnstG.UpdateConstraints(G2frame,data)
4714        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
4715            data = G2frame.PatternTree.GetItemPyData(item)
4716            G2cnstG.UpdateRigidBodies(G2frame,data)
4717        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
4718            data = G2frame.PatternTree.GetItemPyData(item)
4719            Phases = G2frame.GetPhaseData()
4720            phase = ''
4721            phaseName = ''
4722            if Phases:
4723                phaseName = Phases.keys()[0]
4724            G2frame.dataFrame.setSizePosLeft(defWid)
4725            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
4726        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
4727            G2frame.Image = item
4728            G2plt.PlotImage(G2frame,newPlot=True)
4729        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
4730            G2plt.PlotPowderLines(G2frame)
4731        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
4732            #for i in G2frame.ExportPattern: i.Enable(True)
4733            if G2frame.EnablePlot:
4734                UpdatePWHKPlot(G2frame,'PWDR',item)
4735        elif 'SASD' in G2frame.PatternTree.GetItemText(item):
4736            #for i in G2frame.ExportPattern: i.Enable(True)
4737            if G2frame.EnablePlot:
4738                UpdatePWHKPlot(G2frame,'SASD',item)
4739        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
4740            G2frame.Sngl = True
4741            UpdatePWHKPlot(G2frame,'HKLF',item)
4742        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
4743            G2frame.PatternId = item
4744            for i in G2frame.ExportPDF: i.Enable(True)
4745            G2plt.PlotISFG(G2frame,type='S(Q)')
4746        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
4747            G2frame.dataFrame.setSizePosLeft(defWid)
4748            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4749                value='Select one phase to see its parameters')           
4750    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
4751        G2frame.PickId = item
4752        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4753        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
4754    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
4755        G2frame.PickId = item
4756        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4757        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
4758    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
4759        G2frame.PickId = item
4760        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4761        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
4762    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
4763        G2frame.PickId = item
4764        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4765        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
4766    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
4767        G2frame.PickId = item
4768        data = G2frame.PatternTree.GetItemPyData(item)
4769        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
4770    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
4771        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
4772        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4773        G2frame.PickId = item
4774        data = G2frame.PatternTree.GetItemPyData(item)
4775        UpdateComments(G2frame,data)
4776    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
4777        G2frame.dataFrame.SetTitle('Image Controls')
4778        G2frame.PickId = item
4779        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4780        masks = G2frame.PatternTree.GetItemPyData(
4781            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
4782        data = G2frame.PatternTree.GetItemPyData(item)
4783        G2imG.UpdateImageControls(G2frame,data,masks)
4784        G2plt.PlotImage(G2frame)
4785    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
4786        G2frame.dataFrame.SetTitle('Masks')
4787<