source: trunk/GSASIIgrid.py @ 1211

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

more work on var browser

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