source: trunk/GSASIIgrid.py @ 1281

Last change on this file since 1281 was 1281, checked in by vondreele, 8 years ago

fix a problem with plot focus change event & SASD Model GUIs

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