source: trunk/GSASIIgrid.py @ 1033

Last change on this file since 1033 was 1033, checked in by toby, 9 years ago

Fix Select tab menu buttons by getting unused Id values

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