source: trunk/GSASIIgrid.py @ 1248

Last change on this file since 1248 was 1248, checked in by vondreele, 8 years ago

add setscale for SASD data
don't square the 1/cos(2-theta) correction to integrated intensities in ImageIntegrate?
scale SASD error bars by Scale
replace ':' with ';' in BkPk? parameter names
fix bug of missing Residuals in LS Refine

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 180.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2014-03-13 19:16:20 +0000 (Thu, 13 Mar 2014) $
5# $Author: vondreele $
6# $Revision: 1248 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1248 2014-03-13 19:16:20Z 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: 1248 $")
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,wxID_SETSCALE,
109] = [wx.NewId() for item in range(9)]
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,size=(-1,30)),1,wx.ALL|wx.EXPAND,1)
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.SetScale = self.SampleEdit.Append(id=wxID_SETSCALE, kind=wx.ITEM_NORMAL,text='Set scale',
2476            help='Set scale by matching to another histogram')
2477        self.SampleEdit.Append(id=wxID_SAMPLELOAD, kind=wx.ITEM_NORMAL,text='Load',
2478            help='Load sample parameters from file')
2479        self.SampleEdit.Append(id=wxID_SAMPLESAVE, kind=wx.ITEM_NORMAL,text='Save',
2480            help='Save sample parameters to file')
2481        self.SampleEdit.Append(id=wxID_SAMPLECOPY, kind=wx.ITEM_NORMAL,text='Copy',
2482            help='Copy refinable sample parameters to other histograms')
2483        self.SampleEdit.Append(id=wxID_SAMPLEFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
2484            help='Copy sample parameter refinement flags to other histograms')
2485        self.PostfillDataMenu()
2486        self.SetScale.Enable(False)
2487
2488        # PDR / Peak List
2489        self.PeakMenu = wx.MenuBar()
2490        self.PrefillDataMenu(self.PeakMenu,helpType='Peak List')
2491        self.PeakEdit = wx.Menu(title='')
2492        self.PeakMenu.Append(menu=self.PeakEdit, title='Peak Fitting')
2493        self.AutoSearch = self.PeakEdit.Append(help='Automatic peak search', 
2494            id=wxID_AUTOSEARCH, kind=wx.ITEM_NORMAL,text='Auto search')
2495        self.UnDo = self.PeakEdit.Append(help='Undo last least squares refinement', 
2496            id=wxID_UNDO, kind=wx.ITEM_NORMAL,text='UnDo')
2497        self.PeakFit = self.PeakEdit.Append(id=wxID_LSQPEAKFIT, kind=wx.ITEM_NORMAL,text='LSQ PeakFit', 
2498            help='Peak fitting via least-squares' )
2499        self.PFOneCycle = self.PeakEdit.Append(id=wxID_LSQONECYCLE, kind=wx.ITEM_NORMAL,text='LSQ one cycle', 
2500            help='One cycle of Peak fitting via least-squares' )
2501        self.PeakEdit.Append(id=wxID_RESETSIGGAM, kind=wx.ITEM_NORMAL, 
2502            text='Reset sig and gam',help='Reset sigma and gamma to global fit' )
2503        self.PeakEdit.Append(id=wxID_CLEARPEAKS, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2504            help='Clear the peak list' )
2505        self.PostfillDataMenu()
2506        self.UnDo.Enable(False)
2507        self.PeakFit.Enable(False)
2508        self.PFOneCycle.Enable(False)
2509       
2510        # PDR / Index Peak List
2511        self.IndPeaksMenu = wx.MenuBar()
2512        self.PrefillDataMenu(self.IndPeaksMenu,helpType='Index Peak List')
2513        self.IndPeaksEdit = wx.Menu(title='')
2514        self.IndPeaksMenu.Append(menu=self.IndPeaksEdit,title='Operations')
2515        self.IndPeaksEdit.Append(help='Load/Reload index peaks from peak list',id=wxID_INDXRELOAD, 
2516            kind=wx.ITEM_NORMAL,text='Load/Reload')
2517        self.PostfillDataMenu()
2518       
2519        # PDR / Unit Cells List
2520        self.IndexMenu = wx.MenuBar()
2521        self.PrefillDataMenu(self.IndexMenu,helpType='Unit Cells List')
2522        self.IndexEdit = wx.Menu(title='')
2523        self.IndexMenu.Append(menu=self.IndexEdit, title='Cell Index/Refine')
2524        self.IndexPeaks = self.IndexEdit.Append(help='', id=wxID_INDEXPEAKS, kind=wx.ITEM_NORMAL,
2525            text='Index Cell')
2526        self.CopyCell = self.IndexEdit.Append( id=wxID_COPYCELL, kind=wx.ITEM_NORMAL,text='Copy Cell', 
2527            help='Copy selected unit cell from indexing to cell refinement fields')
2528        self.RefineCell = self.IndexEdit.Append( id=wxID_REFINECELL, kind=wx.ITEM_NORMAL, 
2529            text='Refine Cell',help='Refine unit cell parameters from indexed peaks')
2530        self.MakeNewPhase = self.IndexEdit.Append( id=wxID_MAKENEWPHASE, kind=wx.ITEM_NORMAL,
2531            text='Make new phase',help='Make new phase from selected unit cell')
2532        self.PostfillDataMenu()
2533        self.IndexPeaks.Enable(False)
2534        self.CopyCell.Enable(False)
2535        self.RefineCell.Enable(False)
2536        self.MakeNewPhase.Enable(False)
2537       
2538        # PDR / Reflection Lists
2539        self.ReflMenu = wx.MenuBar()
2540        self.PrefillDataMenu(self.ReflMenu,helpType='Reflection List')
2541        self.ReflEdit = wx.Menu(title='')
2542        self.ReflMenu.Append(menu=self.ReflEdit, title='Reflection List')
2543        self.SelectPhase = self.ReflEdit.Append(help='Select phase for reflection list',id=wxID_SELECTPHASE, 
2544            kind=wx.ITEM_NORMAL,text='Select phase')
2545        self.PostfillDataMenu()
2546       
2547        #SASD & REFL/ Substance editor
2548        self.SubstanceMenu = wx.MenuBar()
2549        self.PrefillDataMenu(self.SubstanceMenu,helpType='Substances')
2550        self.SubstanceEdit = wx.Menu(title='')
2551        self.SubstanceMenu.Append(menu=self.SubstanceEdit, title='Edit')
2552        self.SubstanceEdit.Append(id=wxID_LOADSUBSTANCE, kind=wx.ITEM_NORMAL,text='Load substance',
2553            help='Load substance from file')
2554        self.SubstanceEdit.Append(id=wxID_ADDSUBSTANCE, kind=wx.ITEM_NORMAL,text='Add substance',
2555            help='Add new substance to list')
2556        self.SubstanceEdit.Append(id=wxID_COPYSUBSTANCE, kind=wx.ITEM_NORMAL,text='Copy substances',
2557            help='Copy substances')
2558        self.SubstanceEdit.Append(id=wxID_DELETESUBSTANCE, kind=wx.ITEM_NORMAL,text='Delete substance',
2559            help='Delete substance from list')           
2560        self.SubstanceEdit.Append(id=wxID_ELEMENTADD, kind=wx.ITEM_NORMAL,text='Add elements',
2561            help='Add elements to substance')
2562        self.SubstanceEdit.Append(id=wxID_ELEMENTDELETE, kind=wx.ITEM_NORMAL,text='Delete elements',
2563            help='Delete elements from substance')
2564        self.PostfillDataMenu()
2565       
2566        # SASD/ Models
2567        self.ModelMenu = wx.MenuBar()
2568        self.PrefillDataMenu(self.ModelMenu,helpType='Models')
2569        self.ModelEdit = wx.Menu(title='')
2570        self.ModelMenu.Append(menu=self.ModelEdit, title='Models')
2571        self.ModelEdit.Append(id=wxID_MODELFIT, kind=wx.ITEM_NORMAL,text='Fit',
2572            help='Fit model parameters to data')
2573        self.ModelEdit.Append(id=wxID_MODELCOPY, kind=wx.ITEM_NORMAL,text='Copy',
2574            help='Copy model parameters to other histograms')
2575        self.PostfillDataMenu()
2576       
2577        # IMG / Image Controls
2578        self.ImageMenu = wx.MenuBar()
2579        self.PrefillDataMenu(self.ImageMenu,helpType='Image Controls')
2580        self.ImageEdit = wx.Menu(title='')
2581        self.ImageMenu.Append(menu=self.ImageEdit, title='Operations')
2582        self.ImageEdit.Append(help='Calibrate detector by fitting to calibrant lines', 
2583            id=wxID_IMCALIBRATE, kind=wx.ITEM_NORMAL,text='Calibrate')
2584        self.ImageEdit.Append(help='Recalibrate detector by fitting to calibrant lines', 
2585            id=wxID_IMRECALIBRATE, kind=wx.ITEM_NORMAL,text='Recalibrate')
2586        self.ImageEdit.Append(help='Clear calibration data points and rings',id=wxID_IMCLEARCALIB, 
2587            kind=wx.ITEM_NORMAL,text='Clear calibration')
2588        self.ImageEdit.Append(help='Integrate selected image',id=wxID_IMINTEGRATE, 
2589            kind=wx.ITEM_NORMAL,text='Integrate')
2590        self.ImageEdit.Append(help='Integrate all images selected from list',id=wxID_INTEGRATEALL,
2591            kind=wx.ITEM_NORMAL,text='Integrate all')
2592        self.ImageEdit.Append(help='Copy image controls to other images', 
2593            id=wxID_IMCOPYCONTROLS, kind=wx.ITEM_NORMAL,text='Copy Controls')
2594        self.ImageEdit.Append(help='Save image controls to file', 
2595            id=wxID_IMSAVECONTROLS, kind=wx.ITEM_NORMAL,text='Save Controls')
2596        self.ImageEdit.Append(help='Load image controls from file', 
2597            id=wxID_IMLOADCONTROLS, kind=wx.ITEM_NORMAL,text='Load Controls')
2598        self.PostfillDataMenu()
2599           
2600        # IMG / Masks
2601        self.MaskMenu = wx.MenuBar()
2602        self.PrefillDataMenu(self.MaskMenu,helpType='Image Masks')
2603        self.MaskEdit = wx.Menu(title='')
2604        self.MaskMenu.Append(menu=self.MaskEdit, title='Operations')
2605        submenu = wx.Menu()
2606        self.MaskEdit.AppendMenu(
2607            wx.ID_ANY,'Create new', submenu,
2608            help=''
2609            )
2610        self.MaskEdit.Append(help='Copy mask to other images', 
2611            id=wxID_MASKCOPY, kind=wx.ITEM_NORMAL,text='Copy mask')
2612        self.MaskEdit.Append(help='Save mask to file', 
2613            id=wxID_MASKSAVE, kind=wx.ITEM_NORMAL,text='Save mask')
2614        self.MaskEdit.Append(help='Load mask from file', 
2615            id=wxID_MASKLOAD, kind=wx.ITEM_NORMAL,text='Load mask')
2616        submenu.Append(help='Create an arc mask with mouse input', 
2617            id=wxID_NEWMASKARC, kind=wx.ITEM_NORMAL,text='Arc mask')
2618        submenu.Append(help='Create a frame mask with mouse input', 
2619            id=wxID_NEWMASKFRAME, kind=wx.ITEM_NORMAL,text='Frame mask')
2620        submenu.Append(help='Create a polygon mask with mouse input', 
2621            id=wxID_NEWMASKPOLY, kind=wx.ITEM_NORMAL,text='Polygon mask')
2622        submenu.Append(help='Create a ring mask with mouse input', 
2623            id=wxID_NEWMASKRING, kind=wx.ITEM_NORMAL,text='Ring mask')
2624        submenu.Append(help='Create a spot mask with mouse input', 
2625            id=wxID_NEWMASKSPOT, kind=wx.ITEM_NORMAL,text='Spot mask')
2626        self.PostfillDataMenu()
2627           
2628        # IMG / Stress/Strain
2629        self.StrStaMenu = wx.MenuBar()
2630        self.PrefillDataMenu(self.StrStaMenu,helpType='Stress/Strain')
2631        self.StrStaEdit = wx.Menu(title='')
2632        self.StrStaMenu.Append(menu=self.StrStaEdit, title='Operations')
2633        self.StrStaEdit.Append(help='Append d-zero for one ring', 
2634            id=wxID_APPENDDZERO, kind=wx.ITEM_NORMAL,text='Append d-zero')
2635        self.StrStaEdit.Append(help='Fit stress/strain data', 
2636            id=wxID_STRSTAFIT, kind=wx.ITEM_NORMAL,text='Fit stress/strain')
2637        self.StrStaEdit.Append(help='Update d-zero from ave d-zero',
2638            id=wxID_UPDATEDZERO, kind=wx.ITEM_NORMAL,text='Update d-zero')       
2639        self.StrStaEdit.Append(help='Fit stress/strain data for all images', 
2640            id=wxID_STRSTAALLFIT, kind=wx.ITEM_NORMAL,text='All image fit')
2641        self.StrStaEdit.Append(help='Copy stress/strain data to other images', 
2642            id=wxID_STRSTACOPY, kind=wx.ITEM_NORMAL,text='Copy stress/strain')
2643        self.StrStaEdit.Append(help='Save stress/strain data to file', 
2644            id=wxID_STRSTASAVE, kind=wx.ITEM_NORMAL,text='Save stress/strain')
2645        self.StrStaEdit.Append(help='Load stress/strain data from file', 
2646            id=wxID_STRSTALOAD, kind=wx.ITEM_NORMAL,text='Load stress/strain')
2647        self.PostfillDataMenu()
2648           
2649        # PDF / PDF Controls
2650        self.PDFMenu = wx.MenuBar()
2651        self.PrefillDataMenu(self.PDFMenu,helpType='PDF Controls')
2652        self.PDFEdit = wx.Menu(title='')
2653        self.PDFMenu.Append(menu=self.PDFEdit, title='PDF Controls')
2654        self.PDFEdit.Append(help='Add element to sample composition',id=wxID_PDFADDELEMENT, kind=wx.ITEM_NORMAL,
2655            text='Add element')
2656        self.PDFEdit.Append(help='Delete element from sample composition',id=wxID_PDFDELELEMENT, kind=wx.ITEM_NORMAL,
2657            text='Delete element')
2658        self.PDFEdit.Append(help='Copy PDF controls', id=wxID_PDFCOPYCONTROLS, kind=wx.ITEM_NORMAL,
2659            text='Copy controls')
2660        #        self.PDFEdit.Append(help='Load PDF controls from file',id=wxID_PDFLOADCONTROLS, kind=wx.ITEM_NORMAL,
2661        #            text='Load Controls')
2662        #        self.PDFEdit.Append(help='Save PDF controls to file', id=wxID_PDFSAVECONTROLS, kind=wx.ITEM_NORMAL,
2663        #            text='Save controls')
2664        self.PDFEdit.Append(help='Compute PDF', id=wxID_PDFCOMPUTE, kind=wx.ITEM_NORMAL,
2665            text='Compute PDF')
2666        self.PDFEdit.Append(help='Compute all PDFs', id=wxID_PDFCOMPUTEALL, kind=wx.ITEM_NORMAL,
2667            text='Compute all PDFs')
2668        self.PostfillDataMenu()
2669       
2670        # Phase / General tab
2671        self.DataGeneral = wx.MenuBar()
2672        self.PrefillDataMenu(self.DataGeneral,helpType='General', helpLbl='Phase/General')
2673        self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab')
2674        self.GeneralCalc = wx.Menu(title='')
2675        self.DataGeneral.Append(menu=self.GeneralCalc,title='Compute')
2676        self.GeneralCalc.Append(help='Compute Fourier map',id=wxID_FOURCALC, kind=wx.ITEM_NORMAL,
2677            text='Fourier map')
2678        self.GeneralCalc.Append(help='Search Fourier map',id=wxID_FOURSEARCH, kind=wx.ITEM_NORMAL,
2679            text='Search map')
2680        self.GeneralCalc.Append(help='Run charge flipping',id=wxID_CHARGEFLIP, kind=wx.ITEM_NORMAL,
2681            text='Charge flipping')
2682        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
2683            text='Clear map')
2684        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
2685            text='MC/SA')
2686        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
2687            text='Multi MC/SA')            #currently not useful
2688        self.PostfillDataMenu()
2689       
2690        # Phase / Data tab
2691        self.DataMenu = wx.MenuBar()
2692        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
2693        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2694        self.DataEdit = wx.Menu(title='')
2695        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
2696        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
2697            help='Select new powder histograms to be used for this phase')
2698        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
2699            help='Select new single crystal histograms to be used for this phase')
2700        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Remove histograms',
2701            help='Remove histograms from use for this phase')
2702        self.PostfillDataMenu()
2703           
2704        # Phase / Atoms tab
2705        self.AtomsMenu = wx.MenuBar()
2706        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
2707        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2708        self.AtomEdit = wx.Menu(title='')
2709        self.AtomCompute = wx.Menu(title='')
2710        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
2711        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
2712        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
2713            help='Appended as an H atom')
2714        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
2715            help='Appended as an H atom')
2716        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
2717            help='Select atom row to insert before; inserted as an H atom')
2718        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
2719            help='Select atom row to insert before; inserted as an H atom')
2720        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
2721            help='Select single atom to move')
2722        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
2723            help='Select atoms to delete first')
2724        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
2725            help='Select atoms to refine first')
2726        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
2727            help='Select atoms to modify first')
2728        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
2729            help='Select atoms to transform first')
2730        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
2731            help='Reload atom drawing list')
2732        submenu = wx.Menu()
2733        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
2734            help='Reimport atoms from file; sequence must match')
2735        # setup a cascade menu for the formats that have been defined
2736        self.ReImportMenuId = {}  # points to readers for each menu entry
2737        for reader in self.G2frame.ImportPhaseReaderlist:
2738            item = submenu.Append(
2739                wx.ID_ANY,help=reader.longFormatName,
2740                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
2741            self.ReImportMenuId[item.GetId()] = reader
2742        item = submenu.Append(
2743            wx.ID_ANY,
2744            help='Reimport coordinates, try to determine format from file',
2745            kind=wx.ITEM_NORMAL,
2746            text='guess format from file')
2747        self.ReImportMenuId[item.GetId()] = None # try all readers
2748
2749        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Show Distances && Angles',
2750            help='Compute distances & angles for selected atoms')
2751        self.AtomCompute.Append(id=wxID_ATOMSPDISAGL, kind=wx.ITEM_NORMAL,text='Save Distances && Angles',
2752            help='Compute distances & angles for selected atoms')
2753        self.AtomCompute.ISOcalc = self.AtomCompute.Append(
2754            id=wxID_ISODISP, kind=wx.ITEM_NORMAL,
2755            text='Compute ISODISTORT mode values',
2756            help='Compute values of ISODISTORT modes from atom parameters')
2757        self.PostfillDataMenu()
2758                 
2759        # Phase / Draw Options tab
2760        self.DataDrawOptions = wx.MenuBar()
2761        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options')
2762        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
2763        self.PostfillDataMenu()
2764       
2765        # Phase / Draw Atoms tab
2766        self.DrawAtomsMenu = wx.MenuBar()
2767        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
2768        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2769        self.DrawAtomEdit = wx.Menu(title='')
2770        self.DrawAtomCompute = wx.Menu(title='')
2771        self.DrawAtomRestraint = wx.Menu(title='')
2772        self.DrawAtomRigidBody = wx.Menu(title='')
2773        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
2774        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
2775        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
2776        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
2777        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
2778            help='Select atoms first')
2779        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
2780            help='Select atoms first')
2781        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
2782            help='Select atoms first')
2783        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
2784            help='Resets all atom colors to defaults')
2785        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
2786            help='View point is 1st atom selected')
2787        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
2788            help='Add symmetry & cell equivalents to drawing set from selected atoms')
2789        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
2790            help='Transform selected atoms by symmetry & cell translations')
2791        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
2792            help='Fill coordination sphere for selected atoms')           
2793        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
2794            help='Fill unit cell with selected atoms')
2795        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
2796            help='Delete atoms from drawing set')
2797        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2798            help='Compute distance of selected atoms from view point')   
2799        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
2800            help='Compute distance, angle or torsion for 2-4 selected atoms')   
2801        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
2802            help='Compute best plane for 4+ selected atoms')   
2803        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
2804            help='Add bond restraint for selected atoms (2)')
2805        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
2806            help='Add angle restraint for selected atoms (3: one end 1st)')
2807        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
2808            help='Add plane restraint for selected atoms (4+)')
2809        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
2810            help='Add chiral restraint for selected atoms (4: center atom 1st)')
2811        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
2812            help='Define rigid body with selected atoms')
2813        self.PostfillDataMenu()
2814
2815        # Phase / MCSA tab
2816        self.MCSAMenu = wx.MenuBar()
2817        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
2818        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2819        self.MCSAEdit = wx.Menu(title='')
2820        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
2821        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
2822            help='Add single atom to MC/SA model')
2823        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
2824            help='Add rigid body to MC/SA model' )
2825        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
2826            help='Clear all atoms & rigid bodies from MC/SA model' )
2827        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
2828            help='Move MC/SA solution to atom list' )
2829        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
2830            help='Clear table of MC/SA results' )
2831        self.PostfillDataMenu()
2832           
2833        # Phase / Texture tab
2834        self.TextureMenu = wx.MenuBar()
2835        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
2836        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2837        self.TextureEdit = wx.Menu(title='')
2838        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
2839        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture', 
2840            help='Refine the texture coefficients from sequential Pawley results')
2841        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
2842            help='Clear the texture coefficients' )
2843        self.PostfillDataMenu()
2844           
2845        # Phase / Pawley tab
2846        self.PawleyMenu = wx.MenuBar()
2847        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
2848        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2849        self.PawleyEdit = wx.Menu(title='')
2850        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
2851        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
2852            help='Initialize Pawley reflection list')
2853        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
2854            help='Estimate initial Pawley intensities')
2855        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
2856            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
2857        self.PostfillDataMenu()
2858           
2859        # Phase / Map peaks tab
2860        self.MapPeaksMenu = wx.MenuBar()
2861        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
2862        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2863        self.MapPeaksEdit = wx.Menu(title='')
2864        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
2865        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
2866            help='Move selected peaks to atom list')
2867        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
2868            help='View point is 1st peak selected')
2869        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2870            help='Compute distance of selected peaks from view point')   
2871        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
2872            help='Hide or show bonds between peak positions')   
2873        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
2874            help='Calculate distance or angle for selection')
2875        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
2876            help='Find equivalent peaks')
2877        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
2878            help='Select unique set')
2879        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
2880            help='Delete selected peaks')
2881        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2882            help='Clear the map peak list')
2883        self.PostfillDataMenu()
2884
2885        # Phase / Rigid bodies tab
2886        self.RigidBodiesMenu = wx.MenuBar()
2887        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
2888        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2889        self.RigidBodiesEdit = wx.Menu(title='')
2890        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
2891        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
2892            help='Select & position rigid body in structure of existing atoms')
2893        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
2894            help='Auto find of residue RBs in macromolecule')
2895        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
2896            help='Copy rigid body location & TLS parameters')
2897        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
2898            help='Global setting of residue thermal motion models')
2899        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
2900            help='Global setting of residue RB refinement flags')
2901        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
2902            help='Remove all rigid body assignment for atoms')
2903        self.PostfillDataMenu()
2904    # end of GSAS-II menu definitions
2905       
2906    def _init_ctrls(self, parent,name=None,size=None,pos=None):
2907        wx.Frame.__init__(self,parent=parent,
2908            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
2909            size=size,pos=pos,title='GSAS-II data display')
2910        self._init_menus()
2911        if name:
2912            self.SetLabel(name)
2913        self.Show()
2914       
2915    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
2916        self.G2frame = frame
2917        self._init_ctrls(parent,name,size,pos)
2918        self.data = data
2919        clientSize = wx.ClientDisplayRect()
2920        Size = self.GetSize()
2921        xPos = clientSize[2]-Size[0]
2922        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
2923        self.AtomGrid = []
2924        self.selectedRow = 0
2925       
2926    def setSizePosLeft(self,Width):
2927        clientSize = wx.ClientDisplayRect()
2928        Width[1] = min(Width[1],clientSize[2]-300)
2929        Width[0] = max(Width[0],300)
2930        self.SetSize(Width)
2931#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
2932       
2933    def Clear(self):
2934        self.ClearBackground()
2935        self.DestroyChildren()
2936                   
2937################################################################################
2938#####  GSNotebook
2939################################################################################           
2940       
2941class GSNoteBook(wx.aui.AuiNotebook):
2942    '''Notebook used in various locations; implemented with wx.aui extension
2943    '''
2944    def __init__(self, parent, name='',size = None):
2945        wx.aui.AuiNotebook.__init__(self, parent, -1,
2946                                    style=wx.aui.AUI_NB_TOP |
2947                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
2948        if size: self.SetSize(size)
2949                                                     
2950    def Clear(self):       
2951        GSNoteBook.DeleteAllPages(self)
2952       
2953    def FindPage(self,name):
2954        numPage = self.GetPageCount()
2955        for page in range(numPage):
2956            if self.GetPageText(page) == name:
2957                return page
2958
2959    def ChangeSelection(self,page):
2960        # in wx.Notebook ChangeSelection is like SetSelection, but it
2961        # does not invoke the event related to pressing the tab button
2962        # I don't see a way to do that in aui.
2963        oldPage = self.GetSelection()
2964        self.SetSelection(page)
2965        return oldPage
2966
2967    # def __getattribute__(self,name):
2968    #     '''This method provides a way to print out a message every time
2969    #     that a method in a class is called -- to see what all the calls
2970    #     might be, or where they might be coming from.
2971    #     Cute trick for debugging!
2972    #     '''
2973    #     attr = object.__getattribute__(self, name)
2974    #     if hasattr(attr, '__call__'):
2975    #         def newfunc(*args, **kwargs):
2976    #             print('GSauiNoteBook calling %s' %attr.__name__)
2977    #             result = attr(*args, **kwargs)
2978    #             return result
2979    #         return newfunc
2980    #     else:
2981    #         return attr
2982           
2983################################################################################
2984#####  GSGrid
2985################################################################################           
2986       
2987class GSGrid(wg.Grid):
2988    '''Basic wx.Grid implementation
2989    '''
2990    def __init__(self, parent, name=''):
2991        wg.Grid.__init__(self,parent,-1,name=name)                   
2992        #self.SetSize(parent.GetClientSize())
2993        # above removed to speed drawing of initial grid
2994        # does not appear to be needed
2995           
2996    def Clear(self):
2997        wg.Grid.ClearGrid(self)
2998       
2999    def SetCellStyle(self,r,c,color="white",readonly=True):
3000        self.SetCellBackgroundColour(r,c,color)
3001        self.SetReadOnly(r,c,isReadOnly=readonly)
3002       
3003    def GetSelection(self):
3004        #this is to satisfy structure drawing stuff in G2plt when focus changes
3005        return None
3006                                               
3007################################################################################
3008#####  Table
3009################################################################################           
3010       
3011class Table(wg.PyGridTableBase):
3012    '''Basic data table for use with GSgrid
3013    '''
3014    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
3015        wg.PyGridTableBase.__init__(self)
3016        self.colLabels = colLabels
3017        self.rowLabels = rowLabels
3018        self.dataTypes = types
3019        self.data = data
3020       
3021    def AppendRows(self, numRows=1):
3022        self.data.append([])
3023        return True
3024       
3025    def CanGetValueAs(self, row, col, typeName):
3026        if self.dataTypes:
3027            colType = self.dataTypes[col].split(':')[0]
3028            if typeName == colType:
3029                return True
3030            else:
3031                return False
3032        else:
3033            return False
3034
3035    def CanSetValueAs(self, row, col, typeName):
3036        return self.CanGetValueAs(row, col, typeName)
3037
3038    def DeleteRow(self,pos):
3039        data = self.GetData()
3040        self.SetData([])
3041        new = []
3042        for irow,row in enumerate(data):
3043            if irow <> pos:
3044                new.append(row)
3045        self.SetData(new)
3046       
3047    def GetColLabelValue(self, col):
3048        if self.colLabels:
3049            return self.colLabels[col]
3050           
3051    def GetData(self):
3052        data = []
3053        for row in range(self.GetNumberRows()):
3054            data.append(self.GetRowValues(row))
3055        return data
3056       
3057    def GetNumberCols(self):
3058        try:
3059            return len(self.colLabels)
3060        except TypeError:
3061            return None
3062       
3063    def GetNumberRows(self):
3064        return len(self.data)
3065       
3066    def GetRowLabelValue(self, row):
3067        if self.rowLabels:
3068            return self.rowLabels[row]
3069       
3070    def GetColValues(self, col):
3071        data = []
3072        for row in range(self.GetNumberRows()):
3073            data.append(self.GetValue(row, col))
3074        return data
3075       
3076    def GetRowValues(self, row):
3077        data = []
3078        for col in range(self.GetNumberCols()):
3079            data.append(self.GetValue(row, col))
3080        return data
3081       
3082    def GetTypeName(self, row, col):
3083        try:
3084            return self.dataTypes[col]
3085        except TypeError:
3086            return None
3087
3088    def GetValue(self, row, col):
3089        try:
3090            return self.data[row][col]
3091        except IndexError:
3092            return None
3093           
3094    def InsertRows(self, pos, rows):
3095        for row in range(rows):
3096            self.data.insert(pos,[])
3097            pos += 1
3098       
3099    def IsEmptyCell(self,row,col):
3100        try:
3101            return not self.data[row][col]
3102        except IndexError:
3103            return True
3104       
3105    def OnKeyPress(self, event):
3106        dellist = self.GetSelectedRows()
3107        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
3108            grid = self.GetView()
3109            for i in dellist: grid.DeleteRow(i)
3110               
3111    def SetColLabelValue(self, col, label):
3112        numcols = self.GetNumberCols()
3113        if col > numcols-1:
3114            self.colLabels.append(label)
3115        else:
3116            self.colLabels[col]=label
3117       
3118    def SetData(self,data):
3119        for row in range(len(data)):
3120            self.SetRowValues(row,data[row])
3121               
3122    def SetRowLabelValue(self, row, label):
3123        self.rowLabels[row]=label
3124           
3125    def SetRowValues(self,row,data):
3126        self.data[row] = data
3127           
3128    def SetValue(self, row, col, value):
3129        def innerSetValue(row, col, value):
3130            try:
3131                self.data[row][col] = value
3132            except TypeError:
3133                return
3134            except IndexError:
3135                print row,col,value
3136                # add a new row
3137                if row > self.GetNumberRows():
3138                    self.data.append([''] * self.GetNumberCols())
3139                elif col > self.GetNumberCols():
3140                    for row in range(self.GetNumberRows):
3141                        self.data[row].append('')
3142                print self.data
3143                self.data[row][col] = value
3144        innerSetValue(row, col, value)
3145       
3146################################################################################
3147#### Help
3148################################################################################
3149
3150def ShowHelp(helpType,frame):
3151    '''Called to bring up a web page for documentation.'''
3152    global htmlFirstUse
3153    # look up a definition for help info from dict
3154    helplink = helpLocDict.get(helpType)
3155    if helplink is None:
3156        # no defined link to use, create a default based on key
3157        helplink = 'gsasII.html#'+helpType.replace(' ','_')
3158    helplink = os.path.join(path2GSAS2,'help',helplink)
3159    if helpMode == 'internal':
3160        try:
3161            htmlPanel.LoadFile(helplink)
3162            htmlFrame.Raise()
3163        except:
3164            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
3165            htmlFrame.Show(True)
3166            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
3167            htmlPanel = MyHtmlPanel(htmlFrame,-1)
3168            htmlPanel.LoadFile(helplink)
3169    else:
3170        pfx = "file://"
3171        if sys.platform.lower().startswith('win'):
3172            pfx = ''
3173        if htmlFirstUse:
3174            webbrowser.open_new(pfx+helplink)
3175            htmlFirstUse = False
3176        else:
3177            webbrowser.open(pfx+helplink, new=0, autoraise=True)
3178
3179################################################################################
3180#####  Notebook
3181################################################################################           
3182       
3183def UpdateNotebook(G2frame,data):
3184    '''Called when the data tree notebook entry is selected. Allows for
3185    editing of the text in that tree entry
3186    '''
3187    def OnNoteBook(event):
3188        data = G2frame.dataDisplay.GetValue().split('\n')
3189        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
3190        if 'nt' not in os.name:
3191            G2frame.dataDisplay.AppendText('\n')
3192                   
3193    if G2frame.dataDisplay:
3194        G2frame.dataDisplay.Destroy()
3195    G2frame.dataFrame.SetLabel('Notebook')
3196    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3197        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
3198    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
3199    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
3200    for line in data:
3201        G2frame.dataDisplay.AppendText(line+"\n")
3202    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
3203    G2frame.dataFrame.setSizePosLeft([400,250])
3204           
3205################################################################################
3206#####  Controls
3207################################################################################           
3208       
3209def UpdateControls(G2frame,data):
3210    '''Edit overall GSAS-II controls in main Controls data tree entry
3211    '''
3212    #patch
3213    if 'deriv type' not in data:
3214        data = {}
3215        data['deriv type'] = 'analytic Hessian'
3216        data['min dM/M'] = 0.0001
3217        data['shift factor'] = 1.
3218        data['max cyc'] = 3       
3219        data['F**2'] = True
3220        data['minF/sig'] = 0
3221    if 'shift factor' not in data:
3222        data['shift factor'] = 1.
3223    if 'max cyc' not in data:
3224        data['max cyc'] = 3
3225    if 'F**2' not in data:
3226        data['F**2'] = True
3227        data['minF/sig'] = 0
3228    if 'Author' not in data:
3229        data['Author'] = 'no name'
3230    if 'FreePrm1' not in data:
3231        data['FreePrm1'] = 'Sample humidity (%)'
3232    if 'FreePrm2' not in data:
3233        data['FreePrm2'] = 'Sample voltage (V)'
3234    if 'FreePrm3' not in data:
3235        data['FreePrm3'] = 'Applied load (MN)'
3236    if 'Copy2Next' not in data:
3237        data['Copy2Next'] = False
3238    if 'Reverse Seq' not in data:
3239        data['Reverse Seq'] = False   
3240     
3241   
3242    #end patch
3243
3244    def SeqSizer():
3245       
3246        def OnSelectData(event):
3247            choices = ['All',]+GetPatternTreeDataNames(G2frame,['PWDR',])
3248            sel = []
3249            if 'Seq Data' in data:
3250                for item in data['Seq Data']:
3251                    sel.append(choices.index(item))
3252            names = []
3253            dlg = wx.MultiChoiceDialog(G2frame,'Select data:','Sequential refinement',choices)
3254            dlg.SetSelections(sel)
3255            if dlg.ShowModal() == wx.ID_OK:
3256                sel = dlg.GetSelections()
3257                for i in sel: names.append(choices[i])
3258                if 'All' in names:
3259                    names = choices[1:]
3260                data['Seq Data'] = names               
3261            dlg.Destroy()
3262            reverseSel.Enable(True)
3263           
3264        def OnReverse(event):
3265            data['Reverse Seq'] = reverseSel.GetValue()
3266           
3267        def OnCopySel(event):
3268            data['Copy2Next'] = copySel.GetValue() 
3269                   
3270        seqSizer = wx.BoxSizer(wx.VERTICAL)
3271        dataSizer = wx.BoxSizer(wx.HORIZONTAL)
3272        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement Powder Data: '),0,WACV)
3273        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3274        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3275        dataSizer.Add(selSeqData,0,WACV)
3276        seqSizer.Add(dataSizer,0)
3277        if 'Seq Data' in data:
3278            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3279            reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3280            reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3281            reverseSel.SetValue(data['Reverse Seq'])
3282            selSizer.Add(reverseSel,0,WACV)
3283            copySel =  wx.CheckBox(G2frame.dataDisplay,-1,label=' Copy results to next histogram?')
3284            copySel.Bind(wx.EVT_CHECKBOX,OnCopySel)
3285            copySel.SetValue(data['Copy2Next'])
3286            selSizer.Add(copySel,0,WACV)
3287            seqSizer.Add(selSizer,0)
3288        return seqSizer
3289       
3290    def LSSizer():       
3291       
3292        def OnDerivType(event):
3293            data['deriv type'] = derivSel.GetValue()
3294            derivSel.SetValue(data['deriv type'])
3295            wx.CallAfter(UpdateControls,G2frame,data)
3296           
3297        def OnConvergence(event):
3298            try:
3299                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3300            except ValueError:
3301                value = 0.0001
3302            data['min dM/M'] = value
3303            Cnvrg.SetValue('%.2g'%(value))
3304           
3305        def OnMaxCycles(event):
3306            data['max cyc'] = int(maxCyc.GetValue())
3307            maxCyc.SetValue(str(data['max cyc']))
3308                       
3309        def OnFactor(event):
3310            try:
3311                value = min(max(float(Factr.GetValue()),0.00001),100.)
3312            except ValueError:
3313                value = 1.0
3314            data['shift factor'] = value
3315            Factr.SetValue('%.5f'%(value))
3316           
3317        def OnFsqRef(event):
3318            data['F**2'] = fsqRef.GetValue()
3319       
3320        def OnMinSig(event):
3321            try:
3322                value = min(max(float(minSig.GetValue()),0.),5.)
3323            except ValueError:
3324                value = 1.0
3325            data['minF/sig'] = value
3326            minSig.SetValue('%.2f'%(value))
3327
3328        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3329        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,WACV)
3330        Choice=['analytic Jacobian','numeric','analytic Hessian']
3331        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3332            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3333        derivSel.SetValue(data['deriv type'])
3334        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3335           
3336        LSSizer.Add(derivSel,0,WACV)
3337        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,WACV)
3338        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3339        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3340        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3341        LSSizer.Add(Cnvrg,0,WACV)
3342        if 'Hessian' in data['deriv type']:
3343            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,WACV)
3344            Choice = ['0','1','2','3','5','10','15','20']
3345            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3346                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3347            maxCyc.SetValue(str(data['max cyc']))
3348            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3349            LSSizer.Add(maxCyc,0,WACV)
3350        else:
3351            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,WACV)
3352            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3353            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3354            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3355            LSSizer.Add(Factr,0,WACV)
3356        if G2frame.Sngl:
3357            LSSizer.Add((1,0),)
3358            LSSizer.Add((1,0),)
3359            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3360            fsqRef.SetValue(data['F**2'])
3361            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3362            LSSizer.Add(fsqRef,0,WACV)
3363            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,WACV)
3364            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3365            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3366            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3367            LSSizer.Add(minSig,0,WACV)
3368        return LSSizer
3369       
3370    def AuthSizer():
3371
3372        def OnAuthor(event):
3373            data['Author'] = auth.GetValue()
3374
3375        Author = data['Author']
3376        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3377        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,WACV)
3378        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3379        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3380        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3381        authSizer.Add(auth,0,WACV)
3382        return authSizer
3383       
3384       
3385    if G2frame.dataDisplay:
3386        G2frame.dataDisplay.Destroy()
3387    if not G2frame.dataFrame.GetStatusBar():
3388        Status = G2frame.dataFrame.CreateStatusBar()
3389        Status.SetStatusText('')
3390    G2frame.dataFrame.SetLabel('Controls')
3391    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3392    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3393    mainSizer = wx.BoxSizer(wx.VERTICAL)
3394    mainSizer.Add((5,5),0)
3395    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,WACV)   
3396    mainSizer.Add(LSSizer())
3397    mainSizer.Add((5,5),0)
3398    mainSizer.Add(SeqSizer())
3399    mainSizer.Add((5,5),0)
3400    mainSizer.Add(AuthSizer())
3401    mainSizer.Add((5,5),0)
3402       
3403    mainSizer.Layout()   
3404    G2frame.dataDisplay.SetSizer(mainSizer)
3405    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3406    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3407     
3408################################################################################
3409#####  Comments
3410################################################################################           
3411       
3412def UpdateComments(G2frame,data):                   
3413
3414    if G2frame.dataDisplay:
3415        G2frame.dataDisplay.Destroy()
3416    G2frame.dataFrame.SetLabel('Comments')
3417    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3418        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3419    for line in data:
3420        G2frame.dataDisplay.AppendText(line+'\n')
3421    G2frame.dataFrame.setSizePosLeft([400,250])
3422           
3423################################################################################
3424#####  Sequential Results
3425################################################################################           
3426       
3427def UpdateSeqResults(G2frame,data):
3428    """
3429    Called when the Sequential Results data tree entry is selected
3430    to show results from a sequential refinement.
3431   
3432    :param wx.Frame G2frame: main GSAS-II data tree windows
3433
3434    :param dict data: a dictionary containing the following items: 
3435
3436            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3437            * 'varyList' - list of variables - identical over all refinements in sequence
3438            * 'histName' - dictionaries for all data sets processed, which contains:
3439
3440              * 'variables'- result[0] from leastsq call
3441              * 'varyList' - list of variables; same as above
3442              * 'sig' - esds for variables
3443              * 'covMatrix' - covariance matrix from individual refinement
3444              * 'title' - histogram name; same as dict item name
3445              * 'newAtomDict' - new atom parameters after shifts applied
3446              * 'newCellDict' - new cell parameters after shifts to A0-A5 applied'
3447    """
3448    if not data:
3449        print 'No sequential refinement results'
3450        return
3451    histNames = data['histNames']
3452       
3453    def GetSampleParms():
3454        sampleParmDict = {'Temperature':[],'Pressure':[],
3455                          'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],}
3456        sampleParm = {}
3457        for name in histNames:
3458            Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3459            sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3460            for item in sampleParmDict:
3461                sampleParmDict[item].append(sampleData[item])
3462        for item in sampleParmDict:
3463            frstValue = sampleParmDict[item][0]
3464            if np.any(np.array(sampleParmDict[item])-frstValue):
3465                sampleParm[item] = sampleParmDict[item]           
3466        return sampleParm
3467           
3468    def GetRwps():
3469        Rwps = []
3470        for name in histNames:
3471            Rwps.append(data[name]['Rvals']['Rwp'])
3472        return Rwps
3473           
3474    def GetSigData(parm):
3475        sigData = []
3476        for name in histNames:
3477            sigList = data[name]['sig']
3478            if colLabels[parm] in atomList:
3479                sigData.append(sigList[colLabels.index(atomList[colLabels[parm]])-1])
3480            elif colLabels[parm] in cellList:
3481                sigData.append(sigList[colLabels.index(cellList[colLabels[parm]])-1])
3482            else:
3483                sigData.append(sigList[parm-1])
3484        return sigData
3485   
3486    def Select(event):
3487        cols = G2frame.dataDisplay.GetSelectedCols()
3488        rows = G2frame.dataDisplay.GetSelectedRows()
3489        if cols:
3490            plotData = []
3491            plotSig = []
3492            plotNames = []
3493            for col in cols:
3494                plotData.append(G2frame.SeqTable.GetColValues(col))
3495                if col:     #not Rwp
3496                    plotSig.append(GetSigData(col))
3497                else:
3498                    plotSig.append(0.0)
3499                plotNames.append(G2frame.SeqTable.GetColLabelValue(col))
3500            plotData = np.array(plotData)
3501            G2plt.PlotSeq(G2frame,plotData,plotSig,plotNames,sampleParms)
3502        elif rows:
3503            name = histNames[rows[0]]       #only does 1st one selected
3504            G2plt.PlotCovariance(G2frame,data[name])
3505           
3506    def OnSaveSelSeq(event):       
3507        cols = G2frame.dataDisplay.GetSelectedCols()
3508        if cols:
3509            numRows = G2frame.SeqTable.GetNumberRows()
3510            dataNames = []
3511            saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(numRows)]
3512            saveData = []
3513            for col in cols:
3514                dataNames.append(G2frame.SeqTable.GetColLabelValue(col))
3515                if col:     #not Rwp
3516                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),GetSigData(col)))
3517                else:
3518                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),0.0))
3519            lenName = len(saveNames[0])
3520            saveData = np.swapaxes(np.array(saveData),0,1)
3521            dlg = wx.FileDialog(G2frame, 'Choose text output file for your selection', '.', '', 
3522                'Text output file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
3523            try:
3524                if dlg.ShowModal() == wx.ID_OK:
3525                    SeqTextFile = dlg.GetPath()
3526                    SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile)
3527                    SeqFile = open(SeqTextFile,'w')
3528                    line = %s  '%('name'.center(lenName))
3529                    for item in dataNames:
3530                        line += ' %12s %12s '%(item.center(12),'esd'.center(12))
3531                    line += '\n'
3532                    SeqFile.write(line)
3533                    for i,item in enumerate(saveData):
3534                        line = " '%s' "%(saveNames[i])
3535                        for val,esd in item:
3536                            line += ' %12.6f %12.6f '%(val,esd)
3537                        line += '\n'
3538                        SeqFile.write(line)
3539                    SeqFile.close()
3540            finally:
3541                dlg.Destroy()
3542           
3543               
3544    if G2frame.dataDisplay:
3545        G2frame.dataDisplay.Destroy()
3546    atomList = {}
3547    newAtomDict = data[histNames[0]]['newAtomDict']
3548    for item in newAtomDict:
3549        if item in data['varyList']:
3550            atomList[newAtomDict[item][0]] = item
3551    cellList = {}
3552    newCellDict = data[histNames[0]]['newCellDict']
3553    for item in newCellDict:
3554        if item in data['varyList']:
3555            cellList[newCellDict[item][0]] = item
3556    sampleParms = GetSampleParms()
3557    Rwps = GetRwps()
3558    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
3559    G2frame.dataFrame.SetLabel('Sequential refinement results')
3560    G2frame.dataFrame.CreateStatusBar()
3561    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
3562    colLabels = ['Rwp',]+data['varyList']+atomList.keys()+cellList.keys()
3563    Types = (len(data['varyList']+atomList.keys()+cellList.keys())+1)*[wg.GRID_VALUE_FLOAT,]
3564    seqList = [[Rwps[i],]+list(data[name]['variables']) for i,name in enumerate(histNames)]
3565    for i,item in enumerate(seqList):
3566        newAtomDict = data[histNames[i]]['newAtomDict']
3567        newCellDict = data[histNames[i]]['newCellDict']
3568        item += [newAtomDict[atomList[parm]][1] for parm in atomList.keys()]
3569        item += [newCellDict[cellList[parm]][1] for parm in cellList.keys()]
3570    G2frame.SeqTable = Table(seqList,colLabels=colLabels,rowLabels=histNames,types=Types)
3571    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
3572    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
3573    G2frame.dataDisplay.EnableEditing(False)
3574    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, Select)
3575    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
3576    G2frame.dataDisplay.SetMargins(0,0)
3577    G2frame.dataDisplay.AutoSizeColumns(True)
3578    G2frame.dataFrame.setSizePosLeft([700,350])
3579       
3580################################################################################
3581#####  Main PWDR panel
3582################################################################################           
3583       
3584def UpdatePWHKPlot(G2frame,kind,item):
3585    '''Called when the histogram main tree entry is called. Displays the
3586    histogram weight factor, refinement statistics for the histogram
3587    and the range of data for a simulation.
3588
3589    Also invokes a plot of the histogram.
3590    '''
3591    def onEditSimRange(event):
3592        'Edit simulation range'
3593        inp = [
3594            min(data[1][0]),
3595            max(data[1][0]),
3596            None
3597            ]
3598        inp[2] = (inp[1] - inp[0])/(len(data[1][0])-1.)
3599        names = ('start angle', 'end angle', 'step size')
3600        dictlst = [inp] * len(inp)
3601        elemlst = range(len(inp))
3602        dlg = ScrolledMultiEditor(
3603            G2frame,[inp] * len(inp), range(len(inp)), names,
3604            header='Edit simulation range',
3605            minvals=(0.001,0.001,0.0001),
3606            maxvals=(180.,180.,.1),
3607            )
3608        dlg.CenterOnParent()
3609        val = dlg.ShowModal()
3610        dlg.Destroy()
3611        if val != wx.ID_OK: return
3612        if inp[0] > inp[1]:
3613            end,start,step = inp
3614        else:               
3615            start,end,step = inp
3616        step = abs(step)
3617        N = int((end-start)/step)+1
3618        newdata = np.linspace(start,end,N,True)
3619        if len(newdata) < 2: return # too small a range - reject
3620        data[1] = [newdata,np.zeros_like(newdata),np.ones_like(newdata),
3621            np.zeros_like(newdata),np.zeros_like(newdata),np.zeros_like(newdata)]
3622        Tmin = newdata[0]
3623        Tmax = newdata[-1]
3624        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,item,'Limits'),
3625            [(Tmin,Tmax),[Tmin,Tmax]])
3626        UpdatePWHKPlot(G2frame,kind,item) # redisplay data screen
3627
3628    def OnErrorAnalysis(event):
3629        G2plt.PlotDeltSig(G2frame,kind)
3630       
3631    def OnWtFactor(event):
3632        try:
3633            val = float(wtval.GetValue())
3634        except ValueError:
3635            val = data[0]['wtFactor']
3636        data[0]['wtFactor'] = val
3637        wtval.SetValue('%.3f'%(val))
3638           
3639    data = G2frame.PatternTree.GetItemPyData(item)
3640#patches
3641    if 'wtFactor' not in data[0]:
3642        data[0] = {'wtFactor':1.0}
3643    if isinstance(data[1],list) and kind == 'HKLF':
3644        RefData = {'RefList':[],'FF':[]}
3645        for ref in data[1]:
3646            RefData['RefList'].append(ref[:11]+[ref[13],])
3647            RefData['FF'].append(ref[14])
3648        data[1] = RefData
3649        G2frame.PatternTree.SetItemPyData(item,data)
3650#end patches
3651    if G2frame.dataDisplay:
3652        G2frame.dataDisplay.Destroy()
3653    SetDataMenuBar(G2frame,G2frame.dataFrame.ErrorMenu)
3654    G2frame.dataFrame.Bind(wx.EVT_MENU,OnErrorAnalysis, id=wxID_PWDANALYSIS)
3655    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3656   
3657    mainSizer = wx.BoxSizer(wx.VERTICAL)
3658    mainSizer.Add((5,5),)
3659    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
3660    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,WACV)
3661    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
3662    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
3663    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
3664    wtSizer.Add(wtval,0,WACV)
3665    mainSizer.Add(wtSizer)
3666    if data[0].get('Dummy'):
3667        simSizer = wx.BoxSizer(wx.HORIZONTAL)
3668        Tmin = min(data[1][0])
3669        Tmax = max(data[1][0])
3670        num = len(data[1][0])
3671        step = (Tmax - Tmin)/(num-1)
3672        t = u'2\u03b8' # 2theta
3673        lbl =  u'Simulation range: {:.2f} to {:.2f} {:s}\nwith {:.4f} steps ({:d} points)'
3674        lbl += u'\n(Edit range resets observed intensities).'
3675        lbl = lbl.format(Tmin,Tmax,t,step,num)
3676        simSizer.Add(wx.StaticText(G2frame.dataDisplay,wx.ID_ANY,lbl),
3677                    0,WACV)
3678        but = wx.Button(G2frame.dataDisplay,wx.ID_ANY,"Edit range")
3679        but.Bind(wx.EVT_BUTTON,onEditSimRange)
3680        simSizer.Add(but,0,WACV)
3681        mainSizer.Add(simSizer)
3682    if 'Nobs' in data[0]:
3683        mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
3684            ' Data residual wR: %.3f%% on %d observations'%(data[0]['wR'],data[0]['Nobs'])))
3685        for value in data[0]:
3686            if 'Nref' in value:
3687                mainSizer.Add((5,5),)
3688                pfx = value.split('Nref')[0]
3689                name = data[0].get(pfx.split(':')[0]+'::Name','?')
3690                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' For phase '+name+':'))
3691                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
3692                    u' Unweighted phase residuals RF\u00b2: %.3f%%, RF: %.3f%% on %d reflections  '% \
3693                    (data[0][pfx+'Rf^2'],data[0][pfx+'Rf'],data[0][value])))
3694    mainSizer.Add((5,5),)
3695    mainSizer.Layout()   
3696    G2frame.dataDisplay.SetSizer(mainSizer)
3697    Size = mainSizer.Fit(G2frame.dataFrame)
3698    Size[1] += 10
3699    G2frame.dataFrame.setSizePosLeft(Size)
3700    G2frame.PatternTree.SetItemPyData(item,data)
3701    if kind == 'PWDR':
3702        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
3703    elif kind == 'SASD':
3704        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
3705    elif kind == 'HKLF':
3706        G2plt.PlotSngl(G2frame,newPlot=True)
3707                 
3708################################################################################
3709#####  HKLF controls
3710################################################################################           
3711       
3712def UpdateHKLControls(G2frame,data):
3713    '''Needs a doc string
3714    '''
3715   
3716    def OnScaleSlider(event):
3717        scale = int(scaleSel.GetValue())/1000.
3718        scaleSel.SetValue(int(scale*1000.))
3719        data['Scale'] = scale*1.
3720        G2plt.PlotSngl(G2frame)
3721       
3722    def OnLayerSlider(event):
3723        layer = layerSel.GetValue()
3724        data['Layer'] = layer
3725        G2plt.PlotSngl(G2frame)
3726       
3727    def OnSelZone(event):
3728        data['Zone'] = zoneSel.GetValue()
3729        izone = zones.index(data['Zone'])
3730        layerSel.SetRange(maxValue=HKLmax[izone],minValue=HKLmin[izone])
3731        G2plt.PlotSngl(G2frame,newPlot=True)
3732       
3733    def OnSelType(event):
3734        data['Type'] = typeSel.GetValue()
3735        G2plt.PlotSngl(G2frame)
3736       
3737    def SetStatusLine():
3738        Status.SetStatusText("")
3739                                     
3740    if G2frame.dataDisplay:
3741        G2frame.dataDisplay.Destroy()
3742    if not G2frame.dataFrame.GetStatusBar():
3743        Status = G2frame.dataFrame.CreateStatusBar()
3744    SetStatusLine()
3745    zones = ['100','010','001']
3746    HKLmax = data['HKLmax']
3747    HKLmin = data['HKLmin']
3748    typeChoices = ['Fosq','Fo','|DFsq|/sig','|DFsq|>sig','|DFsq|>3sig']
3749    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3750    SetDataMenuBar(G2frame)
3751    G2frame.dataFrame.SetTitle('HKL Plot Controls')
3752    mainSizer = wx.BoxSizer(wx.VERTICAL)
3753    mainSizer.Add((5,10),0)
3754   
3755    scaleSizer = wx.BoxSizer(wx.HORIZONTAL)
3756    scaleSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Scale'),0,
3757        WACV|wx.EXPAND)
3758    scaleSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=1000,minValue=1,
3759        style=wx.SL_HORIZONTAL,value=int(data['Scale']*10))
3760    scaleSizer.Add(scaleSel,1,wx.EXPAND|wx.RIGHT|WACV)
3761    scaleSel.SetLineSize(10)
3762    scaleSel.SetPageSize(10)
3763    scaleSel.Bind(wx.EVT_SLIDER, OnScaleSlider)
3764    mainSizer.Add(scaleSizer,0,wx.EXPAND|wx.RIGHT)
3765    mainSizer.Add((0,10),0)   
3766   
3767    zoneSizer = wx.BoxSizer(wx.HORIZONTAL)
3768    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Zone  '),0,
3769        WACV)
3770    zoneSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Zone'],choices=['100','010','001'],
3771        style=wx.CB_READONLY|wx.CB_DROPDOWN)
3772    zoneSel.Bind(wx.EVT_COMBOBOX, OnSelZone)
3773    zoneSizer.Add(zoneSel,0,WACV)
3774    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Plot type  '),0,
3775        WACV)       
3776    typeSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Type'],choices=typeChoices,
3777        style=wx.CB_READONLY|wx.CB_DROPDOWN)
3778    typeSel.Bind(wx.EVT_COMBOBOX, OnSelType)
3779    zoneSizer.Add(typeSel,0,WACV)
3780    zoneSizer.Add((10,0),0)   
3781    mainSizer.Add(zoneSizer,0,wx.EXPAND|wx.RIGHT)
3782    mainSizer.Add((0,10),0)   
3783       
3784    izone = zones.index(data['Zone'])
3785    layerSizer = wx.BoxSizer(wx.HORIZONTAL)
3786    layerSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Layer'),0,
3787        WACV|wx.EXPAND)
3788    layerSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=HKLmax[izone],minValue=HKLmin[izone],
3789        style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_LABELS,value=0)
3790    layerSel.SetLineSize(1)
3791    layerSel.SetPageSize(1)
3792    layerSel.Bind(wx.EVT_SLIDER, OnLayerSlider)   
3793    layerSizer.Add(layerSel,1,wx.EXPAND|wx.RIGHT|WACV)
3794    layerSizer.Add((10,0),0)   
3795    mainSizer.Add(layerSizer,1,wx.EXPAND|wx.RIGHT)
3796
3797       
3798    mainSizer.Layout()   
3799    G2frame.dataDisplay.SetSizer(mainSizer)
3800    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3801    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3802
3803################################################################################
3804#####  Pattern tree routines
3805################################################################################           
3806       
3807def GetPatternTreeDataNames(G2frame,dataTypes):
3808    '''Needs a doc string
3809    '''
3810    names = []
3811    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
3812    while item:
3813        name = G2frame.PatternTree.GetItemText(item)
3814        if name[:4] in dataTypes:
3815            names.append(name)
3816        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
3817    return names
3818                         
3819def GetPatternTreeItemId(G2frame, parentId, itemText):
3820    '''Needs a doc string
3821    '''
3822    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
3823    while item:
3824        if G2frame.PatternTree.GetItemText(item) == itemText:
3825            return item
3826        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
3827    return 0               
3828
3829def MovePatternTreeToGrid(G2frame,item):
3830    '''Needs a doc string
3831    '''
3832   
3833#    print G2frame.PatternTree.GetItemText(item)
3834   
3835    oldPage = None # will be set later if already on a Phase item
3836    if G2frame.dataFrame:
3837        SetDataMenuBar(G2frame)
3838        if G2frame.dataFrame.GetLabel() == 'Comments':
3839            try:
3840                data = [G2frame.dataDisplay.GetValue()]
3841                G2frame.dataDisplay.Clear() 
3842                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
3843                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
3844            except:     #clumsy but avoids dead window problem when opening another project
3845                pass
3846        elif G2frame.dataFrame.GetLabel() == 'Notebook':
3847            try:
3848                data = [G2frame.dataDisplay.GetValue()]
3849                G2frame.dataDisplay.Clear() 
3850                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
3851                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
3852            except:     #clumsy but avoids dead window problem when opening another project
3853                pass
3854        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
3855            if G2frame.dataDisplay: 
3856                oldPage = G2frame.dataDisplay.GetSelection()
3857        G2frame.dataFrame.Clear()
3858        G2frame.dataFrame.SetLabel('')
3859    else:
3860        #create the frame for the data item window
3861        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
3862        G2frame.dataFrame.PhaseUserSize = None
3863       
3864    G2frame.dataFrame.Raise()           
3865    G2frame.PickId = 0
3866    parentID = G2frame.root
3867    #for i in G2frame.ExportPattern: i.Enable(False)
3868    defWid = [250,150]
3869    if item != G2frame.root:
3870        parentID = G2frame.PatternTree.GetItemParent(item)
3871    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
3872        G2frame.PatternId = item
3873        G2frame.PickId = item
3874        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
3875            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
3876            G2frame.PatternId = 0
3877            #for i in G2frame.ExportPattern: i.Enable(False)
3878            data = G2frame.PatternTree.GetItemPyData(item)
3879            UpdateNotebook(G2frame,data)
3880        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
3881            G2frame.PatternId = 0
3882            #for i in G2frame.ExportPattern: i.Enable(False)
3883            data = G2frame.PatternTree.GetItemPyData(item)
3884            if not data:           #fill in defaults
3885                data = copy.copy(G2obj.DefaultControls)    #least squares controls
3886                G2frame.PatternTree.SetItemPyData(item,data)                             
3887            for i in G2frame.Refine: i.Enable(True)
3888            for i in G2frame.SeqRefine: i.Enable(True)
3889            UpdateControls(G2frame,data)
3890        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
3891            data = G2frame.PatternTree.GetItemPyData(item)
3892            UpdateSeqResults(G2frame,data)           
3893        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
3894            data = G2frame.PatternTree.GetItemPyData(item)
3895            G2frame.dataFrame.setSizePosLeft(defWid)
3896            text = ''
3897            if 'Rvals' in data:
3898                Nvars = len(data['varyList'])
3899                Rvals = data['Rvals']
3900                text = '\nFinal residuals: \nwR = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
3901                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
3902                if 'lamMax' in Rvals:
3903                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
3904            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3905                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
3906            G2plt.PlotCovariance(G2frame,data)
3907        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
3908            data = G2frame.PatternTree.GetItemPyData(item)
3909            G2cnstG.UpdateConstraints(G2frame,data)
3910        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
3911            data = G2frame.PatternTree.GetItemPyData(item)
3912            G2cnstG.UpdateRigidBodies(G2frame,data)
3913        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
3914            data = G2frame.PatternTree.GetItemPyData(item)
3915            Phases = G2frame.GetPhaseData()
3916            phase = ''
3917            phaseName = ''
3918            if Phases:
3919                phaseName = Phases.keys()[0]
3920            G2frame.dataFrame.setSizePosLeft(defWid)
3921            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
3922        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
3923            G2frame.Image = item
3924            G2plt.PlotImage(G2frame,newPlot=True)
3925        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
3926            G2plt.PlotPowderLines(G2frame)
3927        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
3928            #for i in G2frame.ExportPattern: i.Enable(True)
3929            UpdatePWHKPlot(G2frame,'PWDR',item)
3930        elif 'SASD' in G2frame.PatternTree.GetItemText(item):
3931            #for i in G2frame.ExportPattern: i.Enable(True)
3932            UpdatePWHKPlot(G2frame,'SASD',item)
3933        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
3934            G2frame.Sngl = item
3935            UpdatePWHKPlot(G2frame,'HKLF',item)
3936        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
3937            G2frame.PatternId = item
3938            for i in G2frame.ExportPDF: i.Enable(True)
3939            G2plt.PlotISFG(G2frame,type='S(Q)')
3940        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
3941            G2frame.dataFrame.setSizePosLeft(defWid)
3942            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3943                value='Select one phase to see its parameters')           
3944    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
3945        G2frame.PickId = item
3946        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3947        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
3948    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
3949        G2frame.PickId = item
3950        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3951        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
3952    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
3953        G2frame.PickId = item
3954        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3955        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
3956    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
3957        G2frame.PickId = item
3958        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3959        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
3960    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
3961        G2frame.PickId = item
3962        data = G2frame.PatternTree.GetItemPyData(item)
3963        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
3964    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
3965        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
3966        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3967        G2frame.PickId = item
3968        data = G2frame.PatternTree.GetItemPyData(item)
3969        UpdateComments(G2frame,data)
3970    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
3971        G2frame.dataFrame.SetTitle('Image Controls')
3972        G2frame.PickId = item
3973        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3974        masks = G2frame.PatternTree.GetItemPyData(
3975            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
3976        data = G2frame.PatternTree.GetItemPyData(item)
3977        G2imG.UpdateImageControls(G2frame,data,masks)
3978        G2plt.PlotImage(G2frame)
3979    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
3980        G2frame.dataFrame.SetTitle('Masks')
3981        G2frame.PickId = item
3982        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3983        data = G2frame.PatternTree.GetItemPyData(item)
3984        G2imG.UpdateMasks(G2frame,data)
3985        G2plt.PlotImage(G2frame)
3986    elif G2frame.PatternTree.GetItemText(item) == 'Stress/Strain':
3987        G2frame.dataFrame.SetTitle('Stress/Strain')
3988        G2frame.PickId = item
3989        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3990        data = G2frame.PatternTree.GetItemPyData(item)
3991        G2imG.UpdateStressStrain(G2frame,data)
3992        G2plt.PlotImage(G2frame)
3993        G2plt.PlotStrain(G2frame,data,newPlot=True)
3994    elif G2frame.PatternTree.GetItemText(item) == 'HKL Plot Controls':
3995        G2frame.PickId = item
3996        G2frame.Sngl = G2frame.PatternTree.GetItemParent(item)
3997        data = G2frame.PatternTree.GetItemPyData(item)
3998        UpdateHKLControls(G2frame,data)
3999        G2plt.PlotSngl(G2frame)
4000    elif G2frame.PatternTree.GetItemText(item) == 'PDF Controls':
4001        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4002        for i in G2frame.ExportPDF: i.Enable(True)
4003        G2frame.PickId = item
4004        data = G2frame.PatternTree.GetItemPyData(item)
4005        G2pdG.UpdatePDFGrid(G2frame,data)
4006        G2plt.PlotISFG(G2frame,type='I(Q)')
4007        G2plt.PlotISFG(G2frame,type='S(Q)')
4008        G2plt.PlotISFG(G2frame,type='F(Q)')
4009        G2plt.PlotISFG(G2frame,type='G(R)')
4010    elif G2frame.PatternTree.GetItemText(item) == 'Peak List':
4011        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4012        for i in G2frame.ExportPeakList: i.Enable(True)
4013        G2frame.PickId = item
4014        data = G2frame.PatternTree.GetItemPyData(item)
4015        G2pdG.UpdatePeakGrid(G2frame,data)
4016        G2plt.PlotPatterns(G2frame)
4017    elif G2frame.PatternTree.GetItemText(item) == 'Background':
4018        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4019        G2frame.PickId = item
4020        data = G2frame.PatternTree.GetItemPyData(item)
4021        G2pdG.UpdateBackground(G2frame,data)
4022        G2plt.PlotPatterns(G2frame)
4023    elif G2frame.PatternTree.GetItemText(item) == 'Limits':
4024        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4025        datatype = G2frame.PatternTree.GetItemPyData(G2frame.PatternId)[2][:4]
4026        G2frame.PickId = item
4027        data = G2frame.PatternTree.GetItemPyData(item)
4028        G2pdG.UpdateLimitsGrid(G2frame,data,datatype)
4029        G2plt.PlotPatterns(G2frame,plotType=datatype)
4030    elif G2frame.PatternTree.GetItemText(item) == 'Instrument Parameters':
4031        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4032        G2frame.PickId = item
4033        data = G2frame.PatternTree.GetItemPyData(item)[0]
4034        G2pdG.UpdateInstrumentGrid(G2frame,data)
4035        if 'P' in data['Type'][0]:          #powder data only
4036            G2plt.PlotPeakWidths(G2frame)
4037    elif G2frame.PatternTree.GetItemText(item) == 'Models':
4038        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4039        G2frame.PickId = item
4040        data = G2frame.PatternTree.GetItemPyData(item)
4041        G2pdG.UpdateModelsGrid(G2frame,data)
4042        G2plt.PlotPatterns(G2frame,plotType='SASD')
4043        if len(data['Size']['Distribution']):
4044            G2plt.PlotSASDSizeDist(G2frame)
4045    elif G2frame.PatternTree.GetItemText(item) == 'Substances':
4046        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4047        G2frame.PickId = item
4048        data = G2frame.PatternTree.GetItemPyData(item)
4049        G2pdG.UpdateSubstanceGrid(G2frame,data)
4050    elif G2frame.PatternTree.GetItemText(item) == 'Sample Parameters':
4051        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4052        G2frame.PickId = item
4053        data = G2frame.PatternTree.GetItemPyData(item)
4054        datatype = G2frame.PatternTree.GetItemPyData(G2frame.PatternId)[2][:4]
4055
4056        if 'Temperature' not in data:           #temp fix for old gpx files
4057            data = {'Scale':[1.0,True],'Type':'Debye-Scherrer','Absorption':[0.0,False],'DisplaceX':[0.0,False],
4058                'DisplaceY':[0.0,False],'Diffuse':[],'Temperature':300.,'Pressure':1.0,
4059                    'FreePrm1':0.,'FreePrm2':0.,'FreePrm3':0.,
4060                    'Gonio. radius':200.0}
4061            G2frame.PatternTree.SetItemPyData(item,data)
4062   
4063        G2pdG.UpdateSampleGrid(G2frame,data)
4064        G2plt.PlotPatterns(G2frame,plotType=datatype)
4065    elif G2frame.PatternTree.GetItemText(item) == 'Index Peak List':
4066        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4067        for i in G2frame.ExportPeakList: i.Enable(True)
4068        G2frame.PickId = item
4069        data = G2frame.PatternTree.GetItemPyData(item)
4070        G2pdG.UpdateIndexPeaksGrid(G2frame,data)
4071        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
4072            G2plt.PlotPowderLines(G2frame)
4073        else:
4074            G2plt.PlotPatterns(G2frame)
4075    elif G2frame.PatternTree.GetItemText(item) == 'Unit Cells List':
4076        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4077        G2frame.PickId = item
4078        data = G2frame.PatternTree.GetItemPyData(item)
4079        if not data:
4080            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
4081            data.append([0,0,0,0,0,0,0,0,0,0,0,0,0,0])      #Bravais lattice flags
4082            data.append([])                                 #empty cell list
4083            data.append([])                                 #empty dmin
4084            G2frame.PatternTree.SetItemPyData(item,data)                             
4085        G2pdG.UpdateUnitCellsGrid(G2frame,data)
4086        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
4087            G2plt.PlotPowderLines(G2frame)
4088        else:
4089            G2plt.PlotPatterns(G2frame)
4090    elif G2frame.PatternTree.GetItemText(item) == 'Reflection Lists':   #powder reflections
4091        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4092        G2frame.PickId = item
4093        data = G2frame.PatternTree.GetItemPyData(item)
4094        G2frame.RefList = ''
4095        if len(data):
4096            G2frame.RefList = data.keys()[0]
4097        G2pdG.UpdateReflectionGrid(G2frame,data)
4098        G2plt.PlotPatterns(G2frame)
4099    elif G2frame.PatternTree.GetItemText(item) == 'Reflection List':    #HKLF reflections
4100        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4101        name = G2frame.PatternTree.GetItemText(G2frame.PatternId)
4102        data = G2frame.PatternTree.GetItemPyData(G2frame.PatternId)
4103        G2pdG.UpdateReflectionGrid(G2frame,data,HKLF=True,Name=name)
4104
4105def SetDataMenuBar(G2frame,menu=None):
4106    '''Set the menu for the data frame. On the Mac put this
4107    menu for the data tree window instead.
4108
4109    Note that data frame items do not have menus, for these (menu=None)
4110    display a blank menu or on the Mac display the standard menu for
4111    the data tree window.
4112    '''
4113    if sys.platform == "darwin":
4114        if menu is None:
4115            G2frame.SetMenuBar(G2frame.GSASIIMenu)
4116        else:
4117            G2frame.SetMenuBar(menu)
4118    else:
4119        if menu is None:
4120            G2frame.dataFrame.SetMenuBar(G2frame.dataFrame.BlankMenu)
4121        else:
4122            G2frame.dataFrame.SetMenuBar(menu)
4123
4124def HorizontalLine(sizer,parent):
4125    '''Draws a horizontal line as wide as the window.
4126    This shows up on the Mac as a very thin line, no matter what I do
4127    '''
4128    line = wx.StaticLine(parent,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
4129    sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
4130
4131if __name__ == '__main__':
4132    # test ScrolledMultiEditor
4133    app = wx.PySimpleApp()
4134    frm = wx.Frame(None) # create a frame
4135    frm.Show(True)
4136    Data1 = {
4137        'Order':1,
4138        'omega':'string',
4139        'chi':2.0,
4140        'phi':'',
4141        }
4142    elemlst = sorted(Data1.keys())
4143    postlbl = sorted(Data1.keys())
4144    dictlst = len(elemlst)*[Data1,]
4145
4146    Data2 = list(range(100))
4147    elemlst += range(2,6)
4148    postlbl += range(2,6)
4149    dictlst += len(range(2,6))*[Data2,]
4150
4151    prelbl = range(len(elemlst))
4152    postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
4153    header="""This is a longer\nmultiline and perhaps silly header"""
4154    dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
4155                              header=header,CopyButton=True)
4156    print Data1
4157    if dlg.ShowModal() == wx.ID_OK:
4158        for d,k in zip(dictlst,elemlst):
4159            print k,d[k]
4160    dlg.Destroy()
4161    if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
4162                               header=header):
4163        for d,k in zip(dictlst,elemlst):
4164            print k,d[k]
4165
4166#app.MainLoop()
Note: See TracBrowser for help on using the repository browser.