source: branch/logging/GSASIIgrid.py @ 1509

Last change on this file since 1509 was 1509, checked in by toby, 7 years ago

logging refactored, and much cleaner\!

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