source: trunk/GSASIIgrid.py @ 1627

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

fixes to seq results display & update tutorials

  • 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-10 00:33:57 +0000 (Sat, 10 Jan 2015) $
5# $Author: toby $
6# $Revision: 1627 $
7# $URL: trunk/GSASIIgrid.py $
8# $Id: GSASIIgrid.py 1627 2015-01-10 00:33:57Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIgrid: Basic GUI routines*
12--------------------------------
13
14'''
15import wx
16import wx.grid as wg
17import wx.wizard as wz
18import wx.aui
19import wx.lib.scrolledpanel as wxscroll
20import time
21import copy
22import cPickle
23import sys
24import os
25import numpy as np
26import numpy.ma as ma
27import scipy.optimize as so
28import wx.html        # could postpone this for quicker startup
29import webbrowser     # could postpone this for quicker startup
30import GSASIIpath
31GSASIIpath.SetVersionNumber("$Revision: 1627 $")
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        if sv[1]:
3430            sv[1] = insChar
3431        return ':'.join(sv)
3432       
3433    def plotSpCharFix(lbl):
3434        'Change selected unicode characters to their matplotlib equivalent'
3435        for u,p in [
3436            (u'\u03B1',r'$\alpha$'),
3437            (u'\u03B2',r'$\beta$'),
3438            (u'\u03B3',r'$\gamma$'),
3439            (u'\u0394\u03C7',r'$\Delta\chi$'),
3440            ]:
3441            lbl = lbl.replace(u,p)
3442        return lbl
3443   
3444    def SelectXaxis():
3445        'returns a selected column number (or None) as the X-axis selection'
3446        ncols = G2frame.SeqTable.GetNumberCols()
3447        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
3448        dlg = G2SingleChoiceDialog(
3449            G2frame.dataDisplay,
3450            'Select x-axis parameter for plot or Cancel for sequence number',
3451            'Select X-axis',
3452            colNames)
3453        try:
3454            if dlg.ShowModal() == wx.ID_OK:
3455                col = dlg.GetSelection()
3456            else:
3457                col = None
3458        finally:
3459            dlg.Destroy()
3460        return col
3461   
3462    def EnablePseudoVarMenus():
3463        'Enables or disables the PseudoVar menu items that require existing defs'
3464        if Controls['SeqPseudoVars']:
3465            val = True
3466        else:
3467            val = False
3468        G2frame.dataFrame.SequentialPvars.Enable(wxDELSEQVAR,val)
3469        G2frame.dataFrame.SequentialPvars.Enable(wxEDITSEQVAR,val)
3470
3471    def DelPseudoVar(event):
3472        'Ask the user to select a pseudo var expression to delete'
3473        choices = Controls['SeqPseudoVars'].keys()
3474        selected = ItemSelector(
3475            choices,G2frame.dataFrame,
3476            multiple=True,
3477            title='Select expressions to remove',
3478            header='Delete expression')
3479        if selected is None: return
3480        for item in selected:
3481            del Controls['SeqPseudoVars'][choices[item]]
3482        if selected:
3483            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3484
3485    def EditPseudoVar(event):
3486        'Edit an existing pseudo var expression'
3487        choices = Controls['SeqPseudoVars'].keys()
3488        if len(choices) == 1:
3489            selected = 0
3490        else:
3491            selected = ItemSelector(
3492                choices,G2frame.dataFrame,
3493                multiple=False,
3494                title='Select an expression to edit',
3495                header='Edit expression')
3496        if selected is not None:
3497            dlg = G2exG.ExpressionDialog(
3498                G2frame.dataDisplay,PSvarDict,
3499                Controls['SeqPseudoVars'][choices[selected]],
3500                header="Edit the PseudoVar expression",
3501                VarLabel="PseudoVar #"+str(selected+1),
3502                fit=False)
3503            newobj = dlg.Show(True)
3504            if newobj:
3505                calcobj = G2obj.ExpressionCalcObj(newobj)
3506                del Controls['SeqPseudoVars'][choices[selected]]
3507                Controls['SeqPseudoVars'][calcobj.eObj.expression] = newobj
3508                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3509       
3510    def AddNewPseudoVar(event):
3511        'Create a new pseudo var expression'
3512        dlg = G2exG.ExpressionDialog(
3513            G2frame.dataDisplay,PSvarDict,
3514            header='Enter an expression for a PseudoVar here',
3515            VarLabel = "New PseudoVar",
3516            fit=False)
3517        obj = dlg.Show(True)
3518        dlg.Destroy()
3519        if obj:
3520            calcobj = G2obj.ExpressionCalcObj(obj)
3521            Controls['SeqPseudoVars'][calcobj.eObj.expression] = obj
3522            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
3523
3524    def UpdateParmDict(parmDict):
3525        '''generate the atom positions and the direct & reciprocal cell values,
3526        because they might be needed to evaluate the pseudovar
3527        '''
3528        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3529                         ['A'+str(i) for i in range(6)])
3530                     )
3531        delList = []
3532        phaselist = []
3533        for item in parmDict: 
3534            if ':' not in item: continue
3535            key = item.split(':')
3536            if len(key) < 3: continue
3537            # remove the dA[xyz] terms, they would only bring confusion
3538            if key[2].startswith('dA'):
3539                delList.append(item)
3540            # compute and update the corrected reciprocal cell terms using the Dij values
3541            elif key[2] in Ddict:
3542                if key[0] not in phaselist: phaselist.append(key[0])
3543                akey = key[0]+'::'+Ddict[key[2]]
3544                parmDict[akey] -= parmDict[item]
3545                delList.append(item)
3546        for item in delList:
3547            del parmDict[item]               
3548        for i in phaselist:
3549            pId = int(i)
3550            # apply cell symmetry
3551            A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
3552            # convert to direct cell & add the unique terms to the dictionary
3553            for i,val in enumerate(G2lat.A2cell(A)):
3554                if i in uniqCellIndx[pId]:
3555                    lbl = str(pId)+'::'+cellUlbl[i]
3556                    parmDict[lbl] = val
3557            lbl = str(pId)+'::'+'vol'
3558            parmDict[lbl] = G2lat.calc_V(A)
3559        return parmDict
3560
3561    def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD):
3562        '''Evaluate an expression derivative with respect to a
3563        GSAS-II variable name.
3564
3565        Note this likely could be faster if the loop over calcobjs were done
3566        inside after the Dict was created.
3567        '''
3568        step = ESD/10
3569        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
3570                         ['A'+str(i) for i in range(6)])
3571                     )
3572        results = []
3573        phaselist = []
3574        VparmDict = sampleDict.copy()
3575        for incr in step,-step:
3576            VparmDict.update(parmDict.copy())           
3577            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
3578            # values are not zeroed: fix that!
3579            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
3580            VparmDict[var] += incr
3581            G2mv.Dict2Map(VparmDict,[]) # apply constraints
3582            # generate the atom positions and the direct & reciprocal cell values now, because they might
3583            # needed to evaluate the pseudovar
3584            for item in VparmDict:
3585                if item in sampleDict:
3586                    continue 
3587                if ':' not in item: continue
3588                key = item.split(':')
3589                if len(key) < 3: continue
3590                # apply any new shifts to atom positions
3591                if key[2].startswith('dA'):
3592                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
3593                    VparmDict[item] = 0.0
3594                # compute and update the corrected reciprocal cell terms using the Dij values
3595                if key[2] in Ddict:
3596                    if key[0] not in phaselist: phaselist.append(key[0])
3597                    akey = key[0]+'::'+Ddict[key[2]]
3598                    VparmDict[akey] -= VparmDict[item]
3599            for i in phaselist:
3600                pId = int(i)
3601                # apply cell symmetry
3602                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
3603                # convert to direct cell & add the unique terms to the dictionary
3604                for i,val in enumerate(G2lat.A2cell(A)):
3605                    if i in uniqCellIndx[pId]:
3606                        lbl = str(pId)+'::'+cellUlbl[i]
3607                        VparmDict[lbl] = val
3608                lbl = str(pId)+'::'+'vol'
3609                VparmDict[lbl] = G2lat.calc_V(A)
3610            # dict should be fully updated, use it & calculate
3611            calcobj.SetupCalc(VparmDict)
3612            results.append(calcobj.EvalExpression())
3613        return (results[0] - results[1]) / (2.*step)
3614       
3615    def EnableParFitEqMenus():
3616        'Enables or disables the Parametric Fit menu items that require existing defs'
3617        if Controls['SeqParFitEqList']:
3618            val = True
3619        else:
3620            val = False
3621        G2frame.dataFrame.SequentialPfit.Enable(wxDELPARFIT,val)
3622        G2frame.dataFrame.SequentialPfit.Enable(wxEDITPARFIT,val)
3623        G2frame.dataFrame.SequentialPfit.Enable(wxDOPARFIT,val)
3624
3625    def ParEqEval(Values,calcObjList,varyList):
3626        '''Evaluate the parametric expression(s)
3627        :param list Values: a list of values for each variable parameter
3628        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
3629          expression objects to evaluate
3630        :param list varyList: a list of variable names for each value in Values
3631        '''
3632        result = []
3633        for calcobj in calcObjList:
3634            calcobj.UpdateVars(varyList,Values)
3635            result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
3636        return result
3637
3638    def DoParEqFit(event,eqObj=None):
3639        'Parametric fit minimizer'
3640        varyValueDict = {} # dict of variables and their initial values
3641        calcObjList = [] # expression objects, ready to go for each data point
3642        if eqObj is not None:
3643            eqObjList = [eqObj,]
3644        else:
3645            eqObjList = Controls['SeqParFitEqList']
3646        UseFlags = G2frame.SeqTable.GetColValues(0)         
3647        for obj in eqObjList:
3648            expr = obj.expression
3649            # assemble refined vars for this equation
3650            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
3651            # lookup dependent var position
3652            depVar = obj.GetDepVar()
3653            if depVar in colLabels:
3654                indx = colLabels.index(depVar)
3655            else:
3656                raise Exception('Dependent variable '+depVar+' not found')
3657            # assemble a list of the independent variables
3658            indepVars = obj.GetIndependentVars()
3659            # loop over each datapoint
3660            for j,row in enumerate(zip(*colList)):
3661                if not UseFlags[j]: continue
3662                # assemble equations to fit
3663                calcobj = G2obj.ExpressionCalcObj(obj)
3664                # prepare a dict of needed independent vars for this expression
3665                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
3666                calcobj.SetupCalc(indepVarDict)               
3667                # values and sigs for current value of dependent var
3668                calcobj.depVal = row[indx]
3669                calcobj.depSig = colSigs[indx][j]
3670                calcObjList.append(calcobj)
3671        # varied parameters
3672        varyList = varyValueDict.keys()
3673        values = varyValues = [varyValueDict[key] for key in varyList]
3674        if not varyList:
3675            print 'no variables to refine!'
3676            return
3677        try:
3678            result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
3679                                args=(calcObjList,varyList)
3680                                )
3681            values = result[0]
3682            covar = result[1]
3683            if covar is None:
3684                raise Exception
3685            esdDict = {}
3686            for i,avar in enumerate(varyList):
3687                esdDict[avar] = np.sqrt(covar[i,i])
3688        except:
3689            print('====> Fit failed')
3690            return
3691        print('==== Fit Results ====')
3692        for obj in eqObjList:
3693            obj.UpdateVariedVars(varyList,values)
3694            ind = '      '
3695            print('  '+obj.GetDepVar()+' = '+obj.expression)
3696            for var in obj.assgnVars:
3697                print(ind+var+' = '+obj.assgnVars[var])
3698            for var in obj.freeVars:
3699                avar = "::"+obj.freeVars[var][0]
3700                val = obj.freeVars[var][1]
3701                if obj.freeVars[var][2]:
3702                    print(ind+var+' = '+avar + " = " + G2mth.ValEsd(val,esdDict[avar]))
3703                else:
3704                    print(ind+var+' = '+avar + " =" + G2mth.ValEsd(val,0))
3705        # create a plot for each parametric variable
3706        for fitnum,obj in enumerate(eqObjList):
3707            calcobj = G2obj.ExpressionCalcObj(obj)
3708            # lookup dependent var position
3709            indx = colLabels.index(obj.GetDepVar())
3710            # assemble a list of the independent variables
3711            indepVars = obj.GetIndependentVars()           
3712            # loop over each datapoint
3713            fitvals = []
3714            for j,row in enumerate(zip(*colList)):
3715                calcobj.SetupCalc(
3716                    {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
3717                    )
3718                fitvals.append(calcobj.EvalExpression())
3719            G2plt.PlotSelectedSequence(
3720                G2frame,[indx],GetColumnInfo,SelectXaxis,
3721                fitnum,fitvals)
3722
3723    def SingleParEqFit(eqObj):
3724        DoParEqFit(None,eqObj)
3725
3726    def DelParFitEq(event):
3727        'Ask the user to select function to delete'
3728        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3729        selected = ItemSelector(
3730            txtlst,G2frame.dataFrame,
3731            multiple=True,
3732            title='Select a parametric equation(s) to remove',
3733            header='Delete equation')
3734        if selected is None: return
3735        Controls['SeqParFitEqList'] = [obj for i,obj in enumerate(Controls['SeqParFitEqList']) if i not in selected]
3736        EnableParFitEqMenus()
3737        if Controls['SeqParFitEqList']: DoParEqFit(event)
3738       
3739    def EditParFitEq(event):
3740        'Edit an existing parametric equation'
3741        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3742        if len(txtlst) == 1:
3743            selected = 0
3744        else:
3745            selected = ItemSelector(
3746                txtlst,G2frame.dataFrame,
3747                multiple=False,
3748                title='Select a parametric equation to edit',
3749                header='Edit equation')
3750        if selected is not None:
3751            dlg = G2exG.ExpressionDialog(
3752                G2frame.dataDisplay,indepVarDict,
3753                Controls['SeqParFitEqList'][selected],
3754                depVarDict=depVarDict,
3755                header="Edit the formula for this minimization function",
3756                ExtraButton=['Fit',SingleParEqFit])
3757            newobj = dlg.Show(True)
3758            if newobj:
3759                calcobj = G2obj.ExpressionCalcObj(newobj)
3760                Controls['SeqParFitEqList'][selected] = newobj
3761                EnableParFitEqMenus()
3762            if Controls['SeqParFitEqList']: DoParEqFit(event)
3763
3764    def AddNewParFitEq(event):
3765        'Create a new parametric equation to be fit to sequential results'
3766
3767        # compile the variable names used in previous freevars to avoid accidental name collisions
3768        usedvarlist = []
3769        for obj in Controls['SeqParFitEqList']:
3770            for var in obj.freeVars:
3771                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
3772
3773        dlg = G2exG.ExpressionDialog(
3774            G2frame.dataDisplay,indepVarDict,
3775            depVarDict=depVarDict,
3776            header='Define an equation to minimize in the parametric fit',
3777            ExtraButton=['Fit',SingleParEqFit],
3778            usedVars=usedvarlist)
3779        obj = dlg.Show(True)
3780        dlg.Destroy()
3781        if obj:
3782            Controls['SeqParFitEqList'].append(obj)
3783            EnableParFitEqMenus()
3784            if Controls['SeqParFitEqList']: DoParEqFit(event)
3785               
3786    def CopyParFitEq(event):
3787        'Copy an existing parametric equation to be fit to sequential results'
3788        # compile the variable names used in previous freevars to avoid accidental name collisions
3789        usedvarlist = []
3790        for obj in Controls['SeqParFitEqList']:
3791            for var in obj.freeVars:
3792                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
3793        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in Controls['SeqParFitEqList']]
3794        if len(txtlst) == 1:
3795            selected = 0
3796        else:
3797            selected = ItemSelector(
3798                txtlst,G2frame.dataFrame,
3799                multiple=False,
3800                title='Select a parametric equation to copy',
3801                header='Copy equation')
3802        if selected is not None:
3803            newEqn = copy.deepcopy(Controls['SeqParFitEqList'][selected])
3804            for var in newEqn.freeVars:
3805                newEqn.freeVars[var][0] = G2obj.MakeUniqueLabel(newEqn.freeVars[var][0],usedvarlist)
3806            dlg = G2exG.ExpressionDialog(
3807                G2frame.dataDisplay,indepVarDict,
3808                newEqn,
3809                depVarDict=depVarDict,
3810                header="Edit the formula for this minimization function",
3811                ExtraButton=['Fit',SingleParEqFit])
3812            newobj = dlg.Show(True)
3813            if newobj:
3814                calcobj = G2obj.ExpressionCalcObj(newobj)
3815                Controls['SeqParFitEqList'].append(newobj)
3816                EnableParFitEqMenus()
3817            if Controls['SeqParFitEqList']: DoParEqFit(event)
3818                                           
3819    def GridSetToolTip(row,col):
3820        '''Routine to show standard uncertainties for each element in table
3821        as a tooltip
3822        '''
3823        if colSigs[col]:
3824            return u'\u03c3 = '+str(colSigs[col][row])
3825        return ''
3826       
3827    def GridColLblToolTip(col):
3828        '''Define a tooltip for a column. This will be the user-entered value
3829        (from data['variableLabels']) or the default name
3830        '''
3831        if col < 0 or col > len(colLabels):
3832            print 'Illegal column #',col
3833            return
3834        var = colLabels[col]
3835        return variableLabels.get(var,G2obj.fmtVarDescr(var))
3836       
3837    def SetLabelString(event):
3838        '''Define or edit the label for a column in the table, to be used
3839        as a tooltip and for plotting
3840        '''
3841        col = event.GetCol()
3842        if col < 0 or col > len(colLabels):
3843            return
3844        var = colLabels[col]
3845        lbl = variableLabels.get(var,G2obj.fmtVarDescr(var))
3846        dlg = SingleStringDialog(G2frame.dataFrame,'Set variable label',
3847                                 'Set a new name for variable '+var,lbl,size=(400,-1))
3848        if dlg.Show():
3849            variableLabels[var] = dlg.GetValue()
3850        dlg.Destroy()
3851       
3852    #def GridRowLblToolTip(row): return 'Row ='+str(row)
3853   
3854    # lookup table for unique cell parameters by symmetry
3855    cellGUIlist = [
3856        [['m3','m3m'],(0,)],
3857        [['3R','3mR'],(0,3)],
3858        [['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],(0,2)],
3859        [['mmm'],(0,1,2)],
3860        [['2/m'+'a'],(0,1,2,3)],
3861        [['2/m'+'b'],(0,1,2,4)],
3862        [['2/m'+'c'],(0,1,2,5)],
3863        [['-1'],(0,1,2,3,4,5)],
3864        ]
3865    # cell labels
3866    cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') # unicode a,b,c,alpha,beta,gamma
3867
3868    #======================================================================
3869    # start processing sequential results here (UpdateSeqResults)
3870    #======================================================================
3871    if not data:
3872        print 'No sequential refinement results'
3873        return
3874    variableLabels = data.get('variableLabels',{})
3875    data['variableLabels'] = variableLabels
3876    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
3877    Controls = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.root,'Controls'))
3878    # create a place to store Pseudo Vars & Parametric Fit functions, if not present
3879    if 'SeqPseudoVars' not in Controls: Controls['SeqPseudoVars'] = {}
3880    if 'SeqParFitEqList' not in Controls: Controls['SeqParFitEqList'] = []
3881    histNames = data['histNames']
3882    if G2frame.dataDisplay:
3883        G2frame.dataDisplay.Destroy()
3884    if not G2frame.dataFrame.GetStatusBar():
3885        Status = G2frame.dataFrame.CreateStatusBar()
3886        Status.SetStatusText("Select column to export; Double click on column to plot data; on row for Covariance")
3887    sampleParms = GetSampleParms()
3888
3889    # make dict of varied atom coords keyed by absolute position
3890    newAtomDict = data[histNames[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
3891    # Possible error: the next might need to be data[histNames[0]]['varyList']
3892    # error will arise if there constraints on coordinates?
3893    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in data['varyList']}
3894   
3895    # make dict of varied cell parameters equivalents
3896    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
3897    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
3898    # N.B. These Dij vars are missing a histogram #
3899    newCellDict = data[histNames[0]].get('newCellDict',{})
3900    for item in newCellDict:
3901        if item in data['varyList']:
3902            ESDlookup[newCellDict[item][0]] = item
3903            Dlookup[item] = newCellDict[item][0]
3904    # add coordinate equivalents to lookup table
3905    for parm in atomLookup:
3906        Dlookup[atomLookup[parm]] = parm
3907        ESDlookup[parm] = atomLookup[parm]
3908
3909    # get unit cell & symmetry for all phases & initial stuff for later use
3910    RecpCellTerms = {}
3911    SGdata = {}
3912    uniqCellIndx = {}
3913    initialCell = {}
3914    RcellLbls = {}
3915    zeroDict = {}
3916    Rcelldict = {}
3917    for phase in Phases:
3918        phasedict = Phases[phase]
3919        pId = phasedict['pId']
3920        pfx = str(pId)+'::' # prefix for A values from phase
3921        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
3922        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
3923        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
3924        SGdata[pId] = phasedict['General']['SGData']
3925        Rcelldict.update({lbl:val for lbl,val in zip(RcellLbls[pId],RecpCellTerms[pId])})
3926        laue = SGdata[pId]['SGLaue']
3927        if laue == '2/m':
3928            laue += SGdata[pId]['SGUniq']
3929        for symlist,celllist in cellGUIlist:
3930            if laue in symlist:
3931                uniqCellIndx[pId] = celllist
3932                break
3933        else: # should not happen
3934            uniqCellIndx[pId] = range(6)
3935        for i in uniqCellIndx[pId]:
3936            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
3937
3938    SetDataMenuBar(G2frame,G2frame.dataFrame.SequentialMenu)
3939    G2frame.dataFrame.SetLabel('Sequential refinement results')
3940    if not G2frame.dataFrame.GetStatusBar():
3941        Status = G2frame.dataFrame.CreateStatusBar()
3942        Status.SetStatusText('')
3943    G2frame.dataFrame.Bind(wx.EVT_MENU, OnRenameSelSeq, id=wxID_RENAMESEQSEL)
3944    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=wxID_SAVESEQSEL)
3945    G2frame.dataFrame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=wxID_SAVESEQSELCSV)
3946    G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=wxID_PLOTSEQSEL)
3947    G2frame.dataFrame.Bind(wx.EVT_MENU, OnReOrgSelSeq, id=wxID_ORGSEQSEL)
3948    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=wxADDSEQVAR)
3949    G2frame.dataFrame.Bind(wx.EVT_MENU, DelPseudoVar, id=wxDELSEQVAR)
3950    G2frame.dataFrame.Bind(wx.EVT_MENU, EditPseudoVar, id=wxEDITSEQVAR)
3951    G2frame.dataFrame.Bind(wx.EVT_MENU, AddNewParFitEq, id=wxADDPARFIT)
3952    G2frame.dataFrame.Bind(wx.EVT_MENU, CopyParFitEq, id=wxCOPYPARFIT)
3953    G2frame.dataFrame.Bind(wx.EVT_MENU, DelParFitEq, id=wxDELPARFIT)
3954    G2frame.dataFrame.Bind(wx.EVT_MENU, EditParFitEq, id=wxEDITPARFIT)
3955    G2frame.dataFrame.Bind(wx.EVT_MENU, DoParEqFit, id=wxDOPARFIT)
3956    EnablePseudoVarMenus()
3957    EnableParFitEqMenus()
3958
3959    # scan for locations where the variables change
3960    VaryListChanges = [] # histograms where there is a change
3961    combinedVaryList = []
3962    firstValueList = []
3963    vallookup = {}
3964    posdict = {}
3965    for i,name in enumerate(histNames):
3966        newval = False
3967        for var,val in zip(data[name]['varyList'],data[name]['variables']):
3968            svar = striphist(var,'*')
3969            if svar in combinedVaryList: continue
3970            # add variables to list as they appear
3971            combinedVaryList.append(svar)
3972            firstValueList.append(val)
3973            newval = True
3974        if newval:
3975            vallookup[name] = dict(zip(data[name]['varyList'],data[name]['variables']))
3976            posdict[name] = {}
3977            for var in data[name]['varyList']:
3978                svar = striphist(var,'*')
3979                posdict[name][combinedVaryList.index(svar)] = svar
3980            VaryListChanges.append(name)
3981    if len(VaryListChanges) > 1:
3982        G2frame.dataFrame.SequentialFile.Enable(wxID_ORGSEQSEL,True)
3983    else:
3984        G2frame.dataFrame.SequentialFile.Enable(wxID_ORGSEQSEL,False)
3985    #-----------------------------------------------------------------------------------
3986    # build up the data table by columns -----------------------------------------------
3987    nRows = len(histNames)
3988    colList = [nRows*[True]]
3989    colSigs = [None]
3990    colLabels = ['Use']
3991    Types = [wg.GRID_VALUE_BOOL]
3992    # start with Rwp values
3993    if 'IMG ' not in histNames[0][:4]:
3994        colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
3995        colSigs += [None]
3996        colLabels += ['Rwp']
3997        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
3998    # add % change in Chi^2 in last cycle
3999    if histNames[0][:4] not in ['SASD','IMG '] and Controls['ShowCell']:
4000        colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
4001        colSigs += [None]
4002        colLabels += [u'\u0394\u03C7\u00B2 (%)']
4003        Types += [wg.GRID_VALUE_FLOAT,]
4004    deltaChiCol = len(colLabels)-1
4005    # add changing sample parameters to table
4006    for key in sampleParms:
4007        colList += [sampleParms[key]]
4008        colSigs += [None]
4009        colLabels += [key]
4010        Types += [wg.GRID_VALUE_FLOAT,]
4011    sampleDict = {}
4012    for i,name in enumerate(histNames):
4013        sampleDict[name] = dict(zip(sampleParms.keys(),[sampleParms[key][i] for key in sampleParms.keys()])) 
4014    # add unique cell parameters TODO: review this where the cell symmetry changes (when possible)
4015    if Controls.get('ShowCell',False):
4016        for pId in sorted(RecpCellTerms):
4017            pfx = str(pId)+'::' # prefix for A values from phase
4018            cells = []
4019            cellESDs = []
4020            colLabels += [pfx+cellUlbl[i] for i in uniqCellIndx[pId]]
4021            colLabels += [pfx+'Vol']
4022            Types += (1+len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT,]
4023            for name in histNames:
4024                covData = {
4025                    'varyList': [Dlookup.get(striphist(v),v) for v in data[name]['varyList']],
4026                    'covMatrix': data[name]['covMatrix']
4027                    }
4028                A = RecpCellTerms[pId][:] # make copy of starting A values
4029                # update with refined values
4030                for i in range(6):
4031                    var = str(pId)+'::A'+str(i)
4032                    if var in ESDlookup:
4033                        val = data[name]['newCellDict'][ESDlookup[var]][1] # get refined value
4034                        A[i] = val # override with updated value
4035                # apply symmetry
4036                Albls = [pfx+'A'+str(i) for i in range(6)]
4037                cellDict = dict(zip(Albls,A))
4038                A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
4039                # convert to direct cell & add only unique values to table
4040                c = G2lat.A2cell(A)
4041                vol = G2lat.calc_V(A)
4042                cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
4043                cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
4044                cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
4045            colList += zip(*cells)
4046            colSigs += zip(*cellESDs)
4047    # sort out the variables in their selected order
4048    varcols = 0
4049    for d in posdict.itervalues():
4050        varcols = max(varcols,max(d.keys())+1)
4051    # get labels for each column
4052    for i in range(varcols):
4053        lbl = ''
4054        for h in VaryListChanges:
4055            if posdict[h].get(i):
4056                if posdict[h].get(i) in lbl: continue
4057                if lbl != "": lbl += '/'
4058                lbl += posdict[h].get(i)
4059        colLabels.append(lbl)
4060    Types += varcols*[wg.GRID_VALUE_FLOAT]
4061    vals = []
4062    esds = []
4063    varsellist = None        # will be a list of variable names in the order they are selected to appear
4064    # tabulate values for each hist, leaving None for blank columns
4065    for name in histNames:
4066        if name in posdict:
4067            varsellist = [posdict[name].get(i) for i in range(varcols)]
4068            #sellist = [data[name]['varyList'].index(v) if v is not None else None for v in varsellist]
4069            sellist = [i if striphist(v,'*') in varsellist else None for i,v in enumerate(data[name]['varyList'])]
4070        if not varsellist: raise Exception()
4071        vals.append([data[name]['variables'][s] if s is not None else None for s in sellist])
4072        esds.append([data[name]['sig'][s] if s is not None else None for s in sellist])
4073    colList += zip(*vals)
4074    colSigs += zip(*esds)
4075    # add the variables that were refined; change from rows to columns
4076    #colList += zip(*[data[name]['variables'] for name in histNames])
4077    #colLabels += data[histNames[0]]['varyList']
4078    #Types += len(data[histNames[0]]['varyList'])*[wg.GRID_VALUE_FLOAT]
4079    #colSigs += zip(*[data[name]['sig'] for name in histNames])
4080
4081    # for var in combinedVaryList:
4082    #     colLabels += [var]
4083    #     Types += [wg.GRID_VALUE_FLOAT]
4084    #     vals = []
4085    #     sigs = []
4086    #     for name in histNames:
4087    #         try:
4088    #             i = data[name]['varyList'].index(var)
4089    #             vals.append(data[name]['variables'][i])
4090    #             sigs.append(data[name]['sig'][i])
4091    #         except ValueError: # var not in list
4092    #             vals.append(None)
4093    #             sigs.append(None)
4094    #     colList += [vals]
4095    #     colSigs += [sigs]
4096               
4097    # tabulate constrained variables, removing histogram numbers if needed
4098    # from parameter label
4099    depValDict = {}
4100    depSigDict = {}
4101    for name in histNames:
4102        for var in data[name].get('depParmDict',{}):
4103            val,sig = data[name]['depParmDict'][var]
4104            svar = striphist(var,'*')
4105            if svar not in depValDict:
4106               depValDict[svar] = [val]
4107               depSigDict[svar] = [sig]
4108            else:
4109               depValDict[svar].append(val)
4110               depSigDict[svar].append(sig)
4111    # add the dependent constrained variables to the table
4112    for var in sorted(depValDict):
4113        if len(depValDict[var]) != len(histNames): continue
4114        colLabels.append(var)
4115        Types += [wg.GRID_VALUE_FLOAT,]
4116        colSigs += [depSigDict[var]]
4117        colList += [depValDict[var]]
4118
4119    # add atom parameters to table
4120    colLabels += atomLookup.keys()
4121    Types += len(atomLookup)*[wg.GRID_VALUE_FLOAT]
4122    for parm in sorted(atomLookup):
4123        colList += [[data[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
4124        if atomLookup[parm] in data[histNames[0]]['varyList']:
4125            col = data[histNames[0]]['varyList'].index(atomLookup[parm])
4126            colSigs += [[data[name]['sig'][col] for name in histNames]]
4127        else:
4128            colSigs += [None] # should not happen
4129    # evaluate Pseudovars, their ESDs and add them to grid
4130    for expr in Controls['SeqPseudoVars']:
4131        obj = Controls['SeqPseudoVars'][expr]
4132        calcobj = G2obj.ExpressionCalcObj(obj)
4133        valList = []
4134        esdList = []
4135        for seqnum,name in enumerate(histNames):
4136            sigs = data[name]['sig']
4137            G2mv.InitVars()
4138            parmDict = data[name].get('parmDict')
4139            badVary = data[name].get('badVary',[])
4140            constraintInfo = data[name].get('constraintInfo',[[],[],{},[],seqnum])
4141            groups,parmlist,constrDict,fixedList,ihst = constraintInfo
4142            varyList = data[name]['varyList']
4143            parmDict = data[name]['parmDict']
4144            G2mv.GenerateConstraints(groups,parmlist,varyList,constrDict,fixedList,parmDict,SeqHist=ihst)
4145            derivs = np.array(
4146                [EvalPSvarDeriv(calcobj,parmDict.copy(),sampleDict[name],var,ESD)
4147                 for var,ESD in zip(varyList,sigs)]
4148                )
4149            esdList.append(np.sqrt(
4150                np.inner(derivs,np.inner(data[name]['covMatrix'],derivs.T))
4151                ))
4152            PSvarDict = parmDict.copy()
4153            PSvarDict.update(sampleDict[name])
4154            UpdateParmDict(PSvarDict)
4155            calcobj.UpdateDict(PSvarDict)
4156            valList.append(calcobj.EvalExpression())
4157        if not esdList:
4158            esdList = None
4159        colList += [valList]
4160        colSigs += [esdList]
4161        colLabels += [expr]
4162        Types += [wg.GRID_VALUE_FLOAT,]
4163    #---- table build done -------------------------------------------------------------
4164
4165    # Make dict needed for creating & editing pseudovars (PSvarDict).
4166    name = histNames[0]
4167    parmDict = data[name].get('parmDict')
4168    PSvarDict = parmDict.copy()
4169    PSvarDict.update(sampleParms)
4170    UpdateParmDict(PSvarDict)
4171    # Also dicts of dependent (depVarDict) & independent vars (indepVarDict)
4172    # for Parametric fitting from the data table
4173    parmDict = dict(zip(colLabels,zip(*colList)[0])) # scratch dict w/all values in table
4174    parmDict.update(
4175        {var:val for var,val in data[name].get('newCellDict',{}).values()} #  add varied reciprocal cell terms
4176    )
4177    name = histNames[0]
4178   
4179    indepVarDict = {     #  values in table w/o ESDs
4180        var:colList[i][0] for i,var in enumerate(colLabels) if colSigs[i] is None
4181        }
4182    # make dict of dependent vars (w/ESDs) that are not converted (Dij to Ak or dAx to Ax)
4183    depVarDict = {
4184        var:colList[i][0] for i,var in enumerate(colLabels)
4185        if colSigs[i] is not None and striphist(var) not in Dlookup
4186        }
4187    # add recip cell coeff. values
4188    depVarDict.update({var:val for var,val in data[name].get('newCellDict',{}).values()})
4189   
4190    G2frame.dataDisplay = GSGrid(parent=G2frame.dataFrame)
4191    G2frame.SeqTable = Table(
4192        [list(c) for c in zip(*colList)],     # convert from columns to rows
4193        colLabels=colLabels,rowLabels=histNames,types=Types)
4194    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
4195    #G2frame.dataDisplay.EnableEditing(False)
4196    # make all but first column read-only
4197    for c in range(1,len(colLabels)):
4198        for r in range(nRows):
4199            G2frame.dataDisplay.SetCellReadOnly(r,c)
4200    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, PlotSelect)
4201    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_RIGHT_CLICK, SetLabelString)
4202    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
4203    G2frame.dataDisplay.SetMargins(0,0)
4204    G2frame.dataDisplay.AutoSizeColumns(True)
4205    if prevSize:
4206        G2frame.dataDisplay.SetSize(prevSize)
4207    else:
4208        G2frame.dataFrame.setSizePosLeft([700,350])
4209    # highlight unconverged shifts
4210    if histNames[0][:4] not in ['SASD','IMG ']:
4211        for row,name in enumerate(histNames):
4212            deltaChi = G2frame.SeqTable.GetValue(row,deltaChiCol)
4213            if deltaChi > 10.:
4214                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,0,0))
4215            elif deltaChi > 1.0:
4216                G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,255,0))
4217    G2frame.dataDisplay.InstallGridToolTip(GridSetToolTip,GridColLblToolTip)
4218    G2frame.dataDisplay.SendSizeEvent() # resize needed on mac
4219    G2frame.dataDisplay.Refresh() # shows colored text on mac
4220   
4221################################################################################
4222#####  Main PWDR panel
4223################################################################################           
4224       
4225def UpdatePWHKPlot(G2frame,kind,item):
4226    '''Called when the histogram main tree entry is called. Displays the
4227    histogram weight factor, refinement statistics for the histogram
4228    and the range of data for a simulation.
4229
4230    Also invokes a plot of the histogram.
4231    '''
4232    def onEditSimRange(event):
4233        'Edit simulation range'
4234        inp = [
4235            min(data[1][0]),
4236            max(data[1][0]),
4237            None
4238            ]
4239        inp[2] = (inp[1] - inp[0])/(len(data[1][0])-1.)
4240        names = ('start angle', 'end angle', 'step size')
4241        dictlst = [inp] * len(inp)
4242        elemlst = range(len(inp))
4243        dlg = G2G.ScrolledMultiEditor(
4244            G2frame,[inp] * len(inp), range(len(inp)), names,
4245            header='Edit simulation range',
4246            minvals=(0.001,0.001,0.0001),
4247            maxvals=(180.,180.,.1),
4248            )
4249        dlg.CenterOnParent()
4250        val = dlg.ShowModal()
4251        dlg.Destroy()
4252        if val != wx.ID_OK: return
4253        if inp[0] > inp[1]:
4254            end,start,step = inp
4255        else:               
4256            start,end,step = inp
4257        step = abs(step)
4258        N = int((end-start)/step)+1
4259        newdata = np.linspace(start,end,N,True)
4260        if len(newdata) < 2: return # too small a range - reject
4261        data[1] = [newdata,np.zeros_like(newdata),np.ones_like(newdata),
4262            np.zeros_like(newdata),np.zeros_like(newdata),np.zeros_like(newdata)]
4263        Tmin = newdata[0]
4264        Tmax = newdata[-1]
4265        G2frame.PatternTree.SetItemPyData(GetPatternTreeItemId(G2frame,item,'Limits'),
4266            [(Tmin,Tmax),[Tmin,Tmax]])
4267        UpdatePWHKPlot(G2frame,kind,item) # redisplay data screen
4268
4269    def OnPlot3DHKL(event):
4270        refList = data[1]['RefList']
4271        FoMax = np.max(refList.T[8+Super])
4272        Hmin = np.array([int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))])
4273        Hmax = np.array([int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))])
4274        Vpoint = [int(np.mean(refList.T[0])),int(np.mean(refList.T[1])),int(np.mean(refList.T[2]))]
4275        controls = {'Type' : 'Fosq','Iscale' : False,'HKLmax' : Hmax,'HKLmin' : Hmin,
4276            'FoMax' : FoMax,'Scale' : 1.0,'Drawing':{'viewPoint':[Vpoint,[]],'default':Vpoint[:],
4277            'backColor':[0,0,0],'depthFog':False,'Zclip':10.0,'cameraPos':10.,'Zstep':0.05,
4278            'Scale':1.0,'oldxy':[],'viewDir':[1,0,0]},'Super':Super,'SuperVec':SuperVec}
4279        G2plt.Plot3DSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName)
4280       
4281    def OnErrorAnalysis(event):
4282        G2plt.PlotDeltSig(G2frame,kind)
4283       
4284    def OnWtFactor(event):
4285        try:
4286            val = float(wtval.GetValue())
4287        except ValueError:
4288            val = data[0]['wtFactor']
4289        data[0]['wtFactor'] = val
4290        wtval.SetValue('%.3f'%(val))
4291
4292    def onCopySelectedItems(event):
4293        '''Respond to menu item to copy multiple sections from a histogram.
4294        Need this here to pass on the G2frame object.
4295        '''
4296        G2pdG.CopySelectedHistItems(G2frame)
4297           
4298    data = G2frame.PatternTree.GetItemPyData(item)
4299#patches
4300    if 'wtFactor' not in data[0]:
4301        data[0] = {'wtFactor':1.0}
4302    #if isinstance(data[1],list) and kind == 'HKLF':
4303    if 'list' in str(type(data[1])) and kind == 'HKLF':
4304        RefData = {'RefList':[],'FF':[]}
4305        for ref in data[1]:
4306            RefData['RefList'].append(ref[:11]+[ref[13],])
4307            RefData['FF'].append(ref[14])
4308        data[1] = RefData
4309        G2frame.PatternTree.SetItemPyData(item,data)
4310#end patches
4311    if G2frame.dataDisplay:
4312        G2frame.dataDisplay.Destroy()
4313    if kind in ['PWDR','SASD']:
4314        SetDataMenuBar(G2frame,G2frame.dataFrame.PWDRMenu)
4315        G2frame.dataFrame.Bind(wx.EVT_MENU, OnErrorAnalysis, id=wxID_PWDANALYSIS)
4316        G2frame.dataFrame.Bind(wx.EVT_MENU, onCopySelectedItems, id=wxID_PWDCOPY)
4317    elif kind in ['HKLF',]:
4318        SetDataMenuBar(G2frame,G2frame.dataFrame.HKLFMenu)
4319#        G2frame.dataFrame.Bind(wx.EVT_MENU, OnErrorAnalysis, id=wxID_PWDANALYSIS)
4320        G2frame.dataFrame.Bind(wx.EVT_MENU, OnPlot3DHKL, id=wxID_PWD3DHKLPLOT)
4321#        G2frame.dataFrame.Bind(wx.EVT_MENU, onCopySelectedItems, id=wxID_PWDCOPY)
4322    G2frame.dataDisplay = wx.Panel(G2frame.dataFrame)
4323   
4324    mainSizer = wx.BoxSizer(wx.VERTICAL)
4325    mainSizer.Add((5,5),)
4326    wtSizer = wx.BoxSizer(wx.HORIZONTAL)
4327    wtSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' Weight factor: '),0,WACV)
4328    wtval = wx.TextCtrl(G2frame.dataDisplay,-1,'%.3f'%(data[0]['wtFactor']),style=wx.TE_PROCESS_ENTER)
4329    wtval.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
4330    wtval.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
4331    wtSizer.Add(wtval,0,WACV)
4332    mainSizer.Add(wtSizer)
4333    if data[0].get('Dummy'):
4334        simSizer = wx.BoxSizer(wx.HORIZONTAL)
4335        Tmin = min(data[1][0])
4336        Tmax = max(data[1][0])
4337        num = len(data[1][0])
4338        step = (Tmax - Tmin)/(num-1)
4339        t = u'2\u03b8' # 2theta
4340        lbl =  u'Simulation range: {:.2f} to {:.2f} {:s}\nwith {:.4f} steps ({:d} points)'
4341        lbl += u'\n(Edit range resets observed intensities).'
4342        lbl = lbl.format(Tmin,Tmax,t,step,num)
4343        simSizer.Add(wx.StaticText(G2frame.dataDisplay,wx.ID_ANY,lbl),
4344                    0,WACV)
4345        but = wx.Button(G2frame.dataDisplay,wx.ID_ANY,"Edit range")
4346        but.Bind(wx.EVT_BUTTON,onEditSimRange)
4347        simSizer.Add(but,0,WACV)
4348        mainSizer.Add(simSizer)
4349    if 'Nobs' in data[0]:
4350        mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4351            ' Data residual wR: %.3f%% on %d observations'%(data[0]['wR'],data[0]['Nobs'])))
4352        for value in data[0]:
4353            if 'Nref' in value:
4354                mainSizer.Add((5,5),)
4355                pfx = value.split('Nref')[0]
4356                name = data[0].get(pfx.split(':')[0]+'::Name','?')
4357                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,' For phase '+name+':'))
4358                mainSizer.Add(wx.StaticText(G2frame.dataDisplay,-1,
4359                    u' Unweighted phase residuals RF\u00b2: %.3f%%, RF: %.3f%% on %d reflections  '% \
4360                    (data[0][pfx+'Rf^2'],data[0][pfx+'Rf'],data[0][value])))
4361    mainSizer.Add((5,5),)
4362    mainSizer.Layout()   
4363    G2frame.dataDisplay.SetSizer(mainSizer)
4364    Size = mainSizer.Fit(G2frame.dataFrame)
4365    Size[1] += 10
4366    G2frame.dataFrame.setSizePosLeft(Size)
4367    G2frame.PatternTree.SetItemPyData(item,data)
4368    if kind in ['PWDR','SASD']:
4369        G2plt.PlotPatterns(G2frame,plotType=kind,newPlot=True)
4370    elif kind == 'HKLF':
4371        Name = G2frame.PatternTree.GetItemText(item)
4372        phaseName = G2pdG.IsHistogramInAnyPhase(G2frame,Name)
4373        if phaseName:
4374            pId = GetPatternTreeItemId(G2frame,G2frame.root,'Phases')
4375            phaseId =  GetPatternTreeItemId(G2frame,pId,phaseName)
4376            General = G2frame.PatternTree.GetItemPyData(phaseId)['General']
4377            Super = General.get('Super',0)
4378            SuperVec = General.get('SuperVec',[])
4379        else:
4380            Super = 0
4381            SuperVec = []       
4382        refList = data[1]['RefList']
4383        FoMax = np.max(refList.T[5+data[1].get('Super',0)])
4384        controls = {'Type' : 'Fo','ifFc' : True,     
4385            'HKLmax' : [int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))],
4386            'HKLmin' : [int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))],
4387            'FoMax' : FoMax,'Zone' : '001','Layer' : 0,'Scale' : 1.0,'Super':Super,'SuperVec':SuperVec}
4388        G2plt.PlotSngl(G2frame,newPlot=True,Data=controls,hklRef=refList)
4389                 
4390################################################################################
4391#####  Pattern tree routines
4392################################################################################           
4393       
4394def GetPatternTreeDataNames(G2frame,dataTypes):
4395    '''Needs a doc string
4396    '''
4397    names = []
4398    item, cookie = G2frame.PatternTree.GetFirstChild(G2frame.root)       
4399    while item:
4400        name = G2frame.PatternTree.GetItemText(item)
4401        if name[:4] in dataTypes:
4402            names.append(name)
4403        item, cookie = G2frame.PatternTree.GetNextChild(G2frame.root, cookie)
4404    return names
4405                         
4406def GetPatternTreeItemId(G2frame, parentId, itemText):
4407    '''Needs a doc string
4408    '''
4409    item, cookie = G2frame.PatternTree.GetFirstChild(parentId)
4410    while item:
4411        if G2frame.PatternTree.GetItemText(item) == itemText:
4412            return item
4413        item, cookie = G2frame.PatternTree.GetNextChild(parentId, cookie)
4414    return 0               
4415
4416def MovePatternTreeToGrid(G2frame,item):
4417    '''Called from GSASII.OnPatternTreeSelChanged when a item is selected on the tree
4418    '''
4419   
4420#    print G2frame.PatternTree.GetItemText(item)
4421   
4422    oldPage = None # will be set later if already on a Phase item
4423    if G2frame.dataFrame:
4424        SetDataMenuBar(G2frame)
4425        if G2frame.dataFrame.GetLabel() == 'Comments':
4426            try:
4427                data = [G2frame.dataDisplay.GetValue()]
4428                G2frame.dataDisplay.Clear() 
4429                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Comments')
4430                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4431            except:     #clumsy but avoids dead window problem when opening another project
4432                pass
4433        elif G2frame.dataFrame.GetLabel() == 'Notebook':
4434            try:
4435                data = [G2frame.dataDisplay.GetValue()]
4436                G2frame.dataDisplay.Clear() 
4437                Id = GetPatternTreeItemId(G2frame,G2frame.root, 'Notebook')
4438                if Id: G2frame.PatternTree.SetItemPyData(Id,data)
4439            except:     #clumsy but avoids dead window problem when opening another project
4440                pass
4441        elif 'Phase Data for' in G2frame.dataFrame.GetLabel():
4442            if G2frame.dataDisplay: 
4443                oldPage = G2frame.dataDisplay.GetSelection()
4444        G2frame.dataFrame.Clear()
4445        G2frame.dataFrame.SetLabel('')
4446    else:
4447        #create the frame for the data item window
4448        G2frame.dataFrame = DataFrame(parent=G2frame.mainPanel,frame=G2frame)
4449        G2frame.dataFrame.PhaseUserSize = None
4450       
4451    G2frame.dataFrame.Raise()           
4452    G2frame.PickId = 0
4453    parentID = G2frame.root
4454    #for i in G2frame.ExportPattern: i.Enable(False)
4455    defWid = [250,150]
4456    if item != G2frame.root:
4457        parentID = G2frame.PatternTree.GetItemParent(item)
4458    if G2frame.PatternTree.GetItemParent(item) == G2frame.root:
4459        G2frame.PatternId = item
4460        G2frame.PickId = item
4461        if G2frame.PatternTree.GetItemText(item) == 'Notebook':
4462            SetDataMenuBar(G2frame,G2frame.dataFrame.DataNotebookMenu)
4463            G2frame.PatternId = 0
4464            #for i in G2frame.ExportPattern: i.Enable(False)
4465            data = G2frame.PatternTree.GetItemPyData(item)
4466            UpdateNotebook(G2frame,data)
4467        elif G2frame.PatternTree.GetItemText(item) == 'Controls':
4468            G2frame.PatternId = 0
4469            #for i in G2frame.ExportPattern: i.Enable(False)
4470            data = G2frame.PatternTree.GetItemPyData(item)
4471            if not data:           #fill in defaults
4472                data = copy.copy(G2obj.DefaultControls)    #least squares controls
4473                G2frame.PatternTree.SetItemPyData(item,data)                             
4474            for i in G2frame.Refine: i.Enable(True)
4475            G2frame.EnableSeqRefineMenu()
4476            UpdateControls(G2frame,data)
4477        elif G2frame.PatternTree.GetItemText(item) == 'Sequential results':
4478            data = G2frame.PatternTree.GetItemPyData(item)
4479            UpdateSeqResults(G2frame,data)
4480        elif G2frame.PatternTree.GetItemText(item) == 'Covariance':
4481            data = G2frame.PatternTree.GetItemPyData(item)
4482            G2frame.dataFrame.setSizePosLeft(defWid)
4483            text = ''
4484            if 'Rvals' in data:
4485                Nvars = len(data['varyList'])
4486                Rvals = data['Rvals']
4487                text = '\nFinal residuals: \nwR = %.3f%% \nchi**2 = %.1f \nGOF = %.2f'%(Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
4488                text += '\nNobs = %d \nNvals = %d'%(Rvals['Nobs'],Nvars)
4489                if 'lamMax' in Rvals:
4490                    text += '\nlog10 MaxLambda = %.1f'%(np.log10(Rvals['lamMax']))
4491            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4492                value='See plot window for covariance display'+text,style=wx.TE_MULTILINE)
4493            G2plt.PlotCovariance(G2frame,data)
4494        elif G2frame.PatternTree.GetItemText(item) == 'Constraints':
4495            data = G2frame.PatternTree.GetItemPyData(item)
4496            G2cnstG.UpdateConstraints(G2frame,data)
4497        elif G2frame.PatternTree.GetItemText(item) == 'Rigid bodies':
4498            data = G2frame.PatternTree.GetItemPyData(item)
4499            G2cnstG.UpdateRigidBodies(G2frame,data)
4500        elif G2frame.PatternTree.GetItemText(item) == 'Restraints':
4501            data = G2frame.PatternTree.GetItemPyData(item)
4502            Phases = G2frame.GetPhaseData()
4503            phase = ''
4504            phaseName = ''
4505            if Phases:
4506                phaseName = Phases.keys()[0]
4507            G2frame.dataFrame.setSizePosLeft(defWid)
4508            G2restG.UpdateRestraints(G2frame,data,Phases,phaseName)
4509        elif 'IMG' in G2frame.PatternTree.GetItemText(item):
4510            G2frame.Image = item
4511            G2frame.dataFrame.SetTitle('Image Data')
4512            data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId( \
4513                G2frame,item,'Image Controls'))
4514            G2imG.UpdateImageData(G2frame,data)
4515            G2plt.PlotImage(G2frame,newPlot=True)
4516        elif 'PKS' in G2frame.PatternTree.GetItemText(item):
4517            G2plt.PlotPowderLines(G2frame)
4518        elif 'PWDR' in G2frame.PatternTree.GetItemText(item):
4519            #for i in G2frame.ExportPattern: i.Enable(True)
4520            if G2frame.EnablePlot:
4521                UpdatePWHKPlot(G2frame,'PWDR',item)
4522        elif 'SASD' in G2frame.PatternTree.GetItemText(item):
4523            #for i in G2frame.ExportPattern: i.Enable(True)
4524            if G2frame.EnablePlot:
4525                UpdatePWHKPlot(G2frame,'SASD',item)
4526        elif 'HKLF' in G2frame.PatternTree.GetItemText(item):
4527            G2frame.Sngl = True
4528            UpdatePWHKPlot(G2frame,'HKLF',item)
4529        elif 'PDF' in G2frame.PatternTree.GetItemText(item):
4530            G2frame.PatternId = item
4531            for i in G2frame.ExportPDF: i.Enable(True)
4532            G2plt.PlotISFG(G2frame,type='S(Q)')
4533        elif G2frame.PatternTree.GetItemText(item) == 'Phases':
4534            G2frame.dataFrame.setSizePosLeft(defWid)
4535            wx.TextCtrl(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize(),
4536                value='Select one phase to see its parameters')           
4537    elif 'I(Q)' in G2frame.PatternTree.GetItemText(item):
4538        G2frame.PickId = item
4539        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4540        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4541        G2pdG.UpdatePDFGrid(G2frame,data)
4542        G2plt.PlotISFG(G2frame,type='I(Q)',newPlot=True)
4543    elif 'S(Q)' in G2frame.PatternTree.GetItemText(item):
4544        G2frame.PickId = item
4545        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4546        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4547        G2pdG.UpdatePDFGrid(G2frame,data)
4548        G2plt.PlotISFG(G2frame,type='S(Q)',newPlot=True)
4549    elif 'F(Q)' in G2frame.PatternTree.GetItemText(item):
4550        G2frame.PickId = item
4551        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4552        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4553        G2pdG.UpdatePDFGrid(G2frame,data)
4554        G2plt.PlotISFG(G2frame,type='F(Q)',newPlot=True)
4555    elif 'G(R)' in G2frame.PatternTree.GetItemText(item):
4556        G2frame.PickId = item
4557        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4558        data = G2frame.PatternTree.GetItemPyData(GetPatternTreeItemId(G2frame,G2frame.PatternId,'PDF Controls'))
4559        G2pdG.UpdatePDFGrid(G2frame,data)
4560        G2plt.PlotISFG(G2frame,type='G(R)',newPlot=True)           
4561    elif G2frame.PatternTree.GetItemText(parentID) == 'Phases':
4562        G2frame.PickId = item
4563        data = G2frame.PatternTree.GetItemPyData(item)
4564        G2phG.UpdatePhaseData(G2frame,item,data,oldPage)
4565    elif G2frame.PatternTree.GetItemText(item) == 'Comments':
4566        SetDataMenuBar(G2frame,G2frame.dataFrame.DataCommentsMenu)
4567        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4568        G2frame.PickId = item
4569        data = G2frame.PatternTree.GetItemPyData(item)
4570        UpdateComments(G2frame,data)
4571    elif G2frame.PatternTree.GetItemText(item) == 'Image Controls':
4572        G2frame.dataFrame.SetTitle('Image Controls')
4573        G2frame.PickId = item
4574        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4575        masks = G2frame.PatternTree.GetItemPyData(
4576            GetPatternTreeItemId(G2frame,G2frame.Image, 'Masks'))
4577        data = G2frame.PatternTree.GetItemPyData(item)
4578        G2imG.UpdateImageControls(G2frame,data,masks)
4579        G2plt.PlotImage(G2frame)
4580    elif G2frame.PatternTree.GetItemText(item) == 'Masks':
4581        G2frame.dataFrame.SetTitle('Masks')
4582        G2frame.PickId = item
4583        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4584        data = G2frame.PatternTree.GetItemPyData(item)
4585        G2imG.UpdateMasks(G2frame,data)
4586        G2plt.PlotImage(G2frame)
4587    elif G2frame.PatternTree.GetItemText(item) == 'Stress/Strain':
4588        G2frame.dataFrame.SetTitle('Stress/Strain')
4589        G2frame.PickId = item
4590        G2frame.Image = G2frame.PatternTree.GetItemParent(item)
4591        data = G2frame.PatternTree.GetItemPyData(item)
4592        G2plt.PlotImage(G2frame)
4593        G2plt.PlotStrain(G2frame,data,newPlot=True)
4594        G2imG.UpdateStressStrain(G2frame,data)
4595    elif G2frame.PatternTree.GetItemText(item) == 'PDF Controls':
4596        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4597        for i in G2frame.ExportPDF: i.Enable(True)
4598        G2frame.PickId = item
4599        data = G2frame.PatternTree.GetItemPyData(item)
4600        G2pdG.UpdatePDFGrid(G2frame,data)
4601        G2plt.PlotISFG(G2frame,type='I(Q)')
4602        G2plt.PlotISFG(G2frame,type='S(Q)')
4603        G2plt.PlotISFG(G2frame,type='F(Q)')
4604        G2plt.PlotISFG(G2frame,type='G(R)')
4605    elif G2frame.PatternTree.GetItemText(item) == 'Peak List':
4606        G2frame.PatternId = G2frame.PatternTree.GetItemParent(item)
4607        for i in G2frame.ExportPeakList: i.Enable(True)
4608        G2frame.PickId = item
4609        data = G2frame.PatternTree.GetItemPyData(item)
4610#patch
4611        if 'list' in str(type(data)):
4612            data = {'peaks':data,'sigDict':{}}
4613            G2frame.PatternTree.SetItemPyData(item,data)
4614#end patch
4615        G2pdG.UpdatePeakGrid(G2frame,data)
4616        G2plt.PlotPatterns(G2frame)
4617    elif G2frame.PatternTree.GetItemText(item) == 'Background':
4618        G2frame.PatternId = G2frame.PatternTree.GetItemParent