source: trunk/GSASIIgrid.py @ 1172

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

ask to link phases to histograms after an import of either; rethink import initialization

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