source: trunk/GSASIIgrid.py @ 1177

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

try to add more control over number format from ValidatedTxtCtrl?
not entirely successful so far

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