source: trunk/GSASIIgrid.py @ 1028

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

print/export distances & angles

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