# -*- coding: utf-8 -*-
#GSASIIctrlGUI - Custom GSAS-II GUI controls
########### SVN repository information ###################
# $Date: 2023-07-27 23:21:18 +0000 (Thu, 27 Jul 2023) $
# $Author: toby $
# $Revision: 5636 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIctrlGUI.py $
# $Id: GSASIIctrlGUI.py 5636 2023-07-27 23:21:18Z 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 webbrowser # could postpone this for quicker startup
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: 5636 $")
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
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.StaticLine(parent, size=(-1,18), style=wx.LI_HORIZONTAL)
line.SetBackgroundColour((128,128,128))
sizer.Add(line, 0, wx.EXPAND|wx.ALL, 0)
sizer.Add((-1,18))
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))
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()
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)
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'
def __init__(self,parent,title,prompts,values,limits=[[0.,1.],],formats=['%.5g',]):
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.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)
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)
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.Dest