source: trunk/GSASIIgrid.py @ 1017

Last change on this file since 1017 was 1017, checked in by vondreele, 10 years ago

implement excluded regions - 1st attempt

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