source: trunk/GSASIIgrid.py @ 1057

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

reorg UserCalibrants? into ImageCalibrants?; Add trim (whitespace) routine; fix InstName? bug in GetHistogramPhaseData?; rework ValidatedTxtCtrl? for CIF; ScrolledMultiEditor? now resets after Cancel; CIF export progress

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