source: trunk/GSASIIgrid.py @ 981

Last change on this file since 981 was 981, checked in by toby, 8 years ago

introduce regress option; fix esd printing; more docs; new Mac app with drag & drop for open; control reset of ref list on load

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