source: trunk/GSASIIgrid.py @ 1143

Last change on this file since 1143 was 1143, checked in by toby, 9 years ago

rework constraints to handle names and refine flag for new var (input linear constraints); redo powder 'Sample Parameters'

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