source: trunk/GSASIIgrid.py @ 1115

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

rework exports for new types

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