source: trunk/GSASIIgrid.py @ 1072

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

fix distangle dialog reset

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