# -*- coding: utf-8 -*-
#GSASIIctrlGUI - Custom GSAS-II GUI controls
########### SVN repository information ###################
# $Date: 2024-04-12 21:06:19 -0500 (Fri, 12 Apr 2024) $
# $Author: toby $
# $Revision: 5776 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIctrlGUI.py $
# $Id: GSASIIctrlGUI.py 5776 2024-04-13 02:06:19Z toby $
########### SVN repository information ###################
'''Documentation for all the routines in module :mod:`GSASIIctrlGUI`
follows.
'''
# Documentation moved to doc/source/GSASIIGUIr.rst
#
from __future__ import division, print_function
import os
import sys
import platform
try:
import wx
import wx.grid as wg
# import wx.wizard as wz
import wx.aui
import wx.lib.scrolledpanel as wxscroll
import wx.html # could postpone this for quicker startup
import wx.lib.mixins.listctrl as listmix
import wx.richtext as wxrt
import wx.lib.filebrowsebutton as wxfilebrowse
import matplotlib as mpl
import matplotlib.figure as mplfig
except ImportError:
print('ImportError for wx/mpl in GSASIIctrlGUI: ignore if docs build')
import time
import glob
import copy
#import ast
import random as ran
import numpy as np
import matplotlib as mpl
try:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
except ImportError:
from matplotlib.backends.backend_wx import FigureCanvas as Canvas
import GSASIIpath
GSASIIpath.SetVersionNumber("$Revision: 5776 $")
import GSASIIdataGUI as G2gd
import GSASIIpwdGUI as G2pdG
import GSASIIspc as G2spc
import GSASIIlog as log
import GSASIIobj as G2obj
import GSASIIfiles as G2fil
import GSASIIElem as G2elem
import GSASIIscriptable as G2sc
import GSASIIpwd as G2pwd
import GSASIIlattice as G2lat
import GSASIImath as G2mth
import GSASIIstrMain as G2stMn
import GSASIIIO as G2IO
import config_example
from tutorialIndex import tutorialIndex
if sys.version_info[0] >= 3:
unicode = str
basestring = str
# Define a short names for convenience
DULL_YELLOW = (230,230,190)
# Don't depend on wx, for scriptable
try:
WACV = wx.ALIGN_CENTER_VERTICAL
wxaui_NB_TOPSCROLL = wx.aui.AUI_NB_TOP | wx.aui.AUI_NB_SCROLL_BUTTONS
except: # Don't depend on GUI
wxaui_NB_TOPSCROLL = None
try:
wx.NewIdRef
wx.NewId = wx.NewIdRef
except AttributeError:
pass
try: #phoenix
wxValidator = wx.Validator
except AttributeError: #classic - i.e. old
wxValidator = wx.pyValidator
#### Fixed definitions for wx Ids ################################################################################
[docs]
def Define_wxId(*args):
'''routine to create unique global wx Id symbols in this module.
'''
for arg in args:
if GSASIIpath.GetConfigValue('debug') and not arg.startswith('wxID_'):
print ('DBG_Problem in name'+arg)
if arg in globals():
if GSASIIpath.GetConfigValue('debug'): print ('DBG_'+arg+'already defined')
continue
exec('global '+arg+';'+arg+' = wx.NewId()')
#### Tree Control ################################################################################
[docs]
class G2TreeCtrl(wx.TreeCtrl):
'''Create a wrapper around the standard TreeCtrl so we can "wrap"
various events.
This logs when a tree item is selected (in :meth:`onSelectionChanged`)
This also wraps lists and dicts pulled out of the tree to track where
they were retrieved from.
'''
#def SelectItem(self,event):
# print 'Select Item'
# import GSASIIobj as G2obj
# G2obj.HowDidIgetHere()
# wx.TreeCtrl.SelectItem(self,event)
def __init__(self,parent=None,*args,**kwargs):
super(self.__class__,self).__init__(parent=parent,*args,**kwargs)
self.G2frame = parent.GetTopLevelParent()
self.root = self.AddRoot('Loaded Data: ')
self.SelectionChanged = None
self.textlist = None
log.LogInfo['Tree'] = self
def _getTreeItemsList(self,item):
'''Get the full tree hierarchy from a reference to a tree item.
Note that this effectively hard-codes phase and histogram names in the
returned list. We may want to make these names relative in the future.
'''
textlist = [self.GetItemText(item)]
parent = self.GetItemParent(item)
while parent:
if parent == self.root: break
textlist.insert(0,self.GetItemText(parent))
parent = self.GetItemParent(parent)
return textlist
[docs]
def GetItemPyData(self,treeId):
if 'phoenix' in wx.version():
return wx.TreeCtrl.GetItemData(self,treeId)
else:
return wx.TreeCtrl.GetItemPyData(self,treeId)
[docs]
def SetItemPyData(self,treeId,data):
if 'phoenix' in wx.version():
return wx.TreeCtrl.SetItemData(self,treeId,data)
else:
return wx.TreeCtrl.SetItemPyData(self,treeId,data)
def UpdateSelection(self):
TId = self.GetFocusedItem()
self.SelectItem(self.root)
self.SelectItem(TId)
# def onSelectionChanged(self,event):
# '''Log each press on a tree item here.
# '''
# if not self.G2frame.treePanel:
# return
# if self.SelectionChanged:
# textlist = self._getTreeItemsList(event.GetItem())
# if log.LogInfo['Logging'] and event.GetItem() != self.root:
# textlist[0] = self.GetRelativeHistNum(textlist[0])
# if textlist[0] == "Phases" and len(textlist) > 1:
# textlist[1] = self.GetRelativePhaseNum(textlist[1])
# log.MakeTreeLog(textlist)
# if textlist == self.textlist:
# return #same as last time - don't get it again
# self.textlist = textlist
# self.SelectionChanged(event)
# def Bind(self,eventtype,handler,*args,**kwargs):
# '''Override the Bind() function so that page change events can be trapped
# '''
# if eventtype == wx.EVT_TREE_SEL_CHANGED:
# self.SelectionChanged = handler
# wx.TreeCtrl.Bind(self,eventtype,self.onSelectionChanged)
# return
# wx.TreeCtrl.Bind(self,eventtype,handler,*args,**kwargs)
# commented out, disables Logging
# def GetItemPyData(self,*args,**kwargs):
# '''Override the standard method to wrap the contents
# so that the source can be logged when changed
# '''
# data = super(self.__class__,self).GetItemPyData(*args,**kwargs)
# textlist = self._getTreeItemsList(args[0])
# if type(data) is dict:
# return log.dictLogged(data,textlist)
# if type(data) is list:
# return log.listLogged(data,textlist)
# if type(data) is tuple: #N.B. tuples get converted to lists
# return log.listLogged(list(data),textlist)
# return data
[docs]
def GetRelativeHistNum(self,histname):
'''Returns list with a histogram type and a relative number for that
histogram, or the original string if not a histogram
'''
histtype = histname.split()[0]
if histtype != histtype.upper(): # histograms (only) have a keyword all in caps
return histname
item, cookie = self.GetFirstChild(self.root)
i = 0
while item:
itemtext = self.GetItemText(item)
if itemtext == histname:
return histtype,i
elif itemtext.split()[0] == histtype:
i += 1
item, cookie = self.GetNextChild(self.root, cookie)
else:
raise Exception("Histogram not found: "+histname)
[docs]
def ConvertRelativeHistNum(self,histtype,histnum):
'''Converts a histogram type and relative histogram number to a
histogram name in the current project
'''
item, cookie = self.GetFirstChild(self.root)
i = 0
while item:
itemtext = self.GetItemText(item)
if itemtext.split()[0] == histtype:
if i == histnum: return itemtext
i += 1
item, cookie = self.GetNextChild(self.root, cookie)
else:
raise Exception("Histogram #'+str(histnum)+' of type "+histtype+' not found')
[docs]
def GetRelativePhaseNum(self,phasename):
'''Returns a phase number if the string matches a phase name
or else returns the original string
'''
item, cookie = self.GetFirstChild(self.root)
while item:
itemtext = self.GetItemText(item)
if itemtext == "Phases":
parent = item
item, cookie = self.GetFirstChild(parent)
i = 0
while item:
itemtext = self.GetItemText(item)
if itemtext == phasename:
return i
item, cookie = self.GetNextChild(parent, cookie)
i += 1
else:
return phasename # not a phase name
item, cookie = self.GetNextChild(self.root, cookie)
else:
raise Exception("No phases found ")
[docs]
def ConvertRelativePhaseNum(self,phasenum):
'''Converts relative phase number to a phase name in
the current project
'''
item, cookie = self.GetFirstChild(self.root)
while item:
itemtext = self.GetItemText(item)
if itemtext == "Phases":
parent = item
item, cookie = self.GetFirstChild(parent)
i = 0
while item:
if i == phasenum:
return self.GetItemText(item)
item, cookie = self.GetNextChild(parent, cookie)
i += 1
else:
raise Exception("Phase "+str(phasenum)+" not found")
item, cookie = self.GetNextChild(self.root, cookie)
else:
raise Exception("No phases found ")
[docs]
def GetImageLoc(self,TreeId):
'''Get Image data from the Tree. Handles cases where the
image name is specified, as well as where the image file name is
a tuple containing the image file and an image number
'''
size,imagefile = self.GetItemPyData(TreeId)
if type(imagefile) is tuple or type(imagefile) is list:
return size,imagefile[0],imagefile[1]
else:
return size,imagefile,None
[docs]
def UpdateImageLoc(self,TreeId,imagefile):
'''Saves a new imagefile name in the Tree. Handles cases where the
image name is specified, as well as where the image file name is
a tuple containing the image file and an image number
'''
idata = self.GetItemPyData(TreeId)
if type(idata[1]) is tuple or type(idata[1]) is list:
idata[1] = list(idata[1])
idata[1][0] = [imagefile,idata[1][1]]
else:
idata[1] = imagefile
[docs]
def SaveExposedItems(self):
'''Traverse the top level tree items and save names of exposed (expanded) tree items.
Done before a refinement.
'''
self.ExposedItems = []
item, cookie = self.GetFirstChild(self.root)
while item:
name = self.GetItemText(item)
if self.IsExpanded(item): self.ExposedItems.append(name)
item, cookie = self.GetNextChild(self.root, cookie)
# print 'exposed:',self.ExposedItems
[docs]
def RestoreExposedItems(self):
'''Traverse the top level tree items and restore exposed (expanded) tree items
back to their previous state (done after a reload of the tree after a refinement)
'''
item, cookie = self.GetFirstChild(self.root)
while item:
name = self.GetItemText(item)
if name in self.ExposedItems: self.Expand(item)
item, cookie = self.GetNextChild(self.root, cookie)
[docs]
def ReadOnlyTextCtrl(*args,**kwargs):
'''Create a read-only TextCtrl for display of constants
This is probably not ideal as it mixes visual cues, but it does look nice.
Addresses 4.2 bug where TextCtrl has no default size
'''
kwargs['style'] = wx.TE_READONLY
if wx.__version__.startswith('4.2') and 'size' not in kwargs:
kwargs['size'] = (105, 22)
Txt = wx.TextCtrl(*args,**kwargs)
Txt.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
return Txt
#### TextCtrl that stores input as entered with optional validation ################################################################################
[docs]
class ValidatedTxtCtrl(wx.TextCtrl):
'''Create a TextCtrl widget that uses a validator to prevent the
entry of inappropriate characters and changes color to highlight
when invalid input is supplied. As valid values are typed,
they are placed into the dict or list where the initial value
came from. The type of the initial value must be int,
float or str or None (see :obj:`key` and :obj:`typeHint`);
this type (or the one in :obj:`typeHint`) is preserved.
Float values can be entered in the TextCtrl as numbers or also
as algebraic expressions using operators + - / \\* () and \\*\\*,
in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
as well as appreviations s, sin, c, cos, t, tan and sq.
:param wx.Panel parent: name of panel or frame that will be
the parent to the TextCtrl. Can be None.
:param dict/list loc: the dict or list with the initial value to be
placed in the TextCtrl.
:param int/str key: the dict key or the list index for the value to be
edited by the TextCtrl. The ``loc[key]`` element must exist, but may
have value None. If None, the type for the element is taken from
:obj:`typeHint` and the value for the control is set initially
blank (and thus invalid.) This is a way to specify a field without a
default value: a user must set a valid value.
If the value is not None, it must have a base
type of int, float, str or unicode; the TextCrtl will be initialized
from this value.
:param list nDig: number of digits, places and optionally the format
([nDig,nPlc,fmt]) after decimal to use for display of float. The format
is either 'f' (default) or 'g'. Alternately, None can be specified which
causes numbers to be displayed with approximately 5 significant figures
for floats. If this is specified, then :obj:`typeHint` = float becomes the
default.
(Default=None).
:param bool notBlank: if True (default) blank values are invalid
for str inputs.
:param number xmin: minimum allowed valid value. If None (default) the
lower limit is unbounded.
NB: test in NumberValidator is val >= xmin not val > xmin
:param number xmax: maximum allowed valid value. If None (default) the
upper limit is unbounded
NB: test in NumberValidator is val <= xmax not val < xmax
:param list exclLim: if True exclude min/max value ([exclMin,exclMax]);
(Default=[False,False])
:param function OKcontrol: specifies a function or method that will be
called when the input is validated. The called function is supplied
with one argument which is False if the TextCtrl contains an invalid
value and True if the value is valid.
Note that this function should check all values
in the dialog when True, since other entries might be invalid.
The default for this is None, which indicates no function should
be called.
:param function OnLeave: specifies a function or method that will be
called when the focus for the control is lost.
The called function is supplied with (at present) three keyword arguments:
* invalid: (*bool*) True if the value for the TextCtrl is invalid
* value: (*int/float/str*) the value contained in the TextCtrl
* tc: (*wx.TextCtrl*) the TextCtrl object
The number of keyword arguments may be increased in the future should needs arise,
so it is best to code these functions with a \\*\\*kwargs argument so they will
continue to run without errors
The default for OnLeave is None, which indicates no function should
be called.
:param type typeHint: the value of typeHint should be int, float or str (or None).
The value for this will override the initial type taken from value
for the dict/list element ``loc[key]`` if not None and thus specifies the
type for input to the TextCtrl.
Defaults as None, which is ignored, unless :obj:`nDig` is specified in which
case the default is float.
:param bool CIFinput: for str input, indicates that only printable
ASCII characters may be entered into the TextCtrl. Forces output
to be ASCII rather than Unicode. For float and int input, allows
use of a single '?' or '.' character as valid input.
:param dict OnLeaveArgs: a dict with keyword args that are passed to
the :attr:`OnLeave` function. Defaults to ``{}``
:param bool ASCIIonly: if set as True will remove unicode characters from
strings
:param (other): other optional keyword parameters for the
wx.TextCtrl widget such as size or style may be specified.
'''
def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None,
OKcontrol=None,OnLeave=None,typeHint=None,CIFinput=False,exclLim=[False,False],
OnLeaveArgs={}, ASCIIonly=False,
min=None, max=None, # patch: remove this eventually
**kw):
# save passed values needed outside __init__
self.result = loc
self.key = key
self.nDig = nDig
self.OKcontrol=OKcontrol
self.OnLeave = OnLeave
self.OnLeaveArgs = OnLeaveArgs
self.CIFinput = CIFinput
self.notBlank = notBlank
self.ASCIIonly = ASCIIonly
# patch: remove this when min & max are no longer used to call this
if min is not None:
xmin=min
if GSASIIpath.GetConfigValue('debug'):
print('Call to ValidatedTxtCtrl using min (change to xmin) here:')
G2obj.HowDidIgetHere(True)
if max is not None:
xmax=max
if GSASIIpath.GetConfigValue('debug'):
print('Call to ValidatedTxtCtrl using max (change to xmax) here:')
G2obj.HowDidIgetHere(True)
# end patch
# initialization
self.invalid = False # indicates if the control has invalid contents
self.evaluated = False # set to True when the validator recognizes an expression
self.timer = None # tracks pending updates for expressions in float textctrls
self.delay = 5000 # delay for timer update (5 sec)
self.type = str
val = loc[key]
if 'style' in kw: # add a "Process Enter" to style
kw['style'] |= wx.TE_PROCESS_ENTER
else:
kw['style'] = wx.TE_PROCESS_ENTER
if 'size' not in kw: # wx 4.2.0 needs a size
kw['size'] = (105,-1)
if typeHint is not None:
self.type = typeHint
elif nDig is not None:
self.type = float
elif 'int' in str(type(val)):
self.type = int
elif 'float' in str(type(val)):
self.type = float
elif isinstance(val,str) or isinstance(val,unicode):
self.type = str
elif val is None:
raise Exception("ValidatedTxtCtrl error: value of "+str(key)+
" element is None and typeHint not defined as int or float")
else:
raise Exception("ValidatedTxtCtrl error: Unknown element ("+str(key)+
") type: "+str(type(val)))
if self.type is int:
wx.TextCtrl.__init__(self,parent,wx.ID_ANY,
validator=NumberValidator(int,result=loc,key=key,xmin=xmin,xmax=xmax,
exclLim=exclLim,OKcontrol=OKcontrol,CIFinput=CIFinput),**kw)
if val is not None:
self._setValue(val)
else: # no default is invalid for a number
self.invalid = True
self._IndicateValidity()
elif self.type is float:
wx.TextCtrl.__init__(self,parent,wx.ID_ANY,
validator=NumberValidator(float,result=loc,key=key,xmin=xmin,xmax=xmax,
exclLim=exclLim,OKcontrol=OKcontrol,CIFinput=CIFinput),**kw)
if val is not None:
self._setValue(val)
else:
self.invalid = True
self._IndicateValidity()
else:
if self.CIFinput:
wx.TextCtrl.__init__(
self,parent,wx.ID_ANY,
validator=ASCIIValidator(result=loc,key=key),
**kw)
else:
wx.TextCtrl.__init__(self,parent,wx.ID_ANY,**kw)
if val is not None:
self.SetValue(val)
if notBlank:
self.Bind(wx.EVT_CHAR,self._onStringKey)
self.ShowStringValidity() # test if valid input
else:
self.invalid = False
self.Bind(wx.EVT_CHAR,self._GetStringValue)
# When the mouse is moved away or the widget loses focus,
# display the last saved value, if an expression
self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus)
# patch for wx 2.9 on Mac
i,j= wx.__version__.split('.')[0:2]
if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
[docs]
def SetValue(self,val):
if self.result is not None: # note that this bypasses formatting
self.result[self.key] = val
log.LogVarChange(self.result,self.key)
self._setValue(val)
def _setValue(self,val,show=True):
'''Check the validity of an int or float value and convert to a str.
Possibly format it. If show is True, display the formatted value in
the Text widget.
'''
self.invalid = False
if self.type is int:
try:
if int(val) != val:
self.invalid = True
else:
val = int(val)
except:
if self.CIFinput and (val == '?' or val == '.'):
pass
else:
self.invalid = True
if show and not self.invalid: wx.TextCtrl.SetValue(self,str(val))
elif self.type is float:
try:
if type(val) is str: val = val.replace(',','.')
val = float(val) # convert strings, if needed
except:
if self.CIFinput and (val == '?' or val == '.'):
pass
else:
self.invalid = True
if self.nDig and show and not self.invalid:
wx.TextCtrl.SetValue(self,str(G2fil.FormatValue(val,self.nDig)))
self.evaluated = False # expression has been recast as value, reset flag
elif show and not self.invalid:
wx.TextCtrl.SetValue(self,str(G2fil.FormatSigFigs(val)).rstrip('0'))
self.evaluated = False # expression has been recast as value, reset flag
else:
if self.ASCIIonly:
s = ''
for c in val:
if ord(c) < 128:
s += c
else:
s += '!'
if val != s:
val = s
show = True
if show:
try:
wx.TextCtrl.SetValue(self,str(val))
except:
wx.TextCtrl.SetValue(self,val)
self.ShowStringValidity() # test if valid input
return
self._IndicateValidity()
if self.OKcontrol:
self.OKcontrol(not self.invalid)
[docs]
def OnKeyDown(self,event):
'Special callback for wx 2.9+ on Mac where backspace is not processed by validator'
key = event.GetKeyCode()
if key in [wx.WXK_BACK, wx.WXK_DELETE]:
if self.Validator: wx.CallAfter(self.Validator.TestValid,self)
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self._onLoseFocus(None)
if event: event.Skip()
if self.timer:
self.timer.Restart(self.delay)
def _onStringKey(self,event):
if event: event.Skip()
if self.invalid: # check for validity after processing the keystroke
wx.CallAfter(self.ShowStringValidity,True) # was invalid
else:
wx.CallAfter(self.ShowStringValidity,False) # was valid
def _IndicateValidity(self):
'Set the control colors to show invalid input'
if self.invalid:
ins = self.GetInsertionPoint()
self.SetForegroundColour("red")
self.SetBackgroundColour("yellow")
self.SetFocus()
self.Refresh() # this selects text on some Linuxes
self.SetSelection(0,0) # unselect
self.SetInsertionPoint(ins) # put insertion point back
else: # valid input
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
self.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
self.Refresh()
def _GetNumValue(self):
'Get and where needed convert string from GetValue into int or float'
try:
val = self.GetValue()
if self.type is int:
val = int(val)
elif self.type is float:
val = float(val)
except:
if self.CIFinput and (val == '?' or val == '.'):
pass
else:
self.invalid = True
return val
[docs]
def ShowStringValidity(self,previousInvalid=True):
'''Check if input is valid. Anytime the input is
invalid, call self.OKcontrol (if defined) because it is fast.
If valid, check for any other invalid entries only when
changing from invalid to valid, since that is slower.
:param bool previousInvalid: True if the TextCtrl contents were
invalid prior to the current change.
'''
val = self.GetValue().strip()
if self.notBlank:
self.invalid = not val
else:
self.invalid = False
self._IndicateValidity()
if self.invalid:
if self.OKcontrol:
self.OKcontrol(False)
elif self.OKcontrol and previousInvalid:
self.OKcontrol(True)
self._SaveStringValue() # always store the result
def _GetStringValue(self,event):
'''Get string input and store.
'''
if event: event.Skip() # process keystroke
wx.CallAfter(self._SaveStringValue)
def _SaveStringValue(self):
val = self.GetValue().strip()
# always store the result
if self.CIFinput and '2' in platform.python_version_tuple()[0]: # Py2/CIF make results ASCII
self.result[self.key] = val.encode('ascii','replace')
else:
self.result[self.key] = val
log.LogVarChange(self.result,self.key)
def _onLeaveWindow(self,event):
'''If the mouse leaves the text box, save the result, if valid,
but (unlike _onLoseFocus) there is a two second delay before
the textbox contents are updated with the value from the formula.
'''
def delayedUpdate():
self.timer = None
try:
self._setValue(self.result[self.key])
except:
pass
if self.type is not str:
if not self.IsModified(): return #ignore mouse crusing
elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str
return
if self.evaluated and not self.invalid: # deal with computed expressions
if self.timer:
self.timer.Restart(self.delay)
else:
self.timer = wx.CallLater(self.delay,delayedUpdate) # this includes a try in case the widget is deleted
if self.invalid: # don't update an invalid expression
if event: event.Skip()
return
self._setValue(self.result[self.key],show=False) # save value quietly
if self.OnLeave:
self.event = event
self.OnLeave(invalid=self.invalid,value=self.result[self.key],
tc=self,**self.OnLeaveArgs)
if event: event.Skip()
def _onLoseFocus(self,event):
'''Enter has been pressed or focus transferred to another control,
Evaluate and update the current control contents
'''
if event: event.Skip()
if self.type is not str:
if not self.IsModified(): return #ignore mouse crusing
elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str
return
if self.evaluated: # deal with computed expressions
if self.invalid: # don't substitute for an invalid expression
return
self._setValue(self.result[self.key])
elif self.result is not None: # show formatted result, as Bob wants
self.result[self.key] = self._GetNumValue()
if not self.invalid: # don't update an invalid expression
self._setValue(self.result[self.key])
if self.OnLeave:
self.event = event
try:
self.OnLeave(invalid=self.invalid,value=self.result[self.key],
tc=self,**self.OnLeaveArgs)
except:
pass
################################################################################
[docs]
class NumberValidator(wxValidator):
'''A validator to be used with a TextCtrl to prevent
entering characters other than digits, signs, and for float
input, a period and exponents.
The value is checked for validity after every keystroke
If an invalid number is entered, the box is highlighted.
If the number is valid, it is saved in result[key]
:param type typ: the base data type. Must be int or float.
:param bool positiveonly: If True, negative integers are not allowed
(default False). This prevents the + or - keys from being pressed.
Used with typ=int; ignored for typ=float.
:param number xmin: Minimum allowed value. If None (default) the
lower limit is unbounded
:param number xmax: Maximum allowed value. If None (default) the
upper limit is unbounded
:param list exclLim: if True exclude xmin/xmax value ([exclMin,exclMax]);
(Default=[False,False])
:param dict/list result: List or dict where value should be placed when valid
:param any key: key to use for result (int for list)
:param function OKcontrol: function or class method to control
an OK button for a window.
Ignored if None (default)
:param bool CIFinput: allows use of a single '?' or '.' character
as valid input.
'''
def __init__(self, typ, positiveonly=False, xmin=None, xmax=None,exclLim=[False,False],
result=None, key=None, OKcontrol=None, CIFinput=False):
'Create the validator'
wxValidator.__init__(self)
# save passed parameters
self.typ = typ
self.positiveonly = positiveonly
self.xmin = xmin
self.xmax = xmax
self.exclLim = exclLim
self.result = result
self.key = key
self.OKcontrol = OKcontrol
self.CIFinput = CIFinput
# set allowed keys by data type
self.Bind(wx.EVT_CHAR, self.OnChar)
if self.typ == int and self.positiveonly:
self.validchars = '0123456789'
elif self.typ == int:
self.validchars = '0123456789+-'
elif self.typ == float:
# allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
# also addition, subtraction, division, multiplication, exponentiation
self.validchars = '0123456789.-+eE/cosindcqrtap()*,'
else:
self.validchars = None
return
if self.CIFinput:
self.validchars += '?.'
[docs]
def Clone(self):
'Create a copy of the validator, a strange, but required component'
return NumberValidator(typ=self.typ,
positiveonly=self.positiveonly,
xmin=self.xmin, xmax=self.xmax,
result=self.result, key=self.key,
OKcontrol=self.OKcontrol,
CIFinput=self.CIFinput)
[docs]
def TransferToWindow(self):
'Needed by validator, strange, but required component'
return True # Prevent wxDialog from complaining.
[docs]
def TransferFromWindow(self):
'Needed by validator, strange, but required component'
return True # Prevent wxDialog from complaining.
[docs]
def TestValid(self,tc):
'''Check if the value is valid by casting the input string
into the current type.
Set the invalid variable in the TextCtrl object accordingly.
If the value is valid, save it in the dict/list where
the initial value was stored, if appropriate.
:param wx.TextCtrl tc: A reference to the TextCtrl that the validator
is associated with.
'''
tc.invalid = False # assume valid
if self.CIFinput:
val = tc.GetValue().strip()
if val == '?' or val == '.':
self.result[self.key] = val
log.LogVarChange(self.result,self.key)
return
try:
val = self.typ(tc.GetValue())
except (ValueError, SyntaxError):
if self.typ is float: # for float values, see if an expression can be evaluated
val = G2fil.FormulaEval(tc.GetValue().replace(',','.'))
if val is None:
tc.invalid = True
return
else:
tc.evaluated = True
else:
tc.invalid = True
return
if self.xmax != None:
if val >= self.xmax and self.exclLim[1]:
tc.invalid = True
elif val > self.xmax:
tc.invalid = True
if self.xmin != None:
if val <= self.xmin and self.exclLim[0]:
tc.invalid = True
elif val < self.xmin:
tc.invalid = True # invalid
if self.key is not None and self.result is not None and not tc.invalid:
self.result[self.key] = val
log.LogVarChange(self.result,self.key)
[docs]
def ShowValidity(self,tc):
'''Set the control colors to show invalid input
:param wx.TextCtrl tc: A reference to the TextCtrl that the validator
is associated with.
'''
if tc.invalid:
ins = tc.GetInsertionPoint()
tc.SetForegroundColour("red")
tc.SetBackgroundColour("yellow")
tc.SetFocus()
tc.Refresh() # this selects text on some Linuxes
tc.SetSelection(0,0) # unselect
tc.SetInsertionPoint(ins) # put insertion point back
return False
else: # valid input
tc.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
tc.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
tc.Refresh()
return True
[docs]
def OnChar(self, event):
'''Called each type a key is pressed
ignores keys that are not allowed for int and float types
'''
key = event.GetKeyCode()
tc = self.GetWindow()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
if tc.invalid:
self.CheckInput(True)
else:
self.CheckInput(False)
if event: event.Skip()
return
if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
if event: event.Skip()
if tc.invalid:
wx.CallAfter(self.CheckInput,True)
else:
wx.CallAfter(self.CheckInput,False)
return
elif chr(key) in self.validchars: # valid char pressed?
if event: event.Skip()
if tc.invalid:
wx.CallAfter(self.CheckInput,True)
else:
wx.CallAfter(self.CheckInput,False)
return
return # Returning without calling event.Skip, which eats the keystroke
################################################################################
[docs]
class ASCIIValidator(wxValidator):
'''A validator to be used with a TextCtrl to prevent
entering characters other than ASCII characters.
The value is checked for validity after every keystroke
If an invalid number is entered, the box is highlighted.
If the number is valid, it is saved in result[key]
:param dict/list result: List or dict where value should be placed when valid
:param any key: key to use for result (int for list)
'''
def __init__(self, result=None, key=None):
'Create the validator'
import string
wxValidator.__init__(self)
# save passed parameters
self.result = result
self.key = key
self.validchars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
self.Bind(wx.EVT_CHAR, self.OnChar)
[docs]
def Clone(self):
'Create a copy of the validator, a strange, but required component'
return ASCIIValidator(result=self.result, key=self.key)
tc = self.GetWindow()
tc.invalid = False # make sure the validity flag is defined in parent
[docs]
def TransferToWindow(self):
'Needed by validator, strange, but required component'
return True # Prevent wxDialog from complaining.
[docs]
def TransferFromWindow(self):
'Needed by validator, strange, but required component'
return True # Prevent wxDialog from complaining.
[docs]
def TestValid(self,tc):
'''Check if the value is valid by casting the input string
into ASCII.
Save it in the dict/list where the initial value was stored
:param wx.TextCtrl tc: A reference to the TextCtrl that the validator
is associated with.
'''
if '2' in platform.python_version_tuple()[0]:
self.result[self.key] = tc.GetValue().encode('ascii','replace')
else:
self.result[self.key] = tc.GetValue()
log.LogVarChange(self.result,self.key)
[docs]
def OnChar(self, event):
'''Called each type a key is pressed
ignores keys that are not allowed for int and float types
'''
key = event.GetKeyCode()
tc = self.GetWindow()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self.TestValid(tc)
if event: event.Skip()
return
if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
if event: event.Skip()
self.TestValid(tc)
return
elif chr(key) in self.validchars: # valid char pressed?
if event: event.Skip()
self.TestValid(tc)
return
return # Returning without calling event.Skip, which eats the keystroke
[docs]
class G2Slider(wx.Slider):
'''Wrapper around wx.Slider widget that implements scaling
Also casts floats as integers to avoid py3.10+ errors
'''
global ci
ci = lambda x: int(x + 0.5) # closest integer
def __init__(self, parent, id=wx.ID_ANY, value=0, minValue=0, maxValue=100, *arg, **kwarg):
wx.Slider.__init__(self, parent, id, ci(value), ci(minValue), ci(maxValue), *arg, **kwarg)
self.iscale = 1
def SetScaling(self,iscale):
self.iscale = iscale
def SetScaledRange(self,xmin,xmax):
self.SetRange(ci(xmin*self.iscale),ci(xmax*self.iscale))
def SetScaledValue(self,value):
wx.Slider.SetValue(self, ci(self.iscale*value))
def GetScaledValue(self):
return self.GetValue()/float(self.iscale)
[docs]
def SetValue(self,value):
wx.Slider.SetValue(self, ci(value))
[docs]
def SetMax(self,xmax):
wx.Slider.SetMax(self,ci(xmax*self.iscale))
[docs]
def SetMin(self,xmin):
wx.Slider.SetMin(self,ci(xmin*self.iscale))
################################################################################
[docs]
def HorizontalLine(sizer,parent):
'''Draws a horizontal line as wide as the window.
'''
if sys.platform == "darwin":
#sizer.Add((-1,2))
line = wx.Panel(parent, size=(-1, 2))
#line.SetBackgroundColour('red')
line.SetBackgroundColour((128,128,128))
sizer.Add(line, 0, wx.EXPAND|wx.ALL, 0)
sizer.Add((-1,5))
else:
line = wx.StaticLine(parent, size=(-1,3), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.EXPAND|wx.ALL, 5)
################################################################################
################################################################################
[docs]
class EnumSelector(wx.ComboBox):
'''A customized :class:`wxpython.ComboBox` that selects items from a list
of choices, but sets a dict (list) entry to the corresponding
entry from the input list of values.
:param wx.Panel parent: the parent to the :class:`~wxpython.ComboBox` (usually a
frame or panel)
:param dct: a dict or list to contain the value set
for the :class:`~wxpython.ComboBox`.
:param item: the dict key (or list index) where ``dct[item]`` will
be set to the value selected in the :class:`~wxpython.ComboBox`. Also, dct[item]
contains the starting value shown in the widget. If the value
does not match an entry in :data:`values`, the first value
in :data:`choices` is used as the default, but ``dct[item]`` is
not changed.
:param list choices: a list of choices to be displayed to the
user such as
::
["default","option 1","option 2",]
Note that these options will correspond to the entries in
:data:`values` (if specified) item by item.
:param list values: a list of values that correspond to
the options in :data:`choices`, such as
::
[0,1,2]
The default for :data:`values` is to use the same list as
specified for :data:`choices`.
:param function OnChange: an optional routine that will be called
when the
:class:`~wxpython.ComboBox` can be specified.
:param (other): additional keyword arguments accepted by
:class:`~wxpython.ComboBox` can be specified.
'''
def __init__(self,parent,dct,item,choices,values=None,OnChange=None,**kw):
if values is None:
values = choices
if dct[item] in values:
i = values.index(dct[item])
else:
i = 0
startval = choices[i]
wx.ComboBox.__init__(self,parent,wx.ID_ANY,startval,
choices = choices,
style=wx.CB_DROPDOWN|wx.CB_READONLY,
**kw)
self.choices = choices
self.values = values
self.dct = dct
self.item = item
self.OnChange = OnChange
self.Bind(wx.EVT_COMBOBOX, self.onSelection)
def onSelection(self,event):
# respond to a selection by setting the enum value in the CIF dictionary
if self.GetValue() in self.choices: # should always be true!
self.dct[self.item] = self.values[self.choices.index(self.GetValue())]
else:
self.dct[self.item] = self.values[0] # unknown
if self.OnChange: self.OnChange(event)
################################################################################
#### Custom checkbox that saves values into dict/list as used ##############################################################
[docs]
class G2CheckBox(wx.CheckBox):
'''A customized version of a CheckBox that automatically initializes
the control to a supplied list or dict entry and updates that
entry as the widget is used.
:param wx.Panel parent: name of panel or frame that will be
the parent to the widget. Can be None.
:param str label: text to put on check button
:param dict/list loc: the dict or list with the initial value to be
placed in the CheckBox.
:param int/str key: the dict key or the list index for the value to be
edited by the CheckBox. The ``loc[key]`` element must exist.
The CheckBox will be initialized from this value.
If the value is anything other that True (or 1), it will be taken as
False.
:param function OnChange: specifies a function or method that will be
called when the CheckBox is changed (Default is None).
The called function is supplied with one argument, the calling event.
'''
def __init__(self,parent,label,loc,key,OnChange=None):
wx.CheckBox.__init__(self,parent,id=wx.ID_ANY,label=label)
self.loc = loc
self.key = key
self.OnChange = OnChange
self.SetValue(self.loc[self.key]==True)
self.Bind(wx.EVT_CHECKBOX, self._OnCheckBox)
def _OnCheckBox(self,event):
self.loc[self.key] = self.GetValue()
log.LogVarChange(self.loc,self.key)
if self.OnChange: self.OnChange(event)
[docs]
def G2CheckBoxFrontLbl(parent,label,loc,key,OnChange=None):
'''A customized version of a CheckBox that automatically initializes
the control to a supplied list or dict entry and updates that
entry as the widget is used. Same as :class:`G2CheckBox` except the
label is placed before the CheckBox and returns a sizer rather than the
G2CheckBox.
If the CheckBox is needed, reference Sizer.myCheckBox.
'''
Sizer = wx.BoxSizer(wx.HORIZONTAL)
Sizer.Add(wx.StaticText(parent,label=label),0,WACV)
checkBox = G2CheckBox(parent,'',loc,key,OnChange)
Sizer.Add(checkBox,0,WACV)
Sizer.myCheckBox = checkBox
return Sizer
#### Commonly used dialogs ################################################################################
################################################################################
############################################### Multichoice Dialog with set all, toggle & filter options
[docs]
class G2MultiChoiceDialog(wx.Dialog):
'''A dialog similar to wx.MultiChoiceDialog except that buttons are
added to set all choices and to toggle all choices and a filter is
available to select from available entries. Note that if multiple
entries are placed in the filter box separated by spaces, all
of the strings must be present for an item to be shown.
:param wx.Frame ParentFrame: reference to parent frame
:param str title: heading above list of choices
:param str header: Title to place on window frame
:param list ChoiceList: a list of choices where one more will be selected
:param bool toggle: If True (default) the toggle and select all buttons
are displayed
:param bool monoFont: If False (default), use a variable-spaced font;
if True use a equally-spaced font.
:param bool filterBox: If True (default) an input widget is placed on
the window and only entries matching the entered text are shown.
:param dict extraOpts: a dict containing a entries of form label_i and value_i with extra
options to present to the user, where value_i is the default value.
Options are listed ordered by the value_i values.
:param list selected: list of indicies for items that should be
:param kw: optional keyword parameters for the wx.Dialog may
be included such as size [which defaults to `(320,310)`] and
style (which defaults to `wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL`);
note that `wx.OK` and `wx.CANCEL` style items control
the presence of the eponymous buttons in the dialog.
:returns: the name of the created dialog
'''
def __init__(self,parent, title, header, ChoiceList, toggle=True,
monoFont=False, filterBox=True, extraOpts={}, selected=[],
**kw):
# process keyword parameters, notably style
options = {'size':(320,310), # default Frame keywords
'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
}
options.update(kw)
self.ChoiceList = ['%4d) %s'%(i,item) for i,item in enumerate(ChoiceList)] # numbered list of choices (list of str values)
self.Selections = len(self.ChoiceList) * [False,] # selection status for each choice (list of bools)
for i in selected:
self.Selections[i] = True
self.filterlist = range(len(self.ChoiceList)) # list of the choice numbers that have been filtered (list of int indices)
self.Stride = 1
if options['style'] & wx.OK:
useOK = True
options['style'] ^= wx.OK
else:
useOK = False
if options['style'] & wx.CANCEL:
useCANCEL = True
options['style'] ^= wx.CANCEL
else:
useCANCEL = False
# create the dialog frame
wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
# fill the dialog
Sizer = wx.BoxSizer(wx.VERTICAL)
topSizer = wx.BoxSizer(wx.HORIZONTAL)
topSizer.Add(wx.StaticText(self,wx.ID_ANY,title,size=(-1,35)),
1,wx.ALL|wx.EXPAND,1)
if filterBox:
self.timer = wx.Timer()
self.timer.Bind(wx.EVT_TIMER,self.Filter)
topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Name \nFilter: '),0,wx.ALL|WACV,1)
self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),style=wx.TE_PROCESS_ENTER)
self.filterBox.Bind(wx.EVT_TEXT,self.onChar)
self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
topSizer.Add(self.filterBox,0,wx.ALL|WACV,0)
Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
self.settingRange = False
self.rangeFirst = None
self.clb = wx.CheckListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, self.ChoiceList)
self._ShowSelections()
self.clb.Bind(wx.EVT_CHECKLISTBOX,self.OnCheck)
if monoFont:
font1 = wx.Font(self.clb.GetFont().GetPointSize(),
wx.MODERN, wx.NORMAL, wx.NORMAL, False)
self.clb.SetFont(font1)
Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
Sizer.Add((-1,10))
# set/toggle buttons
if toggle:
tSizer = wx.FlexGridSizer(cols=2,hgap=5,vgap=5)
tSizer.Add(wx.StaticText(self,label=' Apply stride:'),0,WACV)
numbs = [str(i+1) for i in range(9)]+[str(2*i+10) for i in range(6)]
self.stride = wx.ComboBox(self,value='1',choices=numbs,style=wx.CB_READONLY|wx.CB_DROPDOWN)
self.stride.Bind(wx.EVT_COMBOBOX,self.OnStride)
tSizer.Add(self.stride,0,WACV)
setBut = wx.Button(self,wx.ID_ANY,'Set All')
setBut.Bind(wx.EVT_BUTTON,self._SetAll)
tSizer.Add(setBut)
togBut = wx.Button(self,wx.ID_ANY,'Toggle All')
togBut.Bind(wx.EVT_BUTTON,self._ToggleAll)
tSizer.Add(togBut)
self.rangeBut = wx.ToggleButton(self,wx.ID_ANY,'Set Range')
self.rangeBut.Bind(wx.EVT_TOGGLEBUTTON,self.SetRange)
tSizer.Add(self.rangeBut)
self.rangeCapt = wx.StaticText(self,wx.ID_ANY,'')
tSizer.Add(self.rangeCapt)
Sizer.Add(tSizer,0,wx.LEFT,12)
# Extra widgets
Sizer.Add((-1,5),0,wx.LEFT,0)
bSizer = wx.BoxSizer(wx.VERTICAL)
for lbl in sorted(extraOpts.keys()):
if not lbl.startswith('label'): continue
key = lbl.replace('label','value')
if key not in extraOpts: continue
eSizer = wx.BoxSizer(wx.HORIZONTAL)
if type(extraOpts[key]) is bool:
eSizer.Add(G2CheckBox(self,extraOpts[lbl],extraOpts,key))
else:
eSizer.Add(wx.StaticText(self,wx.ID_ANY,extraOpts[lbl]))
eSizer.Add(ValidatedTxtCtrl(self,extraOpts,key))
bSizer.Add(eSizer,0,wx.LEFT,0)
Sizer.Add(bSizer,0,wx.CENTER,0)
Sizer.Add((-1,5),0,wx.LEFT,0)
# OK/Cancel buttons
btnsizer = wx.StdDialogButtonSizer()
if useOK:
self.OKbtn = wx.Button(self, wx.ID_OK)
self.OKbtn.SetDefault()
btnsizer.AddButton(self.OKbtn)
self.OKbtn.Bind(wx.EVT_BUTTON,self.onOk)
if useCANCEL:
btn = wx.Button(self, wx.ID_CANCEL)
btn.Bind(wx.EVT_BUTTON,self.onCancel)
btnsizer.AddButton(btn)
btnsizer.Realize()
Sizer.Add((-1,5))
Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
Sizer.Add((-1,20))
# OK done, let's get outa here
self.SetSizer(Sizer)
Sizer.Fit(self)
self.CenterOnParent()
def onOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def onCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
def OnStride(self,event):
self.Stride = int(self.stride.GetValue())
[docs]
def SetRange(self,event):
'''Respond to a press of the Set Range button. Set the range flag and
the caption next to the button
'''
self.settingRange = self.rangeBut.GetValue()
if self.settingRange:
self.rangeCapt.SetLabel('Select range start')
else:
self.rangeCapt.SetLabel('')
self.rangeFirst = None
[docs]
def GetSelections(self):
'Returns a list of the indices for the selected choices'
# update self.Selections with settings for displayed items
for i in range(len(self.filterlist)):
self.Selections[self.filterlist[i]] = self.clb.IsChecked(i)
# return all selections, shown or hidden
return [i for i in range(len(self.Selections)) if self.Selections[i]]
[docs]
def SetSelections(self,selList):
'''Sets the selection indices in selList as selected. Resets any previous
selections for compatibility with wx.MultiChoiceDialog. Note that
the state for only the filtered items is shown.
:param list selList: indices of items to be selected. These indices
are referenced to the order in self.ChoiceList
'''
self.Selections = len(self.ChoiceList) * [False,] # reset selections
for sel in selList:
self.Selections[sel] = True
self._ShowSelections()
def _ShowSelections(self):
'Show the selection state for displayed items'
if 'phoenix' in wx.version():
self.clb.SetCheckedItems(
[i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
) # Note anything previously checked will be cleared.
else:
self.clb.SetChecked(
[i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
) # Note anything previously checked will be cleared.
def _SetAll(self,event):
'Set all viewed choices on'
if 'phoenix' in wx.version():
self.clb.SetCheckedItems(range(0,len(self.filterlist),self.Stride))
else:
self.clb.SetChecked(range(0,len(self.filterlist),self.Stride))
self.stride.SetValue('1')
self.Stride = 1
def _ToggleAll(self,event):
'flip the state of all viewed choices'
for i in range(len(self.filterlist)):
self.clb.Check(i,not self.clb.IsChecked(i))
[docs]
def onChar(self,event):
'Respond to keyboard events in the Filter box'
self.OKbtn.Enable(False)
if self.timer.IsRunning():
self.timer.Stop()
self.timer.Start(1000,oneShot=True)
if event: event.Skip()
[docs]
def OnCheck(self,event):
'''for CheckListBox events; if Set Range is in use, this sets/clears all
entries in range between start and end according to the value in start.
Repeated clicks on the start change the checkbox state, but do not trigger
the range copy.
The caption next to the button is updated on the first button press.
'''
if self.settingRange:
id = event.GetInt()
if self.rangeFirst is None:
name = self.clb.GetString(id)
self.rangeCapt.SetLabel(name+' to...')
self.rangeFirst = id
elif self.rangeFirst == id:
pass
else:
for i in range(min(self.rangeFirst,id), max(self.rangeFirst,id)+1,self.Stride):
self.clb.Check(i,self.clb.IsChecked(self.rangeFirst))
self.rangeBut.SetValue(False)
self.rangeCapt.SetLabel('')
return
[docs]
def Filter(self,event):
'''Read text from filter control and select entries that match. Called by
Timer after a delay with no input or if Enter is pressed.
'''
if self.timer.IsRunning():
self.timer.Stop()
self.GetSelections() # record current selections
txt = self.filterBox.GetValue()
txt = txt.lower()
self.clb.Clear()
self.Update()
self.filterlist = []
if txt:
ChoiceList = []
for i,item in enumerate(self.ChoiceList):
for t in txt.split():
if item.lower().find(t) == -1:
break
else:
ChoiceList.append(item)
self.filterlist.append(i)
else:
self.filterlist = range(len(self.ChoiceList))
ChoiceList = self.ChoiceList
self.clb.AppendItems(ChoiceList)
self._ShowSelections()
self.OKbtn.Enable(True)
############################################### Multichoice in a sizer with set all, toggle & filter options
[docs]
class G2MultiChoiceWindow(wx.BoxSizer):
'''Creates a sizer similar to G2MultiChoiceDialog except that
buttons are added to set all choices and to toggle all choices. This
is placed in a sizer, so that it can be used in a frame or panel.
:param parent: reference to parent frame/panel
:param str title: heading above list of choices
:param list ChoiceList: a list of choices where one more will be selected
:param list SelectList: a list of selected choices
:param bool toggle: If True (default) the toggle and select all buttons
are displayed
:param bool monoFont: If False (default), use a variable-spaced font;
if True use a equally-spaced font.
:param bool filterBox: If True (default) an input widget is placed on
the window and only entries matching the entered text are shown.
:param function OnChange: a reference to a callable object, that
is called each time any a choice is changed. Default is None which
will not be called.
:param list OnChangeArgs: a list of arguments to be supplied to function
OnChange. The default is a null list.
:returns: the name of the created sizer
'''
def __init__(self, parent, title, ChoiceList, SelectList, toggle=True,
monoFont=False, filterBox=True,
OnChange=None, OnChangeArgs=[], helpText=None):
self.SelectList = SelectList
self.ChoiceList = ['%4d) %s'%(i,item) for i,item in enumerate(ChoiceList)] # numbered list of choices (list of str values)
self.frm = parent
self.Selections = len(self.ChoiceList) * [False,] # selection status for each choice (list of bools)
self.filterlist = range(len(self.ChoiceList)) # list of the choice numbers that have been filtered (list of int indices)
self.Stride = 1
self.OnChange = OnChange
self.OnChangeArgs = OnChangeArgs
# fill frame
wx.BoxSizer.__init__(self,wx.VERTICAL)
# fill the sizer
Sizer = self
topSizer = wx.BoxSizer(wx.HORIZONTAL)
topSizer.Add(wx.StaticText(self.frm,wx.ID_ANY,title,size=(-1,35)),0,WACV)
if helpText:
topSizer.Add(HelpButton(self.frm,helpText,wrap=400),0,wx.ALL,5)
topSizer.Add((1,-1),1,wx.ALL|wx.EXPAND,1)
if filterBox:
self.timer = wx.Timer()
self.timer.Bind(wx.EVT_TIMER,self.Filter)
topSizer.Add(wx.StaticText(self.frm,wx.ID_ANY,'Name \nFilter: '),0,wx.ALL|WACV,1)
self.filterBox = wx.TextCtrl(self.frm, wx.ID_ANY, size=(80,-1),style=wx.TE_PROCESS_ENTER)
self.filterBox.Bind(wx.EVT_TEXT,self.onChar)
self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
topSizer.Add(self.filterBox,0,wx.ALL|WACV,0)
Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
self.settingRange = False
self.rangeFirst = None
self.clb = wx.CheckListBox(self.frm, wx.ID_ANY, (30,30), wx.DefaultSize, self.ChoiceList)
self.clb.Bind(wx.EVT_CHECKLISTBOX,self.OnCheck)
if monoFont:
font1 = wx.Font(self.clb.GetFont().GetPointSize(),
wx.MODERN, wx.NORMAL, wx.NORMAL, False)
self.clb.SetFont(font1)
Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
Sizer.Add((-1,10))
# set/toggle buttons
if toggle:
tSizer = wx.BoxSizer(wx.HORIZONTAL)
tSizer.Add(wx.StaticText(self.frm,label=' Apply stride:'),0,WACV)
numbs = [str(i+1) for i in range(9)]+[str(2*i+10) for i in range(6)]
self.stride = wx.ComboBox(self.frm,value='1',choices=numbs,style=wx.CB_READONLY|wx.CB_DROPDOWN)
self.stride.Bind(wx.EVT_COMBOBOX,self.OnStride)
tSizer.Add(self.stride,0,WACV)
Sizer.Add(tSizer,0,wx.LEFT,12)
tSizer = wx.BoxSizer(wx.HORIZONTAL)
setBut = wx.Button(self.frm,wx.ID_ANY,'Set All')
setBut.Bind(wx.EVT_BUTTON,self._SetAll)
tSizer.Add(setBut)
togBut = wx.Button(self.frm,wx.ID_ANY,'Toggle All')
togBut.Bind(wx.EVT_BUTTON,self._ToggleAll)
tSizer.Add(togBut)
self.rangeBut = wx.ToggleButton(self.frm,wx.ID_ANY,'Set Range')
self.rangeBut.Bind(wx.EVT_TOGGLEBUTTON,self.SetRange)
tSizer.Add(self.rangeBut)
Sizer.Add(tSizer,0,wx.LEFT,12)
tSizer = wx.BoxSizer(wx.HORIZONTAL)
self.rangeCapt = wx.StaticText(self.frm,wx.ID_ANY,'')
tSizer.Add(self.rangeCapt,1,wx.EXPAND,1)
Sizer.Add(tSizer,0,wx.LEFT,12)
self.SetSelections(self.SelectList)
def OnStride(self,event):
self.Stride = int(self.stride.GetValue())
[docs]
def SetRange(self,event):
'''Respond to a press of the Set Range button. Set the range flag and
the caption next to the button
'''
self.settingRange = self.rangeBut.GetValue()
if self.settingRange:
self.rangeCapt.SetLabel('Select range start')
else:
self.rangeCapt.SetLabel('')
self.rangeFirst = None
[docs]
def GetSelections(self):
'Returns a list of the indices for the selected choices'
# update self.Selections with settings for displayed items
for i in range(len(self.filterlist)):
self.Selections[self.filterlist[i]] = self.clb.IsChecked(i)
# return all selections, shown or hidden
return [i for i in range(len(self.Selections)) if self.Selections[i]]
[docs]
def SetSelections(self,selList):
'''Sets the selection indices in selList as selected. Resets any previous
selections for compatibility with wx.MultiChoiceDialog. Note that
the state for only the filtered items is shown.
:param list selList: indices of items to be selected. These indices
are referenced to the order in self.ChoiceList
'''
self.Selections = len(self.ChoiceList) * [False,] # reset selections
for sel in selList:
self.Selections[sel] = True
self._ShowSelections()
def _ShowSelections(self):
'Show the selection state for displayed items'
if 'phoenix' in wx.version():
self.clb.SetCheckedItems(
[i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
) # Note anything previously checked will be cleared.
else:
self.clb.SetChecked(
[i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]]
) # Note anything previously checked will be cleared.
if self.OnChange:
self.OnChange(self.GetSelections(),*self.OnChangeArgs)
try:
self.SelectList.clear()
except: # patch: clear not in EPD
for i in reversed((range(len(self.SelectList)))):
del self.SelectList[i]
for i,val in enumerate(self.Selections):
if val: self.SelectList.append(i)
def _SetAll(self,event):
'Set all viewed choices on'
if 'phoenix' in wx.version():
self.clb.SetCheckedItems(range(0,len(self.filterlist),self.Stride))
else:
self.clb.SetChecked(range(0,len(self.filterlist),self.Stride))
self.stride.SetValue('1')
self.Stride = 1
self.GetSelections() # record current selections
self._ShowSelections()
def _ToggleAll(self,event):
'flip the state of all viewed choices'
for i in range(len(self.filterlist)):
self.clb.Check(i,not self.clb.IsChecked(i))
self.GetSelections() # record current selections
self._ShowSelections()
[docs]
def onChar(self,event):
'Respond to keyboard events in the Filter box'
if self.timer.IsRunning():
self.timer.Stop()
self.timer.Start(1000,oneShot=True)
if event: event.Skip()
[docs]
def OnCheck(self,event):
'''for CheckListBox events; if Set Range is in use, this sets/clears all
entries in range between start and end according to the value in start.
Repeated clicks on the start change the checkbox state, but do not trigger
the range copy.
The caption next to the button is updated on the first button press.
'''
if self.settingRange:
id = event.GetInt()
if self.rangeFirst is None:
name = self.clb.GetString(id)
self.rangeCapt.SetLabel(name+' to...')
self.rangeFirst = id
elif self.rangeFirst == id:
pass
else:
for i in range(min(self.rangeFirst,id), max(self.rangeFirst,id)+1,self.Stride):
self.clb.Check(i,self.clb.IsChecked(self.rangeFirst))
self.rangeBut.SetValue(False)
self.rangeCapt.SetLabel('')
self.settingRange = False
self.rangeFirst = None
self.GetSelections() # record current selections
self._ShowSelections()
[docs]
def Filter(self,event):
'''Read text from filter control and select entries that match. Called by
Timer after a delay with no input or if Enter is pressed.
'''
if self.timer.IsRunning():
self.timer.Stop()
self.GetSelections() # record current selections
txt = self.filterBox.GetValue()
self.clb.Clear()
self.filterlist = []
if txt:
txt = txt.lower()
ChoiceList = []
for i,item in enumerate(self.ChoiceList):
if item.lower().find(txt) != -1:
ChoiceList.append(item)
self.filterlist.append(i)
else:
self.filterlist = range(len(self.ChoiceList))
ChoiceList = self.ChoiceList
self.clb.AppendItems(ChoiceList)
self._ShowSelections()
[docs]
def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem):
'''Select a variable from a list, then edit it and select histograms
to copy it to.
:param wx.Frame G2frame: main GSAS-II frame
:param dict array: the array (dict or list) where values to be edited are kept
:param list labelLst: labels for each data item
:param list elemKeysLst: a list of lists of keys needed to be applied (see below)
to obtain the value of each parameter
:param list dspLst: list list of digits to be displayed (10,4) is 10 digits
with 4 decimal places. Can be None.
:param list refFlgElem: a list of lists of keys needed to be applied (see below)
to obtain the refine flag for each parameter or None if the parameter
does not have refine flag.
Example::
array = data
labelLst = ['v1','v2']
elemKeysLst = [['v1'], ['v2',0]]
refFlgElem = [None, ['v2',1]]
* The value for v1 will be in data['v1'] and this cannot be refined while,
* The value for v2 will be in data['v2'][0] and its refinement flag is data['v2'][1]
'''
def unkey(dct,keylist):
'''dive into a nested set of dicts/lists applying keys in keylist
consecutively
'''
d = dct
for k in keylist:
d = d[k]
return d
def OnChoice(event):
'Respond when a parameter is selected in the Choice box'
if 'phoenix' in wx.version():
valSizer.Clear(True)
else:
valSizer.DeleteWindows()
lbl = event.GetString()
copyopts['currentsel'] = lbl
i = labelLst.index(lbl)
OKbtn.Enable(True)
ch.SetLabel(lbl)
args = {}
if dspLst[i]:
args = {'nDig':dspLst[i]}
Val = ValidatedTxtCtrl(
dlg,
unkey(array,elemKeysLst[i][:-1]),
elemKeysLst[i][-1],
**args)
copyopts['startvalue'] = unkey(array,elemKeysLst[i])
#unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] =
valSizer.Add(Val,0,wx.LEFT,5)
dlg.SendSizeEvent()
# SelectEdit1Var execution begins here
saveArray = copy.deepcopy(array) # keep original values
TreeItemType = G2frame.GPXtree.GetItemText(G2frame.PickId)
copyopts = {'InTable':False,"startvalue":None,'currentsel':None}
hst = G2frame.GPXtree.GetItemText(G2frame.PatternId)
histList = G2pdG.GetHistsLikeSelected(G2frame)
if not histList:
G2frame.ErrorDialog('No match','No histograms match '+hst,G2frame)
return
dlg = wx.Dialog(G2frame,wx.ID_ANY,'Set a parameter value',
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add((5,5))
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add((-1,-1),1,wx.EXPAND)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Select a parameter and set a new value'))
subSizer.Add((-1,-1),1,wx.EXPAND)
mainSizer.Add(subSizer,0,wx.EXPAND,0)
mainSizer.Add((0,10))
subSizer = wx.FlexGridSizer(0,2,5,0)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Parameter: '))
ch = wx.Choice(dlg, wx.ID_ANY, choices = sorted(labelLst))
ch.SetSelection(-1)
ch.Bind(wx.EVT_CHOICE, OnChoice)
subSizer.Add(ch)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Value: '))
valSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add(valSizer)
mainSizer.Add(subSizer)
mainSizer.Add((-1,20))
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add(G2CheckBox(dlg, 'Edit in table ', copyopts, 'InTable'))
mainSizer.Add(subSizer)
btnsizer = wx.StdDialogButtonSizer()
OKbtn = wx.Button(dlg, wx.ID_OK,'Continue')
OKbtn.Enable(False)
OKbtn.SetDefault()
OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
btnsizer.AddButton(OKbtn)
btn = wx.Button(dlg, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
mainSizer.Add((-1,5),1,wx.EXPAND,1)
mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
mainSizer.Add((-1,10))
dlg.SetSizer(mainSizer)
dlg.CenterOnParent()
if dlg.ShowModal() != wx.ID_OK:
array.update(saveArray)
dlg.Destroy()
return
dlg.Destroy()
copyList = []
lbl = copyopts['currentsel']
dlg = G2MultiChoiceDialog(G2frame,'Copy parameter '+lbl+' from\n'+hst,
'Copy parameters', histList)
dlg.CenterOnParent()
try:
if dlg.ShowModal() == wx.ID_OK:
for i in dlg.GetSelections():
copyList.append(histList[i])
else:
# reset the parameter since cancel was pressed
array.update(saveArray)
return
finally:
dlg.Destroy()
prelbl = [hst]
i = labelLst.index(lbl)
keyLst = elemKeysLst[i]
refkeys = refFlgElem[i]
dictlst = [unkey(array,keyLst[:-1])]
if refkeys is not None:
refdictlst = [unkey(array,refkeys[:-1])]
else:
refdictlst = None
Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hst)
hstData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Instrument Parameters'))[0]
for h in copyList:
Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,h)
instData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Instrument Parameters'))[0]
if len(hstData) != len(instData) or hstData['Type'][0] != instData['Type'][0]: #don't mix data types or lam & lam1/lam2 parms!
print (h+' not copied - instrument parameters not commensurate')
continue
hData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,TreeItemType))
if TreeItemType == 'Instrument Parameters':
hData = hData[0]
#copy the value if it is changed or we will not edit in a table
valNow = unkey(array,keyLst)
if copyopts['startvalue'] != valNow or not copyopts['InTable']:
unkey(hData,keyLst[:-1])[keyLst[-1]] = valNow
prelbl += [h]
dictlst += [unkey(hData,keyLst[:-1])]
if refdictlst is not None:
refdictlst += [unkey(hData,refkeys[:-1])]
if refdictlst is None:
args = {}
else:
args = {'checkdictlst':refdictlst,
'checkelemlst':len(dictlst)*[refkeys[-1]],
'checklabel':'Refine?'}
if copyopts['InTable']:
dlg = ScrolledMultiEditor(
G2frame,dictlst,
len(dictlst)*[keyLst[-1]],prelbl,
header='Editing parameter '+lbl,
CopyButton=True,**args)
dlg.CenterOnParent()
if dlg.ShowModal() != wx.ID_OK:
array.update(saveArray)
dlg.Destroy()
##### Single choice Dialog with filter options ###############################################################
[docs]
class G2SingleChoiceDialog(wx.Dialog):
'''A dialog similar to wx.SingleChoiceDialog except that a filter can be
added.
:param wx.Frame ParentFrame: reference to parent frame
:param str title: heading above list of choices
:param str header: Title to place on window frame
:param list ChoiceList: a list of choices where one will be selected
:param bool monoFont: If False (default), use a variable-spaced font;
if True use a equally-spaced font.
:param bool filterBox: If True (default) an input widget is placed on
the window and only entries matching the entered text are shown.
:param kw: optional keyword parameters for the wx.Dialog may
be included such as size [which defaults to `(320,310)`] and
style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
note that ``wx.OK`` and ``wx.CANCEL`` controls
the presence of the eponymous buttons in the dialog.
:returns: the name of the created dialog
'''
def __init__(self,parent, title, header, ChoiceList,
monoFont=False, filterBox=True, **kw):
# process keyword parameters, notably style
options = {'size':(320,310), # default Frame keywords
'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
}
options.update(kw)
self.ChoiceList = ChoiceList
self.filterlist = range(len(self.ChoiceList))
if options['style'] & wx.OK:
useOK = True
options['style'] ^= wx.OK
else:
useOK = False
if options['style'] & wx.CANCEL:
useCANCEL = True
options['style'] ^= wx.CANCEL
else:
useCANCEL = False
# create the dialog frame
wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
# fill the dialog
Sizer = wx.BoxSizer(wx.VERTICAL)
topSizer = wx.BoxSizer(wx.HORIZONTAL)
h = max(35,17*int(len(title)/26.+1)) # adjust height of title box with guessed # of lines
topSizer.Add(wx.StaticText(self,wx.ID_ANY,title,size=(-1,h)),
1,wx.ALL|wx.EXPAND,1)
if filterBox:
self.timer = wx.Timer()
self.timer.Bind(wx.EVT_TIMER,self.Filter)
topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Filter: '),0,wx.ALL,1)
self.filterBox = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1),style=wx.TE_PROCESS_ENTER)
self.filterBox.Bind(wx.EVT_CHAR,self.onChar)
self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.Filter)
topSizer.Add(self.filterBox,0,wx.ALL,0)
Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
self.clb = wx.ListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
self.clb.Bind(wx.EVT_LEFT_DCLICK,self.onDoubleClick)
if monoFont:
font1 = wx.Font(self.clb.GetFont().GetPointSize(),wx.MODERN, wx.NORMAL, wx.NORMAL, False)
self.clb.SetFont(font1)
Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
Sizer.Add((-1,10))
# OK/Cancel buttons
btnsizer = wx.StdDialogButtonSizer()
if useOK:
self.OKbtn = wx.Button(self, wx.ID_OK)
self.OKbtn.SetDefault()
btnsizer.AddButton(self.OKbtn)
if useCANCEL:
btn = wx.Button(self, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
Sizer.Add((-1,5))
Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
Sizer.Add((-1,20))
# OK done, let's get outa here
self.SetSizer(Sizer)
[docs]
def GetSelection(self):
'Returns the index of the selected choice'
i = self.clb.GetSelection()
if i < 0 or i >= len(self.filterlist):
return wx.NOT_FOUND
return self.filterlist[i]
def onChar(self,event):
self.OKbtn.Enable(False)
if self.timer.IsRunning():
self.timer.Stop()
self.timer.Start(1000,oneShot=True)
if event: event.Skip()
def Filter(self,event):
if self.timer.IsRunning():
self.timer.Stop()
txt = self.filterBox.GetValue()
self.clb.Clear()
self.Update()
self.filterlist = []
if txt:
txt = txt.lower()
ChoiceList = []
for i,item in enumerate(self.ChoiceList):
if item.lower().find(txt) != -1:
ChoiceList.append(item)
self.filterlist.append(i)
else:
self.filterlist = range(len(self.ChoiceList))
ChoiceList = self.ChoiceList
self.clb.AppendItems(ChoiceList)
self.OKbtn.Enable(True)
def onDoubleClick(self,event):
self.EndModal(wx.ID_OK)
################################################################################
[docs]
class FlagSetDialog(wx.Dialog):
''' Creates popup with table of variables to be checked for e.g. refinement flags
'''
def __init__(self,parent,title,colnames,rownames,flags):
wx.Dialog.__init__(self,parent,-1,title,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = None
self.colnames = colnames
self.rownames = rownames
self.flags = flags
self.newflags = copy.copy(flags)
self.Draw()
def Draw(self):
Indx = {}
def OnSelection(event):
Obj = event.GetEventObject()
[name,ia] = Indx[Obj.GetId()]
self.newflags[name][ia] = Obj.GetValue()
if self.panel:
self.panel.DestroyChildren() #safe: wx.Panel
self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
flagSizer = wx.FlexGridSizer(0,len(self.colnames),5,5)
for item in self.colnames:
flagSizer.Add(wx.StaticText(self.panel,label=item),0,WACV)
for ia,atm in enumerate(self.rownames):
flagSizer.Add(wx.StaticText(self.panel,label=atm),0,WACV)
for name in self.colnames[1:]:
if self.flags[name][ia]:
self.newflags[name][ia] = False #default is off
flg = wx.CheckBox(self.panel,-1,label='')
flg.Bind(wx.EVT_CHECKBOX,OnSelection)
Indx[flg.GetId()] = [name,ia]
flagSizer.Add(flg,0,WACV)
else:
flagSizer.Add(wx.StaticText(self.panel,label='na'),0,WACV)
mainSizer.Add(flagSizer,0)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
CancelBtn = wx.Button(self.panel,-1,'Cancel')
CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add(CancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
def GetSelection(self):
return self.newflags
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
################################################################################
[docs]
def G2MessageBox(parent,msg,title='Error'):
'''Simple code to display a error or warning message
TODO: replace wx.MessageDialog with one derived from wx.Dialog because
on most platforms wx.MessageDialog is a native widget and CentreOnParent
will not function.
'''
dlg = wx.MessageDialog(parent,StripIndents(msg), title, wx.OK|wx.CENTRE)
dlg.CentreOnParent()
dlg.ShowModal()
dlg.Destroy()
[docs]
def ShowScrolledColText(parent,txt,width=600,height=400,header='Warning info',col1len=999):
'''Simple code to display tabular information in a scrolled wx.Dialog
window.
Lines ending with a colon (:) are centered across all columns
and have a grey background.
Lines beginning and ending with '**' are also are centered
across all columns and are given a yellow background
All other lines have columns split by tab (\\t) characters.
:param wx.Frame parent: parent window
:param str txt: text to be displayed
:param int width: lateral of window in pixels (defaults to 600)
:param int height: vertical dimension of window in pixels (defaults to 400)
:param str header: title to be placed on window
'''
dlg = wx.Dialog(parent.GetTopLevelParent(),wx.ID_ANY,header, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
spanel = wxscroll.ScrolledPanel(dlg, wx.ID_ANY, size=(width-20, height))
spanel.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(spanel,1,wx.ALL|wx.EXPAND,1)
cols = 1
for i,line in enumerate(txt.split('\n')):
cols = max(cols,line.count('\t')+1)
txtSizer = wx.GridBagSizer(0,9)
for i,line in enumerate(txt.split('\n')):
if line.strip().endswith(':'):
st = wx.StaticText(spanel,wx.ID_ANY,line)
txtSizer.Add(st,pos=(i,0),span=(0,cols),flag=wx.EXPAND)
st.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
continue
elif line.strip().startswith('**') and line.strip().endswith('**'):
st = wx.StaticText(spanel,wx.ID_ANY,line,style=wx.ALIGN_CENTER)
st.SetBackgroundColour(DULL_YELLOW)
txtSizer.Add(st,pos=(i,0),span=(0,cols),flag=wx.EXPAND)
continue
items = line.split('\t')
for col in range(cols):
if col < len(items):
item = items[col].strip()
else:
item = ''
t = item[:]
s = ''
#if len(t) > col1len: GSASIIpath.IPyBreak()
while col == 0 and len(t) > col1len:
b = -1
for sym in (') ',' * ',' + ',' - ',' && '):
b = max(b, t.rfind(sym,0,col1len))
if b > 20:
s += t[:b+1]
t = '\n\t' + t[b+1:]
continue
break
s += t
st = wx.StaticText(spanel,wx.ID_ANY,s)
if col == 0: st.Wrap(650) # last resort...
st.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
txtSizer.Add(st,pos=(i,col),flag=wx.EXPAND)
#txtSizer.AddGrowableRow(i)
txtSizer.AddGrowableCol(0) #to fill screen
spanel.SetSizer(txtSizer)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
btn = wx.Button(dlg, wx.ID_CLOSE)
btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL))
btnsizer.Add(btn)
mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
dlg.SetSizer(mainSizer)
mainSizer.Fit(dlg)
spanel.SetAutoLayout(1)
spanel.SetupScrolling()
#dlg.SetMaxSize((-1,400))
dlg.CenterOnParent()
dlg.ShowModal()
dlg.Destroy()
################################################################################
[docs]
class PickTwoDialog(wx.Dialog):
'''This does not seem to be in use
'''
def __init__(self,parent,title,prompt,names,choices):
wx.Dialog.__init__(self,parent,-1,title,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = None
self.prompt = prompt
self.choices = choices
self.names = names
self.Draw()
def Draw(self):
Indx = {}
def OnSelection(event):
Obj = event.GetEventObject()
id = Indx[Obj.GetId()]
self.choices[id] = Obj.GetValue().encode() #to avoid Unicode versions
self.Draw()
if self.panel:
self.panel.DestroyChildren() #safe: wx.Panel
self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
for isel,name in enumerate(self.choices):
lineSizer = wx.BoxSizer(wx.HORIZONTAL)
lineSizer.Add(wx.StaticText(self.panel,-1,'Reference atom '+str(isel+1)),0,wx.ALIGN_CENTER)
nameList = self.names[:]
if isel:
if self.choices[0] in nameList:
nameList.remove(self.choices[0])
choice = wx.ComboBox(self.panel,-1,value=name,choices=nameList,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
Indx[choice.GetId()] = isel
choice.Bind(wx.EVT_COMBOBOX, OnSelection)
lineSizer.Add(choice,0,WACV)
mainSizer.Add(lineSizer)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
CancelBtn = wx.Button(self.panel,-1,'Cancel')
CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add(CancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
def GetSelection(self):
return self.choices
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
################################################################################
[docs]
class SingleFloatDialog(wx.Dialog):
'''Dialog to obtain a single float value from user
:param wx.Frame parent: name of parent frame
:param str title: title string for dialog
:param str prompt: string to tell user what they are inputing
:param str value: default input value, if any
:param list limits: upper and lower value used to set bounds for entry, use [None,None]
for no bounds checking, [None,val] for only upper bounds, etc. Default is [0,1].
Values outside of limits will be ignored.
:param str format: string to format numbers. Defaults to '%.5g'. Use '%d' to have
integer input (but dlg.GetValue will still return a float).
Typical usage::
limits = (0,1)
dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for...',default,limits)
if dlg.ShowModal() == wx.ID_OK:
parm = dlg.GetValue()
dlg.Destroy()
'''
def __init__(self,parent,title,prompt,value,limits=[0.,1.],fmt='%.5g'):
wx.Dialog.__init__(self,parent,-1,title,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.CenterOnParent()
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self,-1,prompt),0,wx.ALIGN_CENTER)
#valItem = wx.TextCtrl(self,-1,value=self.format%(self.value),style=wx.TE_PROCESS_ENTER)
self.buffer = {0:float(fmt%(value))}
a,b = fmt[1:].split('.')
f = b[-1]
try:
d = int(b[:-1])
except:
d = 5
try:
w = int(a)
except:
w = 3+d
self.OKbtn = wx.Button(self,wx.ID_OK)
CancelBtn = wx.Button(self,wx.ID_CANCEL)
valItem = ValidatedTxtCtrl(self,self.buffer,0,nDig=(w,d,f),
xmin=limits[0],xmax=limits[1],
OKcontrol=self.ControlOKButton)
mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
self.OKbtn.Bind(wx.EVT_BUTTON, self.OnOk)
CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(self.OKbtn)
btnSizer.Add(CancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.SetSizer(mainSizer)
self.Fit()
def GetValue(self):
return self.buffer[0]
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
[docs]
class SingleIntDialog(SingleFloatDialog):
'''Dialog to obtain a single int value from user
:param wx.Frame parent: name of parent frame
:param str title: title string for dialog
:param str prompt: string to tell user what they are inputing
:param str value: default input value, if any
:param list limits: upper and lower value used to set bounds for entries. Default
is [None,None] -- for no bounds checking; use [None,val] for only upper bounds, etc.
Default is [0,1]. Values outside of limits will be ignored.
Typical usage::
limits = (0,None) # allows zero or positive values only
dlg = G2G.SingleIntDialog(G2frame,'New value','Enter new value for...',default,limits)
if dlg.ShowModal() == wx.ID_OK:
parm = dlg.GetValue()
dlg.Destroy()
'''
def __init__(self,parent,title,prompt,value,limits=[None,None]):
SingleFloatDialog.__init__(self,parent,title,prompt,value,limits=limits,format='%d')
def GetValue(self):
return int(self.value)
################################################################################
[docs]
class MultiDataDialog(wx.Dialog):
'''Dialog to obtain multiple values from user
:param wx.Frame parent: parent frame for dialog to be created
:param str title: title to place on top of window
:param list prompts: a string to describe each item
:param list values: a set of initial values for each item
:param list limits: A nested list with an upper and lower value
for each item
:param list format: an "old-style" format string used to display
each item value
:param str header: a string to be placed at the top of the
window, if specified
example::
dlg = G2G.MultiDataDialog(G2frame,title='ISOCIF search',
prompts=['lattice constants tolerance',
'coordinate tolerance',
'occupancy tolerance'],
values=[0.001,0.01,0.1],
limits=3*[[0.,2.]],formats=3*['%.5g'],
header=isoCite)
dlg.ShowModal()
latTol,coordTol,occTol = dlg.GetValues()
dlg.Destroy()
'''
def __init__(self,parent,title,prompts,values,limits=[[0.,1.],],
formats=['%.5g',],header=None):
wx.Dialog.__init__(self,parent,-1,title,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = None
self.limits = limits
self.values = values
self.prompts = prompts
self.formats = formats
self.header = header
self.Draw()
def Draw(self):
def OnValItem(event):
if event: event.Skip()
Obj = event.GetEventObject()
fmt = Indx[Obj][-1]
if type(fmt) is list:
tid,idl,limits = Indx[Obj][:3]
self.values[tid][idl] = Obj.GetValue()
elif 'bool' in fmt:
self.values[Indx[Obj][0]] = Obj.GetValue()
elif 'str' in fmt:
tid,limits = Indx[Obj][:2]
try:
val = Obj.GetValue()
if val not in limits:
raise ValueError
except ValueError:
val = self.values[tid]
self.values[tid] = val
Obj.SetValue('%s'%(val))
elif 'choice' in fmt:
self.values[Indx[Obj][0]] = Obj.GetValue()
# else:
# tid,limits = Indx[Obj][:2]
# try:
# val = float(Obj.GetValue())
# if val < limits[0] or val > limits[1]:
# raise ValueError
# except ValueError:
# val = self.values[tid]
# self.values[tid] = val
# Obj.SetValue(fmt%(val))
Indx = {}
if self.panel: self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
if self.header:
txt = wx.StaticText(self.panel,wx.ID_ANY,self.header)
txt.Wrap(400)
mainSizer.Add(txt)
mainSizer.Add((-1,5))
HorizontalLine(mainSizer,self.panel)
mainSizer.Add((-1,5))
lineSizer = wx.FlexGridSizer(0,2,5,5)
for tid,[prompt,value,limits,fmt] in enumerate(zip(self.prompts,self.values,self.limits,self.formats)):
lineSizer.Add(wx.StaticText(self.panel,label=prompt),0,wx.ALIGN_CENTER)
if type(fmt) is list: #let's assume these are 'choice' for now
valItem = wx.BoxSizer(wx.HORIZONTAL)
for idl,item in enumerate(fmt):
listItem = wx.ComboBox(self.panel,value=limits[idl][0],choices=limits[idl],style=wx.CB_READONLY|wx.CB_DROPDOWN)
listItem.Bind(wx.EVT_COMBOBOX,OnValItem)
valItem.Add(listItem,0,WACV)
Indx[listItem] = [tid,idl,limits,fmt]
elif 'bool' in fmt:
valItem = wx.CheckBox(self.panel,label='')
valItem.Bind(wx.EVT_CHECKBOX,OnValItem)
valItem.SetValue(value)
elif 'str' in fmt:
valItem = wx.TextCtrl(self.panel,value='%s'%(value),style=wx.TE_PROCESS_ENTER)
valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
valItem.SetValue('%s'%value)
elif 'choice' in fmt:
valItem = wx.ComboBox(self.panel,value=limits[0],choices=limits,style=wx.CB_READONLY|wx.CB_DROPDOWN)
valItem.Bind(wx.EVT_COMBOBOX,OnValItem)
else:
if '%' in fmt:
if 'd' in fmt:
nDig = None
else:
sfmt = fmt[1:].split('.')
if not sfmt[0]: sfmt[0] = '10'
nDig = (int(sfmt[0]),int(sfmt[1][:-1]),sfmt[1][-1])
valItem = ValidatedTxtCtrl(self.panel,self.values,tid,nDig=nDig,xmin=limits[0],xmax=limits[1])
# valItem = wx.TextCtrl(self.panel,value=fmt%(value),style=wx.TE_PROCESS_ENTER)
# valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
# valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
Indx[valItem] = [tid,limits,fmt]
lineSizer.Add(valItem,0,wx.ALIGN_CENTER)
mainSizer.Add(lineSizer)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
CancelBtn = wx.Button(self.panel,-1,'Cancel')
CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
OkBtn.SetDefault()
btnSizer.Add(CancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
def GetValues(self):
return self.values
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
################################################################################
[docs]
class SingleStringDialog(wx.Dialog):
'''Dialog to obtain a single string value from user
:param wx.Frame parent: name of parent frame
:param str title: title string for dialog
:param str prompt: string to tell use what they are inputting
:param str value: default input value, if any
:param tuple size: specifies default size and width for dialog
[default (200,-1)]
:param str help: if supplied, a help button is added to the dialog that
can be used to display the supplied help text/URL for setting this
variable. (Default is '', which is ignored.)
:param list choices: a set of strings that provide optional values that
can be selected from; these can be edited if desired.
'''
def __init__(self,parent,title,prompt,value='',size=(200,-1),help='',
choices=None):
wx.Dialog.__init__(self,parent,wx.ID_ANY,title,pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
self.value = value
self.prompt = prompt
self.CenterOnParent()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
if choices:
self.valItem = wx.ComboBox(self.panel, wx.ID_ANY, value,
size=size,choices=[value]+choices,
style=wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)
else:
self.valItem = wx.TextCtrl(self.panel,-1,value=self.value,size=size)
if help:
sizer1.Add((-1,-1),1,wx.EXPAND)
sizer1.Add(self.valItem,0,wx.ALIGN_CENTER)
sizer1.Add((-1,-1),1,wx.EXPAND)
sizer1.Add(HelpButton(self.panel,help),0,wx.ALL)
else:
sizer1.Add(self.valItem,0,wx.ALIGN_CENTER)
mainSizer.Add(sizer1,0,wx.EXPAND)
btnsizer = wx.StdDialogButtonSizer()
OKbtn = wx.Button(self.panel, wx.ID_OK)
OKbtn.SetDefault()
btnsizer.AddButton(OKbtn)
btn = wx.Button(self.panel, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
[docs]
def Show(self):
'''Use this method after creating the dialog to post it
:returns: True if the user pressed OK; False if the User pressed Cancel
'''
if self.ShowModal() == wx.ID_OK:
self.value = self.valItem.GetValue()
return True
else:
return False
[docs]
def GetValue(self):
'''Use this method to get the value entered by the user
:returns: string entered by user
'''
return self.value
################################################################################
[docs]
class MultiStringDialog(wx.Dialog):
'''Dialog to obtain a multi string values from user
:param wx.Frame parent: name of parent frame
:param str title: title string for dialog
:param list prompts: list of strings to tell user what they are inputting
:param list values: list of str default input values, if any
:param int size: length of the input box in pixels
:param bool addRows: if True, users can add rows to the table
(default is False)
:param str hlp: if supplied, a help button is added to the dialog that
can be used to display the supplied help text in this variable.
:param str lbl: label placed at top of dialog
:returns: a wx.Dialog instance
'''
def __init__(self,parent,title,prompts,values=[],size=-1,
addRows=False,hlp=None, lbl=None):
wx.Dialog.__init__(self,parent,wx.ID_ANY,title,
pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
self.values = list(values)
self.prompts = list(prompts)
self.addRows = addRows
self.size = size
self.hlp = hlp
self.lbl = lbl
self.CenterOnParent()
self.Paint()
def Paint(self):
if self.GetSizer():
self.GetSizer().Clear(True)
mainSizer = wx.BoxSizer(wx.VERTICAL)
if self.hlp:
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
hlp = HelpButton(self, self.hlp, wrap=450)
btnsizer.Add((-1,-1),1, wx.EXPAND, 1)
#btnsizer.Add(hlp,0,wx.ALIGN_RIGHT|wx.ALL)
btnsizer.Add(hlp,0)
mainSizer.Add(btnsizer,0,wx.EXPAND)
if self.lbl:
mainSizer.Add(wx.StaticText(self,wx.ID_ANY,self.lbl))
mainSizer.Add((-1,15))
promptSizer = wx.FlexGridSizer(0,2,5,5)
promptSizer.AddGrowableCol(1,1)
self.Indx = {}
for prompt,value in zip(self.prompts,self.values):
promptSizer.Add(wx.StaticText(self,-1,prompt))
valItem = wx.TextCtrl(self,-1,value=value,style=wx.TE_PROCESS_ENTER,size=(self.size,-1))
self.Indx[valItem.GetId()] = prompt
valItem.Bind(wx.EVT_TEXT,self.newValue)
promptSizer.Add(valItem,1,wx.EXPAND,1)
mainSizer.Add(promptSizer,1,wx.ALL|wx.EXPAND,1)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
OKbtn = wx.Button(self, wx.ID_OK)
OKbtn.SetDefault()
btnsizer.Add((1,1),1,wx.EXPAND,1)
btnsizer.Add(OKbtn)
btn = wx.Button(self, wx.ID_CANCEL)
btnsizer.Add(btn)
btnsizer.Add((1,1),1,wx.EXPAND,1)
if self.addRows:
btn = wx.Button(self, wx.ID_ANY,'+',style=wx.BU_EXACTFIT)
btn.Bind(wx.EVT_BUTTON,self.onExpand)
btnsizer.Add(btn)
mainSizer.Add(btnsizer,0,wx.EXPAND)
self.SetSizer(mainSizer)
self.Fit()
def onExpand(self,event):
self.values.append('')
self.prompts.append('item '+str(len(self.values)))
self.Paint()
def newValue(self,event):
Obj = event.GetEventObject()
item = self.Indx[Obj.GetId()]
id = self.prompts.index(item)
self.values[id] = Obj.GetValue()
[docs]
def Show(self):
'''Use this method after creating the dialog to post it
:returns: True if the user pressed OK; False if the User pressed Cancel
'''
if self.ShowModal() == wx.ID_OK:
return True
else:
return False
[docs]
def GetValues(self):
'''Use this method to get the value(s) entered by the user
:returns: a list of strings entered by user
'''
return self.values
################################################################################
[docs]
class G2ColumnIDDialog(wx.Dialog):
'''A dialog for matching column data to desired items; some columns may be ignored.
:param wx.Frame ParentFrame: reference to parent frame
:param str title: heading above list of choices
:param str header: Title to place on window frame
:param list ChoiceList: a list of possible choices for the columns
:param list ColumnData: lists of column data to be matched with ChoiceList
:param bool monoFont: If False (default), use a variable-spaced font;
if True use a equally-spaced font.
:param kw: optional keyword parameters for the wx.Dialog may
be included such as size [which defaults to `(320,310)`] and
style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
note that ``wx.OK`` and ``wx.CANCEL`` controls
the presence of the eponymous buttons in the dialog.
:returns: the name of the created dialog
'''
def __init__(self,parent, title, header,Comments,ChoiceList, ColumnData,
monoFont=False, **kw):
def OnOk(sevent):
OK = True
selCols = []
for col in self.sel:
item = col.GetValue()
if item != ' ' and item in selCols:
OK = False
break
else:
selCols.append(item)
parent = self.GetParent()
if not OK:
parent.ErrorDialog('Duplicate',item+' selected more than once')
return
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnModify(event):
if event: event.Skip()
Obj = event.GetEventObject()
icol,colData = Indx[Obj.GetId()]
modify = Obj.GetValue()
if not modify:
return
#print 'Modify column',icol,' by', modify
for i,item in enumerate(self.ColumnData[icol]):
self.ColumnData[icol][i] = str(eval(item+modify))
colData.SetValue('\n'.join(self.ColumnData[icol]))
Obj.SetValue('')
# process keyword parameters, notably style
options = {'size':(600,310), # default Frame keywords
'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
}
options.update(kw)
self.Comments = ''.join(Comments)
self.ChoiceList = ChoiceList
self.ColumnData = ColumnData
if options['style'] & wx.OK:
useOK = True
options['style'] ^= wx.OK
else:
useOK = False
if options['style'] & wx.CANCEL:
useCANCEL = True
options['style'] ^= wx.CANCEL
else:
useCANCEL = False
# create the dialog frame
wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
panel = wxscroll.ScrolledPanel(self)
# fill the dialog
Sizer = wx.BoxSizer(wx.VERTICAL)
Sizer.Add((-1,5))
if self.Comments:
if title[-1] == ':':
title = title[:-1] + ' using header line(s):'
else:
title += ' using header line(s):'
Sizer.Add(wx.StaticText(panel,label=title),0)
Sizer.Add((5,5))
if self.Comments[-1] != '\n': self.Comments += '\n'
txt = wx.StaticText(panel,label=self.Comments)
txt.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
font1 = wx.Font(txt.GetFont().GetPointSize(),wx.MODERN, wx.NORMAL, wx.NORMAL, False)
txt.SetFont(font1)
Sizer.Add(txt,0,wx.ALL|wx.EXPAND,0)
txtSize = txt.GetSize()[1]
else:
Sizer.Add(wx.StaticText(panel,label=title),0)
txtSize = 0
columnsSizer = wx.BoxSizer(wx.HORIZONTAL)
self.sel = []
self.mod = []
Indx = {}
for icol,col in enumerate(self.ColumnData):
colSizer = wx.BoxSizer(wx.VERTICAL)
colSizer.Add(wx.StaticText(panel,label=' Column #%d Select:'%(icol)))
self.sel.append(wx.ComboBox(panel,value=' ',choices=self.ChoiceList,style=wx.CB_READONLY|wx.CB_DROPDOWN))
colSizer.Add(self.sel[-1])
colData = wx.TextCtrl(panel,value='\n'.join(self.ColumnData[icol]),size=(120,-1),
style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_DONTWRAP)
colSizer.Add(colData,1,wx.ALL|wx.EXPAND,1)
colSizer.Add(wx.StaticText(panel,label=' Modify by:'))
mod = wx.TextCtrl(panel,size=(120,-1),value='',style=wx.TE_PROCESS_ENTER)
mod.Bind(wx.EVT_TEXT_ENTER,OnModify)
mod.Bind(wx.EVT_KILL_FOCUS,OnModify)
Indx[mod.GetId()] = [icol,colData]
colSizer.Add(mod)
columnsSizer.Add(colSizer,0,wx.ALL|wx.EXPAND,10)
Sizer.Add(columnsSizer,1,wx.ALL|wx.EXPAND,1)
Sizer.Add(wx.StaticText(panel,label=' For modify by, enter arithmetic string eg. "-12345.67". "+", "-", "*", "/", "**" all allowed'),0)
Sizer.Add((-1,10))
# OK/Cancel buttons
btnsizer = wx.StdDialogButtonSizer()
if useOK:
self.OKbtn = wx.Button(panel, wx.ID_OK)
self.OKbtn.SetDefault()
btnsizer.AddButton(self.OKbtn)
self.OKbtn.Bind(wx.EVT_BUTTON, OnOk)
if useCANCEL:
btn = wx.Button(panel, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
Sizer.Add((-1,5))
Sizer.Add(btnsizer,0,wx.ALIGN_LEFT,20)
Sizer.Add((-1,5))
# OK done, let's get outa here
panel.SetSizer(Sizer)
panel.SetAutoLayout(1)
panel.SetupScrolling()
Size = [450,375]
panel.SetSize(Size)
Size[0] += 25; Size[1]+= 25+txtSize
self.SetSize(Size)
[docs]
def GetSelection(self):
'Returns the selected sample parm for each column'
selCols = []
for item in self.sel:
selCols.append(item.GetValue())
return selCols,self.ColumnData
################################################################################
[docs]
class G2HistoDataDialog(wx.Dialog):
'''A dialog for editing histogram data globally.
:param wx.Frame ParentFrame: reference to parent frame
:param str title: heading above list of choices
:param str header: Title to place on window frame
:param list ParmList: a list of names for the columns
:param list ParmFmt: a list of formatting strings for the columns
:param list: HistoList: a list of histogram names
:param list ParmData: a list of lists of data matched to ParmList; one for each item in HistoList
:param bool monoFont: If False (default), use a variable-spaced font;
if True use a equally-spaced font.
:param kw: optional keyword parameters for the wx.Dialog may
be included such as size [which defaults to `(320,310)`] and
style (which defaults to
``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
note that ``wx.OK`` and ``wx.CANCEL`` controls the presence of the eponymous buttons in the dialog.
:returns: the modified ParmData
'''
def __init__(self,parent, title, header,ParmList,ParmFmt,HistoList,ParmData,
monoFont=False, **kw):
def OnOk(sevent):
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnModify(event):
Obj = event.GetEventObject()
irow,it = Indx[Obj.GetId()]
try:
val = float(Obj.GetValue())
except ValueError:
val = self.ParmData[irow][it]
self.ParmData[irow][it] = val
Obj.SetValue(self.ParmFmt[it]%val)
# process keyword parameters, notably style
options = {'size':(600,310), # default Frame keywords
'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
}
options.update(kw)
self.ParmList = ParmList
self.ParmFmt = ParmFmt
self.HistoList = HistoList
self.ParmData = ParmData
nCol = len(ParmList)
if options['style'] & wx.OK:
useOK = True
options['style'] ^= wx.OK
else:
useOK = False
if options['style'] & wx.CANCEL:
useCANCEL = True
options['style'] ^= wx.CANCEL
else:
useCANCEL = False
# create the dialog frame
wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
panel = wxscroll.ScrolledPanel(self)
# fill the dialog
Sizer = wx.BoxSizer(wx.VERTICAL)
Sizer.Add((-1,5))
Sizer.Add(wx.StaticText(panel,label=title),0)
dataSizer = wx.FlexGridSizer(0,nCol+1,0,0)
self.sel = []
self.mod = []
Indx = {}
for item in ['Histogram',]+self.ParmList:
dataSizer.Add(wx.StaticText(panel,-1,label=' %10s '%(item)),0,WACV)
for irow,name in enumerate(self.HistoList):
dataSizer.Add(wx.StaticText(panel,label=name),0,WACV|wx.LEFT|wx.RIGHT,10)
for it,item in enumerate(self.ParmData[irow]):
dat = wx.TextCtrl(panel,-1,value=self.ParmFmt[it]%(item),style=wx.TE_PROCESS_ENTER)
dataSizer.Add(dat,0,WACV)
dat.Bind(wx.EVT_TEXT_ENTER,OnModify)
dat.Bind(wx.EVT_KILL_FOCUS,OnModify)
Indx[dat.GetId()] = [irow,it]
Sizer.Add(dataSizer)
Sizer.Add((-1,10))
# OK/Cancel buttons
btnsizer = wx.StdDialogButtonSizer()
if useOK:
self.OKbtn = wx.Button(panel, wx.ID_OK)
self.OKbtn.SetDefault()
btnsizer.AddButton(self.OKbtn)
self.OKbtn.Bind(wx.EVT_BUTTON, OnOk)
if useCANCEL:
btn = wx.Button(panel, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
Sizer.Add((-1,5))
Sizer.Add(btnsizer,0,wx.ALIGN_LEFT,20)
Sizer.Add((-1,5))
# OK done, let's get outa here
panel.SetSizer(Sizer)
panel.SetAutoLayout(1)
panel.SetupScrolling()
Size = [450,375]
panel.SetSize(Size)
Size[0] += 25; Size[1]+= 25
self.SetSize(Size)
[docs]
def GetData(self):
'Returns the modified ParmData'
return self.ParmData
################################################################################
[docs]
def ItemSelector(ChoiceList, ParentFrame=None,
title='Select an item',
size=None, header='Item Selector',
useCancel=True,multiple=False):
''' Provide a wx dialog to select a single item or multiple items from list of choices
:param list ChoiceList: a list of choices where one will be selected
:param wx.Frame ParentFrame: Name of parent frame (default None)
:param str title: heading above list of choices (default 'Select an item')
:param wx.Size size: Size for dialog to be created (default None -- size as needed)
:param str header: Title to place on window frame (default 'Item Selector')
:param bool useCancel: If True (default) both the OK and Cancel buttons are offered
:param bool multiple: If True then multiple items can be selected (default False)
:returns: the selection index or None or a selection list if multiple is true
Called by GSASIIdataGUI.OnReOrgSelSeq() Which is not fully implemented.
'''
if multiple:
if useCancel:
dlg = G2MultiChoiceDialog(
ParentFrame,title, header, ChoiceList)
else:
dlg = G2MultiChoiceDialog(
ParentFrame,title, header, ChoiceList,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
else:
if useCancel:
dlg = wx.SingleChoiceDialog(
ParentFrame,title, header, ChoiceList)
else:
dlg = wx.SingleChoiceDialog(
ParentFrame,title, header,ChoiceList,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
if size: dlg.SetSize(size)
if dlg.ShowModal() == wx.ID_OK:
if multiple:
dlg.Destroy()
return dlg.GetSelections()
else:
dlg.Destroy()
return dlg.GetSelection()
else:
dlg.Destroy()
return None
dlg.Destroy()
########################################################
# Column-order selection dialog
[docs]
def GetItemOrder(parent,keylist,vallookup,posdict):
'''Creates a dialog where items can be ordered into columns
:param list keylist: is a list of keys for column assignments
:param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
Each inner dict contains variable names as keys and their associated values
:param dict posdict: is a dict keyed by names in keylist where each item is a dict.
Each inner dict contains column numbers as keys and their associated
variable name as a value. This is used for both input and output.
'''
dlg = wx.Dialog(parent,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
sizer = wx.BoxSizer(wx.VERTICAL)
spanel = OrderBox(dlg,keylist,vallookup,posdict)
spanel.Fit()
sizer.Add(spanel,1,wx.EXPAND)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(dlg, wx.ID_OK)
btn.SetDefault()
btnsizer.AddButton(btn)
#btn = wx.Button(dlg, wx.ID_CANCEL)
#btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5)
dlg.SetSizer(sizer)
sizer.Fit(dlg)
dlg.ShowModal()
################################################################################
[docs]
class MultiIntegerDialog(wx.Dialog):
'''Input a series of integers based on prompts
'''
def __init__(self,parent,title,prompts,values):
wx.Dialog.__init__(self,parent,-1,title,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw!
self.values = values
self.prompts = prompts
self.Draw()
def Draw(self):
def OnValItem(event):
event.Skip()
Obj = event.GetEventObject()
ind = Indx[Obj.GetId()]
try:
val = int(Obj.GetValue())
if val <= 0:
raise ValueError
except ValueError:
val = self.values[ind]
self.values[ind] = val
Obj.SetValue('%d'%(val))
self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
Indx = {}
for ival,[prompt,value] in enumerate(zip(self.prompts,self.values)):
mainSizer.Add(wx.StaticText(self.panel,-1,prompt),0,wx.ALIGN_CENTER)
valItem = wx.TextCtrl(self.panel,-1,value='%d'%(value),style=wx.TE_PROCESS_ENTER)
mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
Indx[valItem.GetId()] = ival
valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
CancelBtn = wx.Button(self.panel,-1,'Cancel')
CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add(CancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
def GetValues(self):
return self.values
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_CANCEL)
################################################################################
[docs]
class MultiColumnSelection(wx.Dialog):
'''Defines a Dialog widget that can be used to select an item from a multicolumn list.
The first column should be short, but remaining columns are word-wrapped if the
length of the information extends beyond the column.
When created, the dialog will be shown and <dlg>.Selection will be set to the index
of the selected row, or -1. Be sure to use <dlg>.Destroy() to remove the window
after reading the selection. If the dialog cannot be shown because a very old
version of wxPython is in use, <dlg>.Selection will be None.
If checkLbl is provided with a value, then a set of check buttons starts the table
and <dlg>.Selections has the checked rows.
:param wx.Frame parent: the parent frame (or None)
:param str title: A title for the dialog window
:param list colLabels: labels for each column
:param list choices: a nested list with a value for each row in the table. Within each value
should be a list of values for each column. There must be at least one value, but it is
OK to have more or fewer values than there are column labels (colLabels). Extra are ignored
and unspecified columns are left blank.
:param list colWidths: a list of int values specifying the column width for each
column in the table (pixels). There must be a value for every column label (colLabels).
:param str checkLbl: A label for a row of checkboxes added at the beginning of the table
:param int height: an optional height (pixels) for the table (defaults to 400)
:param bool centerCols: if True, items in each column are centered. Default is False
Example use::
lbls = ('col 1','col 2','col 3')
choices=(['test1','explanation of test 1'],
['b', 'a really really long line that will be word-wrapped'],
['test3','more explanation text','optional 3rd column text'])
colWidths=[200,400,100]
dlg = MultiColumnSelection(frm,'select tutorial',lbls,choices,colWidths)
value = choices[dlg.Selection][0]
dlg.Destroy()
'''
def __init__(self, parent, title, colLabels, choices, colWidths, checkLbl="",
height=400, centerCols=False, *args, **kw):
if len(colLabels) != len(colWidths):
raise ValueError('Length of colLabels) != colWidths')
sizex = 20 # extra room for borders, etc.
for i in colWidths: sizex += i
wx.Dialog.__init__(self, parent, wx.ID_ANY, title, *args,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
size=(sizex,height), **kw)
self.Selections = len(choices)*[False]
try:
from wx.lib.wordwrap import wordwrap
import wx.lib.agw.ultimatelistctrl as ULC
except ImportError:
self.Selection = None
return
self.Selection = -1
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.list = ULC.UltimateListCtrl(self, agwStyle=ULC.ULC_REPORT|ULC.ULC_HAS_VARIABLE_ROW_HEIGHT
|ULC.ULC_HRULES|ULC.ULC_HRULES|ULC.ULC_SINGLE_SEL)
if centerCols:
colPosition = ULC.ULC_FORMAT_CENTER
else:
colPosition = ULC.ULC_FORMAT_LEFT
if checkLbl:
self.list.InsertColumn(0, checkLbl, width=8*len(checkLbl), format=colPosition)
inc = 1
else:
inc = 0
for i,(lbl,wid) in enumerate(zip(colLabels, colWidths)):
self.list.InsertColumn(i+inc, lbl, width=wid, format=colPosition)
for i,item in enumerate(choices):
if item[0].startswith(' '):
item[0] = '--- '+item[0].strip()
if checkLbl:
def OnCheck(event,row=i):
self.Selections[row] = event.EventObject.GetValue()
c = wx.CheckBox(self.list)
c.Bind(wx.EVT_CHECKBOX,OnCheck)
self.list.InsertStringItem(i, "")
citem = self.list.GetItem(i,0)
citem.SetWindow(c)
self.list.SetItem(citem)
self.list.SetStringItem(i, 1, item[0])
else:
self.list.InsertStringItem(i, item[0])
for j,item in enumerate(item[1:len(colLabels)]):
item = wordwrap(StripIndents(item,True), colWidths[j+1], wx.ClientDC(self))
item += "\n==========================================="
self.list.SetStringItem(i,1+j+inc, item)
# make buttons
mainSizer.Add(self.list, 1, wx.EXPAND|wx.ALL, 1)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
OKbtn = wx.Button(self, wx.ID_OK)
OKbtn.SetDefault()
btnsizer.Add(OKbtn)
if not checkLbl:
btn = wx.Button(self, wx.ID_CLOSE,"Cancel")
btnsizer.Add(btn)
mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
# bindings for close of window, double-click,...
self.Bind(wx.EVT_CLOSE, self._onClose)
if not checkLbl:
OKbtn.Bind(wx.EVT_BUTTON,self._onSelect)
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._onSelect)
btn.Bind(wx.EVT_BUTTON,self._onClose)
self.SetSizer(mainSizer)
self.ShowModal()
def _onClose(self,event):
event.Skip()
self.EndModal(wx.ID_CANCEL)
def _onSelect(self,event):
if self.list.GetNextSelected(-1) == -1: return
self.Selection = self.list.GetNextSelected(-1)
self.EndModal(wx.ID_OK)
################################################################################
[docs]
class OrderBox(wxscroll.ScrolledPanel):
'''Creates a panel with scrollbars where items can be ordered into columns
:param list keylist: is a list of keys for column assignments
:param dict vallookup: is a dict keyed by names in keylist where each item is a dict.
Each inner dict contains variable names as keys and their associated values
:param dict posdict: is a dict keyed by names in keylist where each item is a dict.
Each inner dict contains column numbers as keys and their associated
variable name as a value. This is used for both input and output.
'''
def __init__(self,parent,keylist,vallookup,posdict,*arg,**kw):
self.keylist = keylist
self.vallookup = vallookup
self.posdict = posdict
self.maxcol = 0
for nam in keylist:
posdict = self.posdict[nam]
if posdict.keys():
self.maxcol = max(self.maxcol, max(posdict))
wxscroll.ScrolledPanel.__init__(self,parent,wx.ID_ANY,*arg,**kw)
self.GBsizer = wx.GridBagSizer(4,4)
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
self.SetSizer(self.GBsizer)
colList = [str(i) for i in range(self.maxcol+2)]
for i in range(self.maxcol+1):
wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
wid.SetBackgroundColour(DULL_YELLOW)
wid.SetMinSize((50,-1))
self.GBsizer.Add(wid,(0,i),flag=wx.EXPAND)
self.chceDict = {}
for row,nam in enumerate(self.keylist):
posdict = self.posdict[nam]
for col in posdict:
lbl = posdict[col]
pnl = wx.Panel(self,wx.ID_ANY)
pnl.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
insize = wx.BoxSizer(wx.VERTICAL)
wid = wx.Choice(pnl,wx.ID_ANY,choices=colList)
insize.Add(wid,0,wx.EXPAND|wx.BOTTOM,3)
wid.SetSelection(col)
self.chceDict[wid] = (row,col)
wid.Bind(wx.EVT_CHOICE,self.OnChoice)
wid = wx.StaticText(pnl,wx.ID_ANY,lbl)
insize.Add(wid,0,flag=wx.EXPAND)
try:
val = G2fil.FormatSigFigs(self.vallookup[nam][lbl],maxdigits=8)
except KeyError:
val = '?'
wid = wx.StaticText(pnl,wx.ID_ANY,'('+val+')')
insize.Add(wid,0,flag=wx.EXPAND)
pnl.SetSizer(insize)
self.GBsizer.Add(pnl,(row+1,col),flag=wx.EXPAND)
self.SetAutoLayout(1)
self.SetupScrolling()
self.SetMinSize((
min(700,self.GBsizer.GetSize()[0]),
self.GBsizer.GetSize()[1]+20))
[docs]
def OnChoice(self,event):
'''Called when a column is assigned to a variable
'''
row,col = self.chceDict[event.EventObject] # which variable was this?
newcol = event.Selection # where will it be moved?
if newcol == col:
return # no change: nothing to do!
prevmaxcol = self.maxcol # save current table size
key = self.keylist[row] # get the key for the current row
lbl = self.posdict[key][col] # selected variable name
lbl1 = self.posdict[key].get(col+1,'') # next variable name, if any
# if a posXXX variable is selected, and the next variable is posXXX, move them together
repeat = 1
if lbl[:3] == 'pos' and lbl1[:3] == 'int' and lbl[3:] == lbl1[3:]:
repeat = 2
for i in range(repeat): # process the posXXX and then the intXXX (or a single variable)
col += i
newcol += i
if newcol in self.posdict[key]:
# find first non-blank after newcol
for mtcol in range(newcol+1,self.maxcol+2):
if mtcol not in self.posdict[key]: break
l1 = range(mtcol,newcol,-1)+[newcol]
l = range(mtcol-1,newcol-1,-1)+[col]
else:
l1 = [newcol]
l = [col]
# move all of the items, starting from the last column
for newcol,col in zip(l1,l):
#print 'moving',col,'to',newcol
self.posdict[key][newcol] = self.posdict[key][col]
del self.posdict[key][col]
self.maxcol = max(self.maxcol,newcol)
obj = self.GBsizer.FindItemAtPosition((row+1,col))
self.GBsizer.SetItemPosition(obj.GetWindow(),(row+1,newcol))
for wid in obj.GetWindow().Children:
if wid in self.chceDict:
self.chceDict[wid] = (row,newcol)
wid.SetSelection(self.chceDict[wid][1])
# has the table gotten larger? If so we need new column heading(s)
if prevmaxcol != self.maxcol:
for i in range(prevmaxcol+1,self.maxcol+1):
wid = wx.StaticText(self,wx.ID_ANY,str(i),style=wx.ALIGN_CENTER)
wid.SetBackgroundColour(DULL_YELLOW)
wid.SetMinSize((50,-1))
self.GBsizer.Add(wid,(0,i),flag=wx.EXPAND)
colList = [str(i) for i in range(self.maxcol+2)]
for wid in self.chceDict:
wid.SetItems(colList)
wid.SetSelection(self.chceDict[wid][1])
self.GBsizer.Layout()
self.FitInside()
################################################################################
[docs]
def GetImportFile(G2frame, message, defaultDir="", defaultFile="",
style=wx.FD_OPEN, parent=None,*args, **kwargs):
'''Uses a customized dialog that gets files from the appropriate import directory.
Arguments are used the same as in :func:`wx.FileDialog`. Selection of
multiple files is allowed if argument style includes wx.FD_MULTIPLE.
The default initial directory (unless overridden with argument defaultDir)
is found in G2frame.TutorialImportDir, config setting Import_directory or
G2frame.LastImportDir, see :func:`GetImportPath`.
The path of the first file entered is used to set G2frame.LastImportDir
and optionally config setting Import_directory.
:returns: a list of files or an empty list
'''
if not parent: parent = G2frame
pth = GetImportPath(G2frame)
#if GSASIIpath.GetConfigValue('debug'):
# print('debug: GetImportFile from '+defaultDir)
# print('debug: GetImportFile pth '+pth)
dlg = wx.FileDialog(parent, message, defaultDir, defaultFile, *args,style=style, **kwargs)
# dlg.CenterOnParent()
if not defaultDir and pth: dlg.SetDirectory(pth)
try:
if dlg.ShowModal() == wx.ID_OK:
if style & wx.FD_MULTIPLE:
filelist = dlg.GetPaths()
if len(filelist) == 0: return []
else:
filelist = [dlg.GetPath(),]
# not sure if we want to do this (why use wx.CHANGE_DIR?)
if style & wx.FD_CHANGE_DIR: # to get Mac/Linux to change directory like windows!
os.chdir(dlg.GetDirectory())
else: # cancel was pressed
return []
finally:
dlg.Destroy()
# save the path of the first file and reset the TutorialImportDir variable
pth = os.path.split(os.path.abspath(filelist[0]))[0]
if GSASIIpath.GetConfigValue('Save_paths'): SaveImportDirectory(pth)
G2frame.LastImportDir = pth
G2frame.TutorialImportDir = None
return filelist
[docs]
def GetImportPath(G2frame):
'''Determines the default location to use for importing files. Tries sequentially
G2frame.TutorialImportDir, config var Import_directory, G2frame.LastImportDir
and G2frame.LastGPXdir
:returns: a string containing the path to be used when reading files or '.'
if none of the above are specified.
'''
if G2frame.TutorialImportDir:
if os.path.exists(G2frame.TutorialImportDir):
return G2frame.TutorialImportDir
elif GSASIIpath.GetConfigValue('debug'):
print('DBG_Tutorial location (TutorialImportDir) not found: '+G2frame.TutorialImportDir)
pth = GSASIIpath.GetConfigValue('Import_directory')
if pth:
pth = os.path.expanduser(pth)
if os.path.exists(pth):
return pth
elif GSASIIpath.GetConfigValue('debug'):
print('Ignoring Config Import_directory value: '+GSASIIpath.GetConfigValue('Import_directory'))
if G2frame.LastImportDir:
if os.path.exists(G2frame.LastImportDir):
return G2frame.LastImportDir
elif GSASIIpath.GetConfigValue('debug'):
print('DBG_Warning: G2frame.LastImportDir not found = '+G2frame.LastImportDir)
elif G2frame.LastGPXdir:
return G2frame.LastGPXdir
print('Import path not found - set to current directory') #now shouldn't happen
return '.'
[docs]
def GetExportPath(G2frame):
'''Determines the default location to use for writing files. Tries sequentially
G2frame.LastExportDir and G2frame.LastGPXdir.
:returns: a string containing the path to be used when writing files or '.'
if none of the above are specified.
'''
if G2frame.LastExportDir:
return G2frame.LastExportDir
elif G2frame.LastGPXdir:
return G2frame.LastGPXdir
print('Export path not found - set to current directory') #now shouldn't happen
return '.'
################################################################################
[docs]
class SGMessageBox(wx.Dialog):
''' Special version of MessageBox that displays space group & super space group text
in two blocks
'''
def __init__(self,parent,title,text,table,spins=[],):
wx.Dialog.__init__(self,parent,wx.ID_ANY,title,pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
self.text = text
self.table = table
self.panel = wx.Panel(self)
self.spins = spins
self.useAlt = False
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add((0,10))
for line in text:
mainSizer.Add(wx.StaticText(self.panel,label=' %s '%(line)))
ncol = self.table[0].count(',')+1
tableSizer = wx.FlexGridSizer(0,2*ncol+3,0,0)
j = 0
for item in self.table:
if 'for' in item:
mainSizer.Add(tableSizer,0,wx.ALIGN_LEFT)
mainSizer.Add(wx.StaticText(self.panel,label=item),0)
tableSizer = wx.FlexGridSizer(0,2*ncol+3,0,0)
continue
num,flds = item.split(')')
tableSizer.Add(wx.StaticText(self.panel,label=' %s '%(num+')')),0,WACV|wx.ALIGN_LEFT)
flds = flds.replace(' ','').split(',')
for i,fld in enumerate(flds):
if i < ncol-1:
text = wx.StaticText(self.panel,label='%s, '%(fld))
else:
text = wx.StaticText(self.panel,label='%s'%(fld))
if len(self.spins) and self.spins[j] < 0:
text.SetForegroundColour('Red')
tableSizer.Add(text,0,WACV|wx.ALIGN_RIGHT)
if not j%2:
tableSizer.Add((20,0))
j += 1
def OnPrintOps(event):
print(' Symmetry operations for %s:'%self.text[0].split(':')[1])
for iop,opText in enumerate(G2spc.TextOps(self.text,self.table,reverse=True)):
if self.useAlt:
opText = opText.replace('x','x1').replace('y','x2').replace('z','x3').replace('t','x4')
if len(self.spins):
if self.spins[iop] > 0:
print('%s,+%d'%(opText.replace(' ',''),self.spins[iop]))
else:
print('%s,%d'%(opText.replace(' ',''),self.spins[iop]))
else:
print(opText.replace(' ',''))
def OnAlt(event):
self.useAlt = altBtn.GetValue()
mainSizer.Add(tableSizer,0,wx.ALIGN_LEFT)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
OKbtn = wx.Button(self.panel, wx.ID_OK)
OKbtn.Bind(wx.EVT_BUTTON, self.OnOk)
btnsizer.Add(OKbtn)
printBtn = wx.Button(self.panel,label='Print Ops')
printBtn.Bind(wx.EVT_BUTTON, OnPrintOps)
btnsizer.Add(printBtn)
altBtn = wx.CheckBox(self.panel,label=' Use alt. symbols?')
altBtn.Bind(wx.EVT_CHECKBOX,OnAlt)
btnsizer.Add(altBtn,0,WACV)
mainSizer.Add((0,10))
mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
size = self.GetSize()
self.SetSize([size[0]+20,size[1]])
[docs]
def Show(self):
'''Use this method after creating the dialog to post it
'''
self.ShowModal()
return
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
################################################################################
[docs]
class SGMagSpinBox(wx.Dialog):
''' Special version of MessageBox that displays magnetic spin text
'''
def __init__(self,parent,title,text,table,Cents,names,spins,ifGray):
wx.Dialog.__init__(self,parent,wx.ID_ANY,title,pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,size=wx.Size(420,350))
self.text = text
self.table = table
self.names = names
Nnames = len(self.names)
self.spins = spins
self.ifGray = ifGray
self.PrintTable = [' Magnetic symmetry operations for %s:'%self.text[0].split(':')[1],]
self.panel = wxscroll.ScrolledPanel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add((0,10))
cents = [0,]
if len(Cents) > 1:
cents = self.text[-1].split(';')
for line in self.text:
mainSizer.Add(wx.StaticText(self.panel,label=' %s '%(line)),0)
if 'equivalent' in line:
break
ncol = self.table[0].count(',')+2
nG = 1
j = 0
for ng in range(nG):
if ng:
mainSizer.Add(wx.StaticText(self.panel,label=" for (0,0,0)+1'"),0)
j = 0
for ic,cent in enumerate(cents):
Cent = np.zeros(3)
if cent:
cent = cent.strip(' (').strip(')+\n')
Cent = np.array(eval(cent)[:3])
# Cent = np.array(Cents[ic])
if ic:
if cent: cent = cent.strip(' (').strip(')+\n')
label = ' for (%s)+'%(cent)
if ng: #test for gray operators
label += "1'"
mainSizer.Add(wx.StaticText(self.panel,label=label),0)
tableSizer = wx.FlexGridSizer(0,2*ncol+3,0,0)
for item in self.table:
if ')' not in item:
continue
flds = item.split(')')[1]
tableSizer.Add(wx.StaticText(self.panel,label=' (%2d) '%(j+1)),0,WACV)
flds = flds.replace(' ','').split(',')
for i,fld in enumerate(flds):
if i < ncol-1:
text = wx.StaticText(self.panel,label='%s, '%(fld))
else:
text = wx.StaticText(self.panel,label='%s '%(fld))
tableSizer.Add(text,0,WACV)
text = wx.StaticText(self.panel,label=' (%s) '%(self.names[j%Nnames]))
try:
if self.spins[j] < 0:
text.SetForegroundColour('Red')
item += ',-1'
else:
item += ',+1'
except IndexError:
print(self.spins,j,self.names[j%Nnames])
item += ',+1'
M,T,S = G2spc.MagText2MTS(item.split(')')[1].replace(' ',''),CIF=False)
T = (T+Cent)%1.
item = G2spc.MT2text([M,T],reverse=True)
if S > 0:
item += ',+1'
else:
item += ',-1'
self.PrintTable.append(item.replace(' ','').lower())
tableSizer.Add(text,0,WACV)
if not j%2:
tableSizer.Add((20,0))
j += 1
mainSizer.Add(tableSizer,0)
def OnPrintOps(event):
for item in self.PrintTable:
print(item)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
OKbtn = wx.Button(self.panel, wx.ID_OK)
btnsizer.Add(OKbtn)
printBtn = wx.Button(self.panel,label='Print Ops')
printBtn.Bind(wx.EVT_BUTTON, OnPrintOps)
btnsizer.Add(printBtn)
OKbtn.SetFocus()
mainSizer.Add((0,10))
mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
self.panel.SetSizer(mainSizer)
self.panel.SetAutoLayout(True)
self.panel.SetScrollRate(10,10)
self.panel.SendSizeEvent()
[docs]
def Show(self):
'''Use this method after creating the dialog to post it
'''
self.ShowModal()
return
################################################################################
[docs]
class DisAglDialog(wx.Dialog):
'''Distance/Angle Controls input dialog. After
:meth:`ShowModal` returns, the results are found in
dict :attr:`self.data`, which is accessed using :meth:`GetData`.
:param wx.Frame parent: reference to parent frame (or None)
:param dict data: a dict containing the current
search ranges or an empty dict, which causes default values
to be used.
Will be used to set element `DisAglCtls` in
:ref:`Phase Tree Item <Phase_table>`
:param dict default: A dict containing the default
search ranges for each element.
:param bool Reset: if True (default), show Reset button
:param bool Angle: if True (default), show angle radii
'''
def __init__(self,parent,data,default,Reset=True,Angle=True):
text = 'Distance Angle Controls'
if not Angle:
text = 'Distance Controls'
wx.Dialog.__init__(self,parent,wx.ID_ANY,text,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.default = default
self.Reset = Reset
self.Angle = Angle
self.panel = None
self._default(data,self.default)
self.Draw(self.data)
self.CenterOnParent()
def _default(self,data,default):
'''Set starting values for the search values, either from
the input array or from defaults, if input is null
'''
if data:
self.data = copy.deepcopy(data) # don't mess with originals
else:
self.data = {}
self.data['Name'] = default['Name']
self.data['Factors'] = [0.85,0.85]
self.data['AtomTypes'] = default['AtomTypes']
self.data['BondRadii'] = default['BondRadii'][:]
self.data['AngleRadii'] = default['AngleRadii'][:]
[docs]
def Draw(self,data):
'''Creates the contents of the dialog. Normally called
by :meth:`__init__`.
'''
if self.panel: self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self.panel,-1,'Controls for phase '+data['Name']),0,wx.LEFT,10)
mainSizer.Add((10,10),1)
ncol = 3
if not self.Angle:
ncol=2
radiiSizer = wx.FlexGridSizer(0,ncol,5,5)
radiiSizer.Add(wx.StaticText(self.panel,-1,' Type'),0,WACV)
radiiSizer.Add(wx.StaticText(self.panel,-1,'Bond radii'),0,WACV)
if self.Angle:
radiiSizer.Add(wx.StaticText(self.panel,-1,'Angle radii'),0,WACV)
self.objList = {}
for id,item in enumerate(self.data['AtomTypes']):
radiiSizer.Add(wx.StaticText(self.panel,-1,' '+item),0,WACV)
bRadii = ValidatedTxtCtrl(self.panel,data['BondRadii'],id,nDig=(10,3))
radiiSizer.Add(bRadii,0,WACV)
if self.Angle:
aRadii = ValidatedTxtCtrl(self.panel,data['AngleRadii'],id,nDig=(10,3))
radiiSizer.Add(aRadii,0,WACV)
mainSizer.Add(radiiSizer,0,wx.EXPAND)
Names = ['Bond']
factorSizer = wx.FlexGridSizer(0,2,5,5)
if self.Angle:
Names = ['Bond','Angle']
for i,name in enumerate(Names):
factorSizer.Add(wx.StaticText(self.panel,-1,name+' search factor'),0,WACV)
bondFact = ValidatedTxtCtrl(self.panel,data['Factors'],i,nDig=(10,3))
factorSizer.Add(bondFact)
mainSizer.Add(factorSizer,0,wx.EXPAND)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
if self.Reset:
ResetBtn = wx.Button(self.panel,-1,'Reset')
ResetBtn.Bind(wx.EVT_BUTTON, self.OnReset)
btnSizer.Add(ResetBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
[docs]
def GetData(self):
'Returns the values from the dialog'
return self.data
[docs]
def OnOk(self,event):
'Called when the OK button is pressed'
parent = self.GetParent()
if parent is not None: parent.Raise()
self.EndModal(wx.ID_OK)
[docs]
def OnReset(self,event):
'Called when the Reset button is pressed'
data = {}
self._default(data,self.default)
wx.CallAfter(self.Draw,self.data)
################################################################################
[docs]
class ShowLSParms(wx.Dialog):
'''Create frame to show least-squares parameters
'''
def __init__(self,G2frame,title,parmDict,varyList,fullVaryList,
Controls, size=(650,430)):
wx.Dialog.__init__(self,G2frame,wx.ID_ANY,title,size=size,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
self.parmChoice = 'Phase'
self.G2frame = G2frame
self.parmDict = parmDict
self.varyList = varyList
self.fullVaryList = fullVaryList
self.Controls = Controls
self.choiceDict = {}
parmFrozen = Controls.get('parmFrozen',{})
if G2frame.testSeqRefineMode():
frozenList = set()
for h in parmFrozen:
if h == 'FrozenList': continue
frozenList = frozenList.union(parmFrozen[h])
self.frozenList = list(frozenList)
elif 'FrozenList' in parmFrozen:
self.frozenList = copy.copy(parmFrozen['FrozenList'])
else:
self.frozenList = []
# make lists of variables of different types along with lists of parameter names, histogram #s, phase #s,...
self.parmNames = sorted(list(parmDict.keys()))
if '2' in platform.python_version_tuple()[0]:
basestr = basestring
else:
basestr = str
splitNames = [item.split(':') for item in self.parmNames if len(item) > 3 and not isinstance(self.parmDict[item],basestr)]
globNames = [':'.join(item) for item in splitNames if not item[0] and not item[1]]
if len(globNames):
self.choiceDict['Global'] = G2obj.SortVariables(globNames)
self.globVars = sorted(list(set([' ',]+[item[2] for item in splitNames if not item[0] and not item[1]])))
hisNames = [':'.join(item) for item in splitNames if not item[0] and item[1]]
self.choiceDict['Histogram'] = G2obj.SortVariables(hisNames)
self.hisNums = sorted(list(set([int(item.split(':')[1]) for item in hisNames])))
self.hisNums = ['*',]+[str(item) for item in self.hisNums]
self.hisVars = sorted(list(set([' ',]+[item[2] for item in splitNames if not item[0]])))
phasNames = [':'.join(item) for item in splitNames if not item[1] and not item[2].startswith('is')]
self.choiceDict['Phase'] = G2obj.SortVariables(phasNames)
self.phasNums = sorted(['*',]+list(set([item.split(':')[0] for item in phasNames])))
if '' in self.phasNums: self.phasNums.remove('')
self.phasVars = sorted(list(set([' ',]+[item[2] for item in splitNames if not item[1] and not item[2].startswith('is')])))
hapNames = [':'.join(item) for item in splitNames if item[0] and item[1]]
self.choiceDict['Phase/Histo'] = G2obj.SortVariables(hapNames)
self.hapVars = sorted(list(set([' ',]+[item[2] for item in splitNames if item[0] and item[1]])))
self.hisNum = '*'
self.phasNum = '*'
self.varName = ' '
self.listSel = 'Refined'
self.DrawPanel()
#if GSASIIpath.GetConfigValue('debug'):
# print('repaintScrollTbl',time.time()-start)
[docs]
def DrawPanel(self):
'''Draws the contents of the entire dialog. Called initially & when radio buttons are pressed
'''
def _OnParmSel(event):
'New parameter type, reset var name choice as list changes'
self.parmChoice = parmSel.GetStringSelection()
if varSel:
varSel.SetSelection(0)
self.varName = ' '
wx.CallLater(100,self.DrawPanel)
def OnPhasSel(event):
'phase has been selected'
event.Skip()
self.phasNum = phasSel.GetValue()
if varSel:
try:
varSel.SetSelection(varSel.GetItems().index(self.varName))
except:
varSel.SetSelection(0)
self.varName = ' '
wx.CallAfter(self.repaintScrollTbl)
def OnHistSel(event):
'histogram has been selected'
event.Skip()
self.hisNum = histSel.GetValue()
if varSel:
try:
varSel.SetSelection(varSel.GetItems().index(self.varName))
except:
varSel.SetSelection(0)
self.varName = ' '
wx.CallAfter(self.repaintScrollTbl)
def OnVarSel(event):
'parameter name has been selected'
event.Skip()
self.varName = varSel.GetValue()
if phasSel:
try:
phasSel.SetSelection(phasSel.GetItems().index(self.phasNum))
except:
phasSel.SetSelection(0)
self.phasNum = '*'
if histSel:
try:
histSel.SetSelection(histSel.GetItems().index(self.hisNum))
except:
histSel.SetSelection(0)
self.hisNum = '*'
wx.CallAfter(self.repaintScrollTbl)
def OnListSel(event):
self.listSel = listSel.GetStringSelection()
wx.CallLater(100,self.DrawPanel)
def OnVarSpin(event):
'''Respond when any of the SpinButton widgets are pressed'''
event.Skip()
Spinner = event.GetEventObject()
move = Spinner.GetValue()
Spinner.SetValue(0)
varSel,binding = self.SpinDict[Spinner.GetId()]
i = varSel.GetSelection() - move
if i < 0:
i = varSel.GetCount()-1
elif i >= varSel.GetCount():
i = 0
varSel.SetSelection(i)
wx.CallLater(100,binding,event)
def AddSpinner(varSizer,label,SelCtrl,binding):
'''Add a label and a SpinButton to a Combo widget (SelCtrl)
Saves a pointer to the combo widget and the callback used by that widget
'''
SelCtrl.Bind(wx.EVT_COMBOBOX,binding)
varSizer.Add(wx.StaticText(self,label=label))
varSelSizer = wx.BoxSizer(wx.HORIZONTAL)
varSelSizer.Add(SelCtrl,0)
varSpin = wx.SpinButton(self,style=wx.SP_VERTICAL)
varSpin.SetValue(0)
varSpin.SetRange(-1,1)
varSpin.Bind(wx.EVT_SPIN, OnVarSpin)
self.SpinDict[varSpin.GetId()] = SelCtrl,binding
varSelSizer.Add(varSpin,0)
varSizer.Add(varSelSizer,0)
if self.GetSizer(): self.GetSizer().Clear(True)
self.SpinDict = {}
mainSizer = wx.BoxSizer(wx.VERTICAL)
num = len(self.varyList)
mainSizer.Add(wx.StaticText(self,label='View Parameters in Project'),0,wx.ALIGN_CENTER)
parmSizer = wx.BoxSizer(wx.HORIZONTAL)
parmSizer.Add(wx.StaticText(self,label=' Number of refined variables: {}'.format(num)),0,wx.ALIGN_LEFT)
if len(self.varyList) != len(self.fullVaryList):
num = len(self.fullVaryList) - len(self.varyList)
parmSizer.Add(wx.StaticText(self,label=
' + {} varied via constraints'.format(
len(self.fullVaryList) - len(self.varyList))
))
parmFrozen = self.Controls.get('parmFrozen',{})
fcount = 0
if self.G2frame.testSeqRefineMode():
for h in parmFrozen:
if h == 'FrozenList': continue
fcount += len(parmFrozen[h])
elif 'FrozenList' in parmFrozen:
fcount = len(parmFrozen['FrozenList'])
if fcount:
parmSizer.Add(wx.StaticText(self,label=
' - {} frozen variables'.format(fcount)))
mainSizer.Add(parmSizer)
choice = ['Phase','Phase/Histo','Histogram']
if 'Global' in self.choiceDict:
choice += ['Global',]
parmSizer = wx.BoxSizer(wx.HORIZONTAL)
parmSel = wx.RadioBox(self,wx.ID_ANY,'Parameter type:',choices=choice,
majorDimension=1,style=wx.RA_SPECIFY_COLS)
parmSel.Bind(wx.EVT_RADIOBOX,_OnParmSel)
parmSel.SetStringSelection(self.parmChoice)
parmSizer.Add(parmSel,0)
selectionsSizer = wx.BoxSizer(wx.VERTICAL)
varSizer = wx.BoxSizer(wx.VERTICAL)
varSel = None
if self.parmChoice != 'Global':
if self.parmChoice in ['Phase',]:
varSel = wx.ComboBox(self,choices=self.phasVars,value=self.varName,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
elif self.parmChoice in ['Histogram',]:
varSel = wx.ComboBox(self,choices=self.hisVars,value=self.varName,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
elif self.parmChoice in ['Phase/Histo',]:
varSel = wx.ComboBox(self,choices=self.hapVars,value=self.varName,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
AddSpinner(varSizer,'Parameter',varSel,OnVarSel)
selectionsSizer.Add(varSizer,0)
varSizer = wx.BoxSizer(wx.HORIZONTAL)
phasSel = None
if self.parmChoice in ['Phase','Phase/Histo'] and len(self.phasNums) > 1:
numSizer = wx.BoxSizer(wx.VERTICAL)
phasSel = wx.ComboBox(self,choices=self.phasNums,value=self.phasNum,
style=wx.CB_READONLY|wx.CB_DROPDOWN,size=(50,-1))
AddSpinner(numSizer,'Phase',phasSel,OnPhasSel)
varSizer.Add(numSizer)
histSel = None
if self.parmChoice in ['Histogram','Phase/Histo'] and len(self.hisNums) > 1:
numSizer = wx.BoxSizer(wx.VERTICAL)
histSel = wx.ComboBox(self,choices=self.hisNums,value=self.hisNum,
style=wx.CB_READONLY|wx.CB_DROPDOWN,size=(50,-1))
AddSpinner(numSizer,'Histogram',histSel,OnHistSel)
varSizer.Add(numSizer)
selectionsSizer.Add(varSizer,0)
parmSizer.Add(selectionsSizer,0)
refChoices = ['All','Refined']
txt = ('"R" indicates a refined variable\n'+
'"C" indicates generated from a user entered constraint')
if fcount:
refChoices += ['Frozen']
txt += '\n"F" indicates a variable that is Frozen due to exceeding min/max'
listSel = wx.RadioBox(self,wx.ID_ANY,'Refinement Status:',
choices=refChoices,
majorDimension=0,style=wx.RA_SPECIFY_COLS)
listSel.SetStringSelection(self.listSel)
listSel.Bind(wx.EVT_RADIOBOX,OnListSel)
parmSizer.Add(listSel,0,wx.CENTER|wx.ALL,15)
mainSizer.Add(parmSizer,0)
self.countSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.countSizer)
self.headSizer = wx.BoxSizer(wx.HORIZONTAL) # non-scrolling header
mainSizer.Add(self.headSizer,0)
self.varBox = VirtualVarBox(self)
mainSizer.Add(self.varBox,1,wx.ALL|wx.EXPAND,1)
mainSizer.Add(
wx.StaticText(self,label=txt),0, wx.ALL,0)
btnsizer = wx.BoxSizer(wx.HORIZONTAL) # make Close button
btn = wx.Button(self, wx.ID_CLOSE,"Close")
btn.Bind(wx.EVT_BUTTON,self._onClose)
btnsizer.Add(btn)
mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
self.SetSizer(mainSizer)
wx.CallAfter(self.repaintScrollTbl)
def _onClose(self,event):
self.EndModal(wx.ID_CANCEL)
[docs]
class VirtualVarBox(wx.ListCtrl):
def __init__(self, parent):
self.parmWin = parent
#patch (added Oct 2020) convert variable names for parm limits to G2VarObj
G2sc.patchControls(self.parmWin.Controls)
# end patch
wx.ListCtrl.__init__(
self, parent, -1,
style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES
)
for i,(lbl,wid) in enumerate(zip(
('#', "Parameter", "Ref", "Value", "Min", "Max", "Explanation"),
(40 , 125 , 30 , 100 , 75 , 75 , 700),)):
self.InsertColumn(i, lbl)
self.SetColumnWidth(i, wid)
self.SetItemCount(0)
try:
self.attr1 = wx.ItemAttr()
except:
self.attr1 = wx.ListItemAttr() # deprecated in wx4.1
self.attr1.SetBackgroundColour((255,255,150))
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnRowSelected)
def SetContents(self,parent):
self.varList = []
for name in parent.choiceDict[parent.parmChoice]:
if '2' in platform.python_version_tuple()[0]:
basestr = basestring
else:
basestr = str
if isinstance(parent.parmDict[name],basestr): continue
if 'Refined' in parent.listSel and (name not in parent.fullVaryList
) and (name not in parent.varyList):
continue
if 'Frozen' in parent.listSel and not (
name in self.parmWin.fullVaryList and
name in self.parmWin.frozenList):
continue
if 'Phase' in parent.parmChoice:
if parent.phasNum != '*' and name.split(':')[0] != parent.phasNum: continue
if 'Histo' in parent.parmChoice:
if parent.hisNum != '*' and name.split(':')[1] != parent.hisNum: continue
if (parent.varName != ' ') and (parent.varName not in name): continue
self.varList.append(name)
oldlen = self.GetItemCount()
self.SetItemCount(len(self.varList))
[docs]
def OnRowSelected(self, event, row=None):
'Creates an edit window when a parameter is selected'
def ResetFrozen(event):
'''release a frozen parameter (from all histograms in the case of a
sequential fit).
'''
if name in self.parmWin.frozenList:
del self.parmWin.frozenList[self.parmWin.frozenList.index(name)]
parmFrozen = self.parmWin.Controls.get('parmFrozen',{})
if self.parmWin.G2frame.testSeqRefineMode():
for h in parmFrozen:
if h == 'FrozenList': continue
if name in parmFrozen[h]:
del parmFrozen[h][parmFrozen[h].index(name)]
elif 'FrozenList' in parmFrozen:
if name in parmFrozen['FrozenList']:
del parmFrozen['FrozenList'][parmFrozen['FrozenList'].index(name)]
dlg.EndModal(wx.ID_CANCEL)
self.parmWin.SendSizeEvent()
def delM(event):
'Get event info & prepare to delete Max or Min limit'
if hasattr(event.EventObject,'max'):
d = self.parmWin.Controls['parmMaxDict']
else:
d = self.parmWin.Controls['parmMinDict']
# close sub-dialog then delete item and redraw
dlg.EndModal(wx.ID_OK)
wx.CallAfter(delMafter,d,name)
def delMafter(d,name):
'Delete Max or Min limit once dialog is deleted'
key,val = G2obj.prmLookup(name,d) # is this a wild-card?
if val is not None:
del d[key]
self.OnRowSelected(None, row)
def AddM(event):
'Get event info & add a Max or Min limit'
if hasattr(event.EventObject,'max'):
d = self.parmWin.Controls['parmMaxDict']
else:
d = self.parmWin.Controls['parmMinDict']
# close sub-dialog then delete item and redraw
dlg.EndModal(wx.ID_OK)
wx.CallAfter(AddMafter,d,name)
def AddMafter(d,name):
'Add a Max or Min limit & redraw'
try:
d[G2obj.G2VarObj(name)] = float(value)
except:
pass
self.OnRowSelected(None, row)
def SetWild(event):
'Get event info & prepare to set/clear item as wildcard'
if hasattr(event.EventObject,'max'):
d = self.parmWin.Controls['parmMaxDict']
else:
d = self.parmWin.Controls['parmMinDict']
ns = name.split(':')
if hasattr(event.EventObject,'hist'):
ns[1] = '*'
else:
ns[3] = '*'
wname = ':'.join(ns)
# close sub-dialog then delete item and redraw
dlg.EndModal(wx.ID_OK)
wx.CallAfter(SetWildAfter,d,name,wname,event.EventObject.GetValue())
def SetWildAfter(d,name,wname,mode):
'Set/clear item as wildcard & delete old name(s), redraw'
n,val = G2obj.prmLookup(name,d) # is this a wild-card?
if val is None:
print('Error: Limit for parameter {} not found. Should not happen'.format(name))
return
if mode: # make wildcard
for n in list(d.keys()): # delete names matching wildcard
if str(n) == wname: continue
if n == wname: # respects wildcards
del d[n]
d[G2obj.G2VarObj(wname)] = val
else:
del d[n]
d[G2obj.G2VarObj(name)] = val
self.OnRowSelected(None, row)
# start of OnRowSelected
if event is not None:
row = event.Index
elif row is None:
print('Error: row and event should not both be None!')
return
name = self.varList[row]
dlg = wx.Dialog(self.parmWin,wx.ID_ANY,'Parameter {} info'.format(name),
size=(600,-1),
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add((5,5))
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add((-1,-1),1,wx.EXPAND)
try:
value = G2fil.FormatSigFigs(self.parmWin.parmDict[name])
except TypeError:
value = str(self.parmWin.parmDict[name])+' -?' # unexpected
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,
'Parameter "{}" information and settings. Value={}'
.format(name,value)))
subSizer.Add((-1,-1),1,wx.EXPAND)
mainSizer.Add(subSizer,0,wx.EXPAND,0)
mainSizer.Add((0,10))
v = G2obj.getVarDescr(name)
if v is not None and v[-1] is not None:
txt = G2obj.fmtVarDescr(name)
if txt:
#txt = txt.replace('Ph=','Phase: ')
#txt = txt.replace('Pwd=','Histogram: ')
txtwid = wx.StaticText(dlg,wx.ID_ANY,'Parameter meaning is "'+txt+'"')
txtwid.Wrap(580)
mainSizer.Add(txtwid)
mainSizer.Add((0,10))
freezebtn = None
if name in self.parmWin.fullVaryList and name in self.parmWin.frozenList:
msg = "Parameter {} exceeded limits and has been frozen".format(name)
freezebtn = wx.Button(dlg, wx.ID_ANY,'Unfreeze')
freezebtn.Bind(wx.EVT_BUTTON, ResetFrozen)
elif name in self.parmWin.varyList:
msg = "Parameter {} is refined".format(name)
elif name in self.parmWin.fullVaryList:
msg = "Parameter {} is refined via a constraint".format(name)
else:
msg = ""
if msg:
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,msg),0,wx.CENTER)
if freezebtn:
subSizer.Add(freezebtn,0,wx.ALL|wx.CENTER,5)
mainSizer.Add(subSizer,0)
# draw min value widgets
mainSizer.Add((-1,10),0)
if name not in self.parmWin.varyList and name in self.parmWin.fullVaryList:
mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Limits not allowed on constrained variables'),0)
for key in 'parmMinDict','parmMaxDict':
d = self.parmWin.Controls[key]
n,v = G2obj.prmLookup(name,d)
if v is not None and str(n) == name:
try: # strange hard to reproduce problem with this not working
del d[n]
except:
if GSASIIpath.GetConfigValue('debug'):
print('debug: failed to delete ',name,'in',key)
else:
n,val = G2obj.prmLookup(name,self.parmWin.Controls['parmMinDict']) # is this a wild-card?
if val is None:
addMbtn = wx.Button(dlg, wx.ID_ANY,'Add Lower limit')
addMbtn.Bind(wx.EVT_BUTTON, AddM)
mainSizer.Add(addMbtn,0)
else:
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Minimum limit'),0,wx.CENTER)
subSizer.Add(ValidatedTxtCtrl(dlg,self.parmWin.Controls['parmMinDict'],n,nDig=(10,2,'g')),0,WACV)
delMbtn = wx.Button(dlg, wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT)
subSizer.Add((5,-1),0,WACV)
subSizer.Add(delMbtn,0,WACV)
delMbtn.Bind(wx.EVT_BUTTON, delM)
if name.split(':')[1]: # is this using a histogram?
subSizer.Add((5,-1),0,WACV)
wild = wx.CheckBox(dlg,wx.ID_ANY,label='Match all histograms ')
wild.SetValue(str(n).split(':')[1] == '*')
wild.Bind(wx.EVT_CHECKBOX,SetWild)
wild.hist = True
subSizer.Add(wild,0,WACV)
elif len(name.split(':')) > 3:
subSizer.Add((5,-1),0,WACV)
wild = wx.CheckBox(dlg,wx.ID_ANY,label='Match all atoms ')
wild.SetValue(str(n).split(':')[3] == '*')
wild.Bind(wx.EVT_CHECKBOX,SetWild)
subSizer.Add(wild,0,WACV)
mainSizer.Add(subSizer,0)
# draw max value widgets
mainSizer.Add((-1,10),0)
n,val = G2obj.prmLookup(name,self.parmWin.Controls['parmMaxDict']) # is this a wild-card?
if val is None:
addMbtn = wx.Button(dlg, wx.ID_ANY,'Add Upper limit')
addMbtn.Bind(wx.EVT_BUTTON, AddM)
addMbtn.max = True
mainSizer.Add(addMbtn,0)
else:
subSizer = wx.BoxSizer(wx.HORIZONTAL)
subSizer.Add(wx.StaticText(dlg,wx.ID_ANY,'Maximum limit'),0,wx.CENTER)
subSizer.Add(ValidatedTxtCtrl(dlg,self.parmWin.Controls['parmMaxDict'],n,nDig=(10,2,'g')),0,WACV)
delMbtn = wx.Button(dlg, wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT)
subSizer.Add((5,-1),0,WACV)
subSizer.Add(delMbtn,0,WACV)
delMbtn.Bind(wx.EVT_BUTTON, delM)
delMbtn.max = True
if name.split(':')[1]: # is this using a histogram?
subSizer.Add((5,-1),0,WACV)
wild = wx.CheckBox(dlg,wx.ID_ANY,label='Match all histograms ')
wild.SetValue(str(n).split(':')[1] == '*')
wild.Bind(wx.EVT_CHECKBOX,SetWild)
wild.max = True
wild.hist = True
subSizer.Add(wild,0,WACV)
elif len(name.split(':')) > 3:
subSizer.Add((5,-1),0,WACV)
wild = wx.CheckBox(dlg,wx.ID_ANY,label='Match all atoms ')
wild.SetValue(str(n).split(':')[3] == '*')
wild.Bind(wx.EVT_CHECKBOX,SetWild)
wild.max = True
subSizer.Add(wild,0,WACV)
mainSizer.Add(subSizer,0)
btnsizer = wx.StdDialogButtonSizer()
OKbtn = wx.Button(dlg, wx.ID_OK)
OKbtn.SetDefault()
OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
btnsizer.AddButton(OKbtn)
btnsizer.Realize()
mainSizer.Add((-1,5),1,wx.EXPAND,1)
mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
mainSizer.Add((-1,10))
dlg.SetSizer(mainSizer)
dlg.CenterOnParent()
if dlg.ShowModal() != wx.ID_OK: # if not OK, destroy & reopen
dlg.Destroy()
wx.CallAfter(self.OnRowSelected, None, row)
return
dlg.Destroy()
self.parmWin.SendSizeEvent()
#-----------------------------------------------------------------
# Callbacks to display info in table
[docs]
def OnGetItemText(self, item, col):
name = self.varList[item]
if col == 0:
return str(item)
elif col == 1:
return name
elif col == 2:
if name in self.parmWin.fullVaryList and name in self.parmWin.frozenList:
return "F"
elif name in self.parmWin.varyList:
return "R"
elif name in self.parmWin.fullVaryList:
return "C"
return ""
elif col == 3:
try:
value = G2fil.FormatSigFigs(self.parmWin.parmDict[name])
except TypeError:
value = str(self.parmWin.parmDict[name])+' -?' # unexpected
return value
elif col == 4 or col == 5: # min/max value
if col == 4: # min
d = self.parmWin.Controls['parmMinDict']
else:
d = self.parmWin.Controls['parmMaxDict']
n,val = G2obj.prmLookup(name,d)
if val is None: return ""
try:
return G2fil.FormatSigFigs(val,8)
except TypeError:
return "?"
elif col == 6:
v = G2obj.getVarDescr(name)
if v is not None and v[-1] is not None:
txt = G2obj.fmtVarDescr(name)
if txt: return txt
return ""
else:
return "?"
[docs]
def OnGetItemAttr(self, item):
name = self.varList[item]
if name in self.parmWin.varyList and name in self.parmWin.frozenList:
return self.attr1
else:
return None
##### Customized Grid Support ################################################################################
[docs]
class GSGrid(wg.Grid):
'''Basic wx.Grid implementation
'''
def __init__(self, parent, name=''):
wg.Grid.__init__(self,parent,-1,name=name)
if hasattr(parent.TopLevelParent,'currentGrids'):
parent.TopLevelParent.currentGrids.append(self) # save a reference to the grid in the Frame
self.SetScrollRate(0,0) #GSAS-II grids have no scroll bars by default
def Clear(self):
wg.Grid.ClearGrid(self)
def SetCellReadOnly(self,r,c,readonly=True):
self.SetReadOnly(r,c,isReadOnly=readonly)
def SetCellStyle(self,r,c,color="white",readonly=True):
self.SetCellBackgroundColour(r,c,color)
self.SetReadOnly(r,c,isReadOnly=readonly)
[docs]
def SetTable(self, table, *args, **kwargs):
'''Overrides the standard SetTable method with one that uses
GridFractionEditor for all numeric columns (unless useFracEdit
is false)
'''
setFracEdit = kwargs.get('useFracEdit',True)
if 'useFracEdit' in kwargs: del kwargs['useFracEdit']
wg.Grid.SetTable(self, table, *args, **kwargs)
if setFracEdit:
for i,t in enumerate(table.dataTypes):
if not t.startswith(wg.GRID_VALUE_FLOAT): continue
attr = wx.grid.GridCellAttr()
attr.IncRef()
attr.SetEditor(GridFractionEditor(self))
self.SetColAttr(i, attr)
def GetSelection(self):
#this is to satisfy structure drawing stuff in G2plt when focus changes
return None
[docs]
def completeEdits(self):
'complete any outstanding edits'
if self.IsCellEditControlEnabled(): # complete any grid edits in progress
self.SaveEditControlValue()
self.HideCellEditControl()
self.DisableCellEditControl()
################################################################################
[docs]
class Table(wg.GridTableBase):
'''Basic data table for use with GSgrid
'''
def __init__(self, data=[], rowLabels=None, colLabels=None, types = None):
wg.GridTableBase.__init__(self)
self.colLabels = colLabels
self.rowLabels = rowLabels
self.dataTypes = types
self.data = data
[docs]
def AppendRows(self, numRows=1):
self.data.append([])
return True
[docs]
def CanGetValueAs(self, row, col, typeName):
if self.dataTypes:
colType = self.dataTypes[col].split(':')[0]
if typeName == colType:
return True
else:
return False
else:
return False
[docs]
def CanSetValueAs(self, row, col, typeName):
return self.CanGetValueAs(row, col, typeName)
def DeleteRow(self,pos):
data = self.GetData()
self.SetData([])
new = []
for irow,row in enumerate(data):
if irow != pos:
new.append(row)
self.SetData(new)
[docs]
def GetColLabelValue(self, col):
if self.colLabels:
return self.colLabels[col]
def GetData(self):
data = []
for row in range(self.GetNumberRows()):
data.append(self.GetRowValues(row))
return data
[docs]
def GetNumberCols(self):
try:
return len(self.colLabels)
except TypeError:
return None
[docs]
def GetNumberRows(self):
return len(self.data)
[docs]
def GetRowLabelValue(self, row):
if self.rowLabels:
return self.rowLabels[row]
def GetColValues(self, col):
data = []
for row in range(self.GetNumberRows()):
data.append(self.GetValue(row, col))
return data
def GetRowValues(self, row):
data = []
for col in range(self.GetNumberCols()):
data.append(self.GetValue(row, col))
return data
[docs]
def GetTypeName(self, row, col):
try:
if self.data[row][col] is None:
return wg.GRID_VALUE_STRING
return self.dataTypes[col]
except (TypeError,IndexError):
return wg.GRID_VALUE_STRING
[docs]
def GetValue(self, row, col):
try:
if self.data[row][col] is None: return ""
return self.data[row][col]
except IndexError:
return None
[docs]
def InsertRows(self, pos, rows):
for row in range(rows):
self.data.insert(pos,[])
pos += 1
[docs]
def IsEmptyCell(self,row,col):
try:
return not self.data[row][col]
except IndexError:
return True
def OnKeyPress(self, event):
dellist = self.GetSelectedRows()
if event.GetKeyCode() == wx.WXK_DELETE and dellist:
grid = self.GetView()
for i in dellist: grid.DeleteRow(i)
[docs]
def SetColLabelValue(self, col, label):
numcols = self.GetNumberCols()
if col > numcols-1:
self.colLabels.append(label)
else:
self.colLabels[col]=label
def SetData(self,data):
for row in range(len(data)):
self.SetRowValues(row,data[row])
[docs]
def SetRowLabelValue(self, row, label):
self.rowLabels[row]=label
def SetRowValues(self,row,data):
self.data[row] = data
[docs]
def SetValue(self, row, col, value):
def innerSetValue(row, col, value):
try:
self.data[row][col] = value
except TypeError:
return
except IndexError: # has this been tested?
#print row,col,value
if self.GetNumberRows() == 0: return
# add a new row
if row > self.GetNumberRows():
self.data.append([''] * self.GetNumberCols())
elif col > self.GetNumberCols():
for row in range(self.GetNumberRows()): # bug fixed here
self.data[row].append('')
#print self.data
self.data[row][col] = value
innerSetValue(row, col, value)
################################################################################
[docs]
class GridFractionEditor(wg.PyGridCellEditor):
'''A grid cell editor class that allows entry of values as fractions as well
as sine and cosine values [as s() and c(), sin() or sind(), etc]. Any valid
Python expression will be evaluated.
The current value can be incremented, multiplied or divided by prefixing
an expression by +, * or / respectively.
'''
def __init__(self,grid):
if 'phoenix' in wx.version():
wg.GridCellEditor.__init__(self)
else:
wg.PyGridCellEditor.__init__(self)
[docs]
def Create(self, parent, id, evtHandler):
self._tc = wx.TextCtrl(parent, id, "")
self._tc.SetInsertionPoint(0)
self.SetControl(self._tc)
if evtHandler:
self._tc.PushEventHandler(evtHandler)
self._tc.Bind(wx.EVT_CHAR, self.OnChar)
[docs]
def SetSize(self, rect):
if 'phoenix' in wx.version():
self._tc.SetSize(rect.x, rect.y, rect.width+2, rect.height+2,
wx.SIZE_ALLOW_MINUS_ONE)
else:
self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2, wx.SIZE_ALLOW_MINUS_ONE)
[docs]
def BeginEdit(self, row, col, grid):
self.startValue = grid.GetTable().GetValue(row, col)
self._tc.SetValue(str(self.startValue))
self._tc.SetInsertionPointEnd()
self._tc.SetFocus()
self._tc.SetSelection(0, self._tc.GetLastPosition())
[docs]
def EndEdit(self, row, col, grid, oldVal=None):
changed = False
self.nextval = self.startValue
val = self._tc.GetValue().lower().strip()
val = val.replace(',','.') # allow , for decimal
if val != str(self.startValue):
changed = True
neg = False
mult = False
divide = False
add = False
if val.startswith('*'):
mult = True
val = val[1:]
elif val.startswith('/'):
divide = True
val = val[1:]
elif val.startswith('+'):
add = True
val = val[1:]
if val.startswith('-'):
neg = True
val = val[1:]
# allow old GSAS s20 and c20 etc for sind(20) and cosd(20)
if val.startswith('s') and '(' not in val:
val = 'sind('+val.strip('s')+')'
elif val.startswith('c') and '(' not in val:
val = 'cosd('+val.strip('c')+')'
if neg:
val = '-' + val
val = G2fil.FormulaEval(val)
if val is not None:
if mult:
self.nextval *= val
elif divide:
if val != 0: self.nextval /= val
elif add:
self.nextval += val
else:
self.nextval = val
else:
return None
if oldVal is None: # this arg appears in 2.9+; before, we should go ahead & change the table
grid.GetTable().SetValue(row, col, val) # update the table
# otherwise self.ApplyEdit gets called
self.startValue = ''
self._tc.SetValue('')
return changed
[docs]
def ApplyEdit(self, row, col, grid):
""" Called only in wx >= 2.9
Save the value of the control into the grid if EndEdit() returns as True
"""
grid.GetTable().SetValue(row, col, self.nextval) # update the table
[docs]
def Reset(self):
self._tc.SetValue(str(self.startValue))
self._tc.SetInsertionPointEnd()
[docs]
def Clone(self,grid):
return GridFractionEditor(grid)
[docs]
def StartingKey(self, evt):
self.OnChar(evt)
if evt.GetSkipped():
self._tc.EmulateKeyPress(evt)
def OnChar(self, evt):
key = evt.GetKeyCode()
if key < 32 or key >= 127: # outside printable ascii range; needed for backspace etc.
evt.Skip()
elif chr(key).lower() in '.+-*/0123456789cosind(),':
evt.Skip()
else:
evt.StopPropagation()
##### Get an output file or directory ################################################################################
[docs]
def askSaveFile(G2frame,defnam,extension,longFormatName,parent=None):
'''Ask the user to supply a file name; used for svn
:param wx.Frame G2frame: The main GSAS-II window
:param str defnam: a default file name
:param str extension: the default file extension beginning with a '.'
:param str longFormatName: a description of the type of file
:param wx.Frame parent: the parent window for the dialog. Defaults
to G2frame.
:returns: a file name (str) or None if Cancel is pressed
'''
if not parent: parent = G2frame
pth = GetExportPath(G2frame)
#if GSASIIpath.GetConfigValue('debug'): print('debug: askSaveFile to '+pth)
dlg = wx.FileDialog(
parent, 'Input name for file to write', pth, defnam,
longFormatName+' (*'+extension+')|*'+extension,
wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
dlg.CenterOnParent()
try:
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
G2frame.LastExportDir = os.path.split(filename)[0]
filename = os.path.splitext(filename)[0]+extension # make sure extension is correct
else:
filename = None
finally:
dlg.Destroy()
return filename
[docs]
def askSaveDirectory(G2frame):
'''Ask the user to supply a directory name. Path name is used as the
starting point for the next export path search.
:returns: a directory name (str) or None if Cancel is pressed
'''
pth = GetExportPath(G2frame)
dlg = wx.DirDialog(G2frame,'Input directory where file(s) will be written',pth,wx.DD_DEFAULT_STYLE)
dlg.CenterOnParent()
try:
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
G2frame.LastExportDir = filename
else:
filename = None
finally:
dlg.Destroy()
return filename
##### Customized Notebook ################################################################################
[docs]
class GSNoteBook(wx.aui.AuiNotebook):
'''Notebook used in various locations; implemented with wx.aui extension
'''
def __init__(self, parent, name='',size = None,style=wxaui_NB_TOPSCROLL):
wx.aui.AuiNotebook.__init__(self, parent, style=style)
if size: self.SetSize(size)
self.parent = parent
self.PageChangeHandler = None
def PageChangeEvent(self,event):
pass
# G2frame = self.parent.G2frame
# page = event.GetSelection()
# if self.PageChangeHandler:
# if log.LogInfo['Logging']:
# log.MakeTabLog(
# G2frame.dataWindow.GetTitle(),
# G2frame.dataDisplay.GetPageText(page)
# )
# self.PageChangeHandler(event)
# def Bind(self,eventtype,handler,*args,**kwargs):
# '''Override the Bind() function so that page change events can be trapped
# '''
# if eventtype == wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED:
# self.PageChangeHandler = handler
# wx.aui.AuiNotebook.Bind(self,eventtype,self.PageChangeEvent)
# return
# wx.aui.AuiNotebook.Bind(self,eventtype,handler,*args,**kwargs)
def Clear(self):
GSNoteBook.DeleteAllPages(self)
[docs]
def FindPage(self,name):
numPage = self.GetPageCount()
for page in range(numPage):
if self.GetPageText(page) == name:
return page
return None
[docs]
def ChangeSelection(self,page):
# in wx.Notebook ChangeSelection is like SetSelection, but it
# does not invoke the event related to pressing the tab button
# I don't see a way to do that in aui.
oldPage = self.GetSelection()
self.SetSelection(page)
return oldPage
# def __getattribute__(self,name):
# '''This method provides a way to print out a message every time
# that a method in a class is called -- to see what all the calls
# might be, or where they might be coming from.
# Cute trick for debugging!
# '''
# attr = object.__getattribute__(self, name)
# if hasattr(attr, '__call__'):
# def newfunc(*args, **kwargs):
# print('GSauiNoteBook calling %s' %attr.__name__)
# result = attr(*args, **kwargs)
# return result
# return newfunc
# else:
# return attr
#### Help support routines ################################################################################
[docs]
class MyHelp(wx.Menu):
'''
A class that creates the contents of a help menu.
The menu will start with two entries:
* 'Help on <helpType>': where helpType is a reference to an HTML page to
be opened
* About: opens an About dialog using OnHelpAbout. N.B. on the Mac this
gets moved to the App menu to be consistent with Apple style.
NOTE: for this to work properly with respect to system menus, the title
for the menu must be &Help, or it will not be processed properly:
::
menu.Append(menu=MyHelp(self,...),title="&Help")
'''
def __init__(self,frame,includeTree=False,morehelpitems=[]):
wx.Menu.__init__(self,'')
self.HelpById = {}
self.frame = frame
self.Append(wx.ID_ABOUT,'&About GSAS-II',
'Shows version and citation info')
frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT)
if GSASIIpath.HowIsG2Installed():
helpobj = self.Append(wx.ID_ANY,'&Check for updates\tCtrl+U',
'Updates to latest GSAS-II version')
if os.access(GSASIIpath.path2GSAS2, os.W_OK):
frame.Bind(wx.EVT_MENU, self.OnCheckUpdates, helpobj)
else:
helpobj.Enable(False)
helpobj = self.Append(wx.ID_ANY,'&Regress to old GSAS-II version',
'Installs previous GSAS-II version')
if os.access(GSASIIpath.path2GSAS2, os.W_OK):
frame.Bind(wx.EVT_MENU, self.OnSelectVersion, helpobj)
else:
helpobj.Enable(False)
# provide special help topic names for extra items in help menu
for lbl,indx in morehelpitems:
helpobj = self.Append(wx.ID_ANY,lbl,'')
frame.Bind(wx.EVT_MENU, self.OnHelpById, helpobj)
self.HelpById[helpobj.GetId()] = indx
# add help lookup(s) in gsasii.html
self.AppendSeparator()
if includeTree:
helpobj = self.Append(wx.ID_ANY,'Help on GSAS-II',
'Access web page with information on GSAS-II')
frame.Bind(wx.EVT_MENU, self.OnHelpById, id=helpobj.GetId())
self.HelpById[helpobj.GetId()] = 'Data tree'
helpobj = self.Append(wx.ID_ANY,'Help on current data tree item\tF1',
'Access web page on selected item in tree')
frame.Bind(wx.EVT_MENU, self.OnHelpById, id=helpobj.GetId())
[docs]
def OnHelpById(self,event):
'''Called when Help on... is pressed in a menu. Brings up a web page
for documentation. Uses the helpKey value from the dataWindow window
unless a special help key value has been defined for this menu id in
self.HelpById
Note that self should now (2frame) be child of the main window (G2frame)
'''
if hasattr(self.frame,'dataWindow'): # Debug code: check this is called from menu in G2frame
# should always be true in 2 Frame version
dW = self.frame.dataWindow
else:
print('help error: not called from standard menu?')
print (self)
return
try:
helpKey = dW.helpKey # look up help from helpKey in data window
#if GSASIIpath.GetConfigValue('debug'): print 'DBG_dataWindow help: key=',helpKey
except AttributeError:
helpKey = ''
if GSASIIpath.GetConfigValue('debug'): print('DBG_No helpKey for current dataWindow!')
helpType = self.HelpById.get(event.GetId(),helpKey) # see if there is a special help topic
#if GSASIIpath.GetConfigValue('debug'): print 'DBG_helpKey=',helpKey,' helpType=',helpType
if helpType == 'Tutorials':
dlg = OpenTutorial(self.frame)
dlg.ShowModal()
dlg.Destroy()
return
else:
ShowHelp(helpType,self.frame)
[docs]
def OnHelpAbout(self, event):
"Display an 'About GSAS-II' box"
try:
import wx.adv as wxadv # AboutBox moved here in Phoenix
except:
wxadv = wx
info = wxadv.AboutDialogInfo()
info.Name = 'GSAS-II'
info.SetVersion(GSASIIpath.getG2VersionInfo())
#info.Developers = ['Robert B. Von Dreele','Brian H. Toby']
info.Copyright = ('(c) ' + time.strftime('%Y') +
''' Argonne National Laboratory
This product includes software developed
by the UChicago Argonne, LLC, as
Operator of Argonne National Laboratory.''')
info.Description = '''General Structure Analysis System-II (GSAS-II)
Robert B. Von Dreele and Brian H. Toby
Please cite as:
B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013)
For small angle use cite:
R.B. Von Dreele, J. Appl. Cryst. 47, 1748-9 (2014)
For DIFFaX use cite:
M.M.J. Treacy, J.M. Newsam & M.W. Deem,
Proc. Roy. Soc. Lond. A 433, 499-520 (1991)
'''
info.WebSite = ("https://gsasii.github.io","GSAS-II home page")
wxadv.AboutBox(info)
[docs]
def OnCheckUpdates(self,event):
'''Check if the GSAS-II repository has an update for the current source files
and perform that update if requested.
'''
if GSASIIpath.HowIsG2Installed().startswith('git'):
gitCheckUpdates(self.frame)
elif GSASIIpath.HowIsG2Installed().startswith('svn'):
svnCheckUpdates(self.frame)
else:
dlg = wx.MessageDialog(self.frame,
'No VCS','Cannot update GSAS-II because it was not installed with a version control system or the VCS system could not be accessed.',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
[docs]
def OnSelectVersion(self,event):
'''Allow the user to select a specific version of GSAS-II
'''
if GSASIIpath.HowIsG2Installed().startswith('git'):
gitSelectVersion(self.frame)
elif GSASIIpath.HowIsG2Installed().startswith('svn'):
svnSelectVersion(self.frame)
else:
dlg = wx.MessageDialog(self.frame,
'No VCS','Cannot update GSAS-II because it was not installed with a version control system or the VCS system could not be accessed.',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
################################################################################
################################################################################
updateNoticeDict = {4919:True} # example: {1234:True, 5000:False}
'''A dict with versions that should be noted. The value associated with the
tag is if all older projects should show the warning, or only the first
to be opened.
'''
[docs]
def updateNotifier(G2frame,fileVersion):
'''Posts an update notice when a a specially tagged GSAS-II version
is seen for the first time. Versions to be tagged are set in global
updateNoticeDict; version info is found in file versioninfo.txt.
:param wx.Frame G2frame: GSAS-II main window
:param int fileVersion: version of GSAS-II used to create the current
.gpx file
'''
def tblLine(dlg,pixels=3):
'place line in table'
txtbox = wx.StaticText(dlg,wx.ID_ANY,'',size=(-1,pixels))
txtbox.SetBackgroundColour(wx.Colour(0,0,0))
tblSizer.Add(txtbox,0,wx.EXPAND)
txtbox = wx.StaticText(dlg,wx.ID_ANY,'',size=(-1,pixels))
txtbox.SetBackgroundColour(wx.Colour(0,0,0))
tblSizer.Add(txtbox,0,wx.EXPAND)
size = (700,500)
rev = GSASIIpath.GetVersionNumber()
try:
int(rev)
except:
pass
lastNotice = max(GSASIIpath.GetConfigValue('lastUpdateNotice',0),fileVersion)
show = None # first version number to show
allProjects = False # if True notice is shown for all projects, otherwise only once
for key in updateNoticeDict:
if updateNoticeDict[key]:
if key >= fileVersion:
if show is None:
show = fileVersion
else:
show = min(show,fileVersion)
allProjects = True
else:
if key >= lastNotice:
if show is None:
show = lastNotice
else:
show = min(show,lastNotice)
if show is None: return
filnam = os.path.join(GSASIIpath.path2GSAS2,'inputs','versioninfo.txt')
if not os.path.exists(filnam): # patch 3/2024 for svn dir organization
filnam = os.path.join(GSASIIpath.path2GSAS2,'versioninfo.txt')
if not os.path.exists(filnam):
print('Warning: file versioninfo.txt not found')
return
fp = open(filnam, 'r')
vers = None
noticeDict = {}
for line in fp:
if line.strip().startswith('#'): continue
if vers is not None:
if len(line.strip()) == 0:
vers = None
continue
else:
noticeDict[vers] += ' '
noticeDict[vers] += line.strip().replace('%%','\n')
elif ':' in line:
vers,tag = line.strip().split(':',1)
try:
vers = int(vers)
except:
continue
noticeDict[vers] = tag.strip().replace('%%','\n')
fp.close()
for key in list(noticeDict.keys()):
if key <= show: del noticeDict[key]
if len(noticeDict) == 0: return
dlg = wx.Dialog(G2frame,wx.ID_ANY,'Update notices',
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
sizer = wx.BoxSizer(wx.VERTICAL)
txtbox = wx.StaticText(dlg,wx.ID_ANY,
'Please read the notices below about major GSAS-II updates since'
' this project was last saved')
sizer.Add(txtbox,0)
txtbox.Wrap(size[0]-10)
sizer.Add((10,10))
panel = wxscroll.ScrolledPanel(dlg, wx.ID_ANY, size=(size[0]-20, size[1]))
sizer.Add(panel,1,wx.EXPAND,1)
tblSizer = wx.FlexGridSizer(0,2,5,10)
tblLine(panel)
txtbox = wx.StaticText(panel,wx.ID_ANY,'Version')
tblSizer.Add(txtbox,0,wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
txtbox = wx.StaticText(panel,wx.ID_ANY,'Notice')
tblSizer.Add(txtbox,0,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
tblLine(panel)
sizer.Add((10,10))
btnsizer = wx.StdDialogButtonSizer()
if allProjects: # will be shown again
OKbtn = wx.Button(dlg, wx.ID_OK)
else:
OKbtn = wx.Button(dlg, wx.ID_OK,label='Record as seen')
btn = wx.Button(dlg, wx.ID_CANCEL,label='Show again')
btnsizer.AddButton(btn)
OKbtn.SetDefault()
btnsizer.AddButton(OKbtn)
btnsizer.Realize()
sizer.Add((-1,5))
sizer.Add(btnsizer,0,wx.ALIGN_CENTER,50)
for i,key in enumerate(sorted(noticeDict,reverse=True)):
if i != 0: tblLine(panel,1)
txtbox = wx.StaticText(panel,wx.ID_ANY,str(key))
txtbox.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
tblSizer.Add(txtbox,0,wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
txtbox = wx.StaticText(panel,wx.ID_ANY,noticeDict[key])
txtbox.Wrap(size[0]-110)
txtbox.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
tblSizer.Add(txtbox)
tblLine(panel)
panel.SetSizer(tblSizer)
panel.SetAutoLayout(1)
panel.SetupScrolling()
dlg.SetSizer(sizer)
sizer.Fit(dlg)
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
if not allProjects:
vars = GetConfigValsDocs()
vars['lastUpdateNotice'][1] = rev
GSASIIpath.SetConfigValue(vars)
SaveConfigVars(vars)
dlg.Destroy()
return
dlg.Destroy()
################################################################################
[docs]
class MyHtmlPanel(wx.Panel):
'''Defines a panel to display HTML help information, as an alternative to
displaying help information in a web browser.
'''
def __init__(self, frame, newId):
self.frame = frame
wx.Panel.__init__(self, frame, newId)
sizer = wx.BoxSizer(wx.VERTICAL)
back = wx.Button(self, -1, "Back")
back.Bind(wx.EVT_BUTTON, self.OnBack)
self.htmlwin = G2HtmlWindow(self, newId, size=(750,450))
sizer.Add(self.htmlwin, 1,wx.EXPAND)
sizer.Add(back, 0, wx.ALIGN_LEFT, 0)
self.SetSizer(sizer)
sizer.Fit(frame)
self.Bind(wx.EVT_SIZE,self.OnHelpSize)
def OnHelpSize(self,event): #does the job but weirdly!!
anchor = self.htmlwin.GetOpenedAnchor()
if anchor:
self.htmlwin.ScrollToAnchor(anchor)
wx.CallAfter(self.htmlwin.ScrollToAnchor,anchor)
if event: event.Skip()
def OnBack(self, event):
self.htmlwin.HistoryBack()
def LoadFile(self,file):
pos = file.rfind('#')
if pos != -1:
helpfile = file[:pos]
helpanchor = file[pos+1:]
else:
helpfile = file
helpanchor = None
self.htmlwin.LoadPage(helpfile)
if helpanchor is not None:
self.htmlwin.ScrollToAnchor(helpanchor)
xs,ys = self.htmlwin.GetViewStart()
self.htmlwin.Scroll(xs,ys-1)
################################################################################
[docs]
class G2HtmlWindow(wx.html.HtmlWindow):
'''Displays help information in a primitive HTML browser type window
'''
def __init__(self, parent, *args, **kwargs):
self.parent = parent
wx.html.HtmlWindow.__init__(self, parent, *args, **kwargs)
[docs]
def LoadPage(self, *args, **kwargs):
wx.html.HtmlWindow.LoadPage(self, *args, **kwargs)
self.TitlePage()
[docs]
def OnLinkClicked(self, *args, **kwargs):
wx.html.HtmlWindow.OnLinkClicked(self, *args, **kwargs)
xs,ys = self.GetViewStart()
self.Scroll(xs,ys-1)
self.TitlePage()
[docs]
def HistoryBack(self, *args, **kwargs):
wx.html.HtmlWindow.HistoryBack(self, *args, **kwargs)
self.TitlePage()
def TitlePage(self):
self.parent.frame.SetTitle(self.GetOpenedPage() + ' -- ' +
self.GetOpenedPageTitle())
################################################################################
[docs]
def StripIndents(msg,singleLine=False):
'''Strip unintended indentation from multiline strings.
When singleLine is True, all newline are removed, but inserting "%%"
into the string will cause a blank line to be inserted at that point
and %t% will generate a new line and tab (to indent a line)
:param str msg: a string containing one or more lines of text.
spaces or tabs following a newline are removed.
:param bool singleLine: removes all newlines from the msg so that
the text may be wrapped.
:returns: the string but reformatted
'''
msg1 = msg.replace('\n ','\n')
while msg != msg1:
msg = msg1
msg1 = msg.replace('\n ','\n')
msg1 = msg1.replace(' ',' ')
msg1 = msg.replace('\n\t','\n')
while msg != msg1:
msg = msg1
msg1 = msg.replace('\n\t','\n')
if singleLine:
msg = msg.replace('\n',' ')
msg1 = msg.replace(' ',' ')
while msg != msg1:
msg = msg1
msg1 = msg1.replace(' ',' ')
msg = msg.replace('%%','\n\n')
msg = msg.replace('%t%','\n\t')
return msg
[docs]
def StripUnicode(string,subs='.'):
'''Strip non-ASCII characters from strings
:param str string: string to strip Unicode characters from
:param str subs: character(s) to place into string in place of each
Unicode character. Defaults to '.'
:returns: a new string with only ASCII characters
'''
s = ''
for c in string:
if ord(c) < 128:
s += c
else:
s += subs
return s.encode('ascii','replace')
[docs]
def getTextSize(txt):
'Get the size of the text string txt in points, returns (x,y)'
dc = wx.ScreenDC()
return tuple(dc.GetTextExtent(txt))
# wx classes for reading various types of data files ######################################################################
[docs]
def BlockSelector(ChoiceList, ParentFrame=None,title='Select a block',
size=None, header='Block Selector',useCancel=True):
''' Provide a wx dialog to select a single block where one must
be selected. Used for selecting for banks for instrument
parameters if the file contains more than one set.
'''
if useCancel:
dlg = wx.SingleChoiceDialog(
ParentFrame,title, header,ChoiceList)
else:
dlg = wx.SingleChoiceDialog(
ParentFrame,title, header,ChoiceList,
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
if size: dlg.SetMinSize(size)
dlg.CenterOnParent()
dlg.SendSizeEvent()
if dlg.ShowModal() == wx.ID_OK:
sel = dlg.GetSelection()
return sel
else:
return None
dlg.Destroy()
[docs]
def MultipleBlockSelector(ChoiceList, ParentFrame=None,
title='Select a block',size=None, header='Block Selector'):
'''Provide a wx dialog to select a block of data if the
file contains more than one set of data and one must be
selected. Used in :mod:`G2pwd_CIF` only.
:returns: a list of the selected blocks
'''
dlg = wx.MultiChoiceDialog(ParentFrame,title, header,ChoiceList+['Select all'],
wx.CHOICEDLG_STYLE)
if size: dlg.SetMinSize(size)
dlg.CenterOnScreen()
dlg.SendSizeEvent()
if dlg.ShowModal() == wx.ID_OK:
sel = dlg.GetSelections()
else:
return []
dlg.Destroy()
selected = []
if len(ChoiceList) in sel:
return range(len(ChoiceList))
else:
return sel
return selected
[docs]
class MultipleChoicesDialog(wx.Dialog):
'''A dialog that offers a series of choices, each with a
title and a wx.Choice widget. Intended to be used Modally.
typical input:
* choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
* headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
selections are placed in self.chosen when OK is pressed
Also see GSASIIctrlGUI
'''
def __init__(self,choicelist,headinglist,
head='Select options',
title='Please select from options below',
parent=None):
self.chosen = []
wx.Dialog.__init__(
self,parent,wx.ID_ANY,head,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add((10,10),1)
topLabl = wx.StaticText(panel,wx.ID_ANY,title)
mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.CENTER,10)
self.ChItems = []
for choice,lbl in zip(choicelist,headinglist):
mainSizer.Add((10,10),1)
self.chosen.append(0)
topLabl = wx.StaticText(panel,wx.ID_ANY,' '+lbl)
mainSizer.Add(topLabl,0,wx.ALIGN_LEFT,10)
self.ChItems.append(wx.Choice(self, wx.ID_ANY, (100, 50), choices = choice))
mainSizer.Add(self.ChItems[-1],0,wx.ALIGN_CENTER,10)
OkBtn = wx.Button(panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
cancelBtn = wx.Button(panel,-1,"Cancel")
cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add((20,20),1)
btnSizer.Add(cancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
panel.SetSizer(mainSizer)
panel.Fit()
self.Fit()
def OnOk(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
# save the results from the choice widgets
self.chosen = []
for w in self.ChItems:
self.chosen.append(w.GetSelection())
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
if parent is not None: parent.Raise()
self.chosen = []
self.EndModal(wx.ID_CANCEL)
[docs]
def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs):
'''A modal dialog that offers a series of choices, each with a title and a wx.Choice
widget. Used in :mod:`G2pwd_CIF` only.
Typical input:
* choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
* headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
optional keyword parameters are: head (window title) and title
returns a list of selected indicies for each choice (or None)
'''
result = None
dlg = MultipleChoicesDialog(choicelist,headinglist,
parent=ParentFrame, **kwargs)
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
result = dlg.chosen
dlg.Destroy()
return result
[docs]
def PhaseSelector(ChoiceList, ParentFrame=None,
title='Select a phase', size=None,header='Phase Selector'):
''' Provide a wx dialog to select a phase, used in importers if a file
contains more than one phase
'''
return BlockSelector(ChoiceList,ParentFrame,title,
size,header)
[docs]
def showUniqueCell(frame,cellSizer,row,cell,SGData=None,
editAllowed=False,OnCellChange=None):
'''function to put cell values into a GridBagSizer.
First column (#0) is reserved for labels etc.
if editAllowed is True, values are placed in a wx.TextCtrl and if needed
two rows are used in the table.
'''
cellGUIlist = [
[['m3','m3m'],[" Unit cell: a = "],["{:.5f}"],[0]],
[['3R','3mR'],[" a = ",u" \u03B1 = "],["{:.5f}","{:.3f}"],[0,3]],
[['3','3m1','31m','6/m','6/mmm','4/m','4/mmm'],
[" a = "," c = ",u" \u03B3 = "],
["{:.5f}","{:.5f}","{:.3f}"],[0,2,-5]],
[['mmm'],[" a = "," b = "," c = "],["{:.5f}","{:.5f}","{:.5f}"],
[0,1,2]],
[['2/m'+'a'],[" a = "," b = "," c = ",u" \u03B1 = "],
["{:.5f}","{:.5f}","{:.5f}","{:.3f}"],[0,1,2,3]],
[['2/m'+'b'],[" a = "," b = "," c = ",u" \u03B2 = "],
["{:.5f}","{:.5f}","{:.5f}","{:.3f}"],[0,1,2,4]],
[['2/m'+'c'],[" a = "," b = "," c = ",u" \u03B3 = "],
["{:.5f}","{:.5f}","{:.5f}","{:.3f}"],[0,1,2,5]],
[['-1'],[" a = "," b = "," c = ",u" \u03B1 = ",u" \u03B2 = ",u" \u03B3 = "],
["{:.5f}","{:.5f}","{:.5f}","{:.3f}","{:.3f}","{:.3f}"],[0,1,2,3,4,5]]
]
cellList = []
if SGData is None:
laue = '-1'
else:
laue = SGData['SGLaue']
if laue == '2/m': laue += SGData['SGUniq']
for cellGUI in cellGUIlist:
if laue in cellGUI[0]:
useGUI = cellGUI
break
for txt,fmt,indx in zip(*useGUI[1:]):
col = 1+2*abs(indx)
cellrow = row
if editAllowed and abs(indx) > 2:
cellrow = row + 1
col = 1+2*(abs(indx)-3)
cellSizer.Add(wx.StaticText(frame,label=txt),(cellrow,col))
if editAllowed and indx >= 0:
Fmt = (10,5)
if '.3' in fmt: Fmt = (10,3)
cellVal = ValidatedTxtCtrl(frame,cell,indx,
xmin=0.1,xmax=500.,nDig=Fmt,OnLeave=OnCellChange)
cellSizer.Add(cellVal,(cellrow,col+1))
cellList.append(cellVal.GetId())
else:
cellSizer.Add(wx.StaticText(frame,label=fmt.format(cell[abs(indx)])),(cellrow,col+1))
#volume
volCol = 13
if editAllowed:
volCol = 8
cellSizer.Add(wx.StaticText(frame,label=' Vol = '),(row,volCol))
if editAllowed:
volVal = wx.TextCtrl(frame,value=('{:.2f}'.format(cell[6])),style=wx.TE_READONLY)
volVal.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
cellSizer.Add(volVal,(row,volCol+1))
else:
cellSizer.Add(wx.StaticText(frame,label='{:.2f}'.format(cell[6])),(row,volCol+1))
return cellrow,cellList
################################################################################
# configuration routines (for editing config.py)
def SaveGPXdirectory(path,write=True):
if GSASIIpath.GetConfigValue('Starting_directory') == path: return
vars = GetConfigValsDocs()
try:
vars['Starting_directory'][1] = path
if GSASIIpath.GetConfigValue('debug'): print('DBG_Saving GPX path: '+path)
if write: SaveConfigVars(vars)
except KeyError:
pass
def SaveImportDirectory(path):
if GSASIIpath.GetConfigValue('Import_directory') == path: return
vars = GetConfigValsDocs()
try:
vars['Import_directory'][1] = path
if GSASIIpath.GetConfigValue('debug'): print('DBG_Saving Import path: '+path)
SaveConfigVars(vars)
except KeyError:
pass
[docs]
def GetConfigValsDocs():
'''Reads the module referenced in fname (often <module>.__file__) and
return a dict with names of global variables as keys.
For each global variable, the value contains four items:
:returns: a dict where keys are names defined in module config_example.py
where the value is a list of four items, as follows:
* item 0: the default value
* item 1: the current value
* item 2: the initial value (starts same as item 1)
* item 3: the "docstring" that follows variable definition
'''
import config_example
import ast
fname = os.path.splitext(config_example.__file__)[0]+'.py' # convert .pyc to .py
if '3' in platform.python_version_tuple()[0]:
with open(fname, 'r',encoding='utf-8') as f:
fstr = f.read()
else:
with open(fname, 'r') as f:
fstr = f.read()
fstr = fstr.replace('\r\n', '\n').replace('\r', '\n')
if not fstr.endswith('\n'):
fstr += '\n'
tree = ast.parse(fstr)
d = {}
key = None
for node in ast.walk(tree):
if isinstance(node,ast.Assign):
key = node.targets[0].id
d[key] = [config_example.__dict__.get(key),
GSASIIpath.configDict.get(key),
GSASIIpath.configDict.get(key),'']
elif isinstance(node,ast.Expr) and key:
d[key][3] = node.value.s.strip()
else:
key = None
return d
inhibitSave = False
[docs]
def SaveConfigVars(vars,parent=None):
'''Write the current config variable values to config.py
:params dict vars: a dictionary of variable settings and meanings as
created in :func:`GetConfigValsDocs`.
:param parent: wx.Frame object or None (default) for parent
of error message if no file can be written.
:returns: True if unable to write the file, None otherwise
'''
if inhibitSave:
if GSASIIpath.GetConfigValue('debug'):
print('inhibitSave prevents saving configuration')
return
# try to write to where an old config file is located
try:
import config
savefile = config.__file__
except ImportError: # no config.py file yet
savefile = os.path.join(GSASIIpath.path2GSAS2,'config.py')
except Exception: # import failed
# find the bad file, save it in a new name and prepare to overwrite it
for p in sys.path:
savefile = os.path.join(p,'config.py')
if os.path.exists(savefile):
import distutils.file_util as dfu
keepfile = os.path.join(p,'config.py_corrupt')
print('Current config file contains an error:',savefile)
print('saving that file as',keepfile)
dfu.copy_file(savefile,keepfile)
print('preparing to overwrite...')
break
else:
print('unexpected error importing config.py')
savefile = os.path.join(GSASIIpath.path2GSAS2,'config.py')
# try to open file for write
try:
savefile = os.path.splitext(savefile)[0]+'.py' # convert .pyc to .py
fp = open(savefile,'w')
except IOError: # can't write there, write in local mods directory
# create a local mods directory, if needed
g2local = os.path.expanduser('~/.G2local/')
if not os.path.exists(g2local):
try:
print(u'Creating directory '+g2local)
os.mkdir(g2local)
except:
if parent:
G2MessageBox(parent,u'Error trying to create directory '+g2local,
'Unable to save')
else:
print(u'Error trying to create directory '+g2local)
return True
sys.path.insert(0,os.path.expanduser('~/.G2local/'))
savefile = os.path.join(os.path.expanduser('~/.G2local/'),'config.py')
try:
fp = open(savefile,'w')
except IOError:
if parent:
G2MessageBox(parent,'Error trying to write configuration to '+savefile,
'Unable to save')
else:
print('Error trying to write configuration to '+savefile)
return True
import datetime
fp.write("# -*- coding: utf-8 -*-\n'''\n")
fp.write("*config.py: Configuration options*\n----------------------------------\n")
fp.write("This file created in SelectConfigSetting on {:%d %m %Y %H:%M}\n".
format(datetime.datetime.now()))
fp.write("'''\n\n")
fp.write("import os.path\n")
fp.write("import GSASIIpath\n\n")
for var in sorted(vars.keys(),key=lambda s: s.lower()):
if vars[var][1] is None: continue
if vars[var][1] == '': continue
if vars[var][0] == vars[var][1]: continue
try:
float(vars[var][1]) # test for number
fp.write(var + ' = ' + str(vars[var][1])+'\n')
except:
if type(vars[var][1]) is list:
fp.write(var + ' = [\n')
for varstr in vars[var][1]:
if '\\' in varstr:
fp.write('\t os.path.normpath("' + varstr.replace('\\','/') +'"),\n')
else:
fp.write('\t "' + str(varstr)+'",\n')
fp.write(' ]\n')
elif type(vars[var][1]) is tuple:
fp.write(var + ' = ' + str(vars[var][1])+'\n')
else:
try:
eval(vars[var][1]) # test for an expression
fp.write(var + ' = ' + str(vars[var][1])+'\n')
except: # must be a string
varstr = vars[var][1]
if '\\' in varstr:
fp.write(var + ' = os.path.normpath("' + varstr.replace('\\','/') +'")\n')
else:
fp.write(var + ' = "' + str(varstr)+'"\n')
if vars[var][3]:
fp.write("'''" + str(vars[var][3]) + "\n'''\n\n")
fp.close()
print('wrote file '+savefile)
[docs]
class SelectConfigSetting(wx.Dialog):
'''Dialog to select configuration variables and set associated values.
'''
def __init__(self,parent=None):
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Set Config Variable', style=style)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.vars = GetConfigValsDocs()
label = wx.StaticText(
self, wx.ID_ANY,
'Select a GSAS-II configuration variable to change'
)
self.sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.choice = {}
choices = sorted([k for k in self.vars if not k.startswith('enum_')],
key=lambda s: s.lower())
btn = G2ChoiceButton(self, choices,
strLoc=self.choice,strKey=0,onChoice=self.OnSelection)
btn.SetLabel("")
self.sizer.Add(btn)
self.varsizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.varsizer,1,wx.ALL|wx.EXPAND,1)
self.doclbl = wx.StaticBox(self, wx.ID_ANY, "")
self.doclblsizr = wx.StaticBoxSizer(self.doclbl)
self.docinfo = wx.StaticText(self, wx.ID_ANY, "")
self.doclblsizr.Add(self.docinfo, 0, wx.ALIGN_LEFT|wx.ALL, 5)
self.sizer.Add(self.doclblsizr, 0, wx.EXPAND|wx.ALL, 5)
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
self.saveBtn = wx.Button(self,-1,"Save current settings")
btnsizer.Add(self.saveBtn, 0, wx.ALL, 2)
self.saveBtn.Bind(wx.EVT_BUTTON, self.OnSave)
self.saveBtn.Enable(False)
self.applyBtn = wx.Button(self,-1,"Use current (no save)")
btnsizer.Add(self.applyBtn, 0, wx.ALL, 2)
self.applyBtn.Bind(wx.EVT_BUTTON, self.OnApplyChanges)
self.applyBtn.Enable(False)
btn = wx.Button(self,wx.ID_CANCEL)
btnsizer.Add(btn, 0, wx.ALL, 2)
self.sizer.Add(btnsizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.SetSizer(self.sizer)
self.sizer.Fit(self)
self.CenterOnParent()
[docs]
def OnChange(self,event=None):
''' Check if anything been changed. Turn the save button on/off.
'''
for var in self.vars:
if self.vars[var][0] is None and self.vars[var][1] is not None:
# make blank strings into None, if that is the default
if self.vars[var][1].strip() == '': self.vars[var][1] = None
if self.vars[var][1] != self.vars[var][2]:
#print 'changed',var,self.vars[var][:3]
self.saveBtn.Enable(True)
self.applyBtn.Enable(True)
break
else:
self.saveBtn.Enable(False)
self.applyBtn.Enable(False)
try:
self.resetBtn.Enable(True)
except:
pass
[docs]
def OnApplyChanges(self,event=None):
'Set config variables to match the current settings'
GSASIIpath.SetConfigValue(self.vars)
self.EndModal(wx.ID_OK)
global inhibitSave
if event is not None: inhibitSave = True
import GSASIImpsubs as G2mp
G2mp.ResetMP()
[docs]
def OnSave(self,event):
'''Write the config variables to config.py and then set them
as the current settings
'''
global inhibitSave
inhibitSave = False
if not SaveConfigVars(self.vars,parent=self):
self.OnApplyChanges() # force a reload of the config settings
else:
self.EndModal(wx.ID_OK)
[docs]
def OnBoolSelect(self,event):
'Respond to a change in a True/False variable'
rb = event.GetEventObject()
var = self.choice[0]
self.vars[var][1] = (rb.GetSelection() == 0)
self.OnChange()
wx.CallAfter(self.OnSelection)
[docs]
def onSelDir(self,event):
'Select a directory from a menu'
dlg = wx.DirDialog(self, "Choose a directory:",style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
var = self.choice[0]
self.vars[var][1] = dlg.GetPath()
self.strEd.SetValue(self.vars[var][1])
self.OnChange()
dlg.Destroy()
[docs]
def onSelExec(self,event):
'Select an executable file from a menu'
var = self.choice[0]
is_exe = lambda fpath: os.path.isfile(fpath) and os.access(fpath, os.X_OK)
defD = defF = ''
if self.vars[var][1] is not None and os.path.exists(self.vars[var][1]):
defD,defF=os.path.split(self.vars[var][1])
repeat = True
while repeat:
repeat = False
if sys.platform == "win32":
dlg = wx.FileDialog(self, "Choose a .exe file:",
defaultDir=defD,defaultFile=defF,
style=wx.FD_DEFAULT_STYLE|wx.FD_FILE_MUST_EXIST,
wildcard="Executable files|*.exe")
else:
dlg = wx.FileDialog(self, "Choose an executable image:",
defaultDir=defD,defaultFile=defF,
style=wx.FD_DEFAULT_STYLE|wx.FD_FILE_MUST_EXIST)
if dlg.ShowModal() == wx.ID_OK:
val = dlg.GetPath()
if os.path.exists(val) and is_exe(val):
self.vars[var][1] = val
self.strEd.SetValue(self.vars[var][1])
self.OnChange()
else:
dlg.Destroy()
G2MessageBox(self,'File not found or not executable',
'Invalid file')
repeat = True
continue
dlg.Destroy()
[docs]
def OnSelection(self):
'show a selected variable and allow it to be changed'
def OnNewColorBar(event):
self.vars['Contour_color'][1] = self.colSel.GetValue()
self.OnChange(event)
if 'phoenix' in wx.version():
self.varsizer.Clear(True)
else:
self.varsizer.DeleteWindows()
var = self.choice[0]
showdef = True
if var not in self.vars:
raise Exception("How did this happen?")
if 'enum_'+var in self.vars:
choices = self.vars['enum_'+var][0]
self.colSel = EnumSelector(self,self.vars[var],1,choices,
OnChange=self.OnChange)
self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
elif type(self.vars[var][0]) is int:
ed = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=int,OKcontrol=self.OnChange)
self.varsizer.Add(ed, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
elif type(self.vars[var][0]) is float:
ed = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=float,OKcontrol=self.OnChange)
self.varsizer.Add(ed, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
elif type(self.vars[var][0]) is bool:
showdef = False
lbl = "value for "+var
ch = []
for i,v in enumerate((True,False)):
s = str(v)
if v == self.vars[var][0]:
defopt = i
s += ' (default)'
ch += [s]
rb = wx.RadioBox(self, wx.ID_ANY, lbl, wx.DefaultPosition, wx.DefaultSize,
ch, 1, wx.RA_SPECIFY_COLS)
# set initial value
if self.vars[var][1] is None:
rb.SetSelection(defopt)
elif self.vars[var][1]:
rb.SetSelection(0)
else:
rb.SetSelection(1)
rb.Bind(wx.EVT_RADIOBOX,self.OnBoolSelect)
self.varsizer.Add(rb, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
else:
if var.endswith('_directory') or var.endswith('_location'):
btn = wx.Button(self,wx.ID_ANY,'Select from dialog...')
btn.Bind(wx.EVT_BUTTON,self.onSelDir)
sz = (400,-1)
elif var.endswith('_exec'):
btn = wx.Button(self,wx.ID_ANY,'Select from dialog...')
btn.Bind(wx.EVT_BUTTON,self.onSelExec)
sz = (400,-1)
else:
btn = None
sz = (250,-1)
if var == 'Contour_color':
if self.vars[var][1] is None:
self.vars[var][1] = 'Paired'
colorList = sorted([m for m in mpl.cm.datad.keys()]+['GSPaired','GSPaired_r',],key=lambda s: s.lower()) #if not m.endswith("_r")
self.colSel = wx.ComboBox(self,value=self.vars[var][1],choices=colorList,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
self.colSel.Bind(wx.EVT_COMBOBOX, OnNewColorBar)
self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
elif var == 'Image_calibrant':
import ImageCalibrants as calFile
calList = sorted([m for m in calFile.Calibrants.keys()],
key=lambda s: s.lower())
self.colSel = EnumSelector(self,self.vars[var],1,calList,
OnChange=self.OnChange)
self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
else:
self.strEd = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=str,
OKcontrol=self.OnChange,size=sz)
if self.vars[var][1] is not None:
self.strEd.SetValue(self.vars[var][1])
if btn:
self.varsizer.Add(self.strEd, 0, wx.ALL|wx.EXPAND, 5)
self.varsizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
else:
self.varsizer.Add(self.strEd, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
# button for reset to default value
lbl = "Reset to Default"
if showdef: # spell out default when needed
lbl += ' (='+str(self.vars[var][0])+')'
#label = wx.StaticText(self, wx.ID_ANY, 'Default value = '+str(self.vars[var][0]))
#self.varsizer.Add(label, 0, wx.ALIGN_LEFT|wx.ALL, 5)
self.resetBtn = wx.Button(self,-1,lbl)
self.resetBtn.Bind(wx.EVT_BUTTON, self.OnClear)
if self.vars[var][1] is not None and self.vars[var][1] != '': # show current value, if one
#label = wx.StaticText(self, wx.ID_ANY, 'Current value = '+str(self.vars[var][1]))
#self.varsizer.Add(label, 0, wx.ALIGN_LEFT|wx.ALL, 5)
self.resetBtn.Enable(True)
else:
self.resetBtn.Enable(False)
self.varsizer.Add(self.resetBtn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
# show meaning, if defined
self.doclbl.SetLabel("Description of "+str(var))
vartxt = self.vars[var][3]
vartxt = StripIndents(vartxt.replace(' & ',' && '),True)
if vartxt:
self.docinfo.SetLabel(vartxt)
else:
self.docinfo.SetLabel("(not documented)")
self.docinfo.Wrap(500)
self.sizer.Fit(self)
self.CenterOnParent()
wx.CallAfter(self.SendSizeEvent)
def OnClear(self, event):
var = self.choice[0]
self.vars[var][1] = self.vars[var][0]
self.OnChange()
wx.CallAfter(self.OnSelection)
################################################################################
[docs]
class RefinementProgress(wx.ProgressDialog):
'''Defines a wrapper to place around wx.ProgressDialog to be used for
showing refinement progress. At some point a better progress window should be
created that keeps useful info on the screen such as some starting and
current fit metrics, but for now all this adds is window defaults
and a wx.Yield call during progress update calls.
'''
def __init__(self, title='Residual', message='All data Rw =',
maximum=101, parent=None, trialMode=False, seqLen=0,
style=None):
if style is None:
style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT
super(self.__class__,self).__init__(title, message, int(maximum), parent, style)
Size = self.GetSize()
if 50 < Size[0] < 500: # sanity check on size, since this fails w/Win & wx3.0
self.SetSize((int(Size[0]*1.2),Size[1])) # increase size a bit along x
self.CenterOnParent()
self.Show()
[docs]
def Update(self,value, newmsg=""):
wx.GetApp().Yield()
#print('wx Yield called')
#print('Update:',value,newmsg)
return super(self.__class__,self).Update(int(value), newmsg)
################################################################################
fmtRw = lambda value: '{:.2f}'.format(float(value))
[docs]
class G2RefinementProgress(wx.Dialog):
'''Defines an replacement for wx.ProgressDialog to be used for
showing refinement progress.
:param str title: string to place on border of window (default is
'Refinement progress').
:param str message: initial string to place on top line of window.
:param int maximum: maximum value for progress gauge bar on bottom
of window.
:param wx.Frame parent: parent window for creation of this dialog
:param bool trialMode: Set to True for Levenberg-Marquardt fitting
where Rw may be computed several times for a single cycle.
Call :meth:`AdvanceCycle` when trialMode is True to indicate that a cycle
has been completed. Default is False.
:param int seqLen: Number of histograms in sequential fit. A value of
zero (default) means that the fit is not a sequential fit.
:param int seqShow: Number of histograms to shown in a sequential fit (default 3)
:param int style: optional parameters that determine how the dialog is
is displayed.
'''
def __init__(self, title='Refinement progress', message='All data Rw =',
maximum=101, parent=None, trialMode=False,seqLen=0, seqShow=3,style=None):
self.trialRw = trialMode # used for Levenberg-Marquardt fitting
self.SeqLen = seqLen
self.seqShow = seqShow
if self.SeqLen:
self.maxCycle = self.SeqLen
self.SeqCount = -1
self.rows = 4
if self.trialRw: self.rows = 5
if style is None: style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER
super(self.__class__,self).__init__(parent, wx.ID_ANY, title,style=style, size=(-1,-1))
self.Bind(wx.EVT_CLOSE, self._onClose)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.startTime = time.time()
self.abortStatus = False
self.SetSizer(mainSizer)
mainSizer.Add((-1,3))
self.msgLine1 = wx.StaticText(self,wx.ID_ANY,message)
mainSizer.Add(self.msgLine1)
mainSizer.Add((-1,3))
self.msgLine2 = wx.StaticText(self,wx.ID_ANY,'')
mainSizer.Add(self.msgLine2)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
vSizer = wx.BoxSizer(wx.VERTICAL)
lblSizer = self._makeLabeledTable() # make the Table for Rfactors in the RwPanel
vSizer.Add((-1,-1),1,wx.EXPAND,1)
vSizer.Add(lblSizer,0,wx.EXPAND)
vSizer.Add((-1,-1),1,wx.EXPAND,1)
hSizer.Add(vSizer,1,wx.EXPAND,1)
pltPanel = wx.Panel(self,size=(-1,-1))
self.figure = mplfig.Figure(dpi=100,figsize=(3,2))
self.figure.subplots_adjust(right=0.99,top=0.99)
Canvas(pltPanel, wx.ID_ANY, self.figure) # no need to save, get this from self.figure.canvas
self.plotaxis = self.figure.add_subplot()
hSizer.Add(pltPanel,0,wx.EXPAND,0)
mainSizer.Add(hSizer,1,wx.EXPAND,1)
mainSizer.Add((-1,5))
hSizer = wx.BoxSizer(wx.HORIZONTAL)
vSizer = wx.BoxSizer(wx.VERTICAL)
self.gaugemaximum = int(maximum)
self.gauge = wx.Gauge(self,wx.ID_ANY,range=int(self.gaugemaximum))
self.gauge.SetValue(0)
vSizer.Add(self.gauge,1,wx.EXPAND,1)
vSizer.Add((-1,3))
tSizer = wx.BoxSizer(wx.HORIZONTAL)
tSizer.Add((-1,-1),1,wx.EXPAND,1)
tSizer.Add(wx.StaticText(self,wx.ID_ANY,'Elapsed time: '))
self.elapsed = wx.StaticText(self,wx.ID_ANY,'0:00:00.0')
tSizer.Add(self.elapsed)
tSizer.Add((-1,-1),1,wx.EXPAND,1)
vSizer.Add(tSizer)
hSizer.Add(vSizer,1,wx.EXPAND,1)
hSizer.Add((10,-1))
btn = wx.Button(self, wx.ID_CLOSE,"Abort refinement")
btn.Bind(wx.EVT_BUTTON,self._onClose)
hSizer.Add(btn,0,wx.ALIGN_CENTER_VERTICAL)
mainSizer.Add(hSizer,0,wx.EXPAND,5)
self.Labels.label[2].SetLabel('Start')
self.cycleLbl = self.Labels.label[3]
self.cycleLbl.SetLabel('Cycle ?')
if self.trialRw:
self.Labels.label[4].SetLabel('trial parms')
self.Show()
self.Layout()
self.SetSizer(mainSizer)
mainSizer.Fit(self)
self.CenterOnParent()
self.SendSizeEvent()
self.tblCols = {}
self.tblLbls = {}
self.fitVals = {}
self.trialVals = {} # used when self.trialRw is True
self.trialcount = 0
self.cols = 0
self.maxCycle = 10
self.curHist = None # number of current histogram (may be <0 for overall)
self.seqHist = None # number of current sequential histogram (never <0)
self.prevSeqHist = [] # previous sequential histograms that are still shown
self.plotted = []
self.histOff = {}
def _onClose(self,event):
'''Respond to abort button or close of window
'''
self.abortStatus = True
self.Show(False)
[docs]
def Destroy(self):
'''Destroy the window, but allow events to clear before doing so
'''
wx.CallAfter(wx.Dialog.Destroy,self)
def _makeLabeledTable(self):
'''Create two grid sizers, one with row labels and one scrolled.
Use _xferLabeledTable to make the row heights match.
'''
lblSizer = wx.BoxSizer(wx.HORIZONTAL)
self.Labels = wx.GridBagSizer(2,2)
lblSizer.Add(self.Labels)
self.RwPanel = wx.lib.scrolledpanel.ScrolledPanel(self, wx.ID_ANY, size=(180, 130),
style = wx.SUNKEN_BORDER)
lblSizer.Add(self.RwPanel,1,wx.EXPAND,1)
self.Labels.label = {}
for i in range(self.rows):
self.Labels.label[i] = wx.StaticText(self,wx.ID_ANY,'',style=wx.ALIGN_CENTER_VERTICAL)
self.Labels.Add(self.Labels.label[i],(i,0))
mainsizer = wx.BoxSizer(wx.VERTICAL)
tblsizer = wx.BoxSizer(wx.HORIZONTAL)
self.gridSiz = wx.GridBagSizer(2,2)
tblsizer.Add(self.gridSiz)
mainsizer.Add(tblsizer)
self.RwPanel.SetSizer(mainsizer)
self.RwPanel.SetAutoLayout(1)
self.RwPanel.SetupScrolling()
return lblSizer
def _xferLabeledTable(self):
'''Matches the row sizes of the labels to the row heights in the table
'''
for i,h in enumerate(self.gridSiz.GetRowHeights()):
self.Labels.label[i].SetMinSize((-1,h))
self.Labels.Layout()
[docs]
def SetMaxCycle(self,value):
'''Set the maximum number of cycles or histograms (sequential fit).
Used to scale settings so the gauge bar completes close to 100%.
Ignored for sequential refinements.
'''
if self.SeqLen: return
self.maxCycle = value
def _AddTableColumn(self,label='',col=None):
'add a column to the Rfactor table'
if col is None:
self.cols += 1
col = self.cols
lbls = []
for i in range(self.rows-1):
lbls.append(wx.StaticText(self.RwPanel,wx.ID_ANY,'',style=wx.ALIGN_CENTER))
self.gridSiz.Add(lbls[-1],(1+i,col))
return col,lbls
[docs]
def SetHistogram(self,nextHist,histLbl):
'''Set this before beginning processing of each histogram
'''
if nextHist is None:
self.curHist = None
self.msgLine1.SetLabel(histLbl)
return
if self.SeqLen:
self._SetSeqHistogram(nextHist,histLbl)
return
col = None
lbl = 'Hist {}'.format(nextHist)
if nextHist == -1: # overall fit goes in the 1st column, if shown
col = 0
lbl = 'Overall'
elif nextHist == -2: # Restraint Chi2 contribution goes in the last col
lbl = 'Restraints'
if nextHist not in self.tblCols:
self.tblCols[nextHist],lbls = self._AddTableColumn(lbl,col)
self.tblLbls[nextHist] = lbls
self.tblLbls[nextHist][0].SetLabel(lbl)
self.fitVals[nextHist] = []
if nextHist >= 0:
self.msgLine1.SetLabel('Fitting '+histLbl)
self.curHist = nextHist
def _SetSeqHistogram(self,nextHist,histLbl):
'''Set this before beginning processing of each histogram in a sequential fit.
Advances the completion gauge.
'''
if nextHist == -1: # overall fit is not shown
self.curHist = nextHist
return
elif nextHist == -2: # Restraint Chi2 contribution goes in the 1st col
if nextHist not in self.tblCols:
self.tblCols[-2],lbls = self._AddTableColumn('Restraints',0)
self.tblLbls[-2] = lbls
self.tblLbls[-2][0].SetLabel('Restraints')
self.fitVals[-2] = []
elif self.seqHist != nextHist and nextHist >= 0:
if nextHist not in self.tblCols:
lbl = 'Hist {}'.format(nextHist)
self.tblCols[nextHist],lbls = self._AddTableColumn(lbl,None)
self.tblLbls[nextHist] = lbls
self.tblLbls[nextHist][0].SetLabel(lbl)
if len(self.prevSeqHist) < self.seqShow:
self.prevSeqHist.append(nextHist)
else:
del self.fitVals[self.prevSeqHist[0]]
del self.prevSeqHist[0]
self.prevSeqHist.append(nextHist)
self.fitVals[nextHist] = []
if -2 in self.fitVals: self.fitVals[-2] = []
if nextHist >= 0:
self.msgLine1.SetLabel('Fitting '+histLbl)
self.curHist = nextHist
if nextHist >= 0 and self.seqHist != nextHist:
self.seqHist = nextHist
self.SeqCount += 1
self.gauge.SetValue(int(min(self.gaugemaximum,100.*self.SeqCount/self.SeqLen)))
def _plotBar(self,h):
'plot a vertical bar for a histogram'
sym,lbl = self._getPlotSetting(h)
l = len(self.fitVals[h])
wid = 10.
if h < 0:
if h == -2 and -1 in self.fitVals:
self.histOff[h] = h/wid
else:
self.histOff[h] = -1/wid
else:
self.histOff[h] = len([i for i in self.plotted if i >= 0])/wid
self.plotaxis.bar(np.array(range(l))+self.histOff[h],self.fitVals[h],
width=1/wid,label=lbl,color=sym)
self.plotted.append(h)
def _getPlotSetting(self,h):
'determines how a plot is drawn'
if h == -1:
sym = "m" # magenta
lbl = 'o'
elif h == -2:
sym = "c" # cyan
lbl = 'r'
else:
symbols = ['b','r','g']
sym = symbols[h%3]
lbl = str(h)
return sym,lbl
def _SetCycleRw(self,value):
'''Used to process an Rwp value from the :meth:`Update` method.
The value will be associated with the current histogram (as set
in :meth:`SetHistogram`). If this is the 1st supplied value for
that histogram, the value is set and displayed as as the starting
Rwp. If :data`:self.trialRw` is False, the values are saved to a
list specific to the current histogram, and are displayed and
plotted. When :data`:self.trialRw` is True, the Rwp values are
considered trial values and are only saved and plotted when
:meth:`AdvanceCycle` is called.
'''
if self.curHist in self.fitVals:
cycle = len(self.fitVals[self.curHist])
else:
return
if self.maxCycle and not self.SeqLen:
self.gauge.SetValue(int(min(self.gaugemaximum,100.*cycle/self.maxCycle)))
self.cycleLbl.SetLabel('Cycle {:}'.format(cycle))
if cycle == 0:
self.tblLbls[self.curHist][1].SetLabel('{:8.3g}'.format(value))
self.RwPanel.SetupScrolling()
self._xferLabeledTable()
if not self.trialRw: # show & plot current status here
self.fitVals[self.curHist].append(value)
self.tblLbls[self.curHist][2].SetLabel('{:8.3g}'.format(value))
self.plotaxis.clear()
self.plotted = []
if self.SeqLen:
for h in self.fitVals: # loop over all histograms but not all get plotted
if h == -1: continue
if h in self.prevSeqHist:
self._plotBar(h)
else:
for h in self.fitVals: # loop over all histograms but not all get plotted
if h < 0 or h == self.curHist or len(self.fitVals) < 6 or (
self.curHist < 0 and h==0): # plot overall & restraints, or p to 5 histograms
self._plotBar(h)
if self.plotted: self.plotaxis.legend(loc=3)
self.figure.canvas.draw()
else: # save as trial value
self.trialVals[self.curHist] = value
self.tblLbls[self.curHist][3].SetLabel('{:8.3g}'.format(value))
if self.curHist in self.plotted:
sym,lbl = self._getPlotSetting(self.curHist)
c = len(self.fitVals[self.curHist]) - 1 + self.histOff[self.curHist]
self.plotaxis.plot(c,value,'o'+sym)
self.figure.canvas.draw()
if (self.curHist ==-1 and -2 not in self.fitVals) or self.curHist == -2:
self.trialcount += 1
self.Layout() # in case sizes change
self.RwPanel.ScrollChildIntoView(self.tblLbls[self.curHist][1])
[docs]
def AdvanceCycle(self,cycle=None):
'''Call this directly with Levenberg-Marquardt fitting after a
cycle completes.
Plots the results.
'''
self.plotaxis.clear()
self.trialcount = 0
self.plotted = []
for h,value in self.trialVals.items():
if h not in self.fitVals or h not in self.tblLbls: continue
self.fitVals[h].append(value)
self.tblLbls[h][2].SetLabel('{:8.3g}'.format(value))
self.tblLbls[h][3].SetLabel('')
if self.SeqLen or h < 3 or len(self.trialVals) < 6: # plot overall & restraints, or p to 5 histograms
self._plotBar(h)
if self.plotted: self.plotaxis.legend(loc=3)
self.figure.canvas.draw()
[docs]
def Update(self, value=None, newmsg=""):
'''designed to work with calls intended for wx.ProgressDialog.Update
the value is assumed to be the current wR value for the histogram
selected with SetHistogram and newmsg goes into the 2nd status line.
'''
if self.curHist is not None and value != 101.:
self._SetCycleRw(value)
if newmsg:
self.msgLine2.SetLabel(newmsg)
m,s = divmod(time.time()-self.startTime,60)
h,m = divmod(m,60)
self.elapsed.SetLabel('{:0d}:{:02d}:{:04.1f}'.format(int(h), int(m), s))
wx.GetApp().Yield()
return (not self.abortStatus, True)
################################################################################
[docs]
class downdate(wx.Dialog):
'''Dialog to allow a user to select a version of GSAS-II to install
svn version
'''
def __init__(self,parent=None):
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style)
pnl = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
insver = GSASIIpath.svnGetRev(local=True)
curver = int(GSASIIpath.svnGetRev(local=False))
label = wx.StaticText(
pnl, wx.ID_ANY,
'Select a specific GSAS-II version to install'
)
sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add(
wx.StaticText(pnl, wx.ID_ANY,
'Currently installed version: '+str(insver)),
0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(sizer1)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add(
wx.StaticText(pnl, wx.ID_ANY,
'Select GSAS-II version to install: '),
0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1))
self.spin.SetRange(1, curver)
self.spin.SetValue(curver)
self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
sizer1.Add(self.spin)
sizer.Add(sizer1)
line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.EXPAND|wx.ALL, 10)
self.text = wx.StaticText(pnl, wx.ID_ANY, "")
sizer.Add(self.text, 0, wx.EXPAND|wx.ALL, 5)
line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.EXPAND|wx.ALL, 10)
sizer.Add(
wx.StaticText(
pnl, wx.ID_ANY,
'If "Install" is pressed, your project will be saved;\n'
'GSAS-II will exit; The specified version will be loaded\n'
'and GSAS-II will restart. Press "Cancel" to abort.'),
0, wx.EXPAND|wx.ALL, 10)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(pnl, wx.ID_OK, "Install")
btn.SetDefault()
btnsizer.AddButton(btn)
btn = wx.Button(pnl, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
pnl.SetSizer(sizer)
sizer.Fit(self)
self.topsizer=sizer
self.CenterOnParent()
self._onSpin(None)
def _onSpin(self,event):
'Called to load info about the selected version in the dialog'
if event: event.Skip()
ver = self.spin.GetValue()
d = GSASIIpath.svnGetLog(version=ver)
date = d.get('date','?').split('T')[0]
s = '(Version '+str(ver)+' created '+date
s += ' by '+d.get('author','?')+')'
msg = d.get('msg')
if msg: s += '\n\nComment: '+msg
self.text.SetLabel(s)
self.topsizer.Fit(self)
[docs]
def getVersion(self):
'Get the version number in the dialog'
return self.spin.GetValue()
################################################################################
[docs]
class gitVersionSelector(wx.Dialog):
'''Dialog to allow a user to select a version of GSAS-II to install
from a git repository
'''
def __init__(self,parent=None):
import git
self.g2repo = GSASIIpath.openGitRepo(path2GSAS2)
self.githistory = GSASIIpath.gitHistory('hash',self.g2repo)
# patch Feb 2024: don't allow access to versions that are too old
# since they are hard-coded to use svn
import datetime
tz = self.g2repo.commit(self.githistory[0]).committed_datetime.tzinfo
cutoff = datetime.datetime(2024,2,20,tzinfo=tz) # 20-feb-2024
self.githistory = [h for h in self.githistory if
self.g2repo.commit(h).committed_datetime > cutoff]
# end patch
self.initial_commit = self.g2repo.commit('HEAD')
self.initial_commit_info = self.docCommit(self.initial_commit)
wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select GSAS-II Version',
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
sizer = wx.BoxSizer(wx.VERTICAL)
remoteupdates,localupdates = GSASIIpath.gitCountRegressions(self.g2repo)
self.verselection = remoteupdates
sizer.Add((-1,10))
#label = wx.StaticText(
# self, wx.ID_ANY,
# 'Select a specific GSAS-II version to install'
# )
msg = 'This allows you to revert back to a previous GSAS-II version '
msg += 'to compare results between versions and temporarily bypass '
msg += 'bugs. If there is something that works better in an older '
msg += 'GSAS-II version, be sure to test the latest version and '
msg += 'if the problem remains, report it so that it can be fixed.'
label = wx.StaticText(self, wx.ID_ANY, msg)
label.Wrap(400)
sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add((-1,20))
sizer.Add(wx.StaticText(self, wx.ID_ANY,
' Currently installed version:'))
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add((50,-1))
initpnl = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=(450,90),
style = wx.SUNKEN_BORDER)
ssizer = wx.BoxSizer(wx.HORIZONTAL)
txt = wx.StaticText(initpnl, wx.ID_ANY, self.initial_commit_info)
txt.Wrap(435)
ssizer.Add(txt)
initpnl.SetSizer(ssizer)
initpnl.SetAutoLayout(1)
initpnl.SetupScrolling()
sizer1.Add(initpnl)
sizer.Add(sizer1)
sizer.Add((-1,20))
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add(
wx.StaticText(self, wx.ID_ANY,
'Select how many versions to regress: '),
0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.spin = wx.SpinCtrl(self, wx.ID_ANY,size=(150,-1))
self.spin.SetRange(-len(self.githistory)+1, 0)
self.spin.SetValue(-self.verselection)
self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin)
self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin)
sizer1.Add(self.spin)
sizer.Add(sizer1)
sizer.Add((-1,20))
sizer.Add(wx.StaticText(self, wx.ID_ANY,
' Selected version to install:'))
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add((50,-1))
self.spanel = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=(450,90),
style = wx.SUNKEN_BORDER)
ssizer = wx.BoxSizer(wx.HORIZONTAL)
self.text = wx.StaticText(self.spanel, wx.ID_ANY, "")
ssizer.Add(self.text)
self.spanel.SetSizer(ssizer)
self.spanel.SetAutoLayout(1)
self.spanel.SetupScrolling()
sizer1.Add(self.spanel)
sizer.Add(sizer1)
sizer.Add((-1,20))
sizer.Add(
wx.StaticText(
self, wx.ID_ANY,
'Press "Continue" after selecting a version to continue;\n'
'Press "Cancel" to stop regression.'),
0, wx.EXPAND|wx.ALL, 10)
sizer.Add((-1,5))
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(self, wx.ID_OK, "Continue")
btn.SetDefault()
btnsizer.AddButton(btn)
btn = wx.Button(self, wx.ID_CANCEL)
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
siz = self.GetSize()
siz[0] = max(siz[0],500)
self.SetSize(siz)
self.CenterOnParent()
self._onSpin(None)
def _onSpin(self,event):
'Called to load info about the selected version in the dialog'
if event: event.Skip()
try:
commit_info = self.docCommit(self.githistory[-self.spin.GetValue()])
self.text.SetLabel(commit_info)
self.text.Wrap(435)
except IndexError:
return
self.spanel.SetAutoLayout(1)
self.spanel.SetupScrolling()
[docs]
def getVersion(self):
'''Gets the selected version that should be installed
:return: returns one of three values:
* 0: if the newest version is selected, so that the
installation should be updated rather than regressed
* None: if the currently installed version is selected,
so that nothing need be done
* A hexsha string: the regressed version that should be
selected.
'''
if self.spin.GetValue() == 0:
return 0
commit = self.githistory[-self.spin.GetValue()]
if self.g2repo.commit(commit) == self.initial_commit:
return None
return commit
[docs]
def docCommit(self,commit):
'''Provides a string with information about a specific git commit.
:returns: a multi-line string
'''
import datetime
fmtdate = lambda c:"{:%d-%b-%Y %H:%M}".format(c.committed_datetime)
commit = self.g2repo.commit(commit) # converts a hash, if supplied
msg = f'git {commit.hexsha[:10]} from {fmtdate(commit)}'
tags = self.g2repo.git.tag('--points-at',commit).split('\n')
if tags != ['']:
msg += f"\ntags: {', '.join(tags)}"
msg += '\ncomment: ' + commit.message
return msg
################################################################################
[docs]
class SortableLstCtrl(wx.Panel):
'''Creates a read-only table with sortable columns. Sorting is done by
clicking on a column label. A triangle facing up or down is added to
indicate the column is sorted.
To use, the header is labeled using
:meth:`PopulateHeader`, then :meth:`PopulateLine` is called for every
row in table and finally :meth:`SetColWidth` is called to set the column
widths.
:param wx.Frame parent: parent object for control
'''
def __init__(self, parent):
wx.Panel.__init__(self, parent, wx.ID_ANY)#, style=wx.WANTS_CHARS)
sizer = wx.BoxSizer(wx.VERTICAL)
self.list = G2LstCtrl(self, wx.ID_ANY, style=wx.LC_REPORT| wx.BORDER_SUNKEN)
sizer.Add(self.list, 1, wx.EXPAND)
self.SetSizer(sizer)
self.SetAutoLayout(True)
#self.SortListItems(0, True)
[docs]
def PopulateLine(self, key, data):
'''Enters each row into the table
:param int key: a unique int value for each line, probably should
be sequential
:param list data: a list of strings for each column in that row
'''
index = self.list.InsertItem(self.list.GetItemCount(), data[0])
for i,d in enumerate(data[1:]):
self.list.SetItem(index, i+1, d)
self.list.SetItemData(index, key)
self.list.itemDataMap[key] = data
[docs]
def SetColWidth(self,col,width=None,auto=True,minwidth=0,maxwidth=None):
'''Sets the column width.
:param int width: the column width in pixels
:param bool auto: if True (default) and width is None (default) the
width is set by the maximum width entry in the column
:param int minwidth: used when auto is True, sets a minimum
column width
:param int maxwidth: used when auto is True, sets a maximum
column width. Do not use with minwidth
'''
if width:
self.list.SetColumnWidth(col, width)
elif auto:
self.list.SetColumnWidth(col, wx.LIST_AUTOSIZE)
if minwidth and self.list.GetColumnWidth(col) < minwidth:
self.list.SetColumnWidth(col, minwidth)
elif maxwidth and self.list.GetColumnWidth(col) > maxwidth:
self.list.SetColumnWidth(col, maxwidth)
else:
print('Error in SetColWidth: use either auto or width')
try:
class G2LstCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin):
'''Creates a custom ListCtrl with support for images in column labels
'''
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
from wx.lib.embeddedimage import PyEmbeddedImage
# from demo/images.py
SmallUpArrow = PyEmbeddedImage(
b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADxJ"
b"REFUOI1jZGRiZqAEMFGke2gY8P/f3/9kGwDTjM8QnAaga8JlCG3CAJdt2MQxDCAUaOjyjKMp"
b"cRAYAABS2CPsss3BWQAAAABJRU5ErkJggg==")
SmallDnArrow = PyEmbeddedImage(
b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAEhJ"
b"REFUOI1jZGRiZqAEMFGke9QABgYGBgYWdIH///7+J6SJkYmZEacLkCUJacZqAD5DsInTLhDR"
b"bcPlKrwugGnCFy6Mo3mBAQChDgRlP4RC7wAAAABJRU5ErkJggg==")
self.il = wx.ImageList(16, 16)
self.UpArrow = self.il.Add(SmallUpArrow.GetBitmap())
self.DownArrow = self.il.Add(SmallDnArrow.GetBitmap())
self.parent=parent
self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
def GetListCtrl(self): # needed for sorting
return self
[docs]
def GetSortImages(self):
#return (self.parent.DownArrow, self.parent.UpArrow)
return (self.DownArrow, self.UpArrow)
except TypeError:
# avoid "duplicate base class _MockObject" error in class G2LstCtrl():
# where listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin are same
# in docs build
[docs]
class G2LstCtrl(wx.ListCtrl):
'''Creates a custom ListCtrl with support for images in column labels
'''
pass
print('docs build kludge for G2LstCtrl')
#### Display Help information ################################################################################
# define some globals
htmlPanel = None
htmlFrame = None
htmlFirstUse = True
#helpLocDict = {} # to be implemented if we ever split gsasii.html over multiple files
path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # save location of this file
[docs]
def ShowHelp(helpType,frame):
'''Called to bring up a web page for documentation.'''
global htmlFirstUse,htmlPanel,htmlFrame
# no defined link to use, create a default based on key
if helpType.lower().startswith('pwdr'):
helplink = 'gsasII-pwdr.html#'+helpType.replace(')','').replace('(','_').replace(' ','_')
elif helpType.lower().startswith('phase'):
helplink = 'gsasII-phase.html#'+helpType.replace(')','').replace('(','_').replace(' ','_')
elif helpType.lower().startswith('hist/phase'):
helplink = 'gsasII-phase.html#Phase-Data'
elif helpType:
helplink = 'gsasII.html#'+helpType.replace(')','').replace('(','_').replace(' ','_')
else:
helplink = 'gsasII.html'
# determine if a web browser or the internal viewer should be used for help info
if GSASIIpath.GetConfigValue('Help_mode'):
helpMode = GSASIIpath.GetConfigValue('Help_mode')
else:
helpMode = 'browser'
if helpMode == 'internal':
helplink = os.path.join(path2GSAS2,'help',helplink)
try:
htmlPanel.LoadFile(helplink)
htmlFrame.Raise()
except:
htmlFrame = wx.Frame(frame, -1, size=(610, 510))
htmlFrame.Show(True)
htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
htmlPanel = MyHtmlPanel(htmlFrame,-1)
htmlPanel.LoadFile(helplink)
else:
import webbrowser # postpone this until now for quicker startup
wb = webbrowser
if sys.platform == "darwin": # on Mac, use a OSXscript so that file anchors work
# Get the default browser, this will fail in py2.7 and might fail, so
# use safari as a backup
appleScript = '''
use framework "AppKit"
use AppleScript version "2.4"
use scripting additions
property NSWorkspace : a reference to current application's NSWorkspace
property NSURL : a reference to current application's NSURL
set wurl to NSURL's URLWithString:"https://www.apple.com"
set thisBrowser to (NSWorkspace's sharedWorkspace)'s ¬
URLForApplicationToOpenURL:wurl
set appname to (thisBrowser's absoluteString)'s lastPathComponent()'s ¬
stringByDeletingPathExtension() as text
return appname as text
'''
import subprocess
try:
browser = subprocess.check_output(["osascript","-e",appleScript], encoding='UTF-8').strip()
wb = webbrowser.MacOSXOSAScript(browser)
except:
wb = webbrowser.MacOSXOSAScript('safari')
# open the link
helplink = os.path.join(path2GSAS2,'help',helplink)
pfx = "file://"
if sys.platform.lower().startswith('win'):
pfx = ''
#if GSASIIpath.GetConfigValue('debug'): print 'DBG_Help link=',pfx+helplink
if htmlFirstUse:
wb.open_new(pfx+helplink)
htmlFirstUse = False
else:
wb.open(pfx+helplink, new=0, autoraise=True)
[docs]
def ShowWebPage(URL,frame):
'''Called to show a tutorial web page.
'''
global htmlFirstUse,htmlPanel,htmlFrame
# determine if a web browser or the internal viewer should be used for help info
if GSASIIpath.GetConfigValue('Help_mode'):
helpMode = GSASIIpath.GetConfigValue('Help_mode')
else:
helpMode = 'browser'
if helpMode == 'internal':
try:
htmlPanel.LoadFile(URL)
htmlFrame.Raise()
except:
htmlFrame = wx.Frame(frame, -1, size=(610, 510))
htmlFrame.Show(True)
htmlFrame.SetTitle("HTML Window") # N.B. reset later in LoadFile
htmlPanel = MyHtmlPanel(htmlFrame,-1)
htmlPanel.LoadFile(URL)
else:
import webbrowser # postpone this until now for quicker startup
if URL.startswith('http'):
pfx = ''
elif sys.platform.lower().startswith('win'):
pfx = ''
else:
pfx = "file://"
if htmlFirstUse:
webbrowser.open_new(pfx+URL)
htmlFirstUse = False
else:
webbrowser.open(pfx+URL, new=0, autoraise=True)
#### Tutorials support ################################################################################
G2TutURL = "https://advancedphotonsource.github.io/GSAS-II-tutorials/"
tutorialCatalog = [l for l in tutorialIndex if len(l) >= 3]
# A catalog of GSAS-II tutorials generated from the table in :data:`tutorialIndex`
def OpenTutorial(parent):
if GSASIIpath.HowIsG2Installed().startswith('git'):
return OpenGitTutorial(parent)
else:
return OpenSvnTutorial(parent)
[docs]
class OpenSvnTutorial(wx.Dialog):
'''Open a tutorial web page, optionally copying the web page, screen images and
data file(s) to the local disk.
'''
def __init__(self,parent):
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style)
self.G2frame = self.frame = parent
pnl = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(
pnl, wx.ID_ANY,
'Select the tutorial to be run and the mode of access'
)
msg = '''GSAS-II tutorials and their sample data files
require a fair amount of storage space; few users will
use all of them. This dialog allows users to load selected
tutorials (along with their sample data) to their computer;
optionally all tutorials can be downloaded.
Downloaded tutorials can be viewed and run without internet
access. Tutorials can also be viewed without download, but
users will need to download the sample data files manually.
The location used to download tutorials is set using the
"Set download location" which is saved as the "Tutorial_location"
configuration option see File/Preference or the
config_example.py file. System managers can select to have
tutorial files installed at a shared location.
'''
self.SetTutorialPath()
hlp = HelpButton(pnl,msg)
sizer1.Add((-1,-1),1, wx.EXPAND, 0)
sizer1.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 0)
sizer1.Add((-1,-1),1, wx.EXPAND, 0)
sizer1.Add(hlp)
sizer.Add(sizer1,0,wx.EXPAND|wx.ALL,0)
sizer.Add((10,10))
sizer0 = wx.BoxSizer(wx.HORIZONTAL)
sizer1a = wx.BoxSizer(wx.VERTICAL)
sizer1b = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(pnl, wx.ID_ANY, "Download a tutorial and view")
btn.Bind(wx.EVT_BUTTON, self.SelectAndDownload)
sizer1a.Add(btn,0)
btn = wx.Button(pnl, wx.ID_ANY, "Select from downloaded tutorials")
btn.Bind(wx.EVT_BUTTON, self.onSelectDownloaded)
sizer1a.Add(btn,0)
btn = wx.Button(pnl, wx.ID_ANY, "Browse tutorial on web")
btn.Bind(wx.EVT_BUTTON, self.onWebBrowse)
sizer1a.Add(btn,0)
btn = wx.Button(pnl, wx.ID_ANY, "Update downloaded tutorials")
btn.Bind(wx.EVT_BUTTON, self.UpdateDownloaded)
sizer1b.Add(btn,0)
btn = wx.Button(pnl, wx.ID_ANY, "Download all tutorials")
btn.Bind(wx.EVT_BUTTON, self.DownloadAll)
sizer1b.Add(btn,0)
sizer0.Add(sizer1a,0,wx.EXPAND|wx.ALL,0)
sizer0.Add(sizer1b,0,wx.EXPAND|wx.ALL,0)
sizer.Add(sizer0,5,wx.EXPAND|wx.ALL,5)
sizer.Add((10,10))
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
btn = wx.Button(pnl, wx.ID_ANY, "Set download location")
btn.Bind(wx.EVT_BUTTON, self.SelectDownloadLoc)
sizer1.Add(btn,0,WACV)
self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,self.tutorialPath)
sizer1.Add(self.dataLoc,0,WACV)
sizer.Add(sizer1)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(pnl, wx.ID_CANCEL,"Done")
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
pnl.SetSizer(sizer)
sizer.Fit(self)
self.topsizer=sizer
self.CenterOnParent()
[docs]
def SetTutorialPath(self):
'''Get the tutorial location if set; if not pick a default
directory in a logical place
'''
# has the user set a location and is it valid?
if GSASIIpath.GetConfigValue('Tutorial_location'):
tutorialPath = os.path.abspath(GSASIIpath.GetConfigValue('Tutorial_location'))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
print('Unable to use Tutorial_location config setting',
tutorialPath)
# try a system-specific location
if (sys.platform.lower().startswith('win')):
for p in ('Documents','My Documents',''):
if os.path.exists(os.path.abspath(os.path.expanduser(
os.path.join('~',p)))):
tutorialPath = os.path.abspath(os.path.expanduser(
os.path.join('~',p,'G2tutorials')))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
else:
tutorialPath = os.path.abspath(os.path.expanduser(
os.path.join('~','G2tutorials')))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
# no success so far, use current working directory
tutorialPath = os.path.abspath(os.path.join(os.getcwd(),'G2tutorials'))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
# nothing worked, set self.tutorialPath with os.getcwd() and hope for the best
print('Warning: Unable to set a TutorialPath, using',os.getcwd())
tutorialPath = os.getcwd()
[docs]
def SelectAndDownload(self,event):
'''Make a list of all tutorials on web and allow user to choose one to
download and then view
'''
indices = [j for j,i in enumerate(tutorialCatalog)
if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))]
if not indices:
G2MessageBox(self,'All tutorials are downloaded','None to download')
return
# choices = [tutorialCatalog[i][2] for i in indices]
# selected = self.ChooseTutorial(choices)
choices2 = [tutorialCatalog[i][2:4] for i in indices]
selected = self.ChooseTutorial2(choices2)
if selected is None: return
j = indices[selected]
fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1])
fulldir = os.path.join(self.tutorialPath,tutorialCatalog[j][0])
#URL = G2BaseURL+'/Tutorials/'+tutorialCatalog[j][0]+'/'
URL = G2TutURL + tutorialCatalog[j][0]+'/'
if GSASIIpath.svnInstallDir(URL,fulldir):
ShowWebPage(fullpath,self.frame)
else:
G2MessageBox(self,'Error downloading tutorial','Download error')
self.EndModal(wx.ID_OK)
self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data')
[docs]
def onSelectDownloaded(self,event):
'''Select a previously downloaded tutorial
'''
indices = [j for j,i in enumerate(tutorialCatalog)
if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))]
if not indices:
G2MessageBox(self,
'There are no downloaded tutorials in '+self.tutorialPath,
'None downloaded')
return
# choices = [tutorialCatalog[i][2] for i in indices]
# selected = self.ChooseTutorial(choices)
choices2 = [tutorialCatalog[i][2:4] for i in indices]
selected = self.ChooseTutorial2(choices2)
if selected is None: return
j = indices[selected]
fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1])
self.EndModal(wx.ID_OK)
ShowWebPage(fullpath,self.frame)
self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data')
[docs]
def onWebBrowse(self,event):
'''Make a list of all tutorials on web and allow user to view one.
'''
# choices = [i[2] for i in tutorialCatalog]
# selected = self.ChooseTutorial(choices)
choices2 = [i[2:4] for i in tutorialCatalog]
selected = self.ChooseTutorial2(choices2)
if selected is None: return
tutdir = tutorialCatalog[selected][0]
tutfil = tutorialCatalog[selected][1]
# open web page remotely, don't worry about data
#URL = G2BaseURL+'/Tutorials/'+tutdir+'/'+tutfil
URL = G2TutURL + tutdir + '/' +tutfil
self.EndModal(wx.ID_OK)
ShowWebPage(URL,self.frame)
[docs]
def ChooseTutorial2(self,choices):
'''Select tutorials from a two-column table, when possible
'''
lbls = ('tutorial name (indent indicates previous is required)','description')
colWidths=[400,400]
dlg = MultiColumnSelection(self,'select tutorial',lbls,choices,colWidths)
selection = dlg.Selection
dlg.Destroy()
if selection is not None: # wx from EPD Python
if selection == -1: return
return selection
else:
return self.ChooseTutorial([i[0] for i in choices])
[docs]
def ChooseTutorial(self,choices):
'''choose a tutorial from a list
(will eventually only be used with very old wxPython
'''
def onDoubleClick(event):
'double-click closes the dialog'
dlg.EndModal(wx.ID_OK)
dlg = wx.Dialog(self,wx.ID_ANY,
'Select a tutorial to view. NB: indented ones require prerequisite',
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
pnl = wx.Panel(dlg)
sizer = wx.BoxSizer(wx.VERTICAL)
listbox = wx.ListBox(pnl, wx.ID_ANY, choices=choices,size=(450, 100),style=wx.LB_SINGLE)
sizer.Add(listbox,1,wx.EXPAND|wx.ALL,1)
listbox.Bind(wx.EVT_LISTBOX_DCLICK, onDoubleClick)
sizer.Add((10,10))
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(pnl, wx.ID_CANCEL)
btnsizer.AddButton(btn)
OKbtn = wx.Button(pnl, wx.ID_OK)
OKbtn.SetDefault()
btnsizer.AddButton(OKbtn)
btnsizer.Realize()
sizer.Add((-1,5))
sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
pnl.SetSizer(sizer)
sizer.Fit(dlg)
dlg.CenterOnParent()
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
selected = listbox.GetSelection()
dlg.Destroy()
wx.Yield() # close window right away so user sees something happen
if selected < 0: return
return selected
[docs]
def UpdateDownloaded(self,event):
'Find the downloaded tutorials and run an svn update on them'
updated = 0
wx.BeginBusyCursor()
for i in tutorialCatalog:
if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])): continue
print('Updating '+i[0])
GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0]))
updated += 1
wx.EndBusyCursor()
if not updated:
G2MessageBox(self,'Warning, you have no downloaded tutorials','None Downloaded')
else:
G2MessageBox(self,'{} updates completed'.format(updated),'Updates done')
#self.EndModal(wx.ID_OK)
[docs]
def DownloadAll(self,event):
'Download or update all tutorials'
fail = ''
for i in tutorialCatalog:
if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])):
print('Updating '+i[0])
GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0]))
else:
fulldir = os.path.join(self.tutorialPath,i[0])
#URL = G2BaseURL+'/Tutorials/'+i[0]+'/'
URL = G2TutURL + i[0] + '/'
if not GSASIIpath.svnInstallDir(URL,fulldir):
if fail: fail += ', '
fail += i[0]
if fail:
G2MessageBox(self,'Error downloading tutorial(s)\n\t'+fail,'Download error')
self.EndModal(wx.ID_OK)
[docs]
def SelectDownloadLoc(self,event):
'''Select a download location,
Cancel resets to the default
'''
dlg = wx.DirDialog(self, "Choose a directory for tutorial downloads:",
defaultPath=self.tutorialPath)#,style=wx.DD_DEFAULT_STYLE)
try:
if dlg.ShowModal() != wx.ID_OK:
return
pth = dlg.GetPath()
finally:
dlg.Destroy()
if not os.path.exists(pth):
try:
os.makedirs(pth) #failing for no obvious reason
except OSError:
msg = 'The selected directory is not valid.\n\t'
msg += pth
msg += '\n\nAn attempt to create the directory failed'
G2MessageBox(self.frame,msg)
return
if os.path.exists(os.path.join(pth,"help")) and os.path.exists(os.path.join(pth,"Exercises")):
print("Note that you may have old tutorial files in the following directories")
print('\t'+os.path.join(pth,"help"))
print('\t'+os.path.join(pth,"Exercises"))
print('Subdirectories in the above can be deleted to save space\n\n')
self.tutorialPath = pth
self.dataLoc.SetLabel(self.tutorialPath)
if GSASIIpath.GetConfigValue('Tutorial_location') == pth: return
vars = GetConfigValsDocs()
try:
vars['Tutorial_location'][1] = pth
if GSASIIpath.GetConfigValue('debug'): print('DBG_Saving Tutorial_location: '+pth)
GSASIIpath.SetConfigValue(vars)
SaveConfigVars(vars)
except KeyError:
pass
[docs]
class OpenGitTutorial(wx.Dialog):
'''Open a tutorial web page from the git repository,
optionally copying the tutorial's exercise data file(s) to
the local disk.
'''
def __init__(self,parent):
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style)
self.G2frame = self.frame = parent
pnl = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
msg = ('To open a tutorial, select below the mode to be used. '+
'With either choice you can select a tutorial and view it '+
'in a web browser, but the 2nd option offers the option '+
'to automatically download the data files needed to run '+
'that tutorial.')
label = wx.StaticText(pnl, wx.ID_ANY, msg)
label.Wrap(450)
msg = '''The data files needed to run the GSAS-II tutorials
require a fair amount of storage space; few users will
use all of them. This dialog allows you to open a
tutorial in a web browser and select if you want the data
needed to run that exercise to be downloaded to your computer.
The location used to download tutorials is set using the
"Set download location" which is saved as the "Tutorial_location"
configuration option see File/Preference or the
config_example.py file.
'''
self.SetTutorialPath()
hlp = HelpButton(pnl,msg)
sizer1.Add((-1,-1),1, wx.EXPAND, 0)
sizer1.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer1.Add((-1,-1),1, wx.EXPAND, 0)
sizer1.Add(hlp)
sizer.Add(sizer1,0,wx.EXPAND|wx.ALL,0)
sizer.Add((10,10))
sizer0 = wx.BoxSizer(wx.HORIZONTAL)
sizer1 = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(pnl, wx.ID_ANY, "Select a tutorial to view")
btn.Bind(wx.EVT_BUTTON, self.onWebBrowse)
sizer1.Add(btn,0)
btn = wx.Button(pnl, wx.ID_ANY, "Select a tutorial to view and download its data files")
btn.Bind(wx.EVT_BUTTON, self.SelectAndDownload)
sizer1.Add(btn,0,wx.TOP,5)
sizer0.Add(sizer1,0,wx.EXPAND|wx.ALL,0)
sizer.Add(sizer0,5,wx.EXPAND|wx.ALL,5)
sizer.Add((10,10))
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
btn = wx.Button(pnl, wx.ID_ANY, "Set download location")
btn.Bind(wx.EVT_BUTTON, self.SelectDownloadLoc)
sizer1.Add(btn,0,WACV|wx.RIGHT|wx.LEFT,5)
self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,self.tutorialPath)
sizer1.Add(self.dataLoc,0,WACV)
sizer.Add(sizer1)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(pnl, wx.ID_CANCEL,"Close")
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
pnl.SetSizer(sizer)
sizer.Fit(self)
self.topsizer=sizer
self.CenterOnParent()
[docs]
def SetTutorialPath(self):
'''Get the tutorial location if set; if not pick a default
directory in a logical place
'''
# has the user set a location and is it valid?
if GSASIIpath.GetConfigValue('Tutorial_location'):
tutorialPath = os.path.abspath(GSASIIpath.GetConfigValue('Tutorial_location'))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
print('Unable to use Tutorial_location config setting',
tutorialPath)
# try a system-specific location
if (sys.platform.lower().startswith('win')):
for p in ('Documents','My Documents',''):
if os.path.exists(os.path.abspath(os.path.expanduser(
os.path.join('~',p)))):
tutorialPath = os.path.abspath(os.path.expanduser(
os.path.join('~',p,'G2tutorials')))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
else:
tutorialPath = os.path.abspath(os.path.expanduser(
os.path.join('~','G2tutorials')))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
# no success so far, use current working directory
tutorialPath = os.path.abspath(os.path.join(os.getcwd(),'G2tutorials'))
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
try:
os.makedirs(tutorialPath)
if os.path.exists(tutorialPath):
self.tutorialPath = tutorialPath
return
except:
pass
# nothing worked, set self.tutorialPath with os.getcwd() and hope for the best
print('Warning: Unable to set a TutorialPath, using',os.getcwd())
tutorialPath = os.getcwd()
[docs]
def SelectAndDownload(self,event):
'''Shows a list of all tutorials so user can select one to view.
The data files associated with that directory are then downloaded.
'''
tutdir = self.onWebBrowse(event)
GSASIIpath.downloadDirContents([tutdir,'data'],self.tutorialPath)
[docs]
def onWebBrowse(self,event):
'''Shows a list of all tutorials so user can select one to view.
:returns: the name of the directory where the tutorial is located,
which is used if called from :meth:`SelectAndDownload`.
'''
choices2 = [i[2:4] for i in tutorialCatalog]
selected = self.ChooseTutorial2(choices2)
if selected is None: return
tutdir = tutorialCatalog[selected][0]
tutfil = tutorialCatalog[selected][1]
# open web page remotely, don't worry about data
#URL = G2BaseURL+'/Tutorials/'+tutdir+'/'+tutfil
URL = G2TutURL + tutdir + '/' + tutfil
wx.CallAfter(self.EndModal,wx.ID_OK)
ShowWebPage(URL,self.frame)
return tutdir
[docs]
def ChooseTutorial2(self,choices):
'''Select tutorials from a two-column table, when possible
'''
lbls = ('tutorial name (indent indicates previous is required)','description')
colWidths=[400,400]
dlg = MultiColumnSelection(self,'select tutorial',lbls,choices,colWidths)
selection = dlg.Selection
dlg.Destroy()
if selection is not None:
if selection == -1: return
return selection
[docs]
def SelectDownloadLoc(self,event):
'''Select a download location,
Cancel resets to the default
'''
dlg = wx.DirDialog(self, "Choose a directory for tutorial downloads:",
defaultPath=self.tutorialPath)#,style=wx.DD_DEFAULT_STYLE)
try:
if dlg.ShowModal() != wx.ID_OK:
return
pth = dlg.GetPath()
finally:
dlg.Destroy()
if not os.path.exists(pth):
try:
os.makedirs(pth) #failing for no obvious reason
except OSError:
msg = 'The selected directory is not valid.\n\t'
msg += pth
msg += '\n\nAn attempt to create the directory failed'
G2MessageBox(self.frame,msg)
return
if os.path.exists(os.path.join(pth,"help")) and os.path.exists(os.path.join(pth,"Exercises")):
print("Note that you may have old tutorial files in the following directories")
print('\t'+os.path.join(pth,"help"))
print('\t'+os.path.join(pth,"Exercises"))
print('Subdirectories in the above can be deleted to save space\n\n')
self.tutorialPath = pth
self.dataLoc.SetLabel(self.tutorialPath)
if GSASIIpath.GetConfigValue('Tutorial_location') == pth: return
vars = GetConfigValsDocs()
try:
vars['Tutorial_location'][1] = pth
if GSASIIpath.GetConfigValue('debug'): print('DBG_Saving Tutorial_location: '+pth)
GSASIIpath.SetConfigValue(vars)
SaveConfigVars(vars)
except KeyError:
pass
### Autoload PWDR files ################################################################################
AutoLoadWindow = None
def AutoLoadFiles(G2frame,FileTyp='pwd'):
def OnBrowse(event):
'''Responds when the Browse button is pressed to load a file.
The routine determines which button was pressed and gets the
appropriate file type and loads it into the appropriate place
in the dict.
'''
if btn3 == event.GetEventObject():
d = wx.DirDialog(dlg,
'Select directory for input files',
Settings['indir'],wx.DD_DEFAULT_STYLE)
d.CenterOnParent()
try:
if d.ShowModal() == wx.ID_OK:
Settings['indir'] = d.GetPath()
fInp3.SetValue(Settings['indir'])
finally:
d.Destroy()
elif btn4 == event.GetEventObject():
extList = 'GSAS iparm file (*.prm,*.inst,*.ins,.instprm)|*.prm;*.inst;*.ins;*.instprm'
d = wx.FileDialog(dlg,
'Choose instrument parameter file',
'', '',extList, wx.FD_OPEN)
if os.path.exists(Settings['instfile']):
d.SetFilename(Settings['instfile'])
try:
if d.ShowModal() == wx.ID_OK:
Settings['instfile'] = d.GetPath()
fInp4.SetValue(Settings['instfile'])
finally:
d.Destroy()
TestInput()
def onSetFmtSelection():
extSel.Clear()
extSel.AppendItems(fileReaders[Settings['fmt']].extensionlist)
Settings['extStr'] = fileReaders[Settings['fmt']].extensionlist[0]
extSel.SetSelection(0)
onSetExtSelection()
def onSetExtSelection():
Settings['filter'] = os.path.splitext(Settings['filter'])[0] + Settings['extStr']
flterInp.SetValue(Settings['filter'])
TestInput()
def OnQuit(event):
Settings['timer'].Stop()
wx.CallAfter(dlg.Destroy)
def TestInput(*args,**kwargs):
valid = True
if not os.path.exists(Settings['indir']):
valid = False
if FileTyp == 'pwd' and not os.path.exists(Settings['instfile']):
valid = False
btnstart.Enable(valid)
def OnStart(event):
if btnstart.GetLabel() == 'Pause':
Settings['timer'].Stop()
btnstart.SetLabel('Continue')
return
else:
btnstart.SetLabel('Pause')
if Settings['timer'].IsRunning(): return
PollTime = 1 # sec
G2frame.CheckNotebook()
Settings['timer'].Start(int(1000*PollTime),oneShot=False)
# get a list of existing histograms
if FileTyp == 'pwd':
treePrfx = 'PWDR '
else:
treePrfx = 'PDF '
ReadList = []
if G2frame.GPXtree.GetCount():
item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
while item:
name = G2frame.GPXtree.GetItemText(item)
if name.startswith(treePrfx) and name not in ReadList:
ReadList.append(name)
item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
Settings['ReadList'] = ReadList
def RunTimerPWDR(event):
if GSASIIpath.GetConfigValue('debug'):
import datetime
print ("DBG_Timer tick at {:%d %b %Y %H:%M:%S}\n".format(datetime.datetime.now()))
filelist = glob.glob(os.path.join(Settings['indir'],Settings['filter']))
if not filelist: return
#if GSASIIpath.GetConfigValue('debug'): print(filelist)
Id = None
for f in filelist:
if f in Settings['filesread']: continue
Settings['filesread'].append(f)
rd = fileReaders[Settings['fmt']]
rd.ReInitialize()
if not rd.ContentsValidator(f):
Settings['timer'].Stop()
btnstart.SetLabel('Continue')
G2MessageBox(dlg,'Error in reading file {}: {}'.format(
f, rd.errors))
return
#if len(rd.selections) > 1:
# G2fil.G2Print('Warning: Skipping file {}: multibank not yet implemented'.format(f))
# continue
block = 0
rdbuffer = {}
repeat = True
while repeat:
repeat = False
try:
flag = rd.Reader(f,buffer=rdbuffer, blocknum=block)
except:
flag = False
if flag:
rd.readfilename = f
if rd.warnings:
G2fil.G2Print("Read warning by", rd.formatName,
"reader:",
rd.warnings)
elif not block:
G2fil.G2Print("{} read by Reader {}"
.format(f,rd.formatName))
else:
G2fil.G2Print("{} block # {} read by Reader {}"
.format(f,block,rd.formatName))
block += 1
repeat = rd.repeat
else:
G2fil.G2Print("Warning: {} Reader failed to read {}"
.format(rd.formatName,f))
Iparm1, Iparm2 = G2sc.load_iprms(Settings['instfile'],rd)
if 'phoenix' in wx.version():
HistName = 'PWDR '+rd.idstring
else:
HistName = 'PWDR '+G2obj.StripUnicode(rd.idstring,'_')
# make new histogram names unique
HistName = G2obj.MakeUniqueLabel(HistName,Settings['ReadList'])
Settings['ReadList'].append(HistName)
# put into tree
Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=HistName)
if 'T' in Iparm1['Type'][0]:
if not rd.clockWd and rd.GSAS:
rd.powderdata[0] *= 100. #put back the CW centideg correction
cw = np.diff(rd.powderdata[0])
rd.powderdata[0] = rd.powderdata[0][:-1]+cw/2.
if rd.GSAS: #NB: old GSAS wanted intensities*CW even if normalized!
npts = min(len(rd.powderdata[0]),len(rd.powderdata[1]),len(cw))
rd.powderdata[1] = rd.powderdata[1][:npts]/cw[:npts]
rd.powderdata[2] = rd.powderdata[2][:npts]*cw[:npts]**2 #1/var=w at this point
else: #NB: from topas/fullprof type files
rd.powderdata[1] = rd.powderdata[1][:-1]
rd.powderdata[2] = rd.powderdata[2][:-1]
if 'Itype' in Iparm2:
Ibeg = np.searchsorted(rd.powderdata[0],Iparm2['Tminmax'][0])
Ifin = np.searchsorted(rd.powderdata[0],Iparm2['Tminmax'][1])
rd.powderdata[0] = rd.powderdata[0][Ibeg:Ifin]
YI,WYI = G2pwd.calcIncident(Iparm2,rd.powderdata[0])
rd.powderdata[1] = rd.powderdata[1][Ibeg:Ifin]/YI
var = 1./rd.powderdata[2][Ibeg:Ifin]
var += WYI*rd.powderdata[1]**2
var /= YI**2
rd.powderdata[2] = 1./var
rd.powderdata[3] = np.zeros_like(rd.powderdata[0])
rd.powderdata[4] = np.zeros_like(rd.powderdata[0])
rd.powderdata[5] = np.zeros_like(rd.powderdata[0])
Ymin = np.min(rd.powderdata[1])
Ymax = np.max(rd.powderdata[1])
valuesdict = {
'wtFactor':1.0,
'Dummy':False,
'ranId':ran.randint(0,sys.maxsize),
'Offset':[0.0,0.0],'delOffset':0.02*Ymax,'refOffset':-.1*Ymax,'refDelt':0.1*Ymax,
'Yminmax':[Ymin,Ymax]
}
# apply user-supplied corrections to powder data
if 'CorrectionCode' in Iparm1:
print('Warning: CorrectionCode from instprm file not applied')
# code below produces error on Py2.7: unqualified exec is not
# allowed in this function because it is a nested function
# no attempt made to address this.
#
# print('Applying corrections from instprm file')
# corr = Iparm1['CorrectionCode'][0]
# try:
# exec(corr)
# print('done')
# except Exception as err:
# print(u'error: {}'.format(err))
# print('with commands -------------------')
# print(corr)
# print('---------------------------------')
# finally:
# del Iparm1['CorrectionCode']
rd.Sample['ranId'] = valuesdict['ranId'] # this should be removed someday
G2frame.GPXtree.SetItemPyData(Id,[valuesdict,rd.powderdata])
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Comments'),
rd.comments)
Tmin = min(rd.powderdata[0])
Tmax = max(rd.powderdata[0])
Tmin1 = Tmin
if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4:
Tmin1 = G2lat.Dsp2pos(Iparm1,0.4)
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Limits'),
rd.pwdparms.get('Limits',[(Tmin,Tmax),[Tmin1,Tmax]])
)
G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame,Id,'Limits')
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Background'),
rd.pwdparms.get('Background',
[['chebyschev-1',True,3,1.0,0.0,0.0],{'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[],
'background PWDR':['',1.0,False]}]))
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),
[Iparm1,Iparm2])
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),
rd.Sample)
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Peak List')
,{'peaks':[],'sigDict':{}})
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),
[[],[]])
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),
[])
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),
{})
# if any Control values have been set, move them into tree
Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
Controls.update(rd.Controls)
# Tree entries complete
# select and show last PWDR file to be read
if Id:
G2frame.EnablePlot = True
G2frame.GPXtree.Expand(Id)
G2frame.GPXtree.SelectItem(Id)
def RunTimerGR(event):
if GSASIIpath.GetConfigValue('debug'):
import datetime
print ("DBG_Timer tick at {:%d %b %Y %H:%M:%S}\n".format(datetime.datetime.now()))
filelist = glob.glob(os.path.join(Settings['indir'],Settings['filter']))
if not filelist: return
#if GSASIIpath.GetConfigValue('debug'): print(filelist)
Id = None
for f in filelist:
if f in Settings['filesread']: continue
Settings['filesread'].append(f)
rd = fileReaders[Settings['fmt']]
rd.ReInitialize()
if not rd.ContentsValidator(f):
Settings['timer'].Stop()
btnstart.SetLabel('Continue')
G2MessageBox(dlg,'Error in reading file {}: {}'.format(
f, rd.errors))
return
#if len(rd.selections) > 1:
# G2fil.G2Print('Warning: Skipping file {}: multibank not yet implemented'.format(f))
# continue
block = 0
rdbuffer = {}
repeat = True
while repeat:
repeat = False
try:
flag = rd.Reader(f,buffer=rdbuffer, blocknum=block)
except:
flag = False
if flag:
rd.readfilename = f
if rd.warnings:
G2fil.G2Print("Read warning by", rd.formatName,
"reader:",
rd.warnings)
elif not block:
G2fil.G2Print("{} read by Reader {}"
.format(f,rd.formatName))
else:
G2fil.G2Print("{} block # {} read by Reader {}"
.format(f,block,rd.formatName))
block += 1
repeat = rd.repeat
else:
G2fil.G2Print("Warning: {} Reader failed to read {}"
.format(rd.formatName,f))
if 'phoenix' in wx.version():
HistName = 'PDF '+rd.idstring
else:
HistName = 'PDF '+G2obj.StripUnicode(rd.idstring,'_')
HistName = G2obj.MakeUniqueLabel(HistName,Settings['ReadList'])
Settings['ReadList'].append(HistName)
# put into tree
Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=HistName)
Ymin = np.min(rd.pdfdata[1])
Ymax = np.max(rd.pdfdata[1])
valuesdict = {
'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize),
'Offset':[0.0,0.0],'delOffset':0.02*Ymax,
'Yminmax':[Ymin,Ymax],
}
G2frame.GPXtree.SetItemPyData(
G2frame.GPXtree.AppendItem(Id,text='PDF Controls'),
{'G(R)':[valuesdict,rd.pdfdata,HistName],
'diffGRname':'','diffMult':1.0,'Rmax':Ymax,})
G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='PDF Peaks'),
{'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]})
# select and show last PWDR file to be read
if Id:
G2frame.EnablePlot = True
G2frame.GPXtree.Expand(Id)
G2frame.GPXtree.SelectItem(Id)
global AutoLoadWindow
Settings = {}
if AutoLoadWindow: # make sure only one window is open at a time
try:
AutoLoadWindow.Destroy()
except:
pass
AutoLoadWindow = None
if FileTyp == 'pwd':
fileReaders = [i for i in G2fil.LoadImportRoutines("pwd", "Powder_Data")
if i.scriptable]
fmtchoices = [p.longFormatName for p in fileReaders]
Settings['fmt'] = [i for i,v in enumerate(fmtchoices) if 'fxye' in v][0]
else:
fileReaders = [i for i in G2frame.ImportPDFReaderlist]
# if i.scriptable]
fmtchoices = [p.longFormatName for p in fileReaders]
Settings['fmt'] = 0
Settings['ext'] = 0
Settings['extStr'] = ''
Settings['filter'] = '*.*'
Settings['indir'] = os.getcwd()
Settings['instfile'] = ''
Settings['timer'] = wx.Timer()
if FileTyp == 'pwd':
Settings['timer'].Bind(wx.EVT_TIMER,RunTimerPWDR)
else:
Settings['timer'].Bind(wx.EVT_TIMER,RunTimerGR)
Settings['filesread'] = []
dlg = wx.Frame(G2frame,title='Automatic Data Loading',
style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
mnpnl = wx.Panel(dlg)
mnsizer = wx.BoxSizer(wx.VERTICAL)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Select format:'))
fmtSel = G2ChoiceButton(mnpnl,fmtchoices,Settings,'fmt',onChoice=onSetFmtSelection)
sizer.Add(fmtSel,1,wx.EXPAND)
mnsizer.Add(sizer,0,wx.EXPAND)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Select extension:'))
extSel = G2ChoiceButton(mnpnl,[],Settings,'ext',Settings,'extStr',onChoice=onSetExtSelection)
sizer.Add(extSel,0)
mnsizer.Add(sizer,0,wx.EXPAND)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add((-1,-1),1,wx.EXPAND,1)
sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,' File filter: '))
flterInp = ValidatedTxtCtrl(mnpnl,Settings,'filter')
sizer.Add(flterInp)
mnsizer.Add(sizer,0,wx.EXPAND,0)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Read from: '),0,wx.ALIGN_CENTER_VERTICAL)
fInp3 = ValidatedTxtCtrl(mnpnl,Settings,'indir',size=(300,-1),OnLeave=TestInput)
sizer.Add(fInp3,1,wx.EXPAND)
btn3 = wx.Button(mnpnl, wx.ID_ANY, "Browse")
btn3.Bind(wx.EVT_BUTTON, OnBrowse)
sizer.Add(btn3,0,wx.ALIGN_CENTER_VERTICAL)
mnsizer.Add(sizer,0,wx.EXPAND)
if FileTyp == 'pwd':
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Instrument parameter file from: '),0,wx.ALIGN_CENTER_VERTICAL)
fInp4 = ValidatedTxtCtrl(mnpnl,Settings,'instfile',size=(300,-1),OnLeave=TestInput)
sizer.Add(fInp4,1,wx.EXPAND)
btn4 = wx.Button(mnpnl, wx.ID_ANY, "Browse")
btn4.Bind(wx.EVT_BUTTON, OnBrowse)
sizer.Add(btn4,0,wx.ALIGN_CENTER_VERTICAL)
mnsizer.Add(sizer,0,wx.EXPAND)
# buttons on bottom
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add((-1,-1),1,wx.EXPAND)
btnstart = wx.Button(mnpnl, wx.ID_ANY, "Start")
btnstart.Bind(wx.EVT_BUTTON, OnStart)
sizer.Add(btnstart)
sizer.Add((20,-1),0,wx.EXPAND)
btnclose = wx.Button(mnpnl, wx.ID_ANY, "Close")
onSetFmtSelection()
btnclose.Bind(wx.EVT_BUTTON, OnQuit)
sizer.Add(btnclose)
sizer.Add((-1,-1),1,wx.EXPAND)
mnsizer.Add(sizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP,5)
mnpnl.SetSizer(mnsizer)
mnsizer.Fit(dlg)
dlg.Show()
AutoLoadWindow = dlg # save window reference
# Deal with Origin 1/2 ambiguities ################################################################################
def ChooseOrigin(G2frame,rd):
G2elem.SetupGeneral(rd.Phase,G2frame.dirname)
# make copy of Phase but shift atoms Origin 1->2
O2Phase = copy.deepcopy(rd.Phase)
# make copy of atoms, shift to alternate origin
T = G2spc.spg2origins[rd.Phase['General']['SGData']['SpGrp']]
O2atoms = O2Phase['Atoms']
cx,ct,cs,cia = rd.Phase['General']['AtomPtrs']
SGData = rd.Phase['General']['SGData']
for atom in O2atoms:
for i in [0,1,2]:
atom[cx+i] += T[i]
atom[cs:cs+2] = G2spc.SytSym(atom[3:6],SGData)[0:2] # update symmetry & mult
#get density & distances
DisAglData = {}
DisAglData['SGData'] = rd.Phase['General']['SGData']
DisAglData['Cell'] = rd.Phase['General']['Cell'][1:] #+ volume
DisAglCtls = {'Factors': [0.85, 0],
'BondRadii': [], 'AngleRadii': [], 'AtomTypes': []}
for atom in rd.Phase['Atoms']:
DisAglCtls['BondRadii'].append(1.5)
DisAglCtls['AngleRadii'].append(0)
DisAglCtls['AtomTypes'].append(atom[ct])
txt = ''
for i,phObj in enumerate([rd.Phase,O2Phase]):
if i:
txt += "\n\nWith origin shift applied\n"
else:
txt += "\nWith original origin\n"
cellContents = {}
for atom in phObj['Atoms']:
if atom[ct] in cellContents:
cellContents[atom[ct]] += atom[cs+1]
else:
cellContents[atom[ct]] = atom[cs+1]
txt += ' Unit cell Contents: '
for i,k in enumerate(cellContents):
if i: txt += ', '
txt += '{}*{}'.format(cellContents[k],k)
den,_ = G2mth.getDensity(phObj['General'])
txt += "\n Density {:.2f} g/cc\n".format(den)
DisAglData['OrigAtoms'] = DisAglData['TargAtoms'] = [
[i,]+atom[ct-1:ct+1]+atom[cx:cx+3] for
i,atom in enumerate(phObj['Atoms'])]
# lbl,dis,angle = G2stMn.RetDistAngle(DisAglCtls,DisAglData)
# # get unique distances
# minDis = {}
# for i in dis:
# for j,o,s,d,e in dis[i]:
# key = '-'.join(sorted([lbl[i],lbl[j]]))
# if key not in minDis:
# minDis[key] = d
# elif d < minDis[key]:
# minDis[key] = d
# thirdShortest = sorted([minDis[k] for k in minDis])[:3][-1]
# shortTxt = ''
# for k in minDis:
# if minDis[k] <= thirdShortest:
# if shortTxt: shortTxt += ', '
# shortTxt += "{}: {:.2f}".format(k,minDis[k])
# txt += " Shortest distances are "+shortTxt
# do we know if there is a center of symmetry at origin?
centro = None
if 'xyz' in rd.SymOps:
centro = False
if '-x,-y,-z' in [i.replace(' ','').lower() for i in rd.SymOps['xyz']]:
centro = True
msg = 'Be careful here. This space group has two origin settings. GSAS-II requires the origin to be placed at a center of symmetry (Origin 2). You must choose the correct option below or all subsequent results will be *wrong*. For more info, press the help button (bottom right).\n'
if centro:
msg += '\nThere is an -x,-y,-z symmetry op in the file input, so this is likely already in Origin 2.\n'
elif centro is None:
msg += '\nNo symmetry operations read from the input file; you must decide what to do. You are recommended to review a plot of the structure to make sure the symmetry is correct.\n'
else:
msg += '\nSymmetry operations in the input file do not contain -x,-y,-z, indicating an origin shift is likely needed.\n'
msg += '\nNote that computations below, made from the coordinates, may help determine the correct origin choice:'
width = 600
dlg = wx.Dialog(G2frame,wx.ID_ANY,'Warning: Shift origin?',
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE,
size=(width,-1))
dlg.CenterOnParent()
mainSizer = wx.BoxSizer(wx.VERTICAL)
txtbox = wx.StaticText(dlg,wx.ID_ANY,msg)
txtbox.Wrap(width-10)
mainSizer.Add(txtbox,0)
mainSizer.Add((5,5))
txtbox = wx.StaticText(dlg,wx.ID_ANY,txt)
mainSizer.Add(txtbox,0,wx.ALIGN_CENTER,1)
mainSizer.Add((10,10))
O1Btn = wx.Button(dlg,wx.ID_ANY,"Shift to Origin 2")
O1Btn.Bind(wx.EVT_BUTTON, lambda x: dlg.EndModal(wx.ID_OK))
O2Btn = wx.Button(dlg,wx.ID_ANY,"Keep current coordinates")
O2Btn.Bind(wx.EVT_BUTTON, lambda x: dlg.EndModal(wx.ID_YES))
if centro:
O2Btn.SetDefault()
elif centro is not None:
O1Btn.SetDefault()
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(O1Btn)
btnSizer.Add((10,10),0)
btnSizer.Add(O2Btn)
btnSizer.Add((20,20),1)
btnSizer.Add(HelpButton(dlg,helpIndex='Origin1'),0,wx.RIGHT,5)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
dlg.SetSizer(mainSizer)
dlg.Fit()
ans = dlg.ShowModal()
if ans == wx.ID_OK:
dlg.Destroy()
return O2Phase
elif ans == wx.ID_YES:
dlg.Destroy()
return rd.Phase
else:
dlg.Destroy()
return None
[docs]
def makeContourSliders(G2frame,Ymax,PlotPatterns,newPlot,plottype):
'''Create a non-modal dialog for sliders to set contour plot
intensity thresholds.
'''
def updatePlot():
'updates plot after a change in values'
wx.CallAfter(PlotPatterns,G2frame,newPlot=newPlot,plotType=plottype)
def OnSlider(event):
'respond when min or max slider is moved'
obj = event.GetEventObject()
val = obj.GetValue()/100.
if obj.mode == 'max':
G2frame.Cmax = val
else:
G2frame.Cmin = val
obj.txt.SetValue(int(Ymax*val))
updatePlot()
def OnNewVal(*args,**kwargs):
'respond when a value is placed in the min or max text box'
obj = kwargs['tc']
if obj.mode == 'max':
val = Range[1]
G2frame.Cmax = val/Ymax
else:
val = Range[0]
G2frame.Cmin = val/Ymax
obj.slider.SetValue(int((100*val/Ymax) + 0.5))
updatePlot()
# makeContourSliders starts here
Range = [Ymax*G2frame.Cmin,Ymax*G2frame.Cmax]
dlg = wx.Dialog(G2frame.plotFrame,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add((-1,5))
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add((-1,-1),1,wx.EXPAND,1)
hbox.Add(wx.StaticText(
dlg,wx.ID_ANY,'Set Contour Intensity Limits'),1,wx.ALL)
hbox.Add((-1,-1),1,wx.EXPAND,1)
vbox.Add(hbox)
vbox.Add((-1,10))
dlg.slideSizer = wx.FlexGridSizer(2,3,5,5)
dlg.slideSizer.AddGrowableCol(2)
dlg.slideSizer.Add(wx.StaticText(parent=dlg,label=' Min intensity'),0,WACV)
minSel = wx.Slider(parent=dlg,style=wx.SL_HORIZONTAL,
value=int(100*G2frame.Cmin+0.5))
minSel.Bind(wx.EVT_SLIDER, OnSlider)
minVal = ValidatedTxtCtrl(dlg,Range,0,xmin=0,
xmax=Ymax-1, OnLeave=OnNewVal)
minVal.slider = minSel
minVal.mode = 'min'
minSel.txt = minVal
minSel.mode = 'min'
dlg.slideSizer.Add(minVal,0,WACV)
dlg.slideSizer.Add(minSel,0,wx.EXPAND|wx.ALL,3)
dlg.slideSizer.Add(wx.StaticText(parent=dlg,label=' Max intensity'),0,WACV)
maxSel = wx.Slider(parent=dlg,style=wx.SL_HORIZONTAL,
value=int(100*G2frame.Cmax+0.5))
maxSel.Bind(wx.EVT_SLIDER, OnSlider)
maxVal = ValidatedTxtCtrl(dlg,Range,1,xmin=1,
xmax=Ymax, OnLeave=OnNewVal)
maxVal.slider = maxSel
maxVal.mode = 'max'
maxSel.txt = maxVal
maxSel.mode = 'max'
dlg.slideSizer.Add(maxVal,0,WACV)
dlg.slideSizer.Add(maxSel,0,wx.EXPAND|wx.ALL,3)
vbox.Add(dlg.slideSizer,0,wx.EXPAND,0)
vbox.Add((-1,15))
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add((-1,-1),1,wx.EXPAND,1)
btn = wx.Button(dlg, wx.ID_CLOSE)
hbox.Add(btn,0,wx.ALL,0)
btn.Bind(wx.EVT_BUTTON,lambda x: dlg.Destroy())
hbox.Add((-1,-1),1,wx.EXPAND,1)
vbox.Add(hbox,0,wx.EXPAND,0)
vbox.Add((-1,5))
dlg.SetSizer(vbox)
vbox.Fit(dlg)
wx.CallLater(100,minVal.SetValue,Range[0])
dlg.Show()
################################################################################
# GPX browser routines
[docs]
def skimGPX(fl):
'''pull out fit information from a .gpx file quickly
:returns: dict with status info
'''
if fl is None: return {}
result = {'other':[]}
if not os.path.exists(fl):
return {'error':'File does not exist!'}
cnt = 0
hist = 0
fp = open(fl,'rb')
result['last saved'] = time.ctime(os.stat(fl).st_mtime)
try:
while True:
cnt += 1
note = None
try:
data = G2IO.cPickleLoad(fp)
except EOFError:
#print(cnt,'entries read')
break
if cnt > 50: # don't spend too long on this file, if big
result['PWDR'] += 3*[' .']
break
datum = data[0]
if datum[0] == 'Notebook':
result[datum[0]] = datum[1][-1]
elif 'Controls' in datum[0]:
# datum[0]['Seq Data']
if 'LastSavedUsing' in datum[1]:
result['last saved'] += ' (v' + datum[1]['LastSavedUsing'] +')'
elif datum[0] == 'Covariance':
d = datum[1].get('Rvals')
if d:
result[datum[0]] = 'Overall: Rwp={:.2f}, GOF={:.1f}'.format(
d.get('Rwp','?'),d.get('GOF','?'))
if d.get('converged',False): result[datum[0]] += ' **Converged**'
elif datum[0].startswith('PWDR '):
if 'Residuals' not in datum[1][0]: continue
if 'PWDR' not in result: result['PWDR'] = []
result['PWDR'].append(
"hist #{}: wR={:.2f} ({:})".format(
hist,datum[1][0]['Residuals'].get('wR','?'),datum[0]))
hist += 1
elif datum[0].startswith('HKLF '):
note = 'Single crystal histogram(s)'
elif datum[0].startswith('REFD '):
note = 'Reflectivity histogram(s)'
elif datum[0].startswith('SASD '):
note = 'Small angle histogram(s)'
elif datum[0].startswith('PDF '):
note = 'PDF histogram(s)'
elif datum[0].startswith('IMG '):
note = 'Image(s)'
elif datum[0] == 'Sequential results':
note = 'Sequential results'
elif datum[0] in ('Constraints','Restraints','Rigid bodies'):
pass
else:
# print(datum[0])
# breakpoint()
pass
if note:
if note not in result['other']:
result['other'].append(note)
except Exception as msg:
result['error'] = 'read error: '+str(msg)
finally:
fp.close()
return result
[docs]
class gpxFileSelector(wx.Dialog):
'''Create a file selection widget for locating .gpx files as a modal
dialog. Displays status information on selected files. After creating
this use dlg.ShowModal() to wait for selection of a file.
If dlg.ShowModal() returns wx.ID_OK, use dlg.Selection (multiple=False)
to obtain the selected file or dlg.Selections (multiple=True) to
obtain a list of multiple files.
:param wx.Frame parent: name of panel or frame that will be
the parent to the dialog. Can be None.
:param path startdir: Specifies the initial directory that is
opened when the window is initially opened. Default is '.'
:param bool multiple: if True, checkboxes are used to allow
selection of multiple files. Default is False
'''
def __init__(self,parent,startdir='.',multiple=False,*args,**kwargs):
self.timer = None
self.delay = 1500 # time to wait before applying filter (1.5 sec)
self.Selection = None
self.Selections = []
self.startDir = startdir
if startdir == '.':
self.startDir = os.getcwd()
self.multiple = multiple
wx.Dialog.__init__(self, parent=parent,
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
self.CenterOnParent()
topSizer = wx.BoxSizer(wx.VERTICAL)
self.dirBtn = wxfilebrowse.DirBrowseButton(self,wx.ID_ANY, size=(650, -1),
changeCallback = self.DirSelected,
startDirectory = self.startDir
)
topSizer.Add(self.dirBtn,0,wx.EXPAND,1)
subSiz = wx.BoxSizer(wx.HORIZONTAL)
self.opt = {'useBak':False, 'sort':0, 'filter':'*'}
chk = G2CheckBoxFrontLbl(self,' Include .bakXX?',self.opt,'useBak',
OnChange=self.DirSelected)
subSiz.Add(chk,0,wx.ALIGN_CENTER_VERTICAL,0)
subSiz.Add((10,-1),1,wx.EXPAND,1)
subSiz.Add(wx.StaticText(self,wx.ID_ANY,' Sort by: '),0,wx.ALIGN_CENTER_VERTICAL,1)
choices = ['age','name (alpha+case)','name (alpha)']
for w in G2RadioButtons(self,self.opt,'sort',choices,
OnChange=self.DirSelected):
subSiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL,0)
subSiz.Add((10,-1),1,wx.EXPAND,1)
subSiz.Add(wx.StaticText(self,wx.ID_ANY,'Name \nFilter: '),0,wx.ALIGN_CENTER_VERTICAL,1)
self.filterBox = ValidatedTxtCtrl(self, self.opt, 'filter',
size=(80,-1), style=wx.TE_PROCESS_ENTER,
OnLeave=self.DirSelected, notBlank=False)
self.filterBox.Bind(wx.EVT_TEXT,self._startUpdateTimer)
self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.DirSelected)
subSiz.Add(self.filterBox)
subSiz.Add((2,-1))
topSizer.Add(subSiz,0,wx.EXPAND,0)
mainPanel = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_LIVE_UPDATE|wx.SP_3D)
mainPanel.SetMinimumPaneSize(100)
if self.multiple:
self.fileBox = wx.CheckListBox(mainPanel,wx.ID_ANY, size=(200, 200),
style=wx.LB_SINGLE)
self.fileBox.Bind(wx.EVT_CHECKLISTBOX,self.FileSelected)
else:
self.fileBox = wx.ListBox(mainPanel,wx.ID_ANY, size=(200, 200),
style=wx.LB_SINGLE)
self.fileBox.Bind(wx.EVT_LISTBOX,self.FileSelected)
self.rtc = wxrt.RichTextCtrl(mainPanel, style=wx.VSCROLL|wx.HSCROLL|
wx.NO_BORDER|wx.richtext.RE_READONLY)
mainPanel.SplitVertically(self.fileBox, self.rtc, 200)
topSizer.Add(mainPanel,1,wx.EXPAND)
subSiz = wx.BoxSizer(wx.HORIZONTAL)
subSiz.Add((-1,-1),1,wx.EXPAND,1)
self.OKbtn = wx.Button(self, wx.ID_OK, label='Open')
self.OKbtn.Enable(False) # A file must be selected 1st
btn = wx.Button(self, wx.ID_CANCEL)
subSiz.Add(self.OKbtn)
subSiz.Add((5,-1))
subSiz.Add(btn)
subSiz.Add((-1,-1),1,wx.EXPAND,1)
topSizer.Add((-1,5))
topSizer.Add(subSiz,0,wx.EXPAND)
topSizer.Add((-1,5))
self.SetSizer(topSizer)
topSizer.Fit(self)
self.dirBtn.SetValue(self.startDir)
def _startUpdateTimer(self,event):
if self.timer:
self.timer.Restart(self.delay)
else:
self.timer = wx.CallLater(self.delay,self.DirSelected)
[docs]
def DirSelected(self,event=None,*args,**kwargs):
'''Respond to a directory being selected. List files found in fileBox and
clear any selections. Also clear any reference to a timer.
'''
import re
try:
if self.timer: self.timer.Stop()
except:
pass
self.timer = None
self.fileBox.Clear()
self.rtc.Clear()
self.Selection = None
self.Selections = []
self.OKbtn.Enable(False)
glb = self.opt['filter'].strip()
if not glb:
glb = '*'
elif not '*' in glb:
glb = '*' + glb + '*'
fullglob = os.path.join(self.dirBtn.GetValue(),glb+'.gpx')
self.fl = glob.glob(fullglob)
if self.opt['useBak']:
self.sl = [(os.path.split(i)[1],os.stat(i).st_mtime,i) for i in self.fl]
else:
self.sl = [(os.path.split(i)[1],os.stat(i).st_mtime,i) for i in self.fl
if not re.match(r'.*\.bak\d+\.gpx.*',i)]
if self.opt['sort'] == 0:
self.sl.sort(key=lambda x: x[1],reverse=True)
elif self.opt['sort'] == 1:
self.sl.sort(key=lambda x: x[0])
else:
self.sl.sort(key=lambda x: x[0].lower())
items = [i[0]+' ('+self._fmtTimeStampDelta(i[1])+')' for i in self.sl]
if items:
self.fileBox.InsertItems(items,0)
[docs]
def FileSelected(self,event):
'''Respond to a file being selected (or checked in multiple mode)
'''
if self.multiple: # disable Open when nothing is selected
self.Selections = []
OK = False
for i in self.fileBox.GetCheckedItems():
self.Selections.append(self.sl[i][2])
OK = True
self.OKbtn.Enable(OK)
else:
self.OKbtn.Enable(True)
self.Selection = self.sl[self.fileBox.GetSelection()][2]
result = skimGPX(self.Selection)
self.displayGPXrtc(result,self.Selection)
[docs]
def displayGPXrtc(self,result,fwp):
'''Show info about selected file in a RichText display'''
self.rtc.Clear()
if fwp is None: return
self.rtc.Freeze()
self.rtc.BeginSuppressUndo()
self.rtc.BeginAlignment(wx.TEXT_ALIGNMENT_CENTER)
self.rtc.BeginFontSize(14)
self.rtc.BeginBold()
self.rtc.WriteText(os.path.split(fwp)[1])
self.rtc.EndBold()
self.rtc.Newline()
self.rtc.EndFontSize()
self.rtc.EndAlignment()
self.rtc.WriteText('last saved on ')
self.rtc.WriteText(result['last saved'])
self.rtc.Newline()
if 'Covariance' in result:
self.rtc.BeginLeftIndent(0,40)
self.rtc.WriteText(result['Covariance'])
self.rtc.Newline()
self.rtc.EndLeftIndent()
if 'Notebook' in result and len(result.get('Notebook','').strip()):
self.rtc.BeginLeftIndent(0,40)
self.rtc.BeginItalic()
self.rtc.WriteText('Last notebook entry: ')
self.rtc.EndItalic()
self.rtc.WriteText(result['Notebook'])
self.rtc.Newline()
self.rtc.EndLeftIndent()
if len(result.get('other',[])) > 0:
self.rtc.BeginParagraphSpacing(0,0)
self.rtc.BeginLeftIndent(0)
self.rtc.BeginBold()
self.rtc.WriteText('Data types in project:')
self.rtc.EndBold()
self.rtc.EndLeftIndent()
self.rtc.Newline()
self.rtc.BeginLeftIndent(40)
for line in result['other']:
self.rtc.WriteText(line+'\n')
self.rtc.EndLeftIndent()
self.rtc.EndParagraphSpacing()
if 'PWDR' in result:
self.rtc.BeginParagraphSpacing(0,0)
self.rtc.BeginLeftIndent(0)
self.rtc.BeginBold()
self.rtc.WriteText('Powder histograms:')
self.rtc.EndBold()
self.rtc.EndLeftIndent()
self.rtc.Newline()
self.rtc.BeginLeftIndent(40)
for line in result['PWDR']:
self.rtc.WriteText(line+'\n')
self.rtc.EndLeftIndent()
self.rtc.EndParagraphSpacing()
if 'error' in result:
self.rtc.Newline()
self.rtc.BeginBold()
self.rtc.WriteText('Error encountered: ')
self.rtc.EndBold()
self.rtc.WriteText(result['error'])
self.rtc.EndSuppressUndo()
self.rtc.Thaw()
def _fmtTimeStampDelta(self,tm):
'Show file age relative to now'
delta = time.time() - tm
if delta > 60*60*24*365:
return "{:.2f} years".format(delta/(60*60*24*365))
elif delta > 60*60*24*7:
return "{:.1f} weeks".format(delta/(60*60*24*7))
elif delta > 60*60*24:
return "{:.1f} days".format(delta/(60*60*24))
elif delta > 60*60:
return "{:.1f} hours".format(delta/(60*60))
else:
return "{:.1f} minutes".format(delta/60)
def NISTlatUse(msgonly=False):
msg = '''Performing cell symmetry search using NIST*LATTICE. Please cite:
V. L. Karen and A. D. Mighell, NIST Technical Note 1290 (1991),
https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote1290.pdf
and
V. L. Karen & A. D. Mighell, U.S. Patent 5,235,523,
https://patents.google.com/patent/US5235523A/en?oq=5235523'''
print(msg)
if msgonly: return msg
wx.MessageBox(msg,caption='Using NIST*LATTICE',style=wx.ICON_INFORMATION)
[docs]
def Load2Cells(G2frame,phase):
'''Accept two unit cells and use NIST*LATTICE to search for a relationship
that relates them.
The first unit cell is initialized as the currently selected phase and
the second unit cell is set to the first different phase from the tree.
The user can initialize the cell parameters to select a different phase
for either cell or can type in the values themselves.
:param wx.Frame G2frame: The main GSAS-II window
:param dict phase: the currently selected frame
'''
def setRatioMax(*arg,**kwarg):
'''Set the value for the max volume used in the search according
to the type of search selected.
'''
if nistInput[2] == 'I':
volRatW.Validator.xmax = 40
volMaxLbl.SetLabel(' (ratio, 1 to 40)')
else:
volRatW.Validator.xmax = 10
volMaxLbl.SetLabel(' (ratio, 1 to 10)')
volRatW.SetValue(min(volRatW.Validator.xmax,nistInput[3]))
def computeNISTlatCompare(event):
'run NIST*LATTICE after the compute button is pressed'
import nistlat
out = nistlat.CompareCell(cellLen[0], cellCntr[0],
cellLen[1], cellCntr[1],
tolerance=3*[nistInput[0]]+3*[nistInput[1]],
mode=nistInput[2], vrange=nistInput[3])
if len(out):
msg = str(len(out))+' Transformations were found. See console for matrices.'
G2MessageBox(G2frame,msg,'Transforms found')
print(len(out),'transform matrices found')
for i in out:
print(' ',i[5][0],'/',i[5][1],'/',i[5][2])
else:
G2MessageBox(G2frame,
'No transforms were found within supplied limits',
'No transforms found')
def setCellFromPhase(event):
'''respond to "set from phase" button. A phase is selected and
the unit cell info is loaded from that phase into the appropriate
cell widgets.
'''
cell = event.GetEventObject().cellNum
widgets = event.GetEventObject().widgets
phaseList = list(Phases.keys())
if len(Phases) == 0:
print('No phases in project')
return
elif len(Phases) == 1:
p = phaseList[0]
else:
dlg = G2SingleChoiceDialog(G2frame,'Select a phase from list',
'Select phase',phaseList)
dlg.CenterOnParent()
try:
if dlg.ShowModal() == wx.ID_OK:
p = phaseList[dlg.GetSelection()]
else:
return
finally:
dlg.Destroy()
ph2 = Phases[p]
cellLen[cell] = ph2['General']['Cell'][1:7]
cellCntr[cell] = ph2['General']['SGData']['SpGrp'].strip()[0]
for val,wid in zip(cellLen[cell],widgets[:6]):
wid.SetValue(val)
widgets[6].SetValue(cellCntr[cell])
dlg.Raise() # needed to bring modal dialog to front, at least on Mac
# Load2Cells starts here
msg = NISTlatUse(True)
nistInput=[0.2,1.,'I',8]
cellLen = [None,None]
cellCntr = [None,None]
cellLen[0] = phase['General']['Cell'][1:7]
cellCntr[0] = phase['General']['SGData']['SpGrp'].strip()[0]
Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
cellLen[1] = 3*[1.]+3*[90.]
cellCntr[1] = 'P'
for p in Phases:
ph2 = Phases[p]
if ph2['ranId'] != phase['ranId']:
cellLen[1] = ph2['General']['Cell'][1:7]
cellCntr[1] = ph2['General']['SGData']['SpGrp'].strip()[0]
break
dlg = wx.Dialog(G2frame,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
dlg.CenterOnParent()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.StaticText(dlg,label='NIST*LATTICE: Relate Two Unit Cells'),
0,wx.ALIGN_CENTER_HORIZONTAL,0)
sizer.Add((-1,15))
sizer.Add(wx.StaticText(dlg,label=msg))
sizer.Add((-1,15))
tableSizer = wx.FlexGridSizer(0,9,0,0)
tableSizer.Add((-1,-1))
for l in u'abc\u03B1\u03B2\u03B3':
tableSizer.Add(wx.StaticText(dlg,label=l),0,WACV|wx.ALIGN_CENTER)
tableSizer.Add(wx.StaticText(dlg,label='Centering'),0,WACV|wx.ALIGN_LEFT)
tableSizer.Add((-1,-1))
for cell in range(2):
tableSizer.Add(wx.StaticText(dlg,label='Cell '+str(cell+1)),0,wx.ALIGN_CENTER|wx.RIGHT,5)
wlist = []
for i in range(6):
l = 3
if i < 3: l = 4
w = ValidatedTxtCtrl(dlg,cellLen[cell],i,nDig=(7,l),size=(60,-1))
wlist.append(w)
tableSizer.Add(w,0,wx.LEFT|wx.RIGHT,3)
w = EnumSelector(dlg,cellCntr,cell,['P','A','B','C','F','I','R'])
tableSizer.Add(w)
wlist.append(w)
btn = wx.Button(dlg, wx.ID_ANY, 'Load from phase')
btn.cellNum = cell
btn.widgets = wlist
btn.Bind(wx.EVT_BUTTON, setCellFromPhase)
tableSizer.Add(btn)
sizer.Add(tableSizer,0,wx.LEFT|wx.RIGHT,20)
tableSizer = wx.FlexGridSizer(0,3,0,0)
tableSizer.Add(wx.StaticText(dlg,label='Cell length tolerance: '),
0,WACV|wx.ALIGN_LEFT)
w = ValidatedTxtCtrl(dlg,nistInput,0,nDig=(6,2))
tableSizer.Add(w)
tableSizer.Add(wx.StaticText(dlg,label=' (A) '))
tableSizer.Add(wx.StaticText(dlg,label='Cell angle tolerance: '),
0,WACV|wx.ALIGN_LEFT)
w = ValidatedTxtCtrl(dlg,nistInput,1,nDig=(6,1))
tableSizer.Add(w)
tableSizer.Add(wx.StaticText(dlg,label=' (degrees) '))
tableSizer.Add(wx.StaticText(dlg,label='Cell volume range: '),
0,WACV|wx.ALIGN_LEFT)
volRatW = ValidatedTxtCtrl(dlg,nistInput,3,xmin=1,xmax=40)
tableSizer.Add(volRatW)
volMaxLbl = wx.StaticText(dlg,label=' (ratio, 1 to 40)')
tableSizer.Add(volMaxLbl)
sizer.Add(tableSizer,0,wx.EXPAND)
tableSizer = wx.FlexGridSizer(0,2,0,0)
tableSizer.Add(wx.StaticText(dlg,label='Search mode: '),0,WACV|wx.ALIGN_LEFT)
tableSizer.Add(EnumSelector(dlg,nistInput,2,['Integral matrices', 'Fractional matrices'],
['I','F'],OnChange=setRatioMax))
sizer.Add(tableSizer,0,wx.EXPAND)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btn = wx.Button(dlg, wx.ID_ANY,'Compute')
btn.Bind(wx.EVT_BUTTON, computeNISTlatCompare)
btnSizer.Add((-1,-1),1,wx.EXPAND)
btnSizer.Add(btn)
btnSizer.Add((-1,-1),1,wx.EXPAND)
sizer.Add(btnSizer,0,wx.EXPAND|wx.CENTER,0)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(dlg, wx.ID_CLOSE)
btn.SetDefault()
btn.Bind(wx.EVT_BUTTON, lambda x: dlg.EndModal(wx.ID_OK))
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5)
dlg.SetSizer(sizer)
sizer.Fit(dlg)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy()
else:
dlg.Destroy()
return
[docs]
class ScrolledStaticText(wx.StaticText):
'''Fits a long string into a small space by scrolling it. Inspired by
ActiveText.py from J Healey <rolfofsaxony@gmx.com>
https://discuss.wxpython.org/t/activetext-rather-than-statictext/36370
Use examples::
frm = wx.Frame(None) # create a frame
ms = wx.BoxSizer(wx.VERTICAL)
text = 'this is a long string that will be scrolled'
ms.Add(G2G.ScrolledStaticText(frm,label=text))
txt = G2G.ScrolledStaticText(frm,label=text, lbllen=20)
smallfont = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
smallfont.SetPointSize(10)
txt.SetFont(smallfont)
ms.Add(txt)
ms.Add(G2G.ScrolledStaticText(frm,label=text,dots=False,delay=250,lbllen=20))
frm.SetSizer(ms)
:param w.Frame parent: Frame or Panel where widget will be placed
:param str label: string to be displayed
:param int delay: time between updates in ms (default is 100)
:param int lbllen: number of characters to show (default is 15)
:param bool dots: If True (default) ellipsis (...) are placed
at the beginning and end of the string when any characters
in the string are not shown. The displayed string length
will thus be lbllen+6 most of the time
:param (other): other optional keyword parameters for the
wx.StaticText widget such as size or style may be specified.
'''
def __init__(self, parent, label='', delay=100, lbllen=15,
dots=True, **kwargs):
wx.StaticText.__init__(self, parent, wx.ID_ANY, '', **kwargs)
self.fullmsg = label
self.lbllen = lbllen
self.msgpos = 0
self.dots = dots
self.onTimer(None)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer)
self.timer.Start(delay, wx.TIMER_CONTINUOUS)
def onTimer(self,event):
if self.dots and self.msgpos > 0:
txt = '...'
else:
txt = ''
txt += self.fullmsg[self.msgpos:self.msgpos+self.lbllen+1]
if self.dots and self.msgpos+self.lbllen < len(self.fullmsg):
txt += '...'
try:
self.SetLabel(txt)
except:
self.timer.Stop()
self.msgpos += 1
if self.msgpos >= len(self.fullmsg): self.msgpos = 0
#===========================================================================
[docs]
def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable):
'''Open a new and independent GSAS-II session in separate terminal
or console window and as a separate process that will continue
even if the calling process exits.
Intended to work on all platforms.
This could be used to run other scripts inside python other than GSAS-II
:param str project: the name of an optional parameter to be
passed to the script (usually a .gpx file to be opened in
a new GSAS-II session)
:param str g2script: the script to be run. If None (default)
the GSASII.py file in the same directory as this file will
be used.
:param str pythonapp: the Python interpreter to be used.
Defaults to sys.executable which is usually what is wanted.
:param str terminal: a name for a preferred terminal emulator
'''
import subprocess
if g2script is None:
g2script = os.path.join(os.path.dirname(__file__),'GSASII.py')
if sys.platform == "darwin":
if project:
script = f'''
set python to "{pythonapp}"
set appwithpath to "{g2script}"
set filename to "{project}"
set filename to the quoted form of the POSIX path of filename
tell application "Terminal"
activate
do script python & " " & appwithpath & " " & filename & "; exit"
end tell
'''
else:
script = f'''
set python to "{pythonapp}"
set appwithpath to "{g2script}"
tell application "Terminal"
activate
do script python & " " & appwithpath & " " & "; exit"
end tell
'''
subprocess.Popen(["osascript","-e",script])
elif sys.platform.startswith("win"):
cmds = [pythonapp, g2script]
if project: cmds += [project]
subprocess.Popen(cmds,creationflags=subprocess.CREATE_NEW_CONSOLE)
else:
import shutil
script = ''
# try a bunch of common terminal emulators in Linux
# there does not appear to be a good way to way to specify this
# perhaps this should be a GSAS-II config option
for term in ("lxterminal", "gnome-terminal", 'konsole', "xterm",
"terminator", "terminology", "tilix"):
try:
found = shutil.which(term)
if not found: continue
except AttributeError:
print(f'shutil.which() failed (why?); assuming {term} present')
found = True
if term == "gnome-terminal":
#terminal = 'gnome-terminal -t "GSAS-II console" --'
cmds = [term,'--title','"GSAS-II console"','--']
script = "echo; echo Press Enter to close window; read line"
break
elif term == "lxterminal":
#terminal = 'lxterminal -t "GSAS-II console" -e'
cmds = [term,'-t','"GSAS-II console"','-e']
script = "echo;echo Press Enter to close window; read line"
break
elif term == "xterm":
#terminal = 'xterm -title "GSAS-II console" -hold -e'
cmds = [term,'-title','"GSAS-II console"','-hold','-e']
script = "echo; echo This window can now be closed"
break
elif term == "terminator":
cmds = [term,'-T','"GSAS-II console"','-x']
script = "echo;echo Press Enter to close window; read line"
break
elif term == "konsole":
cmds = [term,'-p','tabtitle="GSAS-II console"','--hold','-e']
script = "echo; echo This window can now be closed"
break
elif term == "tilix":
cmds = [term,'-t','"GSAS-II console"','-e']
script = "echo;echo Press Enter to close window; read line"
break
elif term == "terminology":
cmds = [term,'-T="GSAS-II console"','--hold','-e']
script = "echo; echo This window can now be closed"
break
else:
print("No known terminal was found to use, Can't start {}")
return
fil = '/tmp/GSAS2-launch.sh'
cmds += ['/bin/sh',fil]
fp = open(fil,'w')
if project:
fp.write(f"{pythonapp} {g2script} {project}\n")
else:
fp.write(f"{pythonapp} {g2script}\n")
fp.write(f"rm {fil}\n")
if script:
fp.write(f"{script}\n")
fp.close()
subprocess.Popen(cmds,start_new_session=True)
#===========================================================================
def gitFetch(G2frame):
wx.BeginBusyCursor()
pdlg = wx.ProgressDialog('Updating','Performing git update',11,
style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT,
parent=G2frame)
pdlg.CenterOnParent()
if hasattr(G2frame,'UpdateTask'): # check if git update has completed
count = 0
while G2frame.UpdateTask.poll() is None:
count += 1
if count > 10:
G2MessageBox(G2frame,
'Background git update has not completed, try again later',
title='Warning')
pdlg.Destroy()
wx.EndBusyCursor()
return
time.sleep(1)
ok,_ = pdlg.Update(count)
wx.GetApp().Yield()
if not ok:
pdlg.Destroy()
wx.EndBusyCursor()
return
if GSASIIpath.GetConfigValue('debug'): print('background update complete')
# try update one more time just to make sure
GSASIIpath.gitGetUpdate('immediate')
pdlg.Destroy()
wx.EndBusyCursor()
[docs]
def gitCheckUpdates(G2frame):
'''Used to update to the latest GSAS-II version, but checks for a variety
of repository conditions that could make this process more complex. If
there are uncommitted local changes, these changes must be cached or
deleted first. If there are local changes that have been committed or a new
branch has been created, the user (how obstensibly must know use of git)
will probably need to do this manually. If GSAS-II has previously been
regressed (using :func:`gitSelectVersion`), then this is noted as well.
When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to
actually perform the update.
'''
try:
gitFetch(G2frame) # download latest updates from server
except:
G2MessageBox(G2frame,
'Unable to access updates: no internet connection?',
title='git error')
return
status = GSASIIpath.gitTestGSASII()
if status < 0:
G2MessageBox(G2frame,
'Problem with git access to GSAS-II. Seek help or reinstall',
title='git error')
return
localChanges = bool(status & 5) # If True need to select between
# --git-stash or --git-reset
# False: neither should be used
if status&8: # not on local branch
if localChanges:
G2MessageBox(G2frame,
'You have made a local branch and have uncommited changes. Save, stash or restore then before an update can be done.',
title='update not possible')
return
else:
msg = ('You have made a local branch and have switched to that.'+
' Do you want to update anyway? Doing so will return'+
' you to the master branch. This may be better handled'+
' manually.\n\nPress "Yes" to continue with update\n'+
'Press "Cancel" to stop the update.')
dlg = wx.MessageDialog(G2frame, msg, 'Confirm update?',
wx.YES|wx.CANCEL|wx.CANCEL_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
ans = dlg.ShowModal()
dlg.Destroy()
if ans == wx.ID_CANCEL: return
regressmsg = ''
if status&2: # detached head
remoteupdates,localupdates = GSASIIpath.gitCountRegressions()
if localupdates:
msg = ("Your copy of GSAS-II has been regressed. You are "+
f"{remoteupdates} versions behind the current. "+
f"WARNING: you have committed {localupdates} "+
"changes locally. Your local commits will be difficult "+
"to locate if you update. SUGGESTION: if your changes "+
"are meaningful, create a new git branch and return "+
"to the master branch.\n\n"+
'Press "Yes" to continue, stranding your local changes\n'+
'Press "Cancel" to stop the update.')
dlg = wx.MessageDialog(G2frame, msg, 'Confirm update?',
wx.YES|wx.CANCEL|wx.CANCEL_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
ans = dlg.ShowModal()
dlg.Destroy()
if ans == wx.ID_CANCEL: return
regressmsg = f"Your copy of GSAS-II has been regressed. You are {remoteupdates} versions behind the current.\n\n"
else:
# on head, standard update, is update needed or blocked by local changes
rc,lc,_ = GSASIIpath.gitCheckForUpdates(False)
if len(rc) == 0:
G2MessageBox(G2frame,
'Your copy of GSAS-II is up to date. No update is needed.',
title='no updates')
return
if len(lc) != 0:
msg = ('You have made local changes and committed them '+
'into the master branch. GUI-based updates cannot '+
'be made. You should perform a git merge manually')
G2MessageBox(G2frame,msg,title='Do manual update')
return
cmds = ['--git-update']
if localChanges:
if gitAskLocalChanges(G2frame,cmds): return
if gitAskSave(G2frame,regressmsg,cmds): return
# launch changes and restart
GSASIIpath.gitStartUpdate(cmds)
def gitAskLocalChanges(G2frame,cmds):
msg = ('You have locally-made changes to the GSAS-II source '+
'code files. Do you want to discard these changes?\n\n'+
'If you select "Yes" the changes will be overwritten '+
'before the update.\nIf you select "No" the changes will '+
'be archived (git stash).\nSelect "Cancel" '+
' to discontinue the update process.')
dlg = wx.MessageDialog(G2frame, msg, 'Save/Discard Local Changes?',
wx.YES_NO|wx.CANCEL|wx.NO_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
ans = dlg.ShowModal()
dlg.Destroy()
if ans == wx.ID_CANCEL:
return True
elif ans == wx.ID_YES:
cmds += ['--git-reset']
else:
cmds += ['--git-stash']
return False
def gitAskSave(G2frame,regressmsg,cmds):
# before doing update, need to save project
msg = ('Before continuing, do you want to save your project? '+
'Select "Yes" to save, "No" to skip the save, or "Cancel"'+
' to discontinue the update process.\n\n'+
'If "Yes", GSAS-II will reopen the project after the update. '+
'The update will now begin unless Cancel is pressed.')
dlg = wx.MessageDialog(G2frame, regressmsg+msg,
'Save Project and Start Update?',
wx.YES_NO|wx.CANCEL|wx.YES_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
ans = dlg.ShowModal()
dlg.Destroy()
if ans == wx.ID_CANCEL:
return True
elif ans == wx.ID_YES:
ans = G2frame.OnFileSave(None)
if ans:
cmds += [G2frame.GSASprojectfile]
return False
[docs]
def gitSelectVersion(G2frame):
'''Used to regress to a previous GSAS-II version, checking first
for a variety of repository conditions that could make this process
more complex. If there are uncommitted local changes, these changes
must be cached or deleted before a different version can be installed.
If there are local changes that have been committed or a new
branch has been created, the user (how obstensibly must know use of git)
will probably need to do this manually. If GSAS-II has previously been
regressed (using :func:`gitSelectVersion`), then this is noted as well.
When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to
actually perform the update.
'''
# get updates from server
gitFetch(G2frame) # download latest updates from server
status = GSASIIpath.gitTestGSASII()
if status < 0:
G2MessageBox(G2frame,
'Problem with git access to GSAS-II. Seek help or reinstall',
title='git error')
return
localChanges = bool(status & 5) # If True need to select between
# --git-stash or --git-reset
# False: neither should be used
if status&8: # not on local branch
if localChanges:
msg = ('You have switched to a local branch and have '+
'uncommited changes. Save, stash or restore and switch '+
'back to the master branch. Regression is not possible '+
'from the GUI when on any other branch.')
else:
msg = ('You have made and switched to a local branch. '+
'You must manually switch back to the master branch.'+
' Regression is not possible '+
'from the GUI when on any other branch.')
G2MessageBox(G2frame,msg,title='update not possible')
return
regressmsg = ''
if status&2: # detached head
remoteupdates,localupdates = GSASIIpath.gitCountRegressions()
if localupdates:
msg = ("Your copy of GSAS-II has been regressed. You are "+
f"{remoteupdates} versions behind the current. "+
f"WARNING: you have committed {localupdates} "+
"changes locally. Your local commits will be difficult "+
"to locate if you update. SUGGESTION: if your changes "+
"are meaningful, create a new git branch and return "+
"to the master branch.\n\n"+
'Press "Yes" to continue, stranding your local changes\n'+
'Press "Cancel" to stop the update.')
dlg = wx.MessageDialog(G2frame, msg, 'Confirm update?',
wx.YES|wx.CANCEL|wx.CANCEL_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
ans = dlg.ShowModal()
dlg.Destroy()
if ans == wx.ID_CANCEL: return
regressmsg = f"Your copy of GSAS-II has been regressed. You are {remoteupdates} versions behind the current.\n\n"
else:
# on head, standard update, is update blocked by local changes
rc,lc,_ = GSASIIpath.gitCheckForUpdates(False)
if len(lc) != 0:
msg = ('You have made local changes and committed them '+
'into the master branch. GUI-based regression cannot '+
'be done. You should perform a "git checkout" to the '+
'desired version manually')
G2MessageBox(G2frame,msg,title='Do manual update')
return
# browse and select a version here
dlg = gitVersionSelector()
ans = dlg.ShowModal()
if ans == wx.ID_CANCEL: return
githash = dlg.getVersion()
if githash is None:
print('Nothing to be done')
return
if githash == 0:
print('select newest GSAS-II version')
cmds = ['--git-update']
else:
cmds = [f'--git-regress={githash}']
if localChanges:
if gitAskLocalChanges(G2frame,cmds): return
if gitAskSave(G2frame,regressmsg,cmds): return
# launch changes and restart
GSASIIpath.gitStartUpdate(cmds)
#===========================================================================
[docs]
def svnCheckUpdates(G2frame):
'''Check if the GSAS-II repository has an update for the current
source files and perform that update if requested.
'''
wx.BeginBusyCursor()
local = GSASIIpath.svnGetRev()
if local is None:
wx.EndBusyCursor()
dlg = wx.MessageDialog(G2frame,
'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
'Subversion error',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
print ('Installed GSAS-II version: '+local)
repos = GSASIIpath.svnGetRev(local=False)
wx.EndBusyCursor()
# has the current branch disappeared? If so, switch to the trunk -- not fully tested
if (repos is None and "not found" in GSASIIpath.svnLastError.lower()
and "path" in GSASIIpath.svnLastError.lower()):
print('Repository is gone, will switch to trunk')
GSASIIpath.svnSwitch2branch()
return
elif repos is None:
dlg = wx.MessageDialog(G2frame,
'Unable to access the GSAS-II server. Is this computer on the internet?',
'Server unavailable',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
errmsg,warnmsg = G2gd.TestOldVersions()
if (errmsg or warnmsg):
msg = 'Based on the age of Python or an installed Python package (see below)'
msg += ' you are recommended to'
if GSASIIpath.condaTest():
msg += ' either use conda to update (see https://bit.ly/G2pkgs for version recommendations) and then update GSAS-II. Or'
msg += ' reinstall GSAS-II (see https://bit.ly/G2install), which will update both.\n\n'
if errmsg:
opt = wx.YES_NO|wx.ICON_QUESTION|wx.CANCEL|wx.NO_DEFAULT
msg += 'Error(s):\n\t'+errmsg
else:
opt = wx.YES_NO|wx.ICON_QUESTION|wx.CANCEL|wx.YES_DEFAULT
if warnmsg:
if errmsg: msg += '\n\nWarning(s):\n\t'
msg += warnmsg
msg += '\n\nContinue to update GSAS-II?'
dlg = wx.MessageDialog(G2frame, msg,'Confirm update',opt)
result = wx.ID_NO
try:
result = dlg.ShowModal()
finally:
dlg.Destroy()
if result != wx.ID_YES: return
print ('GSAS-II version on server: '+repos)
if local == repos:
up,mod,lock = GSASIIpath.svnGetFileStatus()
else:
up = 0
mods = GSASIIpath.svnFindLocalChanges()
mod = len(mods)
if local == repos and up:
dlg = wx.MessageDialog(G2frame,
'You have the current version '+
' of GSAS-II installed ('+repos+
'). However, '+str(up)+
' file(s) still need to be updated.'+
' Most likely a previous update failed to finish.'+
'\n\nPress OK to retry the update.'+
'\n\nIf this problem continues contact toby@anl.gov',
'Failed Update Likely',
wx.OK|wx.CANCEL)
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
else:
dlg.Destroy()
if lock: GSASIIpath.svnCleanup()
elif local == repos:
dlg = wx.MessageDialog(G2frame,
'GSAS-II is up-to-date. Version '+local+' is already loaded.',
'GSAS-II Up-to-date',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
if mod:
dlg = wx.MessageDialog(G2frame,
'You have version '+local+
' of GSAS-II installed, but the current version is '+repos+
'. However, '+str(mod)+
' file(s) on your local computer have been modified.'
' Updating will attempt to merge your local changes with '
'the latest GSAS-II version, but if '
'conflicts arise, local changes will be '
'discarded. It is also possible that the '
'merge may prevent GSAS-II from running. '
'\n\nPress OK to start an update if this is acceptable:',
'Local GSAS-II Mods',
wx.OK|wx.CANCEL)
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
else:
dlg.Destroy()
else:
dlg = wx.MessageDialog(G2frame,
'You have version '+local+
' of GSAS-II installed, but the current version is '+repos+
'. Press OK to start an update:',
'GSAS-II Updates',
wx.OK|wx.CANCEL)
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
dlg.Destroy()
print ('start updates')
if G2frame.GPXtree.GetCount() > 1:
dlg = wx.MessageDialog(G2frame,
'Your project will now be saved, GSAS-II will exit and an update '+
'will be performed and GSAS-II will restart. Press Cancel '+
'in next dialog to avoid saving the project.',
'Starting update',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
G2frame.OnFileSave(None)
GPX = G2frame.GSASprojectfile
GSASIIpath.svnUpdateProcess(projectfile=GPX)
else:
GSASIIpath.svnUpdateProcess()
return
[docs]
def svnSelectVersion(G2frame):
'''Allow the user to select a specific version of GSAS-II from the
APS svn server
'''
local = GSASIIpath.svnGetRev()
if local is None:
dlg = wx.MessageDialog(G2frame,
'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?',
'Subversion error',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
mods = GSASIIpath.svnFindLocalChanges()
if mods:
dlg = wx.MessageDialog(G2frame,
'You have version '+local+
' of GSAS-II installed'
'. However, '+str(len(mods))+
' file(s) on your local computer have been modified.'
' Downdating will attempt to merge your local changes with '
'the selected GSAS-II version. '
'Downdating is not encouraged because '
'if merging is not possible, your local changes will be '
'discarded. It is also possible that the '
'merge may prevent GSAS-II from running. '
'Press OK to continue anyway.',
'Local GSAS-II Mods',
wx.OK|wx.CANCEL)
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
dlg.Destroy()
if GSASIIpath.svnGetRev(local=False) is None:
dlg = wx.MessageDialog(G2frame,
'Error obtaining current GSAS-II version. Is internet access working correctly?',
'Subversion error',
wx.OK)
dlg.ShowModal()
dlg.Destroy()
return
dlg = downdate(parent=G2frame)
if dlg.ShowModal() == wx.ID_OK:
ver = dlg.getVersion()
else:
dlg.Destroy()
return
dlg.Destroy()
print('start regress to '+str(ver))
G2frame.OnFileSave(None)
GPX = G2frame.GSASprojectfile
GSASIIpath.svnUpdateProcess(projectfile=GPX,version=str(ver))
return
if __name__ == '__main__':
app = wx.App()
GSASIIpath.InvokeDebugOpts()
frm = wx.Frame(None) # create a frame
ms = wx.BoxSizer(wx.VERTICAL)
#siz = G2SliderWidget(pnl,valArr,'k','test slider w/entry',.2,1.2,100)
#ms.Add(siz)
#siz = G2SliderWidget(pnl,valArr,'k','test slider w/entry',20,50,.1)
#ms.Add(siz)
text = 'this is a long string that will be scrolled'
ms.Add(ScrolledStaticText(frm,label=text))
txt = ScrolledStaticText(frm,label=text, lbllen=20)
smallfont = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
smallfont.SetPointSize(10)
txt.SetFont(smallfont)
ms.Add(txt)
ms.Add(ScrolledStaticText(frm,label=text,dots=False,delay=250, lbllen=20))
frm.SetSizer(ms)
frm.Show(True)
app.MainLoop()
# choices = [wx.ID_YES,wx.ID_NO]
# warnmsg = '\nsome info\non a few lines\nand one more'
# ans = ShowScrolledInfo(header='Constraint Warning',
# txt='Warning noted after last constraint edit:\n'+warnmsg+
# '\n\nKeep this change?',
# buttonlist=choices,parent=frm,height=250)
# print(ans, choices)
import sys; sys.exit()
#======================================================================
# test Grid with GridFractionEditor
#======================================================================
# tbl = [[1.,2.,3.],[1.1,2.1,3.1]]
# colTypes = 3*[wg.GRID_VALUE_FLOAT+':10,5',]
# Gtbl = Table(tbl,types=colTypes,rowLabels=['a','b'],colLabels=['1','2','3'])
# Grid = GSGrid(frm)
# Grid.SetTable(Gtbl,True)
# for i in (0,1,2):
# attr = wx.grid.GridCellAttr()
# attr.IncRef()
# attr.SetEditor(GridFractionEditor(Grid))
# Grid.SetColAttr(i, attr)
# frm.SetSize((400,200))
# app.MainLoop()
# sys.exit()
#======================================================================
# test Tutorial access
#======================================================================
# dlg = OpenTutorial(frm)
# if dlg.ShowModal() == wx.ID_OK:
# print("OK")
# else:
# print("Cancel")
# dlg.Destroy()
# sys.exit()
#======================================================================
# test ScrolledMultiEditor
#======================================================================
# Data1 = {
# 'Order':1,
# 'omega':'string',
# 'chi':2.0,
# 'phi':'',
# }
# elemlst = sorted(Data1.keys())
# prelbl = sorted(Data1.keys())
# dictlst = len(elemlst)*[Data1,]
#Data2 = [True,False,False,True]
#Checkdictlst = len(Data2)*[Data2,]
#Checkelemlst = range(len(Checkdictlst))
# print 'before',Data1,'\n',Data2
# dlg = ScrolledMultiEditor(
# frm,dictlst,elemlst,prelbl,
# checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
# checklabel="Refine?",
# header="test")
# if dlg.ShowModal() == wx.ID_OK:
# print "OK"
# else:
# print "Cancel"
# print 'after',Data1,'\n',Data2
# dlg.Destroy()
# Data3 = {
# 'Order':1.0,
# 'omega':1.1,
# 'chi':2.0,
# 'phi':2.3,
# 'Order1':1.0,
# 'omega1':1.1,
# 'chi1':2.0,
# 'phi1':2.3,
# 'Order2':1.0,
# 'omega2':1.1,
# 'chi2':2.0,
# 'phi2':2.3,
# }
# elemlst = sorted(Data3.keys())
# dictlst = len(elemlst)*[Data3,]
# prelbl = elemlst[:]
# prelbl[0]="this is a much longer label to stretch things out"
# Data2 = len(elemlst)*[False,]
# Data2[1] = Data2[3] = True
# Checkdictlst = len(elemlst)*[Data2,]
# Checkelemlst = range(len(Checkdictlst))
#print 'before',Data3,'\n',Data2
#print dictlst,"\n",elemlst
#print Checkdictlst,"\n",Checkelemlst
# dlg = ScrolledMultiEditor(
# frm,dictlst,elemlst,prelbl,
# checkdictlst=Checkdictlst,checkelemlst=Checkelemlst,
# checklabel="Refine?",
# header="test",CopyButton=True)
# if dlg.ShowModal() == wx.ID_OK:
# print "OK"
# else:
# print "Cancel"
#print 'after',Data3,'\n',Data2
# Data2 = list(range(100))
# elemlst += range(2,6)
# postlbl += range(2,6)
# dictlst += len(range(2,6))*[Data2,]
# prelbl = range(len(elemlst))
# postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
# header="""This is a longer\nmultiline and perhaps silly header"""
# dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
# header=header,CopyButton=True)
# print Data1
# if dlg.ShowModal() == wx.ID_OK:
# for d,k in zip(dictlst,elemlst):
# print k,d[k]
# dlg.Destroy()
# if CallScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
# header=header):
# for d,k in zip(dictlst,elemlst):
# print k,d[k]
#======================================================================
# test G2MultiChoiceDialog
#======================================================================
# choices = []
# for i in range(21):
# choices.append("option_"+str(i))
# od = {
# 'label_1':'This is a bool','value_1':True,
# 'label_2':'This is a int','value_2':-1,
# 'label_3':'This is a float','value_3':1.0,
# 'label_4':'This is a string','value_4':'test',}
# dlg = G2MultiChoiceDialog(frm, 'Sequential refinement',
# 'Select dataset to include',
# choices,extraOpts=od)
# sel = range(2,11,2)
# dlg.SetSelections(sel)
# dlg.SetSelections((1,5))
# if dlg.ShowModal() == wx.ID_OK:
# for sel in dlg.GetSelections():
# print (sel,choices[sel])
# print (od)
# od = {}
# dlg = G2MultiChoiceDialog(frm, 'Sequential refinement',
# 'Select dataset to include',
# choices,extraOpts=od)
# sel = range(2,11,2)
# dlg.SetSelections(sel)
# dlg.SetSelections((1,5))
# if dlg.ShowModal() == wx.ID_OK: pass
#======================================================================
# test wx.MultiChoiceDialog
#======================================================================
# dlg = wx.MultiChoiceDialog(frm, 'Sequential refinement',
# 'Select dataset to include',
# choices)
# sel = range(2,11,2)
# dlg.SetSelections(sel)
# dlg.SetSelections((1,5))
# if dlg.ShowModal() == wx.ID_OK:
# for sel in dlg.GetSelections():
# print sel,choices[sel]
# pnl = wx.Panel(frm)
# siz = wx.BoxSizer(wx.VERTICAL)
# td = {'Goni':200.,'a':1.,'int':1,'calc':1./3.,'string':'s'}
# for key in sorted(td):
# txt = ValidatedTxtCtrl(pnl,td,key,typeHint=float)
# siz.Add(txt)
# pnl.SetSizer(siz)
# siz.Fit(frm)
# app.MainLoop()
# print(td)
choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
dlg = MultipleChoicesDialog(choicelist,headinglist,parent=frm)
if dlg.ShowModal() == wx.ID_OK:
print(dlg.chosen)
print(MultipleChoicesSelector(choicelist,headinglist,frm))
pnl = wx.Panel(frm)
valArr = {'k':1.0}
ms = wx.BoxSizer(wx.VERTICAL)
#siz = G2SliderWidget(pnl,valArr,'k','test slider w/entry',.2,1.2,100)
#ms.Add(siz)
siz = G2SliderWidget(pnl,valArr,'k','test slider w/entry',2,5,1)
ms.Add(siz)
#siz = G2SliderWidget(pnl,valArr,'k','test slider w/entry',20,50,.1)
#ms.Add(siz)
pnl.SetSizer(ms)
ms.Fit(frm)
app.MainLoop()
print(valArr)