source: trunk/GSASIIgrid.py @ 1282

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

sequential refinement updates

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