source: trunk/GSASIIgrid.py @ 1067

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

CIF updates

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