source: trunk/GSASIIgrid.py @ 1203

Last change on this file since 1203 was 1203, checked in by vondreele, 9 years ago

show q in powder/sasd plots
work on absorption corr. for sasd data

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