source: trunk/GSASIIgrid.py @ 1138

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

major constraints revision

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