source: trunk/GSASIIgrid.py @ 1016

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

remove PawleyDelete? - not used
fix floating point error in vcov matrix processing

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 140.2 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2013-08-05 02:35:54 +0000 (Mon, 05 Aug 2013) $
5# $Author: vondreele $
6# $Revision: 1016 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1016 2013-08-05 02:35:54Z vondreele $
9########### SVN repository information ###################
10'''
11*GSASIIgrid: Basic GUI routines*
12--------------------------------
13
14'''
15import wx
16import wx.grid as wg
17import wx.wizard as wz
18import wx.aui
19import wx.lib.scrolledpanel as wxscroll
20import time
21import 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: 1016 $")
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
72] = [wx.NewId() for item in range(12)]
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,
104] = [wx.NewId() for item in range(7)]
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='File')
1669        self.LimitEdit.Append(id=wxID_LIMITCOPY, kind=wx.ITEM_NORMAL,text='Copy',
1670            help='Copy limits to other histograms')
1671        self.PostfillDataMenu()
1672           
1673        # PDR / Background
1674        self.BackMenu = wx.MenuBar()
1675        self.PrefillDataMenu(self.BackMenu,helpType='Background')
1676        self.BackEdit = wx.Menu(title='')
1677        self.BackMenu.Append(menu=self.BackEdit, title='File')
1678        self.BackEdit.Append(id=wxID_BACKCOPY, kind=wx.ITEM_NORMAL,text='Copy',
1679            help='Copy background parameters to other histograms')
1680        self.BackEdit.Append(id=wxID_BACKFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
1681            help='Copy background refinement flags to other histograms')
1682        self.PostfillDataMenu()
1683           
1684        # PDR / Instrument Parameters
1685        self.InstMenu = wx.MenuBar()
1686        self.PrefillDataMenu(self.InstMenu,helpType='Instrument Parameters')
1687        self.InstEdit = wx.Menu(title='')
1688        self.InstMenu.Append(menu=self.InstEdit, title='Operations')
1689        self.InstEdit.Append(help='Reset instrument profile parameters to default', 
1690            id=wxID_INSTLOAD, kind=wx.ITEM_NORMAL,text='Load profile...')
1691        self.InstEdit.Append(help='Load instrument profile parameters from file', 
1692            id=wxID_INSTSAVE, kind=wx.ITEM_NORMAL,text='Save profile...')
1693        self.InstEdit.Append(help='Save instrument profile parameters to file', 
1694            id=wxID_INSTPRMRESET, kind=wx.ITEM_NORMAL,text='Reset profile')
1695        self.InstEdit.Append(help='Copy instrument profile parameters to other histograms', 
1696            id=wxID_INSTCOPY, kind=wx.ITEM_NORMAL,text='Copy')
1697        self.InstEdit.Append(id=wxID_INSTFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
1698            help='Copy instrument parameter refinement flags to other histograms')
1699#        self.InstEdit.Append(help='Change radiation type (Ka12 - synch)',
1700#            id=wxID_CHANGEWAVETYPE, kind=wx.ITEM_NORMAL,text='Change radiation')
1701        self.PostfillDataMenu()
1702       
1703        # PDR / Sample Parameters
1704        self.SampleMenu = wx.MenuBar()
1705        self.PrefillDataMenu(self.SampleMenu,helpType='Sample Parameters')
1706        self.SampleEdit = wx.Menu(title='')
1707        self.SampleMenu.Append(menu=self.SampleEdit, title='File')
1708        self.SampleEdit.Append(id=wxID_SAMPLELOAD, kind=wx.ITEM_NORMAL,text='Load',
1709            help='Load sample parameters from file')
1710        self.SampleEdit.Append(id=wxID_SAMPLESAVE, kind=wx.ITEM_NORMAL,text='Save',
1711            help='Save sample parameters to file')
1712        self.SampleEdit.Append(id=wxID_SAMPLECOPY, kind=wx.ITEM_NORMAL,text='Copy',
1713            help='Copy refinable sample parameters to other histograms')
1714        self.SampleEdit.Append(id=wxID_SAMPLEFLAGCOPY, kind=wx.ITEM_NORMAL,text='Copy flags',
1715            help='Copy sample parameter refinement flags to other histograms')
1716        self.PostfillDataMenu()
1717
1718        # PDR / Peak List
1719        self.PeakMenu = wx.MenuBar()
1720        self.PrefillDataMenu(self.PeakMenu,helpType='Peak List')
1721        self.PeakEdit = wx.Menu(title='')
1722        self.PeakMenu.Append(menu=self.PeakEdit, title='Peak Fitting')
1723        self.AutoSearch = self.PeakEdit.Append(help='Automatic peak search', 
1724            id=wxID_AUTOSEARCH, kind=wx.ITEM_NORMAL,text='Auto search')
1725        self.UnDo = self.PeakEdit.Append(help='Undo last least squares refinement', 
1726            id=wxID_UNDO, kind=wx.ITEM_NORMAL,text='UnDo')
1727        self.PeakFit = self.PeakEdit.Append(id=wxID_LSQPEAKFIT, kind=wx.ITEM_NORMAL,text='LSQ PeakFit', 
1728            help='Peak fitting via least-squares' )
1729        self.PFOneCycle = self.PeakEdit.Append(id=wxID_LSQONECYCLE, kind=wx.ITEM_NORMAL,text='LSQ one cycle', 
1730            help='One cycle of Peak fitting via least-squares' )
1731        self.PeakEdit.Append(id=wxID_RESETSIGGAM, kind=wx.ITEM_NORMAL, 
1732            text='Reset sig and gam',help='Reset sigma and gamma to global fit' )
1733        self.PeakEdit.Append(id=wxID_CLEARPEAKS, kind=wx.ITEM_NORMAL,text='Clear peaks', 
1734            help='Clear the peak list' )
1735        self.PostfillDataMenu()
1736        self.UnDo.Enable(False)
1737        self.PeakFit.Enable(False)
1738        self.PFOneCycle.Enable(False)
1739       
1740        # PDR / Index Peak List
1741        self.IndPeaksMenu = wx.MenuBar()
1742        self.PrefillDataMenu(self.IndPeaksMenu,helpType='Index Peak List')
1743        self.IndPeaksEdit = wx.Menu(title='')
1744        self.IndPeaksMenu.Append(menu=self.IndPeaksEdit,title='Operations')
1745        self.IndPeaksEdit.Append(help='Load/Reload index peaks from peak list',id=wxID_INDXRELOAD, 
1746            kind=wx.ITEM_NORMAL,text='Load/Reload')
1747        self.PostfillDataMenu()
1748       
1749        # PDR / Unit Cells List
1750        self.IndexMenu = wx.MenuBar()
1751        self.PrefillDataMenu(self.IndexMenu,helpType='Unit Cells List')
1752        self.IndexEdit = wx.Menu(title='')
1753        self.IndexMenu.Append(menu=self.IndexEdit, title='Cell Index/Refine')
1754        self.IndexPeaks = self.IndexEdit.Append(help='', id=wxID_INDEXPEAKS, kind=wx.ITEM_NORMAL,
1755            text='Index Cell')
1756        self.CopyCell = self.IndexEdit.Append( id=wxID_COPYCELL, kind=wx.ITEM_NORMAL,text='Copy Cell', 
1757            help='Copy selected unit cell from indexing to cell refinement fields')
1758        self.RefineCell = self.IndexEdit.Append( id=wxID_REFINECELL, kind=wx.ITEM_NORMAL, 
1759            text='Refine Cell',help='Refine unit cell parameters from indexed peaks')
1760        self.MakeNewPhase = self.IndexEdit.Append( id=wxID_MAKENEWPHASE, kind=wx.ITEM_NORMAL,
1761            text='Make new phase',help='Make new phase from selected unit cell')
1762        self.PostfillDataMenu()
1763        self.IndexPeaks.Enable(False)
1764        self.CopyCell.Enable(False)
1765        self.RefineCell.Enable(False)
1766        self.MakeNewPhase.Enable(False)
1767       
1768        # PDR / Reflection Lists
1769        self.ReflMenu = wx.MenuBar()
1770        self.PrefillDataMenu(self.ReflMenu,helpType='Reflection List')
1771        self.ReflEdit = wx.Menu(title='')
1772        self.ReflMenu.Append(menu=self.ReflEdit, title='Reflection List')
1773        self.SelectPhase = self.ReflEdit.Append(help='Select phase for reflection list',id=wxID_SELECTPHASE, 
1774            kind=wx.ITEM_NORMAL,text='Select phase')
1775        self.PostfillDataMenu()
1776       
1777        # IMG / Image Controls
1778        self.ImageMenu = wx.MenuBar()
1779        self.PrefillDataMenu(self.ImageMenu,helpType='Image Controls')
1780        self.ImageEdit = wx.Menu(title='')
1781        self.ImageMenu.Append(menu=self.ImageEdit, title='Operations')
1782        self.ImageEdit.Append(help='Calibrate detector by fitting to calibrant lines', 
1783            id=wxID_IMCALIBRATE, kind=wx.ITEM_NORMAL,text='Calibrate')
1784        self.ImageEdit.Append(help='Recalibrate detector by fitting to calibrant lines', 
1785            id=wxID_IMRECALIBRATE, kind=wx.ITEM_NORMAL,text='Recalibrate')
1786        self.ImageEdit.Append(help='Clear calibration data points and rings',id=wxID_IMCLEARCALIB, 
1787            kind=wx.ITEM_NORMAL,text='Clear calibration')
1788        self.ImageEdit.Append(help='Integrate selected image',id=wxID_IMINTEGRATE, 
1789            kind=wx.ITEM_NORMAL,text='Integrate')
1790        self.ImageEdit.Append(help='Integrate all images selected from list',id=wxID_INTEGRATEALL,
1791            kind=wx.ITEM_NORMAL,text='Integrate all')
1792        self.ImageEdit.Append(help='Copy image controls to other images', 
1793            id=wxID_IMCOPYCONTROLS, kind=wx.ITEM_NORMAL,text='Copy Controls')
1794        self.ImageEdit.Append(help='Save image controls to file', 
1795            id=wxID_IMSAVECONTROLS, kind=wx.ITEM_NORMAL,text='Save Controls')
1796        self.ImageEdit.Append(help='Load image controls from file', 
1797            id=wxID_IMLOADCONTROLS, kind=wx.ITEM_NORMAL,text='Load Controls')
1798        self.PostfillDataMenu()
1799           
1800        # IMG / Masks
1801        self.MaskMenu = wx.MenuBar()
1802        self.PrefillDataMenu(self.MaskMenu,helpType='Image Masks')
1803        self.MaskEdit = wx.Menu(title='')
1804        self.MaskMenu.Append(menu=self.MaskEdit, title='Operations')
1805        self.MaskEdit.Append(help='Copy mask to other images', 
1806            id=wxID_MASKCOPY, kind=wx.ITEM_NORMAL,text='Copy mask')
1807        self.MaskEdit.Append(help='Save mask to file', 
1808            id=wxID_MASKSAVE, kind=wx.ITEM_NORMAL,text='Save mask')
1809        self.MaskEdit.Append(help='Load mask from file', 
1810            id=wxID_MASKLOAD, kind=wx.ITEM_NORMAL,text='Load mask')
1811        self.PostfillDataMenu()
1812           
1813        # IMG / Stress/Strain
1814        self.StrStaMenu = wx.MenuBar()
1815        self.PrefillDataMenu(self.StrStaMenu,helpType='Stress/Strain')
1816        self.StrStaEdit = wx.Menu(title='')
1817        self.StrStaMenu.Append(menu=self.StrStaEdit, title='Operations')
1818        self.StrStaEdit.Append(help='Append d-zero for one ring', 
1819            id=wxID_APPENDDZERO, kind=wx.ITEM_NORMAL,text='Append d-zero')
1820        self.StrStaEdit.Append(help='Fit stress/strain data', 
1821            id=wxID_STRSTAFIT, kind=wx.ITEM_NORMAL,text='Fit stress/strain')
1822        self.StrStaEdit.Append(help='Copy stress/strain data to other images', 
1823            id=wxID_STRSTACOPY, kind=wx.ITEM_NORMAL,text='Copy stress/strain')
1824        self.StrStaEdit.Append(help='Save stress/strain data to file', 
1825            id=wxID_STRSTASAVE, kind=wx.ITEM_NORMAL,text='Save stress/strain')
1826        self.StrStaEdit.Append(help='Load stress/strain data from file', 
1827            id=wxID_STRSTALOAD, kind=wx.ITEM_NORMAL,text='Load stress/strain')
1828        self.PostfillDataMenu()
1829           
1830        # PDF / PDF Controls
1831        self.PDFMenu = wx.MenuBar()
1832        self.PrefillDataMenu(self.PDFMenu,helpType='PDF Controls')
1833        self.PDFEdit = wx.Menu(title='')
1834        self.PDFMenu.Append(menu=self.PDFEdit, title='PDF Controls')
1835        self.PDFEdit.Append(help='Add element to sample composition',id=wxID_PDFADDELEMENT, kind=wx.ITEM_NORMAL,
1836            text='Add element')
1837        self.PDFEdit.Append(help='Delete element from sample composition',id=wxID_PDFDELELEMENT, kind=wx.ITEM_NORMAL,
1838            text='Delete element')
1839        self.PDFEdit.Append(help='Copy PDF controls', id=wxID_PDFCOPYCONTROLS, kind=wx.ITEM_NORMAL,
1840            text='Copy controls')
1841        #        self.PDFEdit.Append(help='Load PDF controls from file',id=wxID_PDFLOADCONTROLS, kind=wx.ITEM_NORMAL,
1842        #            text='Load Controls')
1843        #        self.PDFEdit.Append(help='Save PDF controls to file', id=wxID_PDFSAVECONTROLS, kind=wx.ITEM_NORMAL,
1844        #            text='Save controls')
1845        self.PDFEdit.Append(help='Compute PDF', id=wxID_PDFCOMPUTE, kind=wx.ITEM_NORMAL,
1846            text='Compute PDF')
1847        self.PDFEdit.Append(help='Compute all PDFs', id=wxID_PDFCOMPUTEALL, kind=wx.ITEM_NORMAL,
1848            text='Compute all PDFs')
1849        self.PostfillDataMenu()
1850       
1851        # Phase / General tab
1852        self.DataGeneral = wx.MenuBar()
1853        self.PrefillDataMenu(self.DataGeneral,helpType='General', helpLbl='Phase/General')
1854        self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab')
1855        self.GeneralCalc = wx.Menu(title='')
1856        self.DataGeneral.Append(menu=self.GeneralCalc,title='Compute')
1857        self.GeneralCalc.Append(help='Compute Fourier map',id=wxID_FOURCALC, kind=wx.ITEM_NORMAL,
1858            text='Fourier map')
1859        self.GeneralCalc.Append(help='Search Fourier map',id=wxID_FOURSEARCH, kind=wx.ITEM_NORMAL,
1860            text='Search map')
1861        self.GeneralCalc.Append(help='Run charge flipping',id=wxID_CHARGEFLIP, kind=wx.ITEM_NORMAL,
1862            text='Charge flipping')
1863        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
1864            text='Clear map')
1865        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
1866            text='MC/SA')
1867#        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
1868#            text='Multi MC/SA')            #currently not useful
1869        self.PostfillDataMenu()
1870       
1871        # Phase / Data tab
1872        self.DataMenu = wx.MenuBar()
1873        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
1874        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
1875        self.DataEdit = wx.Menu(title='')
1876        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
1877        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
1878            help='Select new powder histograms to be used for this phase')
1879        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
1880            help='Select new single crystal histograms to be used for this phase')
1881        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Delete histograms',
1882            help='Delete histograms from use for this phase')
1883        self.PostfillDataMenu()
1884           
1885        # Phase / Atoms tab
1886        self.AtomsMenu = wx.MenuBar()
1887        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
1888        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
1889        self.AtomEdit = wx.Menu(title='')
1890        self.AtomCompute = wx.Menu(title='')
1891        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
1892        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
1893        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
1894            help='Appended as an H atom')
1895        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
1896            help='Appended as an H atom')
1897        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
1898            help='Select atom row to insert before; inserted as an H atom')
1899        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
1900            help='Select atom row to insert before; inserted as an H atom')
1901        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
1902            help='Select single atom to move')
1903        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
1904            help='Select atoms to delete first')
1905        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
1906            help='Select atoms to refine first')
1907        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
1908            help='Select atoms to modify first')
1909        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
1910            help='Select atoms to transform first')
1911        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
1912            help='Reload atom drawing list')
1913        submenu = wx.Menu()
1914        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
1915            help='Reimport atoms from file; sequence must match')
1916        # setup a cascade menu for the formats that have been defined
1917        self.ReImportMenuId = {}  # points to readers for each menu entry
1918        for reader in self.G2frame.ImportPhaseReaderlist:
1919            item = submenu.Append(
1920                wx.ID_ANY,help=reader.longFormatName,
1921                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
1922            self.ReImportMenuId[item.GetId()] = reader
1923        item = submenu.Append(
1924            wx.ID_ANY,
1925            help='Reimport coordinates, try to determine format from file',
1926            kind=wx.ITEM_NORMAL,
1927            text='guess format from file')
1928        self.ReImportMenuId[item.GetId()] = None # try all readers
1929
1930        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Distances && Angles',
1931            help='Compute distances & angles for selected atoms')
1932        self.PostfillDataMenu()
1933                 
1934        # Phase / Draw Options tab
1935        self.DataDrawOptions = wx.MenuBar()
1936        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options',empty=True)
1937        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
1938        self.PostfillDataMenu(empty=True)
1939       
1940        # Phase / Draw Atoms tab
1941        self.DrawAtomsMenu = wx.MenuBar()
1942        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
1943        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
1944        self.DrawAtomEdit = wx.Menu(title='')
1945        self.DrawAtomCompute = wx.Menu(title='')
1946        self.DrawAtomRestraint = wx.Menu(title='')
1947        self.DrawAtomRigidBody = wx.Menu(title='')
1948        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
1949        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
1950        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
1951        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
1952        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
1953            help='Select atoms first')
1954        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
1955            help='Select atoms first')
1956        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
1957            help='Select atoms first')
1958        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
1959            help='Resets all atom colors to defaults')
1960        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
1961            help='View point is 1st atom selected')
1962        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
1963            help='Add symmetry & cell equivalents to drawing set from selected atoms')
1964        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
1965            help='Transform selected atoms by symmetry & cell translations')
1966        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
1967            help='Fill coordination sphere for selected atoms')           
1968        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
1969            help='Fill unit cell with selected atoms')
1970        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
1971            help='Delete atoms from drawing set')
1972        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
1973            help='Compute distance of selected atoms from view point')   
1974        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
1975            help='Compute distance, angle or torsion for 2-4 selected atoms')   
1976        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
1977            help='Compute best plane for 4+ selected atoms')   
1978        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
1979            help='Add bond restraint for selected atoms (2)')
1980        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
1981            help='Add angle restraint for selected atoms (3: one end 1st)')
1982        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
1983            help='Add plane restraint for selected atoms (4+)')
1984        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
1985            help='Add chiral restraint for selected atoms (4: center atom 1st)')
1986        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
1987            help='Define rigid body with selected atoms')
1988        self.PostfillDataMenu()
1989
1990        # Phase / MCSA tab
1991        self.MCSAMenu = wx.MenuBar()
1992        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
1993        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
1994        self.MCSAEdit = wx.Menu(title='')
1995        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
1996        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
1997            help='Add single atom to MC/SA model')
1998        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
1999            help='Add rigid body to MC/SA model' )
2000        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
2001            help='Clear all atoms & rigid bodies from MC/SA model' )
2002        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
2003            help='Move MC/SA solution to atom list' )
2004        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
2005            help='Clear table of MC/SA results' )
2006        self.PostfillDataMenu()
2007           
2008        # Phase / Texture tab
2009        self.TextureMenu = wx.MenuBar()
2010        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
2011        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2012        self.TextureEdit = wx.Menu(title='')
2013        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
2014        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture', 
2015            help='Refine the texture coefficients from sequential Pawley results')
2016        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
2017            help='Clear the texture coefficients' )
2018        self.PostfillDataMenu()
2019           
2020        # Phase / Pawley tab
2021        self.PawleyMenu = wx.MenuBar()
2022        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
2023        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2024        self.PawleyEdit = wx.Menu(title='')
2025        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
2026        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
2027            help='Initialize Pawley reflection list')
2028        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
2029            help='Estimate initial Pawley intensities')
2030        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
2031            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
2032        self.PostfillDataMenu()
2033           
2034        # Phase / Map peaks tab
2035        self.MapPeaksMenu = wx.MenuBar()
2036        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
2037        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2038        self.MapPeaksEdit = wx.Menu(title='')
2039        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
2040        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
2041            help='Move selected peaks to atom list')
2042        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
2043            help='View point is 1st peak selected')
2044        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2045            help='Compute distance of selected peaks from view point')   
2046        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
2047            help='Hide or show bonds between peak positions')   
2048        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
2049            help='Calculate distance or angle for selection')
2050        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
2051            help='Find equivalent peaks')
2052        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
2053            help='Select unique set')
2054        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
2055            help='Delete selected peaks')
2056        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2057            help='Clear the map peak list')
2058        self.PostfillDataMenu()
2059
2060        # Phase / Rigid bodies tab
2061        self.RigidBodiesMenu = wx.MenuBar()
2062        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
2063        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2064        self.RigidBodiesEdit = wx.Menu(title='')
2065        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
2066        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
2067            help='Select & position rigid body in structure of existing atoms')
2068        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
2069            help='Auto find of residue RBs in macromolecule')
2070        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
2071            help='Copy rigid body location & TLS parameters')
2072        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
2073            help='Global setting of residue thermal motion models')
2074        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
2075            help='Global setting of residue RB refinement flags')
2076        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
2077            help='Remove all rigid body assignment for atoms')
2078        self.PostfillDataMenu()
2079    # end of GSAS-II menu definitions
2080       
2081    def _init_ctrls(self, parent,name=None,size=None,pos=None):
2082        wx.Frame.__init__(self,parent=parent,
2083            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
2084            size=size,pos=pos,title='GSAS-II data display')
2085        self._init_menus()
2086        if name:
2087            self.SetLabel(name)
2088        self.Show()
2089       
2090    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
2091        self.G2frame = frame
2092        self._init_ctrls(parent,name,size,pos)
2093        self.data = data
2094        clientSize = wx.ClientDisplayRect()
2095        Size = self.GetSize()
2096        xPos = clientSize[2]-Size[0]
2097        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
2098        self.AtomGrid = []
2099        self.selectedRow = 0
2100       
2101    def setSizePosLeft(self,Width):
2102        clientSize = wx.ClientDisplayRect()
2103        Width[1] = min(Width[1],clientSize[2]-300)
2104        Width[0] = max(Width[0],300)
2105        self.SetSize(Width)
2106#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
2107       
2108    def Clear(self):
2109        self.ClearBackground()
2110        self.DestroyChildren()
2111                   
2112################################################################################
2113#####  GSNotebook
2114################################################################################           
2115       
2116class GSNoteBook(wx.aui.AuiNotebook):
2117    '''Notebook used in various locations; implemented with wx.aui extension
2118    '''
2119    def __init__(self, parent, name='',size = None):
2120        wx.aui.AuiNotebook.__init__(self, parent, -1,
2121                                    style=wx.aui.AUI_NB_TOP |
2122                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
2123        if size: self.SetSize(size)
2124                                                     
2125    def Clear(self):       
2126        GSNoteBook.DeleteAllPages(self)
2127       
2128    def FindPage(self,name):
2129        numPage = self.GetPageCount()
2130        for page in range(numPage):
2131            if self.GetPageText(page) == name:
2132                return page
2133
2134    def ChangeSelection(self,page):
2135        # in wx.Notebook ChangeSelection is like SetSelection, but it
2136        # does not invoke the event related to pressing the tab button
2137        # I don't see a way to do that in aui.
2138        oldPage = self.GetSelection()
2139        self.SetSelection(page)
2140        return oldPage
2141
2142    # def __getattribute__(self,name):
2143    #     '''This method provides a way to print out a message every time
2144    #     that a method in a class is called -- to see what all the calls
2145    #     might be, or where they might be coming from.
2146    #     Cute trick for debugging!
2147    #     '''
2148    #     attr = object.__getattribute__(self, name)
2149    #     if hasattr(attr, '__call__'):
2150    #         def newfunc(*args, **kwargs):
2151    #             print('GSauiNoteBook calling %s' %attr.__name__)
2152    #             result = attr(*args, **kwargs)
2153    #             return result
2154    #         return newfunc
2155    #     else:
2156    #         return attr
2157           
2158################################################################################
2159#####  GSGrid
2160################################################################################           
2161       
2162class GSGrid(wg.Grid):
2163    '''Basic wx.Grid implementation
2164    '''
2165    def __init__(self, parent, name=''):
2166        wg.Grid.__init__(self,parent,-1,name=name)                   
2167        #self.SetSize(parent.GetClientSize())
2168        # above removed to speed drawing of initial grid
2169        # does not appear to be needed
2170           
2171    def Clear(self):
2172        wg.Grid.ClearGrid(self)
2173       
2174    def SetCellStyle(self,r,c,color="white",readonly=True):
2175        self.SetCellBackgroundColour(r,c,color)
2176        self.SetReadOnly(r,c,isReadOnly=readonly)
2177       
2178    def GetSelection(self):
2179        #this is to satisfy structure drawing stuff in G2plt when focus changes
2180        return None
2181                                               
2182################################################################################
2183#####  Table
2184################################################################################           
2185       
2186class Table(wg.PyGridTableBase):
2187    '''Basic data table for use with GSgrid
2188    '''
2189    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
2190        wg.PyGridTableBase.__init__(self)
2191        self.colLabels = colLabels
2192        self.rowLabels = rowLabels
2193        self.dataTypes = types
2194        self.data = data
2195       
2196    def AppendRows(self, numRows=1):
2197        self.data.append([])
2198        return True
2199       
2200    def CanGetValueAs(self, row, col, typeName):
2201        if self.dataTypes:
2202            colType = self.dataTypes[col].split(':')[0]
2203            if typeName == colType:
2204                return True
2205            else:
2206                return False
2207        else:
2208            return False
2209
2210    def CanSetValueAs(self, row, col, typeName):
2211        return self.CanGetValueAs(row, col, typeName)
2212
2213    def DeleteRow(self,pos):
2214        data = self.GetData()
2215        self.SetData([])
2216        new = []
2217        for irow,row in enumerate(data):
2218            if irow <> pos:
2219                new.append(row)
2220        self.SetData(new)
2221       
2222    def GetColLabelValue(self, col):
2223        if self.colLabels:
2224            return self.colLabels[col]
2225           
2226    def GetData(self):
2227        data = []
2228        for row in range(self.GetNumberRows()):
2229            data.append(self.GetRowValues(row))
2230        return data
2231       
2232    def GetNumberCols(self):
2233        try:
2234            return len(self.colLabels)
2235        except TypeError:
2236            return None
2237       
2238    def GetNumberRows(self):
2239        return len(self.data)
2240       
2241    def GetRowLabelValue(self, row):
2242        if self.rowLabels:
2243            return self.rowLabels[row]
2244       
2245    def GetColValues(self, col):
2246        data = []
2247        for row in range(self.GetNumberRows()):
2248            data.append(self.GetValue(row, col))
2249        return data
2250       
2251    def GetRowValues(self, row):
2252        data = []
2253        for col in range(self.GetNumberCols()):
2254            data.append(self.GetValue(row, col))
2255        return data
2256       
2257    def GetTypeName(self, row, col):
2258        try:
2259            return self.dataTypes[col]
2260        except TypeError:
2261            return None
2262
2263    def GetValue(self, row, col):
2264        try:
2265            return self.data[row][col]
2266        except IndexError:
2267            return None
2268           
2269    def InsertRows(self, pos, rows):
2270        for row in range(rows):
2271            self.data.insert(pos,[])
2272            pos += 1
2273       
2274    def IsEmptyCell(self,row,col):
2275        try:
2276            return not self.data[row][col]
2277        except IndexError:
2278            return True
2279       
2280    def OnKeyPress(self, event):
2281        dellist = self.GetSelectedRows()
2282        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
2283            grid = self.GetView()
2284            for i in dellist: grid.DeleteRow(i)
2285               
2286    def SetColLabelValue(self, col, label):
2287        numcols = self.GetNumberCols()
2288        if col > numcols-1:
2289            self.colLabels.append(label)
2290        else:
2291            self.colLabels[col]=label
2292       
2293    def SetData(self,data):
2294        for row in range(len(data)):
2295            self.SetRowValues(row,data[row])
2296               
2297    def SetRowLabelValue(self, row, label):
2298        self.rowLabels[row]=label
2299           
2300    def SetRowValues(self,row,data):
2301        self.data[row] = data
2302           
2303    def SetValue(self, row, col, value):
2304        def innerSetValue(row, col, value):
2305            try:
2306                self.data[row][col] = value
2307            except TypeError:
2308                return
2309            except IndexError:
2310                print row,col,value
2311                # add a new row
2312                if row > self.GetNumberRows():
2313                    self.data.append([''] * self.GetNumberCols())
2314                elif col > self.GetNumberCols():
2315                    for row in range(self.GetNumberRows):
2316                        self.data[row].append('')
2317                print self.data
2318                self.data[row][col] = value
2319        innerSetValue(row, col, value)
2320       
2321################################################################################
2322#### Help
2323################################################################################
2324
2325def ShowHelp(helpType,frame):
2326    '''Called to bring up a web page for documentation.'''
2327    global htmlFirstUse
2328    # look up a definition for help info from dict
2329    helplink = helpLocDict.get(helpType)
2330    if helplink is None:
2331        # no defined link to use, create a default based on key
2332        helplink = 'gsasII.html#'+helpType.replace(' ','_')
2333    helplink = os.path.join(path2GSAS2,'help',helplink)
2334    if helpMode == 'internal':
2335        try:
2336            htmlPanel.LoadFile(helplink)
2337            htmlFrame.Raise()
2338        except:
2339            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
2340            htmlFrame.Show(True)
2341            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
2342            htmlPanel = MyHtmlPanel(htmlFrame,-1)
2343            htmlPanel.LoadFile(helplink)
2344    else:
2345        pfx = "file://"
2346        if sys.platform.lower().startswith('win'):
2347            pfx = ''
2348        if htmlFirstUse:
2349            webbrowser.open_new(pfx+helplink)
2350            htmlFirstUse = False
2351        else:
2352            webbrowser.open(pfx+helplink, new=0, autoraise=True)
2353
2354################################################################################
2355#####  Notebook
2356################################################################################           
2357       
2358def UpdateNotebook(G2frame,data):
2359    '''Called when the data tree notebook entry is selected. Allows for
2360    editing of the text in that tree entry
2361    '''
2362    def OnNoteBook(event):
2363        data = G2frame.dataDisplay.GetValue()
2364        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
2365                   
2366    if G2frame.dataDisplay:
2367        G2frame.dataDisplay.Destroy()
2368    G2frame.dataFrame.SetLabel('Notebook')
2369    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2370        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
2371    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
2372    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
2373    for line in data:
2374        G2frame.dataDisplay.AppendText(line+"\n")
2375    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
2376    G2frame.dataFrame.setSizePosLeft([400,250])
2377           
2378################################################################################
2379#####  Controls
2380################################################################################           
2381       
2382def UpdateControls(G2frame,data):
2383    '''Edit overall GSAS-II controls in main Controls data tree entry
2384    '''
2385    #patch
2386    if 'deriv type' not in data:
2387        data = {}
2388        data['deriv type'] = 'analytic Hessian'
2389        data['min dM/M'] = 0.0001
2390        data['shift factor'] = 1.
2391        data['max cyc'] = 3       
2392        data['F**2'] = True
2393        data['minF/sig'] = 0
2394    if 'shift factor' not in data:
2395        data['shift factor'] = 1.
2396    if 'max cyc' not in data:
2397        data['max cyc'] = 3
2398    if 'F**2' not in data:
2399        data['F**2'] = True
2400        data['minF/sig'] = 0
2401    #end patch
2402
2403    def SeqSizer():
2404       
2405        def OnSelectData(event):
2406            choices = ['All',]+GetPatternTreeDataNames(G2frame,['PWDR',])
2407            sel = []
2408            if 'Seq Data' in data:
2409                for item in data['Seq Data']:
2410                    sel.append(choices.index(item))
2411            names = []
2412            dlg = wx.MultiChoiceDialog(G2frame,'Select data:','Sequential refinement',choices)
2413            dlg.SetSelections(sel)
2414            if dlg.ShowModal() == wx.ID_OK:
2415                sel = dlg.GetSelections()
2416                for i in sel: names.append(choices[i])
2417                if 'All' in names:
2418                    names = choices[1:]
2419                data['Seq Data'] = names               
2420            dlg.Destroy()
2421            reverseSel.Enable(True)
2422           
2423        def OnReverse(event):
2424            data['Reverse Seq'] = reverseSel.GetValue()
2425                   
2426        seqSizer = wx.BoxSizer(wx.HORIZONTAL)
2427        seqSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement Powder Data: '),0,wx.ALIGN_CENTER_VERTICAL)
2428        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
2429        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
2430        seqSizer.Add(selSeqData,0,wx.ALIGN_CENTER_VERTICAL)
2431        seqSizer.Add((5,0),0)
2432        reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
2433        reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
2434        if 'Seq Data' not in data:
2435            reverseSel.Enable(False)
2436        if 'Reverse Seq' in data:
2437            reverseSel.SetValue(data['Reverse Seq'])
2438        seqSizer.Add(reverseSel,0,wx.ALIGN_CENTER_VERTICAL)
2439        return seqSizer
2440       
2441    def LSSizer():       
2442       
2443        def OnDerivType(event):
2444            data['deriv type'] = derivSel.GetValue()
2445            derivSel.SetValue(data['deriv type'])
2446            wx.CallAfter(UpdateControls,G2frame,data)
2447           
2448        def OnConvergence(event):
2449            try:
2450                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
2451            except ValueError:
2452                value = 0.0001
2453            data['min dM/M'] = value
2454            Cnvrg.SetValue('%.2g'%(value))
2455           
2456        def OnMaxCycles(event):
2457            data['max cyc'] = int(maxCyc.GetValue())
2458            maxCyc.SetValue(str(data['max cyc']))
2459                       
2460        def OnFactor(event):
2461            try:
2462                value = min(max(float(Factr.GetValue()),0.00001),100.)
2463            except ValueError:
2464                value = 1.0
2465            data['shift factor'] = value
2466            Factr.SetValue('%.5f'%(value))
2467           
2468        def OnFsqRef(event):
2469            data['F**2'] = fsqRef.GetValue()
2470       
2471        def OnMinSig(event):
2472            try:
2473                value = min(max(float(minSig.GetValue()),0.),5.)
2474            except ValueError:
2475                value = 1.0
2476            data['minF/sig'] = value
2477            minSig.SetValue('%.2f'%(value))
2478
2479        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
2480        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,wx.ALIGN_CENTER_VERTICAL)
2481        Choice=['analytic Jacobian','numeric','analytic Hessian']
2482        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
2483            style=wx.CB_READONLY|wx.CB_DROPDOWN)
2484        derivSel.SetValue(data['deriv type'])
2485        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
2486           
2487        LSSizer.Add(derivSel,0,wx.ALIGN_CENTER_VERTICAL)
2488        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,wx.ALIGN_CENTER_VERTICAL)
2489        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
2490        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
2491        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
2492        LSSizer.Add(Cnvrg,0,wx.ALIGN_CENTER_VERTICAL)
2493        if 'Hessian' in data['deriv type']:
2494            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,wx.ALIGN_CENTER_VERTICAL)
2495            Choice = ['0','1','2','3','5','10','15','20']
2496            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
2497                style=wx.CB_READONLY|wx.CB_DROPDOWN)
2498            maxCyc.SetValue(str(data['max cyc']))
2499            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
2500            LSSizer.Add(maxCyc,0,wx.ALIGN_CENTER_VERTICAL)
2501        else:
2502            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,wx.ALIGN_CENTER_VERTICAL)
2503            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
2504            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
2505            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
2506            LSSizer.Add(Factr,0,wx.ALIGN_CENTER_VERTICAL)
2507        if G2frame.Sngl:
2508            LSSizer.Add((1,0),)
2509            LSSizer.Add((1,0),)
2510            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
2511            fsqRef.SetValue(data['F**2'])
2512            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
2513            LSSizer.Add(fsqRef,0,wx.ALIGN_CENTER_VERTICAL)
2514            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,wx.ALIGN_CENTER_VERTICAL)
2515            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
2516            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
2517            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
2518            LSSizer.Add(minSig,0,wx.ALIGN_CENTER_VERTICAL)
2519        return LSSizer
2520       
2521    if G2frame.dataDisplay:
2522        G2frame.dataDisplay.Destroy()
2523    if not G2frame.dataFrame.GetStatusBar():
2524        Status = G2frame.dataFrame.CreateStatusBar()
2525        Status.SetStatusText('')
2526    G2frame.dataFrame.SetLabel('Controls')
2527    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
2528    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
2529    mainSizer = wx.BoxSizer(wx.VERTICAL)
2530    mainSizer.Add((5,5),0)
2531    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,wx.ALIGN_CENTER_VERTICAL)   
2532    mainSizer.Add(LSSizer())
2533    mainSizer.Add((5,5),0)
2534    mainSizer.Add(SeqSizer())
2535    mainSizer.Add((5,5),0)
2536       
2537    mainSizer.Layout()   
2538    G2frame.dataDisplay.SetSizer(mainSizer)
2539    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
2540    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
2541     
2542################################################################################
2543#####  Comments
2544################################################################################           
2545       
2546def UpdateComments(G2frame,data):                   
2547
2548    if G2frame.dataDisplay:
2549        G2frame.dataDisplay.Destroy()
2550    G2frame.dataFrame.SetLabel('Comments')
2551    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2552        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
2553    for line in data:
2554        G2frame.dataDisplay.AppendText(line+'\n')
2555    G2frame.dataFrame.setSizePosLeft([400,250])
2556           
2557################################################################################
2558#####  Sequential Results
2559################################################################################           
2560       
2561def UpdateSeqResults(G2frame,data):
2562    """
2563    Called when the Sequential Results data tree entry is selected
2564    to show results from a sequential refinement.
2565   
2566    :param wx.Frame G2frame: main GSAS-II data tree windows
2567
2568    :param dict data: a dictionary containing the following items: 
2569
2570            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
2571            * 'varyList' - list of variables - identical over all refinements in sequence
2572            * 'histName' - dictionaries for all data sets processed, which contains:
2573
2574              * 'variables'- result[0] from leastsq call
2575              * 'varyList' - list of variables; same as above
2576              * 'sig' - esds for variables
2577              * 'covMatrix' - covariance matrix from individual refinement
2578              * 'title' - histogram name; same as dict item name
2579              * 'newAtomDict' - new atom parameters after shifts applied
2580              * 'newCellDict' - new cell parameters after shifts to A0-A5 applied'
2581    """
2582    if not data:
2583        print 'No sequential refinement results'
2584        return
2585    histNames = data['histNames']
2586       
2587    def GetSampleParms():
2588        sampleParmDict = {'Temperature':[],'Pressure':[],'Humidity':[],'Voltage':[],'Force':[],}
2589        sampleParm = {}
2590        for name in histNames:
2591            Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
2592            sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
2593            for item in sampleParmDict:
2594                sampleParmDict[item].append(sampleData[item])
2595        for item in sampleParmDict:
2596            frstValue = sampleParmDict[item][0]
2597            if np.any(np.array(sampleParmDict[item])-frstValue):
2598                sampleParm[item] = sampleParmDict[item]           
2599        return sampleParm
2600           
2601    def GetRwps():
2602        Rwps = []
2603        for name in histNames:
2604            Rwps.append(data[name]['Rvals']['Rwp'])
2605        return Rwps
2606           
2607    def GetSigData(parm):
2608        sigData = []
2609        for name in histNames:
2610            sigList = data[name]['sig']
2611            if colLabels[parm] in atomList:
2612                sigData.append(sigList[colLabels.index(atomList[colLabels[parm]])-1])
2613            elif colLabels[parm] in cellList:
2614                sigData.append(sigList[colLabels.index(cellList[colLabels[parm]])-1])
2615            else:
2616                sigData.append(sigList[parm-1])
2617        return sigData
2618   
2619    def Select(event):
2620        cols = G2frame.dataDisplay.GetSelectedCols()
2621        rows = G2frame.dataDisplay.GetSelectedRows()
2622        if cols:
2623            plotData = []
2624            plotSig = []
2625            plotNames = []
2626            for col in cols:
2627                plotData.append(G2frame.SeqTable.GetColValues(col))
2628                if col:     #not Rwp
2629                    plotSig.append(GetSigData(col))
2630                else:
2631                    plotSig.append(0.0)
2632                plotNames.append(G2frame.SeqTable.GetColLabelValue(col))
2633            plotData = np.array(plotData)
2634            G2plt.PlotSeq(G2frame,plotData,plotSig,plotNames,sampleParms)
2635        elif rows:
2636            name = histNames[rows[0]]       #only does 1st one selected
2637            G2plt.PlotCovariance(G2frame,data[name])
2638           
2639    def OnSaveSelSeq(event):       
2640        cols = G2frame.dataDisplay.GetSelectedCols()
2641        if cols:
2642            numRows = G2frame.SeqTable.GetNumberRows()
2643            dataNames = []
2644            saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(numRows)]
2645            saveData = []
2646            for col in cols:
2647                dataNames.append(G2frame.SeqTable.GetColLabelValue(col))
2648                if col:     #not Rwp
2649                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),GetSigData(col)))
2650                else:
2651                    saveData.append(zip(G2frame.SeqTable.GetColValues(col),0.0))
2652            lenName = len(saveNames[0])
2653            saveData = np.swapaxes(np.array(saveData),0,1)
2654            dlg = wx.FileDialog(G2frame, 'Choose text output file for your selection', '.', '', 
2655                'Text output file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
2656            try:
2657                if dlg.ShowModal() == wx.ID_OK:
2658                    SeqTextFile = dlg.GetPath()
2659                    SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile)
2660                    SeqFile = open(SeqTextFile,'w')
2661                    line = %s  '%('name'.center(lenName))
2662                    for item in dataNames:
2663                        line += ' %12s %12s '%(item.center(12),'esd'.center(12))
2664                    line += '\n'
2665                    SeqFile.write(line)
2666                    for i,item in enumerate(saveData):
2667                        line = " '%s' "%(saveNames[i])
2668                        for val,esd in item:
2669                            line += ' %12.6f %12.6f '%(val,esd)
2670                        line += '\n'
2671                        SeqFile.write(line)
2672                    SeqFile.close()
2673            finally:
2674                dlg.Destroy()
2675           
2676               
2677    if G2frame.dataDisplay:
2678        G2frame.dataDisplay.Destroy()
2679    atomList = {}
2680    newAtomDict = data[histNames[0]]['newAtomDict']
2681    for item in newAtomDict:
2682        if item in data['varyList']:
2683            atomList[newAtomDict[item][0]] = item
2684    cellList = {}
2685    newCellDict = data[histNames[0]]['newCellDict']
2686    for item in newCellDict:
2687        if item in data['varyList']:
2688            cellList[newCellDict[item][0]] = item
2689    sampleParms = GetSampleParms()
2690    Rwps = GetRwps()
2691    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
2692    G2frame.dataFrame.SetLabel('Sequential refinement results')
2693    G2frame.dataFrame.CreateStatusBar()
2694    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
2695    colLabels = ['Rwp',]+data['varyList']+atomList.keys()+cellList.keys()
2696    Types = (len(data['varyList']+atomList.keys()+cellList.keys())+1)*[wg.GRID_VALUE_FLOAT,]
2697    seqList = [[Rwps[i],]+list(data[name]['variables']) for i,name in enumerate(histNames)]
2698    for i,item in enumerate(seqList):
2699        newAtomDict = data[histNames[i]]['newAtomDict']
2700        newCellDict = data[histNames[i]]['newCellDict']
2701        item += [newAtomDict[atomList[parm]][1] for parm in atomList.keys()]
2702        item += [newCellDict[cellList[parm]][1] for parm in cellList.keys()]
2703    G2frame.SeqTable = Table(seqList,colLabels=colLabels,rowLabels=histNames,types=Types)
2704    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
2705    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
2706    G2frame.dataDisplay.EnableEditing(False)
2707    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, Select)
2708    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
2709    G2frame.dataDisplay.SetMargins(0,0)
2710    G2frame.dataDisplay.AutoSizeColumns(True)
2711    G2frame.dataFrame.setSizePosLeft([700,350])
2712       
2713################################################################################
2714#####  Main PWDR panel
2715################################################################################           
2716       
2717def UpdatePWHKPlot(G2frame,kind,item):
2718    '''Needs a doc string
2719    '''
2720
2721    def OnErrorAnalysis(event):
2722        G2plt.PlotDeltSig(G2frame,kind)
2723       
2724    def OnWtFactor(event):
2725        try:
2726            val = float(wtval.GetValue())
2727        except ValueError:
2728            val = data[0]['wtFactor']
2729        data[0]['wtFactor'] = val
2730        wtval.SetValue('%.3f'%(val))
2731           
2732    data = G2frame.PatternTree.GetItemPyData(item)
2733    if 'wtFactor' not in data[0]:
2734        data[0] = {'wtFactor':1.0}
2735    if G2frame.dataDisplay:
2736        G2frame.dataDisplay.Destroy()
2737    SetDataMenuBar(G2frame,G2frame.dataFrame.ErrorMenu)
2738    G2frame.dataFrame.Bind(wx.EVT_MENU,OnErrorAnalysis, id=wxID_PWDANALYSIS)
2739    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
2740   
2741    mainSizer = wx.BoxSizer(wx.VERTICAL)
2742    mainSizer.Add((5,5),)
2743    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
2744    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,wx.ALIGN_CENTER_VERTICAL)
2745    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
2746    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
2747    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
2748    wtSizer.Add(wtval,0,wx.ALIGN_CENTER_VERTICAL)
2749    mainSizer.Add(wtSizer)
2750    mainSizer.Layout()   
2751    G2frame.dataDisplay.SetSizer(mainSizer)
2752    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
2753    G2frame.PatternTree.SetItemPyData(item,data)
2754    if kind == 'PWDR':
2755        G2plt.PlotPatterns(G2frame,newPlot=True)
2756    elif kind == 'HKLF':
2757        G2plt.PlotSngl(G2frame,newPlot=True)
2758                 
2759################################################################################
2760#####  HKLF controls
2761################################################################################           
2762       
2763def UpdateHKLControls(G2frame,data):
2764    '''Needs a doc string
2765    '''
2766   
2767    def OnScaleSlider(event):
2768        scale = int(scaleSel.GetValue())/1000.
2769        scaleSel.SetValue(int(scale*1000.))
2770        data['Scale'] = scale*1.
2771        G2plt.PlotSngl(G2frame)
2772       
2773    def OnLayerSlider(event):
2774        layer = layerSel.GetValue()
2775        data['Layer'] = layer
2776        G2plt.PlotSngl(G2frame)
2777       
2778    def OnSelZone(event):
2779        data['Zone'] = zoneSel.GetValue()
2780        izone = zones.index(data['Zone'])
2781        layerSel.SetRange(maxValue=HKLmax[izone],minValue=HKLmin[izone])
2782        G2plt.PlotSngl(G2frame,newPlot=True)
2783       
2784    def OnSelType(event):
2785        data['Type'] = typeSel.GetValue()
2786        G2plt.PlotSngl(G2frame)
2787       
2788    def SetStatusLine():
2789        Status.SetStatusText("")
2790                                     
2791    if G2frame.dataDisplay:
2792        G2frame.dataDisplay.Destroy()
2793    if not G2frame.dataFrame.GetStatusBar():
2794        Status = G2frame.dataFrame.CreateStatusBar()
2795    SetStatusLine()
2796    zones = ['100','010','001']
2797    HKLmax = data['HKLmax']
2798    HKLmin = data['HKLmin']
2799    typeChoices = ['Fosq','Fo','|DFsq|/sig','|DFsq|>sig','|DFsq|>3sig']
2800    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
2801    SetDataMenuBar(G2frame)
2802    G2frame.dataFrame.SetTitle('HKL Plot Controls')
2803    mainSizer = wx.BoxSizer(wx.VERTICAL)
2804    mainSizer.Add((5,10),0)
2805   
2806    scaleSizer = wx.BoxSizer(wx.HORIZONTAL)
2807    scaleSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Scale'),0,
2808        wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
2809    scaleSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=1000,minValue=1,
2810        style=wx.SL_HORIZONTAL,value=int(data['Scale']*10))
2811    scaleSizer.Add(scaleSel,1,wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
2812    scaleSel.SetLineSize(10)
2813    scaleSel.SetPageSize(10)
2814    scaleSel.Bind(wx.EVT_SLIDER, OnScaleSlider)
2815    mainSizer.Add(scaleSizer,0,wx.EXPAND|wx.RIGHT)
2816    mainSizer.Add((0,10),0)   
2817   
2818    zoneSizer = wx.BoxSizer(wx.HORIZONTAL)
2819    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Zone  '),0,
2820        wx.ALIGN_CENTER_VERTICAL)
2821    zoneSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Zone'],choices=['100','010','001'],
2822        style=wx.CB_READONLY|wx.CB_DROPDOWN)
2823    zoneSel.Bind(wx.EVT_COMBOBOX, OnSelZone)
2824    zoneSizer.Add(zoneSel,0,wx.ALIGN_CENTER_VERTICAL)
2825    zoneSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Plot type  '),0,
2826        wx.ALIGN_CENTER_VERTICAL)       
2827    typeSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['Type'],choices=typeChoices,
2828        style=wx.CB_READONLY|wx.CB_DROPDOWN)
2829    typeSel.Bind(wx.EVT_COMBOBOX, OnSelType)
2830    zoneSizer.Add(typeSel,0,wx.ALIGN_CENTER_VERTICAL)
2831    zoneSizer.Add((10,0),0)   
2832    mainSizer.Add(zoneSizer,0,wx.EXPAND|wx.RIGHT)
2833    mainSizer.Add((0,10),0)   
2834       
2835    izone = zones.index(data['Zone'])
2836    layerSizer = wx.BoxSizer(wx.HORIZONTAL)
2837    layerSizer.Add(wx.StaticText(parent=G2frame.dataDisplay,label=' Layer'),0,
2838        wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
2839    layerSel = wx.Slider(parent=G2frame.dataDisplay,maxValue=HKLmax[izone],minValue=HKLmin[izone],
2840        style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_LABELS,value=0)
2841    layerSel.SetLineSize(1)
2842    layerSel.SetPageSize(1)
2843    layerSel.Bind(wx.EVT_SLIDER, OnLayerSlider)   
2844    layerSizer.Add(layerSel,1,wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
2845    layerSizer.Add((10,0),0)   
2846    mainSizer.Add(layerSizer,1,wx.EXPAND|wx.RIGHT)
2847
2848       
2849    mainSizer.Layout()   
2850    G2frame.dataDisplay.SetSizer(mainSizer)
2851    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
2852    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
2853
2854################################################################################
2855#####  Pattern tree routines
2856################################################################################           
2857       
2858def GetPatternTreeDataNames(G2frame,dataTypes):
2859    '''Needs a doc string
2860    '''
2861    names = []
2862    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
2863    while item:
2864        name = G2frame.PatternTree.GetItemText(item)
2865        if name[:4] in dataTypes:
2866            names.append(name)
2867        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
2868    return names
2869                         
2870def GetPatternTreeItemId(G2frame, parentId, itemText):
2871    '''Needs a doc string
2872    '''
2873    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
2874    while item:
2875        if G2frame.PatternTree.GetItemText(item) == itemText:
2876            return item
2877        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
2878    return 0               
2879
2880def MovePatternTreeToGrid(G2frame,item):
2881    '''Needs a doc string
2882    '''
2883   
2884#    print G2frame.PatternTree.GetItemText(item)
2885   
2886    oldPage = None # will be set later if already on a Phase item
2887    if G2frame.dataFrame:
2888        SetDataMenuBar(G2frame)
2889        if G2frame.dataFrame.GetLabel() == 'Comments':
2890            try:
2891                data = [G2frame.dataDisplay.GetValue()]
2892                G2frame.dataDisplay.Clear() 
2893                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
2894                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
2895            except:     #clumsy but avoids dead window problem when opening another project
2896                pass
2897        elif G2frame.dataFrame.GetLabel() == 'Notebook':
2898            try:
2899                data = [G2frame.dataDisplay.GetValue()]
2900                G2frame.dataDisplay.Clear() 
2901                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
2902                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
2903            except:     #clumsy but avoids dead window problem when opening another project
2904                pass
2905        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
2906            if G2frame.dataDisplay: 
2907                oldPage = G2frame.dataDisplay.GetSelection()
2908        G2frame.dataFrame.Clear()
2909        G2frame.dataFrame.SetLabel('')
2910    else:
2911        #create the frame for the data item window
2912        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
2913        G2frame.dataFrame.PhaseUserSize = None
2914       
2915    G2frame.dataFrame.Raise()           
2916    G2frame.PickId = 0
2917    parentID = G2frame.root
2918    for i in G2frame.ExportPattern: i.Enable(False)
2919    defWid = [250,150]
2920    if item != G2frame.root:
2921        parentID = G2frame.PatternTree.GetItemParent(item)
2922    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
2923        G2frame.PatternId = item
2924        G2frame.PickId = item
2925        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
2926            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
2927            G2frame.PatternId = 0
2928            for i in G2frame.ExportPattern: i.Enable(False)
2929            data = G2frame.PatternTree.GetItemPyData(item)
2930            UpdateNotebook(G2frame,data)
2931        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
2932            G2frame.PatternId = 0
2933            for i in G2frame.ExportPattern: i.Enable(False)
2934            data = G2frame.PatternTree.GetItemPyData(item)
2935            if not data:           #fill in defaults
2936                data = {
2937                    #least squares controls
2938                    'deriv type':'analytic Hessian','min dM/M':0.0001,'shift factor':1.0,'max cyc':3}
2939                G2frame.PatternTree.SetItemPyData(item,data)                             
2940            for i in G2frame.Refine: i.Enable(True)
2941            for i in G2frame.SeqRefine: i.Enable(True)
2942            UpdateControls(G2frame,data)
2943        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
2944            data = G2frame.PatternTree.GetItemPyData(item)
2945            UpdateSeqResults(G2frame,data)           
2946        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
2947            data = G2frame.PatternTree.GetItemPyData(item)
2948            G2frame.dataFrame.setSizePosLeft(defWid)
2949            text = ''
2950            if 'Rvals' in data:
2951                Nvars = len(data['varyList'])
2952                Rvals = data['Rvals']
2953                text = '\nFinal residuals: \nRw = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
2954                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
2955                if 'lamMax' in Rvals:
2956                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
2957            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2958                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
2959            G2plt.PlotCovariance(G2frame,data)
2960        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
2961            data = G2frame.PatternTree.GetItemPyData(item)
2962            G2cnstG.UpdateConstraints(G2frame,data)
2963        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
2964            data = G2frame.PatternTree.GetItemPyData(item)
2965            G2cnstG.UpdateRigidBodies(G2frame,data)
2966        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
2967            data = G2frame.PatternTree.GetItemPyData(item)
2968            Phases = G2frame.GetPhaseData()
2969            phase = ''
2970            phaseName = ''
2971            if Phases:
2972                phaseName = Phases.keys()[0]
2973            G2frame.dataFrame.setSizePosLeft(defWid)
2974            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
2975        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
2976            G2frame.Image = item
2977            G2plt.PlotImage(G2frame,newPlot=True)
2978        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
2979            G2plt.PlotPowderLines(G2frame)
2980        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
2981            for i in G2frame.ExportPattern: i.Enable(True)
2982            UpdatePWHKPlot(G2frame,'PWDR',item)
2983        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
2984            G2frame.Sngl = item
2985            UpdatePWHKPlot(G2frame,'HKLF',item)
2986        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
2987            G2frame.PatternId = item
2988            for i in G2frame.ExportPDF: i.Enable(True)
2989            G2plt.PlotISFG(G2frame,type='S(Q)')
2990        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
2991            G2frame.dataFrame.setSizePosLeft(defWid)
2992            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2993                value='Select one phase to see its parameters')           
2994    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
2995        G2frame.PickId = item
2996        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
2997        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
2998    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
2999        G2frame.PickId = item
3000        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3001        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
3002    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
3003        G2frame.PickId = item
3004        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3005        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
3006    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
3007        G2frame.PickId = item
3008        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3009        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
3010    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
3011        G2frame.PickId = item
3012        data = G2frame.PatternTree.GetItemPyData(item)
3013        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
3014    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
3015        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
3016        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3017        G2frame.PickId = item
3018        data = G2frame.PatternTree.GetItemPyData(item)
3019        UpdateComments(G2frame,data)
3020    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
3021        G2frame.dataFrame.SetTitle('Image Controls')
3022        G2frame.PickId = item
3023        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3024        masks = G2frame.PatternTree.GetItemPyData(
3025            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
3026        data = G2frame.PatternTree.GetItemPyData(item)
3027        G2imG.UpdateImageControls(G2frame,data,masks)
3028        G2plt.PlotImage(G2frame)
3029    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
3030        G2frame.dataFrame.SetTitle('Masks')
3031        G2frame.PickId = item
3032        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3033        data = G2frame.PatternTree.GetItemPyData(item)
3034        G2imG.UpdateMasks(G2frame,data)
3035        G2plt.PlotImage(G2frame)
3036    elif G2frame.PatternTree.GetItemText(item) == 'Stress/Strain':
3037        G2frame.dataFrame.SetTitle('Stress/Strain')
3038        G2frame.PickId = item
3039        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
3040        data = G2frame.PatternTree.GetItemPyData(item)
3041        G2imG.UpdateStressStrain(G2frame,data)
3042        G2plt.PlotImage(G2frame)
3043    elif G2frame.PatternTree.GetItemText(item) == 'HKL Plot Controls':
3044        G2frame.PickId = item
3045        G2frame.Sngl = G2frame.PatternTree.GetItemParent(item)
3046        data = G2frame.PatternTree.GetItemPyData(item)
3047        UpdateHKLControls(G2frame,data)
3048        G2plt.PlotSngl(G2frame)
3049    elif G2frame.PatternTree.GetItemText(item) == 'PDF Controls':
3050        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3051        for i in G2frame.ExportPDF: i.Enable(True)
3052        G2frame.PickId = item
3053        data = G2frame.PatternTree.GetItemPyData(item)
3054        G2pdG.UpdatePDFGrid(G2frame,data)
3055        G2plt.PlotISFG(G2frame,type='I(Q)')
3056        G2plt.PlotISFG(G2frame,type='S(Q)')
3057        G2plt.PlotISFG(G2frame,type='F(Q)')
3058        G2plt.PlotISFG(G2frame,type='G(R)')
3059    elif G2frame.PatternTree.GetItemText(item) == 'Peak List':
3060        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3061        for i in G2frame.ExportPeakList: i.Enable(True)
3062        G2frame.PickId = item
3063        data = G2frame.PatternTree.GetItemPyData(item)
3064        G2pdG.UpdatePeakGrid(G2frame,data)
3065        G2plt.PlotPatterns(G2frame)
3066    elif G2frame.PatternTree.GetItemText(item) == 'Background':
3067        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3068        G2frame.PickId = item
3069        data = G2frame.PatternTree.GetItemPyData(item)
3070        G2pdG.UpdateBackground(G2frame,data)
3071        G2plt.PlotPatterns(G2frame)
3072    elif G2frame.PatternTree.GetItemText(item) == 'Limits':
3073        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3074        G2frame.PickId = item
3075        data = G2frame.PatternTree.GetItemPyData(item)
3076        G2pdG.UpdateLimitsGrid(G2frame,data)
3077        G2plt.PlotPatterns(G2frame)
3078    elif G2frame.PatternTree.GetItemText(item) == 'Instrument Parameters':
3079        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3080        G2frame.PickId = item
3081        data = G2frame.PatternTree.GetItemPyData(item)[0]
3082        G2pdG.UpdateInstrumentGrid(G2frame,data)
3083        G2plt.PlotPeakWidths(G2frame)
3084    elif G2frame.PatternTree.GetItemText(item) == 'Sample Parameters':
3085        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3086        G2frame.PickId = item
3087        data = G2frame.PatternTree.GetItemPyData(item)
3088
3089        if 'Temperature' not in data:           #temp fix for old gpx files
3090            data = {'Scale':[1.0,True],'Type':'Debye-Scherrer','Absorption':[0.0,False],'DisplaceX':[0.0,False],
3091                'DisplaceY':[0.0,False],'Diffuse':[],'Temperature':300.,'Pressure':1.0,'Humidity':0.0,'Voltage':0.0,
3092                'Force':0.0,'Gonio. radius':200.0}
3093            G2frame.PatternTree.SetItemPyData(item,data)
3094   
3095        G2pdG.UpdateSampleGrid(G2frame,data)
3096        G2plt.PlotPatterns(G2frame)
3097    elif G2frame.PatternTree.GetItemText(item) == 'Index 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.UpdateIndexPeaksGrid(G2frame,data)
3103        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
3104            G2plt.PlotPowderLines(G2frame)
3105        else:
3106            G2plt.PlotPatterns(G2frame)
3107    elif G2frame.PatternTree.GetItemText(item) == 'Unit Cells List':
3108        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3109        G2frame.PickId = item
3110        data = G2frame.PatternTree.GetItemPyData(item)
3111        if not data:
3112            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
3113            data.append([0,0,0,0,0,0,0,0,0,0,0,0,0,0])      #Bravais lattice flags
3114            data.append([])                                 #empty cell list
3115            data.append([])                                 #empty dmin
3116            G2frame.PatternTree.SetItemPyData(item,data)                             
3117        G2pdG.UpdateUnitCellsGrid(G2frame,data)
3118        if 'PKS' in G2frame.PatternTree.GetItemText(G2frame.PatternId):
3119            G2plt.PlotPowderLines(G2frame)
3120        else:
3121            G2plt.PlotPatterns(G2frame)
3122    elif G2frame.PatternTree.GetItemText(item) == 'Reflection Lists':   #powder reflections
3123        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3124        G2frame.PickId = item
3125        data = G2frame.PatternTree.GetItemPyData(item)
3126        G2frame.RefList = ''
3127        if len(data):
3128            G2frame.RefList = data.keys()[0]
3129        G2pdG.UpdateReflectionGrid(G2frame,data)
3130        G2plt.PlotPatterns(G2frame)
3131    elif G2frame.PatternTree.GetItemText(item) == 'Reflection List':    #HKLF reflections
3132        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
3133        name = G2frame.PatternTree.GetItemText(G2frame.PatternId)
3134        data = G2frame.PatternTree.GetItemPyData(G2frame.PatternId)
3135        G2pdG.UpdateReflectionGrid(G2frame,data,HKLF=True,Name=name)
3136
3137def SetDataMenuBar(G2frame,menu=None):
3138    '''Set the menu for the data frame. On the Mac put this
3139    menu for the data tree window instead.
3140
3141    Note that data frame items do not have menus, for these (menu=None)
3142    display a blank menu or on the Mac display the standard menu for
3143    the data tree window.
3144    '''
3145    if sys.platform == "darwin":
3146        if menu is None:
3147            G2frame.SetMenuBar(G2frame.GSASIIMenu)
3148        else:
3149            G2frame.SetMenuBar(menu)
3150    else:
3151        if menu is None:
3152            G2frame.dataFrame.SetMenuBar(G2frame.dataFrame.BlankMenu)
3153        else:
3154            G2frame.dataFrame.SetMenuBar(menu)
3155
3156def HorizontalLine(sizer,parent):
3157    '''Draws a horizontal line as wide as the window.
3158    This shows up on the Mac as a very thin line, no matter what I do
3159    '''
3160    line = wx.StaticLine(parent,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
3161    sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
3162
3163if __name__ == '__main__':
3164    # test ScrolledMultiEditor
3165    app = wx.PySimpleApp()
3166    frm = wx.Frame(None) # create a frame
3167    frm.Show(True)
3168    Data1 = {
3169        'Order':1,
3170        'omega':'string',
3171        'chi':2.0,
3172        'phi':'',
3173        }
3174    elemlst = sorted(Data1.keys())
3175    postlbl = sorted(Data1.keys())
3176    dictlst = len(elemlst)*[Data1,]
3177
3178    Data2 = list(range(100))
3179    elemlst += range(2,60)
3180    postlbl += range(2,60)
3181    dictlst += len(range(2,60))*[Data2,]
3182
3183    prelbl = range(len(elemlst))
3184    postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
3185    header="""This is a longer\nmultiline and perhaps silly header"""
3186    dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
3187                              header=header)
3188    print Data1
3189    if dlg.ShowModal() == wx.ID_OK:
3190        for d,k in zip(dictlst,elemlst):
3191            print k,d[k]
3192    dlg.Destroy()
3193    if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
3194                               header=header):
3195        for d,k in zip(dictlst,elemlst):
3196            print k,d[k]
3197
3198#app.MainLoop()
Note: See TracBrowser for help on using the repository browser.