source: trunk/GSASIIgrid.py @ 1380

Last change on this file since 1380 was 1380, checked in by vondreele, 9 years ago

sequential refinement of strain from IMG data
and plotting of seq results.
fixes of small angle seq refinement

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