source: trunk/GSASIIgrid.py @ 1147

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

Complete initial ISODISPLACE implementation; mod. phase initialization; change atom pointer init.; rework parameter display window

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