source: trunk/GSASIIgrid.py @ 1145

Last change on this file since 1145 was 1145, checked in by toby, 10 years ago

move DefaultControls? out of g2grid to avoid wx requirement; now import mmCIF reflection files

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