source: trunk/GSASIIgrid.py @ 1236

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

Add FXYE & XYE exporters; allow multiple powder exports; fix formatting of float32 intensities (which should be gone); minor sequential refinement changes; remove old export patterns items; new padded format routine

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