source: trunk/GSASIIgrid.py @ 1087

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

simulated CW powder data now complete with Poisson errors

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