source: trunk/GSASIIgrid.py @ 1626

Last change on this file since 1626 was 1626, checked in by vondreele, 7 years ago

Changed "Transform atom " to "Transform draw atom" to fix menu problem
implement a 4D version of findOffset - works OK
implement facility to allow moving map on 4th dimension

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 213.2 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIgrid - data display routines
3########### SVN repository information ###################
4# $Date: 2015-01-09 22:00:01 +0000 (Fri, 09 Jan 2015) $
5# $Author: vondreele $
6# $Revision: 1626 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1626 2015-01-09 22:00:01Z vondreele $
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: 1626 $")
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,wxID_4DCHARGEFLIP,
71] = [wx.NewId() for item in range(17)]
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 disabled 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='Run 4D charge flipping',id=wxID_4DCHARGEFLIP, kind=wx.ITEM_NORMAL,
2373            text='4D Charge flipping')
2374        self.GeneralCalc.Enable(wxID_4DCHARGEFLIP,False)   
2375        self.GeneralCalc.Append(help='Clear map',id=wxID_FOURCLEAR, kind=wx.ITEM_NORMAL,
2376            text='Clear map')
2377        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing',id=wxID_SINGLEMCSA, kind=wx.ITEM_NORMAL,
2378            text='MC/SA')
2379        self.GeneralCalc.Append(help='Run Monte Carlo - Simulated Annealing on multiprocessors',id=wxID_MULTIMCSA, kind=wx.ITEM_NORMAL,
2380            text='Multi MC/SA')            #currently not useful
2381        self.PostfillDataMenu()
2382       
2383        # Phase / Data tab
2384        self.DataMenu = wx.MenuBar()
2385        self.PrefillDataMenu(self.DataMenu,helpType='Data', helpLbl='Phase/Data')
2386        self.DataMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2387        self.DataEdit = wx.Menu(title='')
2388        self.DataMenu.Append(menu=self.DataEdit, title='Edit')
2389        self.DataEdit.Append(id=wxID_PWDRADD, kind=wx.ITEM_NORMAL,text='Add powder histograms',
2390            help='Select new powder histograms to be used for this phase')
2391        self.DataEdit.Append(id=wxID_HKLFADD, kind=wx.ITEM_NORMAL,text='Add single crystal histograms',
2392            help='Select new single crystal histograms to be used for this phase')
2393        self.DataEdit.Append(id=wxID_DATADELETE, kind=wx.ITEM_NORMAL,text='Remove histograms',
2394            help='Remove histograms from use for this phase')
2395        self.PostfillDataMenu()
2396           
2397        # Phase / Atoms tab
2398        self.AtomsMenu = wx.MenuBar()
2399        self.PrefillDataMenu(self.AtomsMenu,helpType='Atoms')
2400        self.AtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2401        self.AtomEdit = wx.Menu(title='')
2402        self.AtomCompute = wx.Menu(title='')
2403        self.AtomsMenu.Append(menu=self.AtomEdit, title='Edit')
2404        self.AtomsMenu.Append(menu=self.AtomCompute, title='Compute')
2405        self.AtomEdit.Append(id=wxID_ATOMSEDITADD, kind=wx.ITEM_NORMAL,text='Append atom',
2406            help='Appended as an H atom')
2407        self.AtomEdit.Append(id=wxID_ATOMSVIEWADD, kind=wx.ITEM_NORMAL,text='Append view point',
2408            help='Appended as an H atom')
2409        self.AtomEdit.Append(id=wxID_ATOMSEDITINSERT, kind=wx.ITEM_NORMAL,text='Insert atom',
2410            help='Select atom row to insert before; inserted as an H atom')
2411        self.AtomEdit.Append(id=wxID_ATOMVIEWINSERT, kind=wx.ITEM_NORMAL,text='Insert view point',
2412            help='Select atom row to insert before; inserted as an H atom')
2413        self.AtomEdit.Append(id=wxID_ATOMMOVE, kind=wx.ITEM_NORMAL,text='Move atom to view point',
2414            help='Select single atom to move')
2415        self.AtomEdit.Append(id=wxID_ATOMSEDITDELETE, kind=wx.ITEM_NORMAL,text='Delete atom',
2416            help='Select atoms to delete first')
2417        self.AtomEdit.Append(id=wxID_ATOMSREFINE, kind=wx.ITEM_NORMAL,text='Set atom refinement flags',
2418            help='Select atoms to refine first')
2419        self.AtomEdit.Append(id=wxID_ATOMSMODIFY, kind=wx.ITEM_NORMAL,text='Modify atom parameters',
2420            help='Select atoms to modify first')
2421        self.AtomEdit.Append(id=wxID_ATOMSTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform atoms',
2422            help='Select atoms to transform first')
2423        self.AtomEdit.Append(id=wxID_RELOADDRAWATOMS, kind=wx.ITEM_NORMAL,text='Reload draw atoms',
2424            help='Reload atom drawing list')
2425        submenu = wx.Menu()
2426        self.AtomEdit.AppendMenu(wx.ID_ANY, 'Reimport atoms', submenu, 
2427            help='Reimport atoms from file; sequence must match')
2428        # setup a cascade menu for the formats that have been defined
2429        self.ReImportMenuId = {}  # points to readers for each menu entry
2430        for reader in self.G2frame.ImportPhaseReaderlist:
2431            item = submenu.Append(
2432                wx.ID_ANY,help=reader.longFormatName,
2433                kind=wx.ITEM_NORMAL,text='reimport coordinates from '+reader.formatName+' file')
2434            self.ReImportMenuId[item.GetId()] = reader
2435        item = submenu.Append(
2436            wx.ID_ANY,
2437            help='Reimport coordinates, try to determine format from file',
2438            kind=wx.ITEM_NORMAL,
2439            text='guess format from file')
2440        self.ReImportMenuId[item.GetId()] = None # try all readers
2441
2442        self.AtomCompute.Append(id=wxID_ATOMSDISAGL, kind=wx.ITEM_NORMAL,text='Show Distances && Angles',
2443            help='Compute distances & angles for selected atoms')
2444        self.AtomCompute.Append(id=wxID_ATOMSPDISAGL, kind=wx.ITEM_NORMAL,text='Save Distances && Angles',
2445            help='Compute distances & angles for selected atoms')
2446        self.AtomCompute.ISOcalc = self.AtomCompute.Append(
2447            id=wxID_ISODISP, kind=wx.ITEM_NORMAL,
2448            text='Compute ISODISTORT mode values',
2449            help='Compute values of ISODISTORT modes from atom parameters')
2450        self.PostfillDataMenu()
2451       
2452        # Phase / Imcommensurate "waves" tab
2453        self.WavesData = wx.MenuBar()
2454        self.PrefillDataMenu(self.WavesData,helpType='Wave Data', helpLbl='Imcommensurate wave data')
2455        self.WavesData.Append(menu=wx.Menu(title=''),title='Select tab')
2456        self.WavesDataCompute = wx.Menu(title='')
2457        self.WavesData.Append(menu=self.WavesDataCompute,title='Compute')
2458        self.WavesDataCompute.Append(id=wxID_4DMAPCOMPUTE, kind=wx.ITEM_NORMAL,text='Compute 4D map',
2459            help='Compute 4-dimensional map')
2460        self.PostfillDataMenu()
2461                 
2462        # Phase / Draw Options tab
2463        self.DataDrawOptions = wx.MenuBar()
2464        self.PrefillDataMenu(self.DataDrawOptions,helpType='Draw Options', helpLbl='Phase/Draw Options')
2465        self.DataDrawOptions.Append(menu=wx.Menu(title=''),title='Select tab')
2466        self.PostfillDataMenu()
2467       
2468        # Phase / Draw Atoms tab
2469        self.DrawAtomsMenu = wx.MenuBar()
2470        self.PrefillDataMenu(self.DrawAtomsMenu,helpType='Draw Atoms')
2471        self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2472        self.DrawAtomEdit = wx.Menu(title='')
2473        self.DrawAtomCompute = wx.Menu(title='')
2474        self.DrawAtomRestraint = wx.Menu(title='')
2475        self.DrawAtomRigidBody = wx.Menu(title='')
2476        self.DrawAtomsMenu.Append(menu=self.DrawAtomEdit, title='Edit')
2477        self.DrawAtomsMenu.Append(menu=self.DrawAtomCompute,title='Compute')
2478        self.DrawAtomsMenu.Append(menu=self.DrawAtomRestraint, title='Restraints')
2479        self.DrawAtomsMenu.Append(menu=self.DrawAtomRigidBody, title='Rigid body')
2480        self.DrawAtomEdit.Append(id=wxID_DRAWATOMSTYLE, kind=wx.ITEM_NORMAL,text='Atom style',
2481            help='Select atoms first')
2482        self.DrawAtomEdit.Append(id=wxID_DRAWATOMLABEL, kind=wx.ITEM_NORMAL,text='Atom label',
2483            help='Select atoms first')
2484        self.DrawAtomEdit.Append(id=wxID_DRAWATOMCOLOR, kind=wx.ITEM_NORMAL,text='Atom color',
2485            help='Select atoms first')
2486        self.DrawAtomEdit.Append(id=wxID_DRAWATOMRESETCOLOR, kind=wx.ITEM_NORMAL,text='Reset atom colors',
2487            help='Resets all atom colors to defaults')
2488        self.DrawAtomEdit.Append(id=wxID_DRAWVIEWPOINT, kind=wx.ITEM_NORMAL,text='View point',
2489            help='View point is 1st atom selected')
2490        self.DrawAtomEdit.Append(id=wxID_DRAWADDEQUIV, kind=wx.ITEM_NORMAL,text='Add atoms',
2491            help='Add symmetry & cell equivalents to drawing set from selected atoms')
2492        self.DrawAtomEdit.Append(id=wxID_DRAWTRANSFORM, kind=wx.ITEM_NORMAL,text='Transform draw atoms',
2493            help='Transform selected atoms by symmetry & cell translations')
2494        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCOORD, kind=wx.ITEM_NORMAL,text='Fill CN-sphere',
2495            help='Fill coordination sphere for selected atoms')           
2496        self.DrawAtomEdit.Append(id=wxID_DRAWFILLCELL, kind=wx.ITEM_NORMAL,text='Fill unit cell',
2497            help='Fill unit cell with selected atoms')
2498        self.DrawAtomEdit.Append(id=wxID_DRAWDELETE, kind=wx.ITEM_NORMAL,text='Delete atoms',
2499            help='Delete atoms from drawing set')
2500        self.DrawAtomCompute.Append(id=wxID_DRAWDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2501            help='Compute distance of selected atoms from view point')   
2502        self.DrawAtomCompute.Append(id=wxID_DRAWDISAGLTOR, kind=wx.ITEM_NORMAL,text='Dist. Ang. Tors.',
2503            help='Compute distance, angle or torsion for 2-4 selected atoms')   
2504        self.DrawAtomCompute.Append(id=wxID_DRAWPLANE, kind=wx.ITEM_NORMAL,text='Best plane',
2505            help='Compute best plane for 4+ selected atoms')   
2506        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRBOND, kind=wx.ITEM_NORMAL,text='Add bond restraint',
2507            help='Add bond restraint for selected atoms (2)')
2508        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRANGLE, kind=wx.ITEM_NORMAL,text='Add angle restraint',
2509            help='Add angle restraint for selected atoms (3: one end 1st)')
2510        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRPLANE, kind=wx.ITEM_NORMAL,text='Add plane restraint',
2511            help='Add plane restraint for selected atoms (4+)')
2512        self.DrawAtomRestraint.Append(id=wxID_DRAWRESTRCHIRAL, kind=wx.ITEM_NORMAL,text='Add chiral restraint',
2513            help='Add chiral restraint for selected atoms (4: center atom 1st)')
2514        self.DrawAtomRigidBody.Append(id=wxID_DRAWDEFINERB, kind=wx.ITEM_NORMAL,text='Define rigid body',
2515            help='Define rigid body with selected atoms')
2516        self.PostfillDataMenu()
2517
2518        # Phase / MCSA tab
2519        self.MCSAMenu = wx.MenuBar()
2520        self.PrefillDataMenu(self.MCSAMenu,helpType='MC/SA')
2521        self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2522        self.MCSAEdit = wx.Menu(title='')
2523        self.MCSAMenu.Append(menu=self.MCSAEdit, title='MC/SA')
2524        self.MCSAEdit.Append(id=wxID_ADDMCSAATOM, kind=wx.ITEM_NORMAL,text='Add atom', 
2525            help='Add single atom to MC/SA model')
2526        self.MCSAEdit.Append(id=wxID_ADDMCSARB, kind=wx.ITEM_NORMAL,text='Add rigid body', 
2527            help='Add rigid body to MC/SA model' )
2528        self.MCSAEdit.Append(id=wxID_CLEARMCSARB, kind=wx.ITEM_NORMAL,text='Clear rigid bodies', 
2529            help='Clear all atoms & rigid bodies from MC/SA model' )
2530        self.MCSAEdit.Append(id=wxID_MOVEMCSA, kind=wx.ITEM_NORMAL,text='Move MC/SA solution', 
2531            help='Move MC/SA solution to atom list' )
2532        self.MCSAEdit.Append(id=wxID_MCSACLEARRESULTS, kind=wx.ITEM_NORMAL,text='Clear results', 
2533            help='Clear table of MC/SA results' )
2534        self.PostfillDataMenu()
2535           
2536        # Phase / Texture tab
2537        self.TextureMenu = wx.MenuBar()
2538        self.PrefillDataMenu(self.TextureMenu,helpType='Texture')
2539        self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2540        self.TextureEdit = wx.Menu(title='')
2541        self.TextureMenu.Append(menu=self.TextureEdit, title='Texture')
2542#        self.TextureEdit.Append(id=wxID_REFINETEXTURE, kind=wx.ITEM_NORMAL,text='Refine texture',
2543#            help='Refine the texture coefficients from sequential Pawley results')
2544# N.B. Binding is now commented out
2545        self.TextureEdit.Append(id=wxID_CLEARTEXTURE, kind=wx.ITEM_NORMAL,text='Clear texture', 
2546            help='Clear the texture coefficients' )
2547        self.PostfillDataMenu()
2548           
2549        # Phase / Pawley tab
2550        self.PawleyMenu = wx.MenuBar()
2551        self.PrefillDataMenu(self.PawleyMenu,helpType='Pawley')
2552        self.PawleyMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2553        self.PawleyEdit = wx.Menu(title='')
2554        self.PawleyMenu.Append(menu=self.PawleyEdit,title='Operations')
2555        self.PawleyEdit.Append(id=wxID_PAWLEYLOAD, kind=wx.ITEM_NORMAL,text='Pawley create',
2556            help='Initialize Pawley reflection list')
2557        self.PawleyEdit.Append(id=wxID_PAWLEYESTIMATE, kind=wx.ITEM_NORMAL,text='Pawley estimate',
2558            help='Estimate initial Pawley intensities')
2559        self.PawleyEdit.Append(id=wxID_PAWLEYUPDATE, kind=wx.ITEM_NORMAL,text='Pawley update',
2560            help='Update negative Pawley intensities with -0.5*Fobs and turn off refinemnt')
2561        self.PostfillDataMenu()
2562           
2563        # Phase / Map peaks tab
2564        self.MapPeaksMenu = wx.MenuBar()
2565        self.PrefillDataMenu(self.MapPeaksMenu,helpType='Map peaks')
2566        self.MapPeaksMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2567        self.MapPeaksEdit = wx.Menu(title='')
2568        self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks')
2569        self.MapPeaksEdit.Append(id=wxID_PEAKSMOVE, kind=wx.ITEM_NORMAL,text='Move peaks', 
2570            help='Move selected peaks to atom list')
2571        self.MapPeaksEdit.Append(id=wxID_PEAKSVIEWPT, kind=wx.ITEM_NORMAL,text='View point',
2572            help='View point is 1st peak selected')
2573        self.MapPeaksEdit.Append(id=wxID_PEAKSDISTVP, kind=wx.ITEM_NORMAL,text='View pt. dist.',
2574            help='Compute distance of selected peaks from view point')   
2575        self.MapPeaksEdit.Append(id=wxID_SHOWBONDS, kind=wx.ITEM_NORMAL,text='Hide bonds',
2576            help='Hide or show bonds between peak positions')   
2577        self.MapPeaksEdit.Append(id=wxID_PEAKSDA, kind=wx.ITEM_NORMAL,text='Calc dist/ang', 
2578            help='Calculate distance or angle for selection')
2579        self.MapPeaksEdit.Append(id=wxID_FINDEQVPEAKS, kind=wx.ITEM_NORMAL,text='Equivalent peaks', 
2580            help='Find equivalent peaks')
2581        self.MapPeaksEdit.Append(id=wxID_PEAKSUNIQUE, kind=wx.ITEM_NORMAL,text='Unique peaks', 
2582            help='Select unique set')
2583        self.MapPeaksEdit.Append(id=wxID_PEAKSDELETE, kind=wx.ITEM_NORMAL,text='Delete peaks', 
2584            help='Delete selected peaks')
2585        self.MapPeaksEdit.Append(id=wxID_PEAKSCLEAR, kind=wx.ITEM_NORMAL,text='Clear peaks', 
2586            help='Clear the map peak list')
2587        self.PostfillDataMenu()
2588
2589        # Phase / Rigid bodies tab
2590        self.RigidBodiesMenu = wx.MenuBar()
2591        self.PrefillDataMenu(self.RigidBodiesMenu,helpType='Rigid bodies')
2592        self.RigidBodiesMenu.Append(menu=wx.Menu(title=''),title='Select tab')
2593        self.RigidBodiesEdit = wx.Menu(title='')
2594        self.RigidBodiesMenu.Append(menu=self.RigidBodiesEdit, title='Edit')
2595        self.RigidBodiesEdit.Append(id=wxID_ASSIGNATMS2RB, kind=wx.ITEM_NORMAL,text='Assign atoms to rigid body',
2596            help='Select & position rigid body in structure of existing atoms')
2597        self.RigidBodiesEdit.Append(id=wxID_AUTOFINDRESRB, kind=wx.ITEM_NORMAL,text='Auto find residues',
2598            help='Auto find of residue RBs in macromolecule')
2599        self.RigidBodiesEdit.Append(id=wxID_COPYRBPARMS, kind=wx.ITEM_NORMAL,text='Copy rigid body parms',
2600            help='Copy rigid body location & TLS parameters')
2601        self.RigidBodiesEdit.Append(id=wxID_GLOBALTHERM, kind=wx.ITEM_NORMAL,text='Global thermal motion',
2602            help='Global setting of residue thermal motion models')
2603        self.RigidBodiesEdit.Append(id=wxID_GLOBALRESREFINE, kind=wx.ITEM_NORMAL,text='Global residue refine',
2604            help='Global setting of residue RB refinement flags')
2605        self.RigidBodiesEdit.Append(id=wxID_RBREMOVEALL, kind=wx.ITEM_NORMAL,text='Remove all rigid bodies',
2606            help='Remove all rigid body assignment for atoms')
2607        self.PostfillDataMenu()
2608    # end of GSAS-II menu definitions
2609       
2610    def _init_ctrls(self, parent,name=None,size=None,pos=None):
2611        wx.Frame.__init__(
2612            self,parent=parent,
2613            #style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT ,
2614            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX,
2615            size=size,pos=pos,title='GSAS-II data display')
2616        self._init_menus()
2617        if name:
2618            self.SetLabel(name)
2619        self.Show()
2620       
2621    def __init__(self,parent,frame,data=None,name=None, size=None,pos=None):
2622        self.G2frame = frame
2623        self._init_ctrls(parent,name,size,pos)
2624        self.data = data
2625        clientSize = wx.ClientDisplayRect()
2626        Size = self.GetSize()
2627        xPos = clientSize[2]-Size[0]
2628        self.SetPosition(wx.Point(xPos,clientSize[1]+250))
2629        self.AtomGrid = []
2630        self.selectedRow = 0
2631       
2632    def setSizePosLeft(self,Width):
2633        clientSize = wx.ClientDisplayRect()
2634        Width[1] = min(Width[1],clientSize[2]-300)
2635        Width[0] = max(Width[0],300)
2636        self.SetSize(Width)
2637#        self.SetPosition(wx.Point(clientSize[2]-Width[0],clientSize[1]+250))
2638       
2639    def Clear(self):
2640        self.ClearBackground()
2641        self.DestroyChildren()
2642                   
2643################################################################################
2644#####  GSNotebook
2645################################################################################           
2646       
2647class GSNoteBook(wx.aui.AuiNotebook):
2648    '''Notebook used in various locations; implemented with wx.aui extension
2649    '''
2650    def __init__(self, parent, name='',size = None):
2651        wx.aui.AuiNotebook.__init__(self, parent, -1,
2652                                    style=wx.aui.AUI_NB_TOP |
2653                                    wx.aui.AUI_NB_SCROLL_BUTTONS)
2654        if size: self.SetSize(size)
2655        self.parent = parent
2656        self.PageChangeHandler = None
2657       
2658    def PageChangeEvent(self,event):
2659        G2frame = self.parent.G2frame
2660        page = event.GetSelection()
2661        if self.PageChangeHandler:
2662            if log.LogInfo['Logging']:
2663                log.MakeTabLog(
2664                    G2frame.dataFrame.GetTitle(),
2665                    G2frame.dataDisplay.GetPageText(page)
2666                    )
2667            self.PageChangeHandler(event)
2668           
2669    def Bind(self,eventtype,handler,*args,**kwargs):
2670        '''Override the Bind() function so that page change events can be trapped
2671        '''
2672        if eventtype == wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED:
2673            self.PageChangeHandler = handler
2674            wx.aui.AuiNotebook.Bind(self,eventtype,self.PageChangeEvent)
2675            return
2676        wx.aui.AuiNotebook.Bind(self,eventtype,handler,*args,**kwargs)
2677                                                     
2678    def Clear(self):       
2679        GSNoteBook.DeleteAllPages(self)
2680       
2681    def FindPage(self,name):
2682        numPage = self.GetPageCount()
2683        for page in range(numPage):
2684            if self.GetPageText(page) == name:
2685                return page
2686
2687    def ChangeSelection(self,page):
2688        # in wx.Notebook ChangeSelection is like SetSelection, but it
2689        # does not invoke the event related to pressing the tab button
2690        # I don't see a way to do that in aui.
2691        oldPage = self.GetSelection()
2692        self.SetSelection(page)
2693        return oldPage
2694
2695    # def __getattribute__(self,name):
2696    #     '''This method provides a way to print out a message every time
2697    #     that a method in a class is called -- to see what all the calls
2698    #     might be, or where they might be coming from.
2699    #     Cute trick for debugging!
2700    #     '''
2701    #     attr = object.__getattribute__(self, name)
2702    #     if hasattr(attr, '__call__'):
2703    #         def newfunc(*args, **kwargs):
2704    #             print('GSauiNoteBook calling %s' %attr.__name__)
2705    #             result = attr(*args, **kwargs)
2706    #             return result
2707    #         return newfunc
2708    #     else:
2709    #         return attr
2710           
2711################################################################################
2712#####  GSGrid
2713################################################################################           
2714       
2715class GSGrid(wg.Grid):
2716    '''Basic wx.Grid implementation
2717    '''
2718    def __init__(self, parent, name=''):
2719        wg.Grid.__init__(self,parent,-1,name=name)                   
2720        #self.SetSize(parent.GetClientSize())
2721        # above removed to speed drawing of initial grid
2722        # does not appear to be needed
2723           
2724    def Clear(self):
2725        wg.Grid.ClearGrid(self)
2726       
2727    def SetCellReadOnly(self,r,c,readonly=True):
2728        self.SetReadOnly(r,c,isReadOnly=readonly)
2729       
2730    def SetCellStyle(self,r,c,color="white",readonly=True):
2731        self.SetCellBackgroundColour(r,c,color)
2732        self.SetReadOnly(r,c,isReadOnly=readonly)
2733       
2734    def GetSelection(self):
2735        #this is to satisfy structure drawing stuff in G2plt when focus changes
2736        return None
2737
2738    def InstallGridToolTip(self, rowcolhintcallback,
2739                           colLblCallback=None,rowLblCallback=None):
2740        '''code to display a tooltip for each item on a grid
2741        from http://wiki.wxpython.org/wxGrid%20ToolTips (buggy!), expanded to
2742        column and row labels using hints from
2743        https://groups.google.com/forum/#!topic/wxPython-users/bm8OARRVDCs
2744
2745        :param function rowcolhintcallback: a routine that returns a text
2746          string depending on the selected row and column, to be used in
2747          explaining grid entries.
2748        :param function colLblCallback: a routine that returns a text
2749          string depending on the selected column, to be used in
2750          explaining grid columns (if None, the default), column labels
2751          do not get a tooltip.
2752        :param function rowLblCallback: a routine that returns a text
2753          string depending on the selected row, to be used in
2754          explaining grid rows (if None, the default), row labels
2755          do not get a tooltip.
2756        '''
2757        prev_rowcol = [None,None,None]
2758        def OnMouseMotion(event):
2759            # event.GetRow() and event.GetCol() would be nice to have here,
2760            # but as this is a mouse event, not a grid event, they are not
2761            # available and we need to compute them by hand.
2762            x, y = self.CalcUnscrolledPosition(event.GetPosition())
2763            row = self.YToRow(y)
2764            col = self.XToCol(x)
2765            hinttext = ''
2766            win = event.GetEventObject()
2767            if [row,col,win] == prev_rowcol: # no change from last position
2768                event.Skip()
2769                return
2770            if win == self.GetGridWindow() and row >= 0 and col >= 0:
2771                hinttext = rowcolhintcallback(row, col)
2772            elif win == self.GetGridColLabelWindow() and col >= 0:
2773                if colLblCallback: hinttext = colLblCallback(col)
2774            elif win == self.GetGridRowLabelWindow() and row >= 0:
2775                if rowLblCallback: hinttext = rowLblCallback(row)
2776            else: # this should be the upper left corner, which is empty
2777                event.Skip()
2778                return
2779            if hinttext is None: hinttext = ''
2780            win.SetToolTipString(hinttext)
2781            prev_rowcol[:] = [row,col,win]
2782            event.Skip()
2783
2784        wx.EVT_MOTION(self.GetGridWindow(), OnMouseMotion)
2785        if colLblCallback: wx.EVT_MOTION(self.GetGridColLabelWindow(), OnMouseMotion)
2786        if rowLblCallback: wx.EVT_MOTION(self.GetGridRowLabelWindow(), OnMouseMotion)
2787                                                   
2788################################################################################
2789#####  Table
2790################################################################################           
2791       
2792class Table(wg.PyGridTableBase):
2793    '''Basic data table for use with GSgrid
2794    '''
2795    def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
2796        wg.PyGridTableBase.__init__(self)
2797        self.colLabels = colLabels
2798        self.rowLabels = rowLabels
2799        self.dataTypes = types
2800        self.data = data
2801       
2802    def AppendRows(self, numRows=1):
2803        self.data.append([])
2804        return True
2805       
2806    def CanGetValueAs(self, row, col, typeName):
2807        if self.dataTypes:
2808            colType = self.dataTypes[col].split(':')[0]
2809            if typeName == colType:
2810                return True
2811            else:
2812                return False
2813        else:
2814            return False
2815
2816    def CanSetValueAs(self, row, col, typeName):
2817        return self.CanGetValueAs(row, col, typeName)
2818
2819    def DeleteRow(self,pos):
2820        data = self.GetData()
2821        self.SetData([])
2822        new = []
2823        for irow,row in enumerate(data):
2824            if irow <> pos:
2825                new.append(row)
2826        self.SetData(new)
2827       
2828    def GetColLabelValue(self, col):
2829        if self.colLabels:
2830            return self.colLabels[col]
2831           
2832    def GetData(self):
2833        data = []
2834        for row in range(self.GetNumberRows()):
2835            data.append(self.GetRowValues(row))
2836        return data
2837       
2838    def GetNumberCols(self):
2839        try:
2840            return len(self.colLabels)
2841        except TypeError:
2842            return None
2843       
2844    def GetNumberRows(self):
2845        return len(self.data)
2846       
2847    def GetRowLabelValue(self, row):
2848        if self.rowLabels:
2849            return self.rowLabels[row]
2850       
2851    def GetColValues(self, col):
2852        data = []
2853        for row in range(self.GetNumberRows()):
2854            data.append(self.GetValue(row, col))
2855        return data
2856       
2857    def GetRowValues(self, row):
2858        data = []
2859        for col in range(self.GetNumberCols()):
2860            data.append(self.GetValue(row, col))
2861        return data
2862       
2863    def GetTypeName(self, row, col):
2864        try:
2865            if self.data[row][col] is None: return None
2866            return self.dataTypes[col]
2867        except TypeError:
2868            return None
2869
2870    def GetValue(self, row, col):
2871        try:
2872            if self.data[row][col] is None: return ""
2873            return self.data[row][col]
2874        except IndexError:
2875            return None
2876           
2877    def InsertRows(self, pos, rows):
2878        for row in range(rows):
2879            self.data.insert(pos,[])
2880            pos += 1
2881       
2882    def IsEmptyCell(self,row,col):
2883        try:
2884            return not self.data[row][col]
2885        except IndexError:
2886            return True
2887       
2888    def OnKeyPress(self, event):
2889        dellist = self.GetSelectedRows()
2890        if event.GetKeyCode() == wx.WXK_DELETE and dellist:
2891            grid = self.GetView()
2892            for i in dellist: grid.DeleteRow(i)
2893               
2894    def SetColLabelValue(self, col, label):
2895        numcols = self.GetNumberCols()
2896        if col > numcols-1:
2897            self.colLabels.append(label)
2898        else:
2899            self.colLabels[col]=label
2900       
2901    def SetData(self,data):
2902        for row in range(len(data)):
2903            self.SetRowValues(row,data[row])
2904               
2905    def SetRowLabelValue(self, row, label):
2906        self.rowLabels[row]=label
2907           
2908    def SetRowValues(self,row,data):
2909        self.data[row] = data
2910           
2911    def SetValue(self, row, col, value):
2912        def innerSetValue(row, col, value):
2913            try:
2914                self.data[row][col] = value
2915            except TypeError:
2916                return
2917            except IndexError:
2918                print row,col,value
2919                # add a new row
2920                if row > self.GetNumberRows():
2921                    self.data.append([''] * self.GetNumberCols())
2922                elif col > self.GetNumberCols():
2923                    for row in range(self.GetNumberRows):
2924                        self.data[row].append('')
2925                print self.data
2926                self.data[row][col] = value
2927        innerSetValue(row, col, value)
2928       
2929################################################################################
2930#### Help
2931################################################################################
2932
2933def ShowHelp(helpType,frame):
2934    '''Called to bring up a web page for documentation.'''
2935    global htmlFirstUse
2936    # look up a definition for help info from dict
2937    helplink = helpLocDict.get(helpType)
2938    if helplink is None:
2939        # no defined link to use, create a default based on key
2940        helplink = 'gsasII.html#'+helpType.replace(' ','_')
2941    helplink = os.path.join(path2GSAS2,'help',helplink)
2942    if helpMode == 'internal':
2943        try:
2944            htmlPanel.LoadFile(helplink)
2945            htmlFrame.Raise()
2946        except:
2947            htmlFrame = wx.Frame(frame, -1, size=(610, 510))
2948            htmlFrame.Show(True)
2949            htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
2950            htmlPanel = MyHtmlPanel(htmlFrame,-1)
2951            htmlPanel.LoadFile(helplink)
2952    else:
2953        pfx = "file://"
2954        if sys.platform.lower().startswith('win'):
2955            pfx = ''
2956        if htmlFirstUse:
2957            webbrowser.open_new(pfx+helplink)
2958            htmlFirstUse = False
2959        else:
2960            webbrowser.open(pfx+helplink, new=0, autoraise=True)
2961
2962################################################################################
2963#####  Notebook
2964################################################################################           
2965       
2966def UpdateNotebook(G2frame,data):
2967    '''Called when the data tree notebook entry is selected. Allows for
2968    editing of the text in that tree entry
2969    '''
2970    def OnNoteBook(event):
2971        data = G2frame.dataDisplay.GetValue().split('\n')
2972        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Notebook'),data)
2973        if 'nt' not in os.name:
2974            G2frame.dataDisplay.AppendText('\n')
2975                   
2976    if G2frame.dataDisplay:
2977        G2frame.dataDisplay.Destroy()
2978    G2frame.dataFrame.SetLabel('Notebook')
2979    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
2980        style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER | wx.TE_DONTWRAP)
2981    G2frame.dataDisplay.Bind(wx.EVT_TEXT_ENTER,OnNoteBook)
2982    G2frame.dataDisplay.Bind(wx.EVT_KILL_FOCUS,OnNoteBook)
2983    for line in data:
2984        G2frame.dataDisplay.AppendText(line+"\n")
2985    G2frame.dataDisplay.AppendText('Notebook entry @ '+time.ctime()+"\n")
2986    G2frame.dataFrame.setSizePosLeft([400,250])
2987           
2988################################################################################
2989#####  Controls
2990################################################################################           
2991       
2992def UpdateControls(G2frame,data):
2993    '''Edit overall GSAS-II controls in main Controls data tree entry
2994    '''
2995    #patch
2996    if 'deriv type' not in data:
2997        data = {}
2998        data['deriv type'] = 'analytic Hessian'
2999        data['min dM/M'] = 0.0001
3000        data['shift factor'] = 1.
3001        data['max cyc'] = 3       
3002        data['F**2'] = True
3003        data['minF/sig'] = 0
3004    if 'shift factor' not in data:
3005        data['shift factor'] = 1.
3006    if 'max cyc' not in data:
3007        data['max cyc'] = 3
3008    if 'F**2' not in data:
3009        data['F**2'] = True
3010        data['minF/sig'] = 0
3011    if 'Author' not in data:
3012        data['Author'] = 'no name'
3013    if 'FreePrm1' not in data:
3014        data['FreePrm1'] = 'Sample humidity (%)'
3015    if 'FreePrm2' not in data:
3016        data['FreePrm2'] = 'Sample voltage (V)'
3017    if 'FreePrm3' not in data:
3018        data['FreePrm3'] = 'Applied load (MN)'
3019    if 'Copy2Next' not in data:
3020        data['Copy2Next'] = False
3021    if 'Reverse Seq' not in data:
3022        data['Reverse Seq'] = False   
3023     
3024   
3025    #end patch
3026
3027    def SeqSizer():
3028       
3029        def OnSelectData(event):
3030            choices = GetPatternTreeDataNames(G2frame,['PWDR',])
3031            sel = []
3032            if 'Seq Data' in data:
3033                for item in data['Seq Data']:
3034                    sel.append(choices.index(item))
3035                sel = [choices.index(item) for item in data['Seq Data']]
3036            dlg = G2MultiChoiceDialog(G2frame.dataFrame, 'Sequential refinement',
3037                                      'Select dataset to include',
3038                                      choices)
3039            dlg.SetSelections(sel)
3040            names = []
3041            if dlg.ShowModal() == wx.ID_OK:
3042                for sel in dlg.GetSelections():
3043                    names.append(choices[sel])
3044                data['Seq Data'] = names               
3045                G2frame.EnableSeqRefineMenu()
3046            dlg.Destroy()
3047            wx.CallAfter(UpdateControls,G2frame,data)
3048           
3049        def OnReverse(event):
3050            data['Reverse Seq'] = reverseSel.GetValue()
3051           
3052        def OnCopySel(event):
3053            data['Copy2Next'] = copySel.GetValue() 
3054                   
3055        seqSizer = wx.BoxSizer(wx.VERTICAL)
3056        dataSizer = wx.BoxSizer(wx.HORIZONTAL)
3057        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Sequential Refinement: '),0,WACV)
3058        selSeqData = wx.Button(G2frame.dataDisplay,-1,label=' Select data')
3059        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
3060        dataSizer.Add(selSeqData,0,WACV)
3061        SeqData = data.get('Seq Data',[])
3062        if not SeqData:
3063            lbl = ' (no powder data selected)'
3064        else:
3065            lbl = ' ('+str(len(SeqData))+' dataset(s) selected)'
3066
3067        dataSizer.Add(wx.StaticText(G2frame.dataDisplay,label=lbl),0,WACV)
3068        seqSizer.Add(dataSizer,0)
3069        if SeqData:
3070            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3071            reverseSel = wx.CheckBox(G2frame.dataDisplay,-1,label=' Reverse order?')
3072            reverseSel.Bind(wx.EVT_CHECKBOX,OnReverse)
3073            reverseSel.SetValue(data['Reverse Seq'])
3074            selSizer.Add(reverseSel,0,WACV)
3075            copySel =  wx.CheckBox(G2frame.dataDisplay,-1,label=' Copy results to next histogram?')
3076            copySel.Bind(wx.EVT_CHECKBOX,OnCopySel)
3077            copySel.SetValue(data['Copy2Next'])
3078            selSizer.Add(copySel,0,WACV)
3079            seqSizer.Add(selSizer,0)
3080        return seqSizer
3081       
3082    def LSSizer():       
3083       
3084        def OnDerivType(event):
3085            data['deriv type'] = derivSel.GetValue()
3086            derivSel.SetValue(data['deriv type'])
3087            wx.CallAfter(UpdateControls,G2frame,data)
3088           
3089        def OnConvergence(event):
3090            try:
3091                value = max(1.e-9,min(1.0,float(Cnvrg.GetValue())))
3092            except ValueError:
3093                value = 0.0001
3094            data['min dM/M'] = value
3095            Cnvrg.SetValue('%.2g'%(value))
3096           
3097        def OnMaxCycles(event):
3098            data['max cyc'] = int(maxCyc.GetValue())
3099            maxCyc.SetValue(str(data['max cyc']))
3100                       
3101        def OnFactor(event):
3102            try:
3103                value = min(max(float(Factr.GetValue()),0.00001),100.)
3104            except ValueError:
3105                value = 1.0
3106            data['shift factor'] = value
3107            Factr.SetValue('%.5f'%(value))
3108           
3109        def OnFsqRef(event):
3110            data['F**2'] = fsqRef.GetValue()
3111       
3112        def OnMinSig(event):
3113            try:
3114                value = min(max(float(minSig.GetValue()),0.),5.)
3115            except ValueError:
3116                value = 1.0
3117            data['minF/sig'] = value
3118            minSig.SetValue('%.2f'%(value))
3119
3120        LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5)
3121        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement derivatives: '),0,WACV)
3122        Choice=['analytic Jacobian','numeric','analytic Hessian']
3123        derivSel = wx.ComboBox(parent=G2frame.dataDisplay,value=data['deriv type'],choices=Choice,
3124            style=wx.CB_READONLY|wx.CB_DROPDOWN)
3125        derivSel.SetValue(data['deriv type'])
3126        derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType)
3127           
3128        LSSizer.Add(derivSel,0,WACV)
3129        LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Min delta-M/M: '),0,WACV)
3130        Cnvrg = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2g'%(data['min dM/M']),style=wx.TE_PROCESS_ENTER)
3131        Cnvrg.Bind(wx.EVT_TEXT_ENTER,OnConvergence)
3132        Cnvrg.Bind(wx.EVT_KILL_FOCUS,OnConvergence)
3133        LSSizer.Add(Cnvrg,0,WACV)
3134        if 'Hessian' in data['deriv type']:
3135            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Max cycles: '),0,WACV)
3136            Choice = ['0','1','2','3','5','10','15','20']
3137            maxCyc = wx.ComboBox(parent=G2frame.dataDisplay,value=str(data['max cyc']),choices=Choice,
3138                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3139            maxCyc.SetValue(str(data['max cyc']))
3140            maxCyc.Bind(wx.EVT_COMBOBOX, OnMaxCycles)
3141            LSSizer.Add(maxCyc,0,WACV)
3142        else:
3143            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Initial shift factor: '),0,WACV)
3144            Factr = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.5f'%(data['shift factor']),style=wx.TE_PROCESS_ENTER)
3145            Factr.Bind(wx.EVT_TEXT_ENTER,OnFactor)
3146            Factr.Bind(wx.EVT_KILL_FOCUS,OnFactor)
3147            LSSizer.Add(Factr,0,WACV)
3148        if G2frame.Sngl:
3149            LSSizer.Add((1,0),)
3150            LSSizer.Add((1,0),)
3151            fsqRef = wx.CheckBox(G2frame.dataDisplay,-1,label='Refine HKLF as F^2? ')
3152            fsqRef.SetValue(data['F**2'])
3153            fsqRef.Bind(wx.EVT_CHECKBOX,OnFsqRef)
3154            LSSizer.Add(fsqRef,0,WACV)
3155            LSSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,label='Min obs/sig (0-5): '),0,WACV)
3156            minSig = wx.TextCtrl(G2frame.dataDisplay,-1,value='%.2f'%(data['minF/sig']),style=wx.TE_PROCESS_ENTER)
3157            minSig.Bind(wx.EVT_TEXT_ENTER,OnMinSig)
3158            minSig.Bind(wx.EVT_KILL_FOCUS,OnMinSig)
3159            LSSizer.Add(minSig,0,WACV)
3160        return LSSizer
3161       
3162    def AuthSizer():
3163
3164        def OnAuthor(event):
3165            data['Author'] = auth.GetValue()
3166
3167        Author = data['Author']
3168        authSizer = wx.BoxSizer(wx.HORIZONTAL)
3169        authSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' CIF Author (last, first):'),0,WACV)
3170        auth = wx.TextCtrl(G2frame.dataDisplay,-1,value=Author,style=wx.TE_PROCESS_ENTER)
3171        auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor)
3172        auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor)
3173        authSizer.Add(auth,0,WACV)
3174        return authSizer
3175       
3176       
3177    if G2frame.dataDisplay:
3178        G2frame.dataDisplay.Destroy()
3179    if not G2frame.dataFrame.GetStatusBar():
3180        Status = G2frame.dataFrame.CreateStatusBar()
3181        Status.SetStatusText('')
3182    G2frame.dataFrame.SetLabel('Controls')
3183    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
3184    SetDataMenuBar(G2frame,G2frame.dataFrame.ControlsMenu)
3185    mainSizer = wx.BoxSizer(wx.VERTICAL)
3186    mainSizer.Add((5,5),0)
3187    mainSizer.Add(wx.StaticText(G2frame.dataDisplay,label=' Refinement Controls:'),0,WACV)   
3188    mainSizer.Add(LSSizer())
3189    mainSizer.Add((5,5),0)
3190    mainSizer.Add(SeqSizer())
3191    mainSizer.Add((5,5),0)
3192    mainSizer.Add(AuthSizer())
3193    mainSizer.Add((5,5),0)
3194       
3195    mainSizer.Layout()   
3196    G2frame.dataDisplay.SetSizer(mainSizer)
3197    G2frame.dataDisplay.SetSize(mainSizer.Fit(G2frame.dataFrame))
3198    G2frame.dataFrame.setSizePosLeft(mainSizer.Fit(G2frame.dataFrame))
3199     
3200################################################################################
3201#####  Comments
3202################################################################################           
3203       
3204def UpdateComments(G2frame,data):                   
3205
3206    if G2frame.dataDisplay:
3207        G2frame.dataDisplay.Destroy()
3208    G2frame.dataFrame.SetLabel('Comments')
3209    G2frame.dataDisplay = wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
3210        style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
3211    for line in data:
3212        G2frame.dataDisplay.AppendText(line+'\n')
3213    G2frame.dataFrame.setSizePosLeft([400,250])
3214           
3215################################################################################
3216#####  Display of Sequential Results
3217################################################################################           
3218       
3219def UpdateSeqResults(G2frame,data,prevSize=None):
3220    """
3221    Called when the Sequential Results data tree entry is selected
3222    to show results from a sequential refinement.
3223   
3224    :param wx.Frame G2frame: main GSAS-II data tree windows
3225
3226    :param dict data: a dictionary containing the following items: 
3227
3228            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
3229            * 'varyList' - list of variables - identical over all refinements in sequence
3230              note that this is the original list of variables, prior to processing
3231              constraints.
3232            * 'variableLabels' -- a dict of labels to be applied to each parameter
3233              (this is created as an empty dict if not present in data).
3234            * keyed by histName - dictionaries for all data sets processed, which contains:
3235
3236              * 'variables'- result[0] from leastsq call
3237              * 'varyList' - list of variables passed to leastsq call (not same as above)
3238              * 'sig' - esds for variables
3239              * 'covMatrix' - covariance matrix from individual refinement
3240              * 'title' - histogram name; same as dict item name
3241              * 'newAtomDict' - new atom parameters after shifts applied
3242              * 'newCellDict' - refined cell parameters after shifts to A0-A5 from Dij terms applied'
3243    """
3244
3245    def GetSampleParms():
3246        '''Make a dictionary of the sample parameters are not the same over the
3247        refinement series.
3248        '''
3249        if 'IMG' in histNames[0]:
3250            sampleParmDict = {'Sample load':[],}
3251        else:
3252            sampleParmDict = {'Temperature':[],'Pressure':[],'Time':[],
3253                              'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],}
3254        Controls = G2frame.PatternTree.GetItemPyData(
3255            GetPatternTreeItemId(G2frame,G2frame.root, 'Controls'))
3256        sampleParm = {}
3257        for name in histNames:
3258            if 'IMG' in name:
3259                for item in sampleParmDict:
3260                    sampleParmDict[item].append(data[name]['parmDict'][item])
3261            else:
3262                Id = GetPatternTreeItemId(G2frame,G2frame.root,name)
3263                sampleData = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,Id,'Sample Parameters'))
3264                for item in sampleParmDict:
3265                    sampleParmDict[item].append(sampleData[item])
3266        for item in sampleParmDict:
3267            frstValue = sampleParmDict[item][0]
3268            if np.any(np.array(sampleParmDict[item])-frstValue):
3269                if item.startswith('FreePrm'):
3270                    sampleParm[Controls[item]] = sampleParmDict[item]
3271                else:
3272                    sampleParm[item] = sampleParmDict[item]
3273        return sampleParm
3274
3275    def GetColumnInfo(col):
3276        '''returns column label, lists of values and errors (or None) for each column in the table
3277        for plotting. The column label is reformatted from Unicode to MatPlotLib encoding
3278        '''
3279        colName = G2frame.SeqTable.GetColLabelValue(col)
3280        plotName = variableLabels.get(colName,colName)
3281        plotName = plotSpCharFix(plotName)
3282        return plotName,colList[col],colSigs[col]
3283           
3284    def PlotSelect(event):
3285        'Plots a row (covariance) or column on double-click'
3286        cols = G2frame.dataDisplay.GetSelectedCols()
3287        rows = G2frame.dataDisplay.GetSelectedRows()
3288        if cols:
3289            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
3290        elif rows:
3291            name = histNames[rows[0]]       #only does 1st one selected
3292            G2plt.PlotCovariance(G2frame,data[name])
3293        else:
3294            G2frame.ErrorDialog(
3295                'Select row or columns',
3296                'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.'
3297                )
3298           
3299    def OnPlotSelSeq(event):
3300        'plot the selected columns or row from menu command'
3301        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
3302        rows = G2frame.dataDisplay.GetSelectedRows()
3303        if cols:
3304            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
3305        elif rows:
3306            name = histNames[rows[0]]       #only does 1st one selected
3307            G2plt.PlotCovariance(G2frame,data[name])
3308        else:
3309            G2frame.ErrorDialog(
3310                'Select columns',
3311                'No columns or rows selected in table. Click on row or column labels to select fields for plotting.'
3312                )
3313               
3314    def OnRenameSelSeq(event):
3315        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
3316        colNames = [G2frame.SeqTable.GetColLabelValue(c) for c in cols]
3317        newNames = colNames[:]
3318        for i,name in enumerate(colNames):
3319            if name in variableLabels:
3320                newNames[i] = variableLabels[name]
3321        if not cols:
3322            G2frame.ErrorDialog('Select columns',
3323                'No columns selected in table. Click on column labels to select fields for rename.')
3324            return
3325        dlg = MultiStringDialog(G2frame.dataDisplay,'Set column names',colNames,newNames)
3326        if dlg.Show():
3327            newNames = dlg.GetValues()           
3328            variableLabels.update(dict(zip(colNames,newNames)))
3329        data['variableLabels'] = variableLabels
3330        dlg.Destroy()
3331        UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3332        G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
3333           
3334    def OnReOrgSelSeq(event):
3335        'Reorder the columns'
3336        G2G.GetItemOrder(G2frame,VaryListChanges,vallookup,posdict)   
3337        UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3338
3339    def OnSaveSelSeqCSV(event):
3340        'export the selected columns to a .csv file from menu command'
3341        OnSaveSelSeq(event,csv=True)
3342       
3343    def OnSaveSelSeq(event,csv=False):
3344        'export the selected columns to a .txt file from menu command'
3345        def WriteCSV():
3346            def WriteList(headerItems):
3347                line = ''
3348                for lbl in headerItems:
3349                    if line: line += ','
3350                    line += '"'+lbl+'"'
3351                return line
3352            head = ['name']
3353            for col in cols:
3354                item = G2frame.SeqTable.GetColLabelValue(col)
3355                if col in havesig:
3356                    head += [item,'esd-'+item]
3357                else:
3358                    head += [item]
3359            SeqFile.write(WriteList(head)+'\n')
3360            for row,name in enumerate(saveNames):
3361                line = '"'+saveNames[row]+'"'
3362                for col in cols:
3363                    if col in havesig:
3364                        line += ','+str(saveData[col][row])+','+str(saveSigs[col][row])
3365                    else:
3366                        line += ','+str(saveData[col][row])
3367                SeqFile.write(line+'\n')
3368        def WriteSeq():
3369            lenName = len(saveNames[0])
3370            line = %s  '%('name'.center(lenName))
3371            for col in cols:
3372                item = G2frame.SeqTable.GetColLabelValue(col)
3373                if col in havesig:
3374                    line += ' %12s %12s '%(item.center(12),'esd'.center(12))
3375                else:
3376                    line += ' %12s '%(item.center(12))
3377            SeqFile.write(line+'\n')
3378            for row,name in enumerate(saveNames):
3379                line = " '%s' "%(saveNames[row])
3380                for col in cols:
3381                    if col in havesig:
3382                        line += ' %12.6f %12.6f '%(saveData[col][row],saveSigs[col][row])
3383                    else:
3384                        line += ' %12.6f '%saveData[col][row]
3385                SeqFile.write(line+'\n')
3386
3387        # start of OnSaveSelSeq code
3388        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
3389        nrows = G2frame.SeqTable.GetNumberRows()
3390        if not cols:
3391            G2frame.ErrorDialog('Select columns',
3392                             'No columns selected in table. Click on column labels to select fields for output.')
3393            return
3394        saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(nrows)]
3395        saveData = {}
3396        saveSigs = {}
3397        havesig = []
3398        for col in cols:
3399            name,vals,sigs = GetColumnInfo(col)
3400            saveData[col] = vals
3401            if sigs:
3402                havesig.append(col)
3403                saveSigs[col] = sigs
3404        if csv:
3405            wild = 'CSV output file (*.csv)|*.csv'
3406        else:
3407            wild = 'Text output file (*.txt)|*.txt'
3408        dlg = wx.FileDialog(
3409            G2frame,
3410            'Choose text output file for your selection', '.', '', 
3411            wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
3412        try:
3413            if dlg.ShowModal() == wx.ID_OK:
3414                SeqTextFile = dlg.GetPath()
3415                SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) 
3416                SeqFile = open(SeqTextFile,'w')
3417                if csv:
3418                    WriteCSV()
3419                else:
3420                    WriteSeq()
3421                SeqFile.close()
3422        finally:
3423            dlg.Destroy()
3424               
3425    def striphist(var,insChar=''):
3426        'strip a histogram number from a var name'
3427        sv = var.split(':')
3428        if len(sv) <= 1: return var
3429        sv[1] = insChar
3430        return ':'.join(sv)
3431       
3432    def plotSpCharFix(lbl):
3433        'Change selected unicode characters to their matplotlib equivalent'
3434        for u,p in [
3435            (u'\u03B1',r'$\alpha$'),
3436            (u'\u03B2',r'$\beta$'),
3437            (u'\u03B3',r'$\gamma$'),
3438            (u'\u0394\u03C7',r'$\Delta\chi$'),
3439            ]:
3440            lbl = lbl.replace(u,p)
3441        return lbl
3442   
3443    def SelectXaxis():
3444        'returns a selected column number (or None) as the X-axis selection'
3445        ncols = G2frame.SeqTable.GetNumberCols()
3446        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
3447        dlg = G2SingleChoiceDialog(
3448            G2frame.dataDisplay,
3449            'Select x-axis parameter for plot or Cancel for sequence number',
3450            'Select X-axis',
3451            colNames)
3452        try:
3453            if dlg.ShowModal() == wx.ID_OK:
3454                col = dlg.GetSelection()
3455            else:
3456                col = None
3457        finally:
3458            dlg.Destroy()
3459        return col
3460   
3461    def EnablePseudoVarMenus():
3462        'Enables or disables the PseudoVar menu items that require existing defs'
3463        if Controls['SeqPseudoVars']:
3464            val = True
3465        else:
3466            val = False
3467        G2frame.dataFrame.SequentialPvars.Enable(wxDELSEQVAR,val)
3468        G2frame.dataFrame.SequentialPvars.Enable(wxEDITSEQVAR,val)
3469
3470    def DelPseudoVar(event):
3471        'Ask the user to select a pseudo var expression to delete'
3472        choices = Controls['SeqPseudoVars'].keys()
3473        selected = ItemSelector(
3474            choices,G2frame.dataFrame,
3475            multiple=True,
3476            title='Select expressions to remove',
3477            header='Delete expression')
3478        if selected is None: return
3479        for item in selected:
3480            del Controls['SeqPseudoVars'][choices[item]]
3481        if selected:
3482            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3483
3484    def EditPseudoVar(event):
3485        'Edit an existing pseudo var expression'
3486        choices = Controls['SeqPseudoVars'].keys()
3487        if len(choices) == 1:
3488            selected = 0
3489        else:
3490            selected = ItemSelector(
3491                choices,G2frame.dataFrame,
3492                multiple=False,
3493                title='Select an expression to edit',
3494                header='Edit expression')
3495        if selected is not None:
3496            dlg = G2exG.ExpressionDialog(
3497                G2frame.dataDisplay,PSvarDict,
3498                Controls['SeqPseudoVars'][choices[selected]],
3499                header="Edit the PseudoVar expression",
3500                VarLabel="PseudoVar #"+str(selected+1),
3501                fit=False)
3502            newobj = dlg.Show(True)
3503            if newobj:
3504                calcobj = G2obj.ExpressionCalcObj(newobj)
3505                del Controls['SeqPseudoVars'][choices[selected]]
3506                Controls['SeqPseudoVars'][calcobj.eObj.expression] = newobj
3507                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3508       
3509    def AddNewPseudoVar(event):
3510        'Create a new pseudo var expression'
3511        dlg = G2exG.ExpressionDialog(
3512            G2frame.dataDisplay,PSvarDict,
3513            header='Enter an expression for a PseudoVar here',
3514            VarLabel = "New PseudoVar",
3515            fit=False)
3516        obj = dlg.Show(True)
3517        dlg.Destroy()
3518        if obj:
3519            calcobj = G2obj.ExpressionCalcObj(obj)
3520            Controls['SeqPseudoVars'][calcobj.eObj.expression] = obj
3521            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3522
3523    def UpdateParmDict(parmDict):
3524        '''generate the atom positions and the direct & reciprocal cell values,
3525        because they might be needed to evaluate the pseudovar
3526        '''
3527        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3528                         ['A'+str(i) for i in range(6)])
3529                     )
3530        delList = []
3531        phaselist = []
3532        for item in parmDict: 
3533            if ':' not in item: continue
3534            key = item.split(':')
3535            if len(key) < 3: continue
3536            # remove the dA[xyz] terms, they would only bring confusion
3537            if key[2].startswith('dA'):
3538                delList.append(item)
3539            # compute and update the corrected reciprocal cell terms using the Dij values
3540            elif key[2] in Ddict:
3541                if key[0] not in phaselist: phaselist.append(key[0])
3542                akey = key[0]+'::'+Ddict[key[2]]
3543                parmDict[akey] -= parmDict[item]
3544                delList.append(item)
3545        for item in delList:
3546            del parmDict[item]               
3547        for i in phaselist:
3548            pId = int(i)
3549            # apply cell symmetry
3550            A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
3551            # convert to direct cell & add the unique terms to the dictionary
3552            for i,val in enumerate(G2lat.A2cell(A)):
3553                if i in uniqCellIndx[pId]:
3554                    lbl = str(pId)+'::'+cellUlbl[i]
3555                    parmDict[lbl] = val
3556            lbl = str(pId)+'::'+'vol'
3557            parmDict[lbl] = G2lat.calc_V(A)
3558        return parmDict
3559
3560    def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD):
3561        '''Evaluate an expression derivative with respect to a
3562        GSAS-II variable name.
3563
3564        Note this likely could be faster if the loop over calcobjs were done
3565        inside after the Dict was created.
3566        '''
3567        step = ESD/10
3568        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3569                         ['A'+str(i) for i in range(6)])
3570                     )
3571        results = []
3572        phaselist = []
3573        VparmDict = sampleDict.copy()
3574        for incr in step,-step:
3575            VparmDict.update(parmDict.copy())           
3576            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
3577            # values are not zeroed: fix that!
3578            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
3579            VparmDict[var] += incr
3580            G2mv.Dict2Map(VparmDict,[]) # apply constraints
3581            # generate the atom positions and the direct & reciprocal cell values now, because they might
3582            # needed to evaluate the pseudovar
3583            for item in VparmDict:
3584                if item in sampleDict:
3585                    continue 
3586                if ':' not in item: continue
3587                key = item.split(':')
3588                if len(key) < 3: continue
3589                # apply any new shifts to atom positions
3590                if key[2].startswith('dA'):
3591                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
3592                    VparmDict[item] = 0.0
3593                # compute and update the corrected reciprocal cell terms using the Dij values
3594                if key[2] in Ddict:
3595                    if key[0] not in phaselist: phaselist.append(key[0])
3596                    akey = key[0]+'::'+Ddict[key[2]]
3597                    VparmDict[akey] -= VparmDict[item]
3598            for i in phaselist:
3599                pId = int(i)
3600                # apply cell symmetry
3601                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
3602                # convert to direct cell & add the unique terms to the dictionary
3603                for i,val in enumerate(G2lat.A2cell(A)):
3604                    if i in uniqCellIndx[pId]:
3605                        lbl = str(pId)+'::'+cellUlbl[i]
3606                        VparmDict[lbl] = val
3607                lbl = str(pId)+'::'+'vol'
3608                VparmDict[lbl] = G2lat.calc_V(A)
3609            # dict should be fully updated, use it & calculate
3610            calcobj.SetupCalc(VparmDict)
3611            results.append(calcobj.EvalExpression())
3612        return (results[0] - results[1]) / (2.*step)
3613       
3614    def EnableParFitEqMenus():
3615        'Enables or disables the Parametric Fit menu items that require existing defs'
3616        if Controls['SeqParFitEqList']:
3617            val = True
3618        else:
3619            val = False
3620        G2frame.dataFrame.SequentialPfit.Enable(wxDELPARFIT,val)
3621        G2frame.dataFrame.SequentialPfit.Enable(wxEDITPARFIT,val)
3622        G2frame.dataFrame.SequentialPfit.Enable(wxDOPARFIT,val)
3623
3624    def ParEqEval(Values,calcObjList,varyList):
3625        '''Evaluate the parametric expression(s)
3626        :param list Values: a list of values for each variable parameter
3627        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
3628          expression objects to evaluate
3629        :param list varyList: a list of variable names for each value in Values
3630        '''
3631        result = []
3632        for calcobj in calcObjList:
3633            calcobj.UpdateVars(varyList,Values)
3634            result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
3635        return result
3636
3637    def DoParEqFit(event,eqObj=None):
3638        'Parametric fit minimizer'
3639        varyValueDict = {} # dict of variables and their initial values
3640        calcObjList = [] # expression objects, ready to go for each data point
3641        if eqObj is not None:
3642            eqObjList = [eqObj,]
3643        else:
3644            eqObjList = Controls['SeqParFitEqList']
3645        UseFlags = G2frame.SeqTable.GetColValues(0)         
3646        for obj in eqObjList:
3647            expr = obj.expression
3648            # assemble refined vars for this equation
3649            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
3650            # lookup dependent var position
3651            depVar = obj.GetDepVar()
3652            if depVar in colLabels:
3653                indx = colLabels.index(depVar)
3654            else:
3655                raise Exception('Dependent variable '+depVar+' not found')
3656            # assemble a list of the independent variables
3657            indepVars = obj.GetIndependentVars()
3658            # loop over each datapoint
3659            for j,row in enumerate(zip(*colList)):
3660                if not UseFlags[j]: continue
3661                # assemble equations to fit
3662                calcobj = G2obj.ExpressionCalcObj(obj)
3663                # prepare a dict of needed independent vars for this expression
3664                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
3665                calcobj.SetupCalc(indepVarDict)               
3666                # values and sigs for current value of dependent var
3667                calcobj.depVal = row[indx]
3668                calcobj.depSig = colSigs[indx][j]
3669                calcObjList.append(calcobj)
3670        # varied parameters
3671        varyList = varyValueDict.keys()
3672        values = varyValues = [varyValueDict[key] for key in varyList]
3673        if not varyList:
3674            print 'no variables to refine!'
3675            return
3676        try:
3677            result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
3678                                args=(calcObjList,varyList)
3679                                )
3680            values = result[0]
3681            covar = result[1]
3682            if covar is None:
3683                raise Exception
3684            esdDict = {}
3685            for i,avar in enumerate(varyList):
3686                esdDict[avar] = np.sqrt(covar[i,i])
3687        except:
3688            print('====> Fit failed')
3689            return
3690        print('==== Fit Results ====')
3691        for obj in eqObjList:
3692            obj.UpdateVariedVars(varyList,values)
3693            ind = '      '
3694            print('  '+obj.GetDepVar()+' = '+obj.expression)
3695            for var in obj.assgnVars:
3696                print(ind+var+' = '+obj.assgnVars[var])
3697            for var in obj.freeVars:
3698                avar = "::"+obj.freeVars[var][0]
3699                val = obj.freeVars[var][1]
3700                if obj.freeVars[var][2]:
3701                    print(ind+var+' = '+avar + " = " + G2mth.ValEsd(val,esdDict[avar]))
3702                else:
3703                    print(ind+var+' = '+avar + " =" + G2mth.ValEsd(val,0))
3704        # create a plot for each parametric variable
3705        for fitnum,obj in enumerate(eqObjList):
3706            calcobj = G2obj.ExpressionCalcObj(obj)
3707            # lookup dependent var position
3708            indx = colLabels.index(obj.GetDepVar())
3709            # assemble a list of the independent variables
3710            indepVars = obj.GetIndependentVars()           
3711            # loop over each datapoint
3712            fitvals = []
3713            for j,row in enumerate(zip(*colList)):
3714                calcobj.SetupCalc(
3715                    {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
3716                    )
3717                fitvals.append(calcobj.EvalExpression())
3718            G2plt.PlotSelectedSequence(
3719                G2frame,[indx],GetColumnInfo,SelectXaxis,
3720                fitnum,fitvals)
3721
3722    def SingleParEqFit(eqObj):
3723        DoParEqFit(None,eqObj)
3724
3725    def DelParFitEq(event):
3726        'Ask the user to select function to delete'
3727        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3728        selected = ItemSelector(
3729            txtlst,G2frame.dataFrame,
3730            multiple=True,
3731            title='Select a parametric equation(s) to remove',
3732            header='Delete equation')
3733        if selected is None: return
3734        Controls['SeqParFitEqList'] = [obj for i,obj in enumerate(Controls['SeqParFitEqList']) if i not in selected]
3735        EnableParFitEqMenus()
3736        if Controls['SeqParFitEqList']: DoParEqFit(event)
3737       
3738    def EditParFitEq(event):
3739        'Edit an existing parametric equation'
3740        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3741        if len(txtlst) == 1:
3742            selected = 0
3743        else:
3744            selected = ItemSelector(
3745                txtlst,G2frame.dataFrame,
3746                multiple=False,
3747                title='Select a parametric equation to edit',
3748                header='Edit equation')
3749        if selected is not None:
3750            dlg = G2exG.ExpressionDialog(
3751                G2frame.dataDisplay,indepVarDict,
3752                Controls['SeqParFitEqList'][selected],
3753                depVarDict=depVarDict,
3754                header="Edit the formula for this minimization function",
3755                ExtraButton=['Fit',SingleParEqFit])
3756            newobj = dlg.Show(True)
3757            if newobj:
3758                calcobj = G2obj.ExpressionCalcObj(newobj)
3759                Controls['SeqParFitEqList'][selected] = newobj
3760                EnableParFitEqMenus()
3761            if Controls['SeqParFitEqList']: DoParEqFit(event)
3762
3763    def AddNewParFitEq(event):
3764        'Create a new parametric equation to be fit to sequential results'
3765
3766        # compile the variable names used in previous freevars to avoid accidental name collisions
3767        usedvarlist = []
3768        for obj in Controls['SeqParFitEqList']:
3769            for var in obj.freeVars:
3770                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
3771
3772        dlg = G2exG.ExpressionDialog(
3773            G2frame.dataDisplay,indepVarDict,
3774            depVarDict=depVarDict,
3775            header='Define an equation to minimize in the parametric fit',
3776            ExtraButton=['Fit',SingleParEqFit],
3777            usedVars=usedvarlist)
3778        obj = dlg.Show(True)
3779        dlg.Destroy()
3780        if obj:
3781            Controls['SeqParFitEqList'].append(obj)
3782            EnableParFitEqMenus()
3783            if Controls['SeqParFitEqList']: DoParEqFit(event)
3784               
3785    def CopyParFitEq(event):
3786        'Copy an existing parametric equation to be fit to sequential results'
3787        # compile the variable names used in previous freevars to avoid accidental name collisions
3788        usedvarlist = []
3789        for obj in Controls['SeqParFitEqList']:
3790            for var in obj.freeVars:
3791                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
3792        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3793        if len(txtlst) == 1:
3794            selected = 0
3795        else:
3796            selected = ItemSelector(
3797                txtlst,G2frame.dataFrame,
3798                multiple=False,
3799                title='Select a parametric equation to copy',
3800                header='Copy equation')
3801        if selected is not None:
3802            newEqn = copy.deepcopy(Controls['SeqParFitEqList'][selected])
3803            for var in newEqn.freeVars:
3804                newEqn.freeVars[var][0] = G2obj.MakeUniqueLabel(newEqn.freeVars[var][0],usedvarlist)
3805            dlg = G2exG.ExpressionDialog(
3806                G2frame.dataDisplay,indepVarDict,
3807                newEqn,
3808                depVarDict=depVarDict,
3809                header="Edit the formula for this minimization function",
3810                ExtraButton=['Fit',SingleParEqFit])
3811            newobj = dlg.Show(True)
3812            if newobj:
3813                calcobj = G2obj.ExpressionCalcObj(newobj)
3814                Controls['SeqParFitEqList'].append(newobj)
3815                EnableParFitEqMenus()
3816            if Controls['SeqParFitEqList']: DoParEqFit(event)
3817                                           
3818    def GridSetToolTip(row,col):
3819        '''Routine to show standard uncertainties for each element in table
3820        as a tooltip
3821        '''
3822        if colSigs[col]:
3823            return u'\u03c3 = '+str(colSigs[col][row])
3824        return ''
3825       
3826    def GridColLblToolTip(col):
3827        '''Define a tooltip for a column. This will be the user-entered value
3828        (from data['variableLabels']) or the default name
3829        '''
3830        if col < 0 or col > len(colLabels):
3831            print 'Illegal column #',col
3832            return
3833        var = colLabels[col]
3834        return variableLabels.get(var,G2obj.fmtVarDescr(var))
3835       
3836    def SetLabelString(event):
3837        '''Define or edit the label for a column in the table, to be used
3838        as a tooltip and for plotting
3839        '''
3840        col = event.GetCol()
3841        if col < 0 or col > len(colLabels):
3842            return
3843        var = colLabels[col]
3844        lbl = variableLabels.get(var,G2obj.fmtVarDescr(var))
3845        dlg = SingleStringDialog(G2frame.dataFrame,'Set variable label',
3846                                 'Set a new name for variable '+var,lbl,size=(400,-1))
3847        if dlg.Show():
3848            variableLabels[var] = dlg.GetValue()
3849        dlg.Destroy()
3850       
3851    #def GridRowLblToolTip(row): return 'Row ='+str(row)
3852   
3853    # lookup table for unique cell parameters by symmetry
3854    cellGUIlist = [
3855        [['m3','m3m'],(0,)],
3856        [['3R','3mR'],(0,3)],
3857        [['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],(0,2)],
3858        [['mmm'],(0,1,2)],
3859        [['2/m'+'a'],(0,1,2,3)],
3860        [['2/m'+'b'],(0,1,2,4)],
3861        [['2/m'+'c'],(0,1,2,5)],
3862        [['-1'],(0,1,2,3,4,5)],
3863        ]
3864    # cell labels
3865    cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') # unicode a,b,c,alpha,beta,gamma
3866
3867    #======================================================================
3868    # start processing sequential results here (UpdateSeqResults)
3869    #======================================================================
3870    if not data:
3871        print 'No sequential refinement results'
3872        return
3873    variableLabels = data.get('variableLabels',{})
3874    data['variableLabels'] = variableLabels
3875    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
3876    Controls = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Controls'))
3877    # create a place to store Pseudo Vars & Parametric Fit functions, if not present
3878    if 'SeqPseudoVars' not in Controls: Controls['SeqPseudoVars'] = {}
3879    if 'SeqParFitEqList' not in Controls: Controls['SeqParFitEqList'] = []
3880    histNames = data['histNames']
3881    if G2frame.dataDisplay:
3882        G2frame.dataDisplay.Destroy()
3883    if not G2frame.dataFrame.GetStatusBar():
3884        Status = G2frame.dataFrame.CreateStatusBar()
3885        Status.SetStatusText("Select column to export; Double click on column to plot data; on row for Covariance")
3886    sampleParms = GetSampleParms()
3887
3888    # make dict of varied atom coords keyed by absolute position
3889    newAtomDict = data[histNames[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
3890    # Possible error: the next might need to be data[histNames[0]]['varyList']
3891    # error will arise if there constraints on coordinates?
3892    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in data['varyList']}
3893   
3894    # make dict of varied cell parameters equivalents
3895    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
3896    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
3897    # N.B. These Dij vars are missing a histogram #
3898    newCellDict = data[histNames[0]].get('newCellDict',{})
3899    for item in newCellDict:
3900        if item in data['varyList']:
3901            ESDlookup[newCellDict[item][0]] = item
3902            Dlookup[item] = newCellDict[item][0]
3903    # add coordinate equivalents to lookup table
3904    for parm in atomLookup:
3905        Dlookup[atomLookup[parm]] = parm
3906        ESDlookup[parm] = atomLookup[parm]
3907
3908    # get unit cell & symmetry for all phases & initial stuff for later use
3909    RecpCellTerms = {}
3910    SGdata = {}
3911    uniqCellIndx = {}
3912    initialCell = {}
3913    RcellLbls = {}
3914    zeroDict = {}
3915    Rcelldict = {}
3916    for phase in Phases:
3917        phasedict = Phases[phase]
3918        pId = phasedict['pId']
3919        pfx = str(pId)+'::' # prefix for A values from phase
3920        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
3921        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
3922        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
3923        SGdata[pId] = phasedict['General']['SGData']
3924        Rcelldict.update({lbl:val for lbl,val in zip(RcellLbls[pId],RecpCellTerms[pId])})
3925        laue = SGdata[pId]['SGLaue']
3926        if laue == '2/m':
3927            laue += SGdata[pId]['SGUniq']
3928        for symlist,celllist in cellGUIlist:
3929            if laue in symlist:
3930                uniqCellIndx[pId] = celllist
3931                break
3932        else: # should not happen
3933            uniqCellIndx[pId] = range(6)
3934        for i in uniqCellIndx[pId]:
3935            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
3936
3937    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
3938    G2frame.dataFrame.SetLabel('Sequential refinement results')
3939    if not G2frame.dataFrame.GetStatusBar():
3940        Status = G2frame.dataFrame.CreateStatusBar()
3941        Status.SetStatusText('')
3942    G2frame.dataFrame.Bind(wx.EVT_MENU, OnRenameSelSeq, id=wxID_RENAMESEQSEL)
3943    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
3944    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=wxID_SAVESEQSELCSV)
3945    G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=wxID_PLOTSEQSEL)
3946    G2frame.dataFrame.Bind(wx.EVT_MENU, OnReOrgSelSeq, id=wxID_ORGSEQSEL)
3947    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=wxADDSEQVAR)
3948    G2frame.dataFrame.Bind(wx.EVT_MENU, DelPseudoVar, id=wxDELSEQVAR)
3949    G2frame.dataFrame.Bind(wx.EVT_MENU, EditPseudoVar, id=wxEDITSEQVAR)
3950    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewParFitEq, id=wxADDPARFIT)
3951    G2frame.dataFrame.Bind(wx.EVT_MENU, CopyParFitEq, id=wxCOPYPARFIT)
3952    G2frame.dataFrame.Bind(wx.EVT_MENU, DelParFitEq, id=wxDELPARFIT)
3953    G2frame.dataFrame.Bind(wx.EVT_MENU, EditParFitEq, id=wxEDITPARFIT)
3954    G2frame.dataFrame.Bind(wx.EVT_MENU, DoParEqFit, id=wxDOPARFIT)
3955    EnablePseudoVarMenus()
3956    EnableParFitEqMenus()
3957
3958    # scan for locations where the variables change
3959    VaryListChanges = [] # histograms where there is a change
3960    prevVaryList = []
3961    combinedVaryList = []
3962    firstValueList = []
3963    vallookup = {}
3964    posdict = {}
3965    for i,name in enumerate(histNames):
3966        if i == 0 or prevVaryList != sorted(data[name]['varyList']):
3967            # add variables to list as they appear
3968            for j,var in enumerate(data[name]['varyList']):
3969                if var in combinedVaryList: continue
3970                combinedVaryList.append(data[name]['varyList'][j])
3971                firstValueList.append(data[name]['variables'][j])
3972            vallookup[name] = dict(zip(data[name]['varyList'],data[name]['variables']))
3973            posdict[name] = {}
3974            for lbl in data[name]['varyList']:
3975                posdict[name][combinedVaryList.index(lbl)] = lbl           
3976            prevVaryList = sorted(data[name]['varyList'])
3977            VaryListChanges.append(name)
3978    if len(VaryListChanges) > 1:
3979        G2frame.dataFrame.SequentialFile.Enable(wxID_ORGSEQSEL,True)
3980    else:
3981        G2frame.dataFrame.SequentialFile.Enable(wxID_ORGSEQSEL,False)
3982    #-----------------------------------------------------------------------------------
3983    # build up the data table by columns -----------------------------------------------
3984    nRows = len(histNames)
3985    colList = [nRows*[True]]
3986    colSigs = [None]
3987    colLabels = ['Use']
3988    Types = [wg.GRID_VALUE_BOOL]
3989    # start with Rwp values
3990    if 'IMG ' not in histNames[0][:4]:
3991        colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
3992        colSigs += [None]
3993        colLabels += ['Rwp']
3994        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
3995    # add % change in Chi^2 in last cycle
3996    if histNames[0][:4] not in ['SASD','IMG '] and Controls['ShowCell']:
3997        colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
3998        colSigs += [None]
3999        colLabels += [u'\u0394\u03C7\u00B2 (%)']
4000        Types += [wg.GRID_VALUE_FLOAT,]
4001    deltaChiCol = len(colLabels)-1
4002    # add changing sample parameters to table
4003    for key in sampleParms:
4004        colList += [sampleParms[key]]
4005        colSigs += [None]
4006        colLabels += [key]
4007        Types += [wg.GRID_VALUE_FLOAT,]
4008    sampleDict = {}
4009    for i,name in enumerate(histNames):
4010        sampleDict[name] = dict(zip(sampleParms.keys(),[sampleParms[key][i] for key in sampleParms.keys()])) 
4011    # add unique cell parameters TODO: review this where the cell symmetry changes (when possible)
4012    if Controls.get('ShowCell',False):
4013        for pId in sorted(RecpCellTerms):
4014            pfx = str(pId)+'::' # prefix for A values from phase
4015            cells = []
4016            cellESDs = []
4017            colLabels += [pfx+cellUlbl[i] for i in uniqCellIndx[pId]]
4018            colLabels += [pfx+'Vol']
4019            Types += (1+len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT,]
4020            for name in histNames:
4021                covData = {
4022                    'varyList': [Dlookup.get(striphist(v),v) for v in data[name]['varyList']],
4023                    'covMatrix': data[name]['covMatrix']
4024                    }
4025                A = RecpCellTerms[pId][:] # make copy of starting A values
4026                # update with refined values
4027                for i in range(6):
4028                    var = str(pId)+'::A'+str(i)
4029                    if var in ESDlookup:
4030                        val = data[name]['newCellDict'][ESDlookup[var]][1] # get refined value
4031                        A[i] = val # override with updated value
4032                # apply symmetry
4033                Albls = [pfx+'A'+str(i) for i in range(6)]
4034                cellDict = dict(zip(Albls,A))
4035                A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
4036                # convert to direct cell & add only unique values to table
4037                c = G2lat.A2cell(A)
4038                vol = G2lat.calc_V(A)
4039                cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
4040                cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
4041                cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
4042            colList += zip(*cells)
4043            colSigs += zip(*cellESDs)
4044    # sort out the variables in their selected order
4045    varcols = 0
4046    for d in posdict.itervalues():
4047        varcols = max(varcols,max(d.keys())+1)
4048    # get labels for each column
4049    for i in range(varcols):
4050        lbl = ''
4051        for h in VaryListChanges:
4052            if posdict[h].get(i):
4053                if posdict[h].get(i) in lbl: continue
4054                if lbl != "": lbl += '/'
4055                lbl += posdict[h].get(i)
4056        colLabels.append(lbl)
4057    Types += varcols*[wg.GRID_VALUE_FLOAT]
4058    vals = []
4059    esds = []
4060    varsellist = None        # will be a list of variable names in the order they are selected to appear
4061    # tabulate values for each hist, leaving None for blank columns
4062    for name in histNames:
4063        if name in posdict:
4064            varsellist = [posdict[name].get(i) for i in range(varcols)]
4065            sellist = [data[name]['varyList'].index(v) if v is not None else None for v in varsellist]
4066        if not varsellist: raise Exception()
4067        vals.append([data[name]['variables'][s] if s is not None else None for s in sellist])
4068        esds.append([data[name]['sig'][s] if s is not None else None for s in sellist])
4069    colList += zip(*vals)
4070    colSigs += zip(*esds)
4071    # add the variables that were refined; change from rows to columns
4072    #colList += zip(*[data[name]['variables'] for name in histNames])
4073    #colLabels += data[histNames[0]]['varyList']
4074    #Types += len(data[histNames[0]]['varyList'])*[wg.GRID_VALUE_FLOAT]
4075    #colSigs += zip(*[data[name]['sig'] for name in histNames])
4076
4077    # for var in combinedVaryList:
4078    #     colLabels += [var]
4079    #     Types += [wg.GRID_VALUE_FLOAT]
4080    #     vals = []
4081    #     sigs = []
4082    #     for name in histNames:
4083    #         try:
4084    #             i = data[name]['varyList'].index(var)
4085    #             vals.append(data[name]['variables'][i])
4086    #             sigs.append(data[name]['sig'][i])
4087    #         except ValueError: # var not in list
4088    #             vals.append(None)
4089    #             sigs.append(None)
4090    #     colList += [vals]
4091    #     colSigs += [sigs]
4092               
4093    # tabulate constrained variables, removing histogram numbers if needed
4094    # from parameter label
4095    depValDict = {}
4096    depSigDict = {}
4097    for name in histNames:
4098        for var in data[name].get('depParmDict',{}):
4099            val,sig = data[name]['depParmDict'][var]
4100            svar = striphist(var,'*')
4101            if svar not in depValDict:
4102               depValDict[svar] = [val]
4103               depSigDict[svar] = [sig]
4104            else:
4105               depValDict[svar].append(val)
4106               depSigDict[svar].append(sig)
4107    # add the dependent constrained variables to the table
4108    for var in sorted(depValDict):
4109        if len(depValDict[var]) != len(histNames): continue
4110        colLabels.append(var)
4111        Types += [wg.GRID_VALUE_FLOAT,]
4112        colSigs += [depSigDict[var]]
4113        colList += [depValDict[var]]
4114
4115    # add atom parameters to table
4116    colLabels += atomLookup.keys()
4117    Types += len(atomLookup)*[wg.GRID_VALUE_FLOAT]
4118    for parm in sorted(atomLookup):
4119        colList += [[data[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
4120        if atomLookup[parm] in data[histNames[0]]['varyList']:
4121            col = data[histNames[0]]['varyList'].index(atomLookup[parm])
4122            colSigs += [[data[name]['sig'][col] for name in histNames]]
4123        else:
4124            colSigs += [None] # should not happen
4125    # evaluate Pseudovars, their ESDs and add them to grid
4126    for expr in Controls['SeqPseudoVars']:
4127        obj = Controls['SeqPseudoVars'][expr]
4128        calcobj = G2obj.ExpressionCalcObj(obj)
4129        valList = []
4130        esdList = []
4131        for seqnum,name in enumerate(histNames):
4132            sigs = data[name]['sig']
4133            G2mv.InitVars()
4134            parmDict = data[name].get('parmDict')
4135            badVary = data[name].get('badVary',[])
4136            constraintInfo = data[name].get('constraintInfo',[[],[],{},[],seqnum])
4137            groups,parmlist,constrDict,fixedList,ihst = constraintInfo
4138            varyList = data[name]['varyList']
4139            parmDict = data[name]['parmDict']
4140            G2mv.GenerateConstraints(groups,parmlist,varyList,constrDict,fixedList,parmDict,SeqHist=ihst)
4141            derivs = np.array(
4142                [EvalPSvarDeriv(calcobj,parmDict.copy(),sampleDict[name],var,ESD)
4143                 for var,ESD in zip(varyList,sigs)]
4144                )
4145            esdList.append(np.sqrt(
4146                np.inner(derivs,np.inner(data[name]['covMatrix'],derivs.T))
4147                ))
4148            PSvarDict = parmDict.copy()
4149            PSvarDict.update(sampleDict[name])
4150            UpdateParmDict(PSvarDict)
4151            calcobj.UpdateDict(PSvarDict)
4152            valList.append(calcobj.EvalExpression())
4153        if not esdList:
4154            esdList = None
4155        colList += [valList]
4156        colSigs += [esdList]
4157        colLabels += [expr]
4158        Types += [wg.GRID_VALUE_FLOAT,]
4159    #---- table build done -------------------------------------------------------------
4160
4161    # Make dict needed for creating & editing pseudovars (PSvarDict).
4162    name = histNames[0]
4163    parmDict = data[name].get('parmDict')
4164    PSvarDict = parmDict.copy()
4165    PSvarDict.update(sampleParms)
4166    UpdateParmDict(PSvarDict)
4167    # Also dicts of dependent (depVarDict) & independent vars (indepVarDict)
4168    # for Parametric fitting from the data table
4169    parmDict = dict(zip(colLabels,zip(*colList)[0])) # scratch dict w/all values in table
4170    parmDict.update(
4171        {var:val for var,val in data[name].get('newCellDict',{}).values()} #  add varied reciprocal cell terms
4172    )
4173    name = histNames[0]
4174   
4175    indepVarDict = {     #  values in table w/o ESDs
4176        var:colList[i][0] for i,var in enumerate(colLabels) if colSigs[i] is None
4177        }
4178    # make dict of dependent vars (w/ESDs) that are not converted (Dij to Ak or dAx to Ax)
4179    depVarDict = {
4180        var:colList[i][0] for i,var in enumerate(colLabels)
4181        if colSigs[i] is not None and striphist(var) not in Dlookup
4182        }
4183    # add recip cell coeff. values
4184    depVarDict.update({var:val for var,val in data[name].get('newCellDict',{}).values()})
4185   
4186    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
4187    G2frame.SeqTable = Table(
4188        [list(c) for c in zip(*colList)],     # convert from columns to rows
4189        colLabels=colLabels,rowLabels=histNames,types=Types)
4190    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
4191    #G2frame.dataDisplay.EnableEditing(False)
4192    # make all but first column read-only
4193    for c in range(1,len(colLabels)):
4194        for r in range(nRows):
4195            G2frame.dataDisplay.SetCellReadOnly(r,c)
4196    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, PlotSelect)
4197    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_RIGHT_CLICK, SetLabelString)
4198    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
4199    G2frame.dataDisplay.SetMargins(0,0)
4200    G2frame.dataDisplay.AutoSizeColumns(True)
4201    if prevSize:
4202        G2frame.dataDisplay.SetSize(prevSize)
4203    else:
4204        G2frame.dataFrame.setSizePosLeft([700,350])
4205    # highlight unconverged shifts
4206    if histNames[0][:4] not in ['SASD','IMG ']:
4207        for row,name in enumerate(histNames):
4208            deltaChi = G2frame.SeqTable.GetValue(row,deltaChiCol)
4209            if deltaChi > 10.:
4210                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,0,0))
4211            elif deltaChi > 1.0:
4212                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,255,0))
4213    G2frame.dataDisplay.InstallGridToolTip(GridSetToolTip,GridColLblToolTip)
4214    G2frame.dataDisplay.SendSizeEvent() # resize needed on mac
4215    G2frame.dataDisplay.Refresh() # shows colored text on mac
4216   
4217################################################################################
4218#####  Main PWDR panel
4219################################################################################           
4220       
4221def UpdatePWHKPlot(G2frame,kind,item):
4222    '''Called when the histogram main tree entry is called. Displays the
4223    histogram weight factor, refinement statistics for the histogram
4224    and the range of data for a simulation.
4225
4226    Also invokes a plot of the histogram.
4227    '''
4228    def onEditSimRange(event):
4229        'Edit simulation range'
4230        inp = [
4231            min(data[1][0]),
4232            max(data[1][0]),
4233            None
4234            ]
4235        inp[2] = (inp[1] - inp[0])/(len(data[1][0])-1.)
4236        names = ('start angle', 'end angle', 'step size')
4237        dictlst = [inp] * len(inp)
4238        elemlst = range(len(inp))
4239        dlg = G2G.ScrolledMultiEditor(
4240            G2frame,[inp] * len(inp), range(len(inp)), names,
4241            header='Edit simulation range',
4242            minvals=(0.001,0.001,0.0001),
4243            maxvals=(180.,180.,.1),
4244            )
4245        dlg.CenterOnParent()
4246        val = dlg.ShowModal()
4247        dlg.Destroy()
4248        if val != wx.ID_OK: return
4249        if inp[0] > inp[1]:
4250            end,start,step = inp
4251        else:               
4252            start,end,step = inp
4253        step = abs(step)
4254        N = int((end-start)/step)+1
4255        newdata = np.linspace(start,end,N,True)
4256        if len(newdata) < 2: return # too small a range - reject
4257        data[1] = [newdata,np.zeros_like(newdata),np.ones_like(newdata),
4258            np.zeros_like(newdata),np.zeros_like(newdata),np.zeros_like(newdata)]
4259        Tmin = newdata[0]
4260        Tmax = newdata[-1]
4261        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,item,'Limits'),
4262            [(Tmin,Tmax),[Tmin,Tmax]])
4263        UpdatePWHKPlot(G2frame,kind,item) # redisplay data screen
4264
4265    def OnPlot3DHKL(event):
4266        refList = data[1]['RefList']
4267        FoMax = np.max(refList.T[8+Super])
4268        Hmin = np.array([int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))])
4269        Hmax = np.array([int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))])
4270        Vpoint = [int(np.mean(refList.T[0])),int(np.mean(refList.T[1])),int(np.mean(refList.T[2]))]
4271        controls = {'Type' : 'Fosq','Iscale' : False,'HKLmax' : Hmax,'HKLmin' : Hmin,
4272            'FoMax' : FoMax,'Scale' : 1.0,'Drawing':{'viewPoint':[Vpoint,[]],'default':Vpoint[:],
4273            'backColor':[0,0,0],'depthFog':False,'Zclip':10.0,'cameraPos':10.,'Zstep':0.05,
4274            'Scale':1.0,'oldxy':[],'viewDir':[1,0,0]},'Super':Super,'SuperVec':SuperVec}
4275        G2plt.Plot3DSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName)
4276       
4277    def OnErrorAnalysis(event):
4278        G2plt.PlotDeltSig(G2frame,kind)
4279       
4280    def OnWtFactor(event):
4281        try:
4282            val = float(wtval.GetValue())
4283        except ValueError:
4284            val = data[0]['wtFactor']
4285        data[0]['wtFactor'] = val
4286        wtval.SetValue('%.3f'%(val))
4287
4288    def onCopySelectedItems(event):
4289        '''Respond to menu item to copy multiple sections from a histogram.
4290        Need this here to pass on the G2frame object.
4291        '''
4292        G2pdG.CopySelectedHistItems(G2frame)
4293           
4294    data = G2frame.PatternTree.GetItemPyData(item)
4295#patches
4296    if 'wtFactor' not in data[0]:
4297        data[0] = {'wtFactor':1.0}
4298    #if isinstance(data[1],list) and kind == 'HKLF':
4299    if 'list' in str(type(data[1])) and kind == 'HKLF':
4300        RefData = {'RefList':[],'FF':[]}
4301        for ref in data[1]:
4302            RefData['RefList'].append(ref[:11]+[ref[13],])
4303            RefData['FF'].append(ref[14])
4304        data[1] = RefData
4305        G2frame.PatternTree.SetItemPyData(item,data)
4306#end patches
4307    if G2frame.dataDisplay:
4308        G2frame.dataDisplay.Destroy()
4309    if kind in ['PWDR','SASD']:
4310        SetDataMenuBar(G2frame,G2frame.dataFrame.PWDRMenu)
4311        G2frame.dataFrame.Bind(wx.EVT_MENU, OnErrorAnalysis, id=wxID_PWDANALYSIS)
4312        G2frame.dataFrame.Bind(wx.EVT_MENU, onCopySelectedItems, id=wxID_PWDCOPY)
4313    elif kind in ['HKLF',]:
4314        SetDataMenuBar(G2frame,G2frame.dataFrame.HKLFMenu)
4315#        G2frame.dataFrame.Bind(wx.EVT_MENU, OnErrorAnalysis, id=wxID_PWDANALYSIS)
4316        G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlot3DHKL, id=wxID_PWD3DHKLPLOT)
4317#        G2frame.dataFrame.Bind(wx.EVT_MENU, onCopySelectedItems, id=wxID_PWDCOPY)
4318    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
4319   
4320    mainSizer = wx.BoxSizer(wx.VERTICAL)
4321    mainSizer.Add((5,5),)
4322    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
4323    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,WACV)
4324    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
4325    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
4326    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
4327    wtSizer.Add(wtval,0,WACV)
4328    mainSizer.Add(wtSizer)
4329    if data[0].get('Dummy'):
4330        simSizer = wx.BoxSizer(wx.HORIZONTAL)
4331        Tmin = min(data[1][0])
4332        Tmax = max(data[1][0])
4333        num = len(data[1][0])
4334        step = (Tmax - Tmin)/(num-1)
4335        t = u'2\u03b8' # 2theta
4336        lbl =  u'Simulation range: {:.2f} to {:.2f} {:s}\nwith {:.4f} steps ({:d} points)'
4337        lbl += u'\n(Edit range resets observed intensities).'
4338        lbl = lbl.format(Tmin,Tmax,t,step,num)
4339        simSizer.Add(wx.StaticText(G2frame.dataDisplay,wx.ID_ANY,lbl),
4340                    0,WACV)
4341        but = wx.Button(G2frame.dataDisplay,wx.ID_ANY,"Edit range")
4342        but.Bind(wx.EVT_BUTTON,onEditSimRange)
4343        simSizer.Add(but,0,WACV)
4344        mainSizer.Add(simSizer)
4345    if 'Nobs' in data[0]:
4346        mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4347            ' Data residual wR: %.3f%% on %d observations'%(data[0]['wR'],data[0]['Nobs'])))
4348        for value in data[0]:
4349            if 'Nref' in value:
4350                mainSizer.Add((5,5),)
4351                pfx = value.split('Nref')[0]
4352                name = data[0].get(pfx.split(':')[0]+'::Name','?')
4353                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' For phase '+name+':'))
4354                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4355                    u' Unweighted phase residuals RF\u00b2: %.3f%%, RF: %.3f%% on %d reflections  '% \
4356                    (data[0][pfx+'Rf^2'],data[0][pfx+'Rf'],data[0][value])))
4357    mainSizer.Add((5,5),)
4358    mainSizer.Layout()   
4359    G2frame.dataDisplay.SetSizer(mainSizer)
4360    Size = mainSizer.Fit(G2frame.dataFrame)
4361    Size[1] += 10
4362    G2frame.dataFrame.setSizePosLeft(Size)
4363    G2frame.PatternTree.SetItemPyData(item,data)
4364    if kind in ['PWDR','SASD']:
4365        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
4366    elif kind == 'HKLF':
4367        Name = G2frame.PatternTree.GetItemText(item)
4368        phaseName = G2pdG.IsHistogramInAnyPhase(G2frame,Name)
4369        if phaseName:
4370            pId = GetPatternTreeItemId(G2frame,G2frame.root,'Phases')
4371            phaseId =  GetPatternTreeItemId(G2frame,pId,phaseName)
4372            General = G2frame.PatternTree.GetItemPyData(phaseId)['General']
4373            Super = General.get('Super',0)
4374            SuperVec = General.get('SuperVec',[])
4375        else:
4376            Super = 0
4377            SuperVec = []       
4378        refList = data[1]['RefList']
4379        FoMax = np.max(refList.T[5+data[1].get('Super',0)])
4380        controls = {'Type' : 'Fo','ifFc' : True,     
4381            'HKLmax' : [int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))],
4382            'HKLmin' : [int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))],
4383            'FoMax' : FoMax,'Zone' : '001','Layer' : 0,'Scale' : 1.0,'Super':Super,'SuperVec':SuperVec}
4384        G2plt.PlotSngl(G2frame,newPlot=True,Data=controls,hklRef=refList)
4385                 
4386################################################################################
4387#####  Pattern tree routines
4388################################################################################           
4389       
4390def GetPatternTreeDataNames(G2frame,dataTypes):
4391    '''Needs a doc string
4392    '''
4393    names = []
4394    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
4395    while item:
4396        name = G2frame.PatternTree.GetItemText(item)
4397        if name[:4] in dataTypes:
4398            names.append(name)
4399        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
4400    return names
4401                         
4402def GetPatternTreeItemId(G2frame, parentId, itemText):
4403    '''Needs a doc string
4404    '''
4405    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
4406    while item:
4407        if G2frame.PatternTree.GetItemText(item) == itemText:
4408            return item
4409        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
4410    return 0               
4411
4412def MovePatternTreeToGrid(G2frame,item):
4413    '''Called from GSASII.OnPatternTreeSelChanged when a item is selected on the tree
4414    '''
4415   
4416#    print G2frame.PatternTree.GetItemText(item)
4417   
4418    oldPage = None # will be set later if already on a Phase item
4419    if G2frame.dataFrame:
4420        SetDataMenuBar(G2frame)
4421        if G2frame.dataFrame.GetLabel() == 'Comments':
4422            try:
4423                data = [G2frame.dataDisplay.GetValue()]
4424                G2frame.dataDisplay.Clear() 
4425                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
4426                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4427            except:     #clumsy but avoids dead window problem when opening another project
4428                pass
4429        elif G2frame.dataFrame.GetLabel() == 'Notebook':
4430            try:
4431                data = [G2frame.dataDisplay.GetValue()]
4432                G2frame.dataDisplay.Clear() 
4433                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
4434                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4435            except:     #clumsy but avoids dead window problem when opening another project
4436                pass
4437        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
4438            if G2frame.dataDisplay: 
4439                oldPage = G2frame.dataDisplay.GetSelection()
4440        G2frame.dataFrame.Clear()
4441        G2frame.dataFrame.SetLabel('')
4442    else:
4443        #create the frame for the data item window
4444        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
4445        G2frame.dataFrame.PhaseUserSize = None
4446       
4447    G2frame.dataFrame.Raise()           
4448    G2frame.PickId = 0
4449    parentID = G2frame.root
4450    #for i in G2frame.ExportPattern: i.Enable(False)
4451    defWid = [250,150]
4452    if item != G2frame.root:
4453        parentID = G2frame.PatternTree.GetItemParent(item)
4454    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
4455        G2frame.PatternId = item
4456        G2frame.PickId = item
4457        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
4458            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
4459            G2frame.PatternId = 0
4460            #for i in G2frame.ExportPattern: i.Enable(False)
4461            data = G2frame.PatternTree.GetItemPyData(item)
4462            UpdateNotebook(G2frame,data)
4463        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
4464            G2frame.PatternId = 0
4465            #for i in G2frame.ExportPattern: i.Enable(False)
4466            data = G2frame.PatternTree.GetItemPyData(item)
4467            if not data:           #fill in defaults
4468                data = copy.copy(G2obj.DefaultControls)    #least squares controls
4469                G2frame.PatternTree.SetItemPyData(item,data)                             
4470            for i in G2frame.Refine: i.Enable(True)
4471            G2frame.EnableSeqRefineMenu()
4472            UpdateControls(G2frame,data)
4473        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
4474            data = G2frame.PatternTree.GetItemPyData(item)
4475            UpdateSeqResults(G2frame,data)
4476        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
4477            data = G2frame.PatternTree.GetItemPyData(item)
4478            G2frame.dataFrame.setSizePosLeft(defWid)
4479            text = ''
4480            if 'Rvals' in data:
4481                Nvars = len(data['varyList'])
4482                Rvals = data['Rvals']
4483                text = '\nFinal residuals: \nwR = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
4484                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
4485                if 'lamMax' in Rvals:
4486                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
4487            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4488                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
4489            G2plt.PlotCovariance(G2frame,data)
4490        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
4491            data = G2frame.PatternTree.GetItemPyData(item)
4492            G2cnstG.UpdateConstraints(G2frame,data)
4493        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
4494            data = G2frame.PatternTree.GetItemPyData(item)
4495            G2cnstG.UpdateRigidBodies(G2frame,data)
4496        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
4497            data = G2frame.PatternTree.GetItemPyData(item)
4498            Phases = G2frame.GetPhaseData()
4499            phase = ''
4500            phaseName = ''
4501            if Phases:
4502                phaseName = Phases.keys()[0]
4503            G2frame.dataFrame.setSizePosLeft(defWid)
4504            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
4505        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
4506            G2frame.Image = item
4507            G2frame.dataFrame.SetTitle('Image Data')
4508            data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId( \
4509                G2frame,item,'Image Controls'))
4510            G2imG.UpdateImageData(G2frame,data)
4511            G2plt.PlotImage(G2frame,newPlot=True)
4512        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
4513            G2plt.PlotPowderLines(G2frame)
4514        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
4515            #for i in G2frame.ExportPattern: i.Enable(True)
4516            if G2frame.EnablePlot:
4517                UpdatePWHKPlot(G2frame,'PWDR',item)
4518        elif 'SASD' in G2frame.PatternTree.GetItemText(item):
4519            #for i in G2frame.ExportPattern: i.Enable(True)
4520            if G2frame.EnablePlot:
4521                UpdatePWHKPlot(G2frame,'SASD',item)
4522        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
4523            G2frame.Sngl = True
4524            UpdatePWHKPlot(G2frame,'HKLF',item)
4525        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
4526            G2frame.PatternId = item
4527            for i in G2frame.ExportPDF: i.Enable(True)
4528            G2plt.PlotISFG(G2frame,type='S(Q)')
4529        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
4530            G2frame.dataFrame.setSizePosLeft(defWid)
4531            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4532                value='Select one phase to see its parameters')           
4533    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
4534        G2frame.PickId = item
4535        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4536        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4537        G2pdG.UpdatePDFGrid(G2frame,data)
4538        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
4539    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
4540        G2frame.PickId = item
4541        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4542        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4543        G2pdG.UpdatePDFGrid(G2frame,data)
4544        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
4545    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
4546        G2frame.PickId = item
4547        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4548        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4549        G2pdG.UpdatePDFGrid(G2frame,data)
4550        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
4551    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
4552        G2frame.PickId = item
4553        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4554        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4555        G2pdG.UpdatePDFGrid(G2frame,data)
4556        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
4557    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
4558        G2frame.PickId = item
4559        data = G2frame.PatternTree.GetItemPyData(item)
4560        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
4561    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
4562        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
4563        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4564        G2frame.PickId = item
4565        data = G2frame.PatternTree.GetItemPyData(item)
4566        UpdateComments(G2frame,data)
4567    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
4568        G2frame.dataFrame.SetTitle('Image Controls')
4569        G2frame.PickId = item
4570        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4571        masks = G2frame.PatternTree.GetItemPyData(
4572            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
4573        data = G2frame.PatternTree.GetItemPyData(item)
4574        G2imG.UpdateImageControls(G2frame,data,masks)
4575        G2plt.PlotImage(G2frame)
4576    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
4577        G2frame.dataFrame.SetTitle('Masks')
4578        G2frame.PickId = item
4579        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4580        data = G2frame.PatternTree.GetItemPyData(item)
4581        G2imG.UpdateMasks(G2frame,data)
4582        G2plt.PlotImage(G2frame)
4583    elif G2frame.PatternTree.GetItemText(item) == 'Stress/Strain':
4584        G2frame.dataFrame.SetTitle('Stress/Strain')
4585        G2frame.PickId = item
4586        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4587        data = G2frame.PatternTree.GetItemPyData(item)
4588        G2plt.PlotImage(G2frame)
4589        G2plt.PlotStrain(G2frame,data,newPlot=True)
4590        G2imG.UpdateStressStrain(G2frame,data)
4591    elif G2frame.PatternTree.GetItemText(item) == 'PDF Controls':
4592        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4593        for i in G2frame.ExportPDF: i.Enable(True)
4594        G2frame.PickId = item
4595        data = G2frame.PatternTree.GetItemPyData(item)
4596        G2pdG.UpdatePDFGrid(G2frame,data)
4597        G2plt.PlotISFG(G2frame,type='I(Q)')
4598        G2plt.PlotISFG(G2frame,type='S(Q)')
4599        G2plt.PlotISFG(G2frame,type='F(Q)')
4600        G2plt.PlotISFG(G2frame,type='G(R)')
4601    elif G2frame.PatternTree.GetItemText(item) == 'Peak List':
4602        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4603        for i in G2frame.ExportPeakList: i.Enable(True)
4604        G2frame.PickId = item
4605        data = G2frame.PatternTree.GetItemPyData(item)
4606#patch
4607        if 'list' in str(type(data)):
4608            data = {'peaks':data,'sigDict':{}}
4609            G2frame.PatternTree.SetItemPyData(item,data)
4610#end patch
4611        G2pdG.UpdatePeakGrid(G2frame,data)
4612        G2plt.PlotPatterns(G2frame)
4613    elif G2frame.PatternTree.GetItemText(item) == 'Background':
4614        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4615        G2frame.