source: trunk/GSASIIgrid.py @ 1619

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

add columnsorter for hetrogeneous seq ref; reorg to start moving widgets out of g2grid

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