source: trunk/GSASIIgrid.py @ 1246

Last change on this file since 1246 was 1246, checked in by vondreele, 11 years ago

add PlotSASDSizeDist to auto plots for SASD/Model
fix shapes form factors & volumes to work with numpy arrays - all seem to work

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