Source code for GSASIIctrlGUI

# -*- 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 CheckInput(self,previousInvalid): '''called to test every change to the TextCtrl for validity and to change the appearance of the TextCtrl 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. ''' tc = self.GetWindow() self.TestValid(tc) self.ShowValidity(tc) # if invalid if tc.invalid and self.OKcontrol: self.OKcontrol(False) if not tc.invalid and self.OKcontrol and previousInvalid: self.OKcontrol(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 G2SliderWidget(parent,loc,key,label,xmin,xmax,iscale, onChange=None,onChangeArgs=[],sizer=None,nDig=None,size=(50,20)): '''A customized combination of a wx.Slider and a validated wx.TextCtrl (see :class:`ValidatedTxtCtrl`) that allows either a slider or text entry to set a value within a range. :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 and should have a float value. It will be forced to an initial value between xmin and xmax. :param str label: A label to be placed to the left of the slider. :param float xmin: the minimum allowed valid value. :param float xmax: the maximum allowed valid value. :param float iscale: number to scale values to integers, which is what the Scale widget uses. If the xmin=1 and xmax=4 and iscale=1 then values only the values 1,2,3 and 4 can be set with the slider. However, if iscale=2 then the values 1, 1.5, 2, 2.5, 3, 3.5 and 4 are all allowed. :param callable onChange: function to call when value is changed. Default is None where nothing will be called. :param list onChangeArgs: arguments to be passed to onChange function when called. :returns: returns a wx.BoxSizer containing the widgets ''' def onScale(event): loc[key] = vScale.GetScaledValue() wx.TextCtrl.SetValue(vEntry,str(loc[key])) # will not trigger onValSet if onChange: onChange(*onChangeArgs) def onValSet(*args,**kwargs): vScale.SetScaledValue(loc[key]) if onChange: onChange(*onChangeArgs) loc[key] = min(xmax,max(xmin,loc[key])) if sizer is None: hSizer = wx.BoxSizer(wx.HORIZONTAL) else: hSizer = sizer hSizer.Add(wx.StaticText(parent,wx.ID_ANY,label),0,wx.ALL|wx.ALIGN_CENTER_VERTICAL) vScale = G2Slider(parent,style=wx.SL_HORIZONTAL,size=(200,25)) vScale.SetScaling(iscale) vScale.SetScaledRange(xmin,xmax) vScale.SetScaledValue(loc[key]) vScale.Bind(wx.EVT_SLIDER, onScale) if nDig is None: nDig = (10,int(0.9+np.log10(iscale))) vEntry = ValidatedTxtCtrl(parent,loc,key,nDig=nDig,OnLeave=onValSet, xmin=xmin,xmax=xmax,typeHint=float,size=size) if sizer is None: hSizer.Add(vEntry,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL,5) hSizer.Add(vScale,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL,0) return hSizer else: hSizer.Add(vEntry,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5) hSizer.Add(vScale,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL) return vEntry,vScale
[docs]def G2SpinWidget(parent,loc,key,label,xmin=None,xmax=None, onChange=None,onChangeArgs=[],hsize=35): '''A customized combination of a wx.SpinButton and a validated wx.TextCtrl (see :class:`ValidatedTxtCtrl`) that allows either a the spin button or text entry to set a value within a range. :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 and should have a float or int value. It will be forced to an integer initial value between xmin and xmax. :param str label: A label to be placed to the left of the entry widget. :param int xmin: the minimum allowed valid value. If None it is ignored. :param int xmax: the maximum allowed valid value. If None it is ignored. :param callable onChange: function to call when value is changed. Default is None where nothing will be called. :param list onChangeArgs: arguments to be passed to onChange function when called. :param int hsize: length of TextCtrl in pixels. Defaults to 35. :returns: returns a wx.BoxSizer containing the widgets ''' def _onSpin(event): Obj = event.GetEventObject() loc[key] += Obj.GetValue() # +1 or -1 if xmin is not None and loc[key] < xmin: loc[key] = xmin if xmax is not None and loc[key] > xmax: loc[key] = xmax wx.TextCtrl.SetValue(vEntry,str(loc[key])) # will not trigger onValSet Obj.SetValue(0) if onChange: onChange(*onChangeArgs) def _onValSet(*args,**kwargs): if onChange: onChange(*onChangeArgs) if xmin is not None: loc[key] = max(xmin,loc[key]) if xmax is not None: loc[key] = min(xmax,loc[key]) hSizer = wx.BoxSizer(wx.HORIZONTAL) if label: hSizer.Add(wx.StaticText(parent,wx.ID_ANY,label),0, wx.ALL|wx.ALIGN_CENTER_VERTICAL) spin = wx.SpinButton(parent,style=wx.SP_VERTICAL,size=wx.Size(20,20)) spin.SetRange(-1,1) spin.Bind(wx.EVT_SPIN, _onSpin) loc[key] = int(loc[key]+0.5) vEntry = ValidatedTxtCtrl(parent,loc,key,OnLeave=_onValSet, xmin=xmin,xmax=xmax,typeHint=int,size=(hsize,-1)) hSizer.Add(vEntry,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL,5) hSizer.Add(spin,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL) return hSizer
################################################################################
[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 G2LoggedButton(wx.Button): '''A version of wx.Button that creates logging events. Bindings are saved in the object, and are looked up rather than directly set with a bind. An index to these buttons is saved as log.ButtonBindingLookup :param wx.Panel parent: parent widget :param int id: Id for button :param str label: label for button :param str locationcode: a label used internally to uniquely indentify the button :param function handler: a routine to call when the button is pressed ''' def __init__(self,parent,id=wx.ID_ANY,label='',locationcode='', handler=None,*args,**kwargs): super(self.__class__,self).__init__(parent,id,label,*args,**kwargs) self.label = label self.handler = handler self.locationcode = locationcode key = locationcode + '+' + label # hash code to find button self.Bind(wx.EVT_BUTTON,self.onPress) log.ButtonBindingLookup[key] = self
[docs] def onPress(self,event): 'create log event and call handler' log.MakeButtonLog(self.locationcode,self.label) self.handler(event)
################################################################################
[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)
################################################################################
[docs]class G2ChoiceButton(wx.Choice): '''A customized version of a wx.Choice that automatically initializes the control to match a supplied value and saves the choice directly into an array or list. Optionally a function can be called each time a choice is selected. The widget can be used with an array item that is set to to the choice by number (``indLoc[indKey]``) or by string value (``strLoc[strKey]``) or both. The initial value is taken from ``indLoc[indKey]`` if not None or ``strLoc[strKey]`` if not None. :param wx.Panel parent: name of panel or frame that will be the parent to the widget. Can be None. :param list choiceList: a list or tuple of choices to offer the user. :param dict/list indLoc: a dict or list with the initial value to be placed in the Choice button. If this is None, this is ignored. :param int/str indKey: the dict key or the list index for the value to be edited by the Choice button. If indLoc is not None then this must be specified and the ``indLoc[indKey]`` will be set. If the value for ``indLoc[indKey]`` is not None, it should be an integer in range(len(choiceList)). The Choice button will be initialized to the choice corresponding to the value in this element if not None. :param dict/list strLoc: a dict or list with the string value corresponding to indLoc/indKey. Default (None) means that this is not used. :param int/str strKey: the dict key or the list index for the string value The ``strLoc[strKey]`` element must exist or strLoc must be None (default). :param function onChoice: name of a function to call when the choice is made. ''' def __init__(self,parent,choiceList,indLoc=None,indKey=None,strLoc=None,strKey=None, onChoice=None,**kwargs): wx.Choice.__init__(self,parent,choices=choiceList,id=wx.ID_ANY,**kwargs) self.choiceList = choiceList self.indLoc = indLoc self.indKey = indKey self.strLoc = strLoc self.strKey = strKey self.onChoice = None self.SetSelection(wx.NOT_FOUND) if self.indLoc is not None and self.indKey is not None: try: self.SetSelection(self.indLoc[self.indKey]) if self.strLoc is not None and self.strKey is not None: self.strLoc[self.strKey] = self.GetStringSelection() #log.LogVarChange(self.strLoc,self.strKey) except (KeyError,ValueError,TypeError): pass elif self.strLoc is not None and self.strKey is not None: try: self.SetSelection(choiceList.index(self.strLoc[self.strKey])) if self.indLoc is not None: self.indLoc[self.indKey] = self.GetSelection() log.LogVarChange(self.indLoc,self.indKey) except (KeyError,ValueError,TypeError): pass self.Bind(wx.EVT_CHOICE, self._OnChoice) #if self.strLoc is not None: # make sure strLoc gets initialized # self._OnChoice(None) # note that onChoice will not be called self.onChoice = onChoice def _OnChoice(self,event): if self.indLoc is not None: self.indLoc[self.indKey] = self.GetSelection() log.LogVarChange(self.indLoc,self.indKey) if self.strLoc is not None: self.strLoc[self.strKey] = self.GetStringSelection() log.LogVarChange(self.strLoc,self.strKey) if self.onChoice: self.onChoice()
[docs] def setByString(self,string): 'Find an entry matching string and select it' num = self.FindString(string) if num >= 0: self.SetSelection(num)
#### 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
[docs]def G2RadioButtons(parent,loc,key,choices,values=None,OnChange=None): '''A customized version of wx.RadioButton that returns a list of coupled RadioButtons :param wx.Panel parent: name of panel or frame that will be the parent to the widgets. Can be None. :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 CheckButton will be initialized from this value. :param list choices: :param list values: :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 _OnEvent(event): if event.GetEventObject() not in buttons: if GSASIIpath.GetConfigValue('debug'): print('Strange: unknown button') return loc[key] = values[buttons.index(event.GetEventObject())] #log.LogVarChange(self.loc,self.key) if OnChange: OnChange(event) if not values: values = list(range(len(choices))) buttons = [] kw = {'style':wx.RB_GROUP} for i,c in enumerate(choices): if i == 1: kw = {} buttons.append(wx.RadioButton(parent,wx.ID_ANY,c,**kw)) if loc[key] == values[i]: buttons[-1].SetValue(True) buttons[-1].Bind(wx.EVT_RADIOBUTTON, _OnEvent) return buttons
#### Commonly used dialogs ################################################################################
[docs]def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[], title='Edit items',header='',size=(300,250), CopyButton=False, ASCIIonly=False, **kw): '''Shell routine to call a ScrolledMultiEditor dialog. See :class:`ScrolledMultiEditor` for parameter definitions. :returns: True if the OK button is pressed; False if the window is closed with the system menu or the Cancel button. ''' dlg = ScrolledMultiEditor(parent,dictlst,elemlst,prelbl,postlbl, title,header,size, CopyButton, ASCIIonly, **kw) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: dlg.Destroy() return True else: dlg.Destroy() return False
################################################################################
[docs]class ScrolledMultiEditor(wx.Dialog): '''Define a window for editing a potentially large number of dict- or list-contained values with validation for each item. Edited values are automatically placed in their source location. If invalid entries are provided, the TextCtrl is turned yellow and the OK button is disabled. The type for each TextCtrl validation is determined by the initial value of the entry (int, float or string). 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.Frame parent: name of parent window, or may be None :param tuple dictlst: a list of dicts or lists containing values to edit :param tuple elemlst: a list of keys/indices for items in dictlst. Note that elemlst must have the same length as dictlst, where each item in elemlst will will match an entry for an entry for successive dicts/lists in dictlst. :param tuple prelbl: a list of labels placed before the TextCtrl for each item (optional) :param tuple postlbl: a list of labels placed after the TextCtrl for each item (optional) :param str title: a title to place in the frame of the dialog :param str header: text to place at the top of the window. May contain new line characters. :param wx.Size size: a size parameter that dictates the size for the scrolled region of the dialog. The default is (300,250). :param bool CopyButton: if True adds a small button that copies the value for the current row to all fields below (default is False) :param bool ASCIIonly: if set as True will remove unicode characters from strings :param list minvals: optional list of minimum values for validation of float or int values. Ignored if value is None. :param list maxvals: optional list of maximum values for validation of float or int values. Ignored if value is None. :param list sizevals: optional list of wx.Size values for each input widget. Ignored if value is None. :param tuple checkdictlst: an optional list of dicts or lists containing bool values (similar to dictlst). :param tuple checkelemlst: an optional list of dicts or lists containing bool key values (similar to elemlst). Must be used with checkdictlst. :param string checklabel: a string to use for each checkbutton :returns: the wx.Dialog created here. Use method .ShowModal() to display it. *Example for use of ScrolledMultiEditor:* :: dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl, header=header) if dlg.ShowModal() == wx.ID_OK: for d,k in zip(dictlst,elemlst): print d[k] *Example definitions for dictlst and elemlst:* :: dictlst = (dict1,list1,dict1,list1) elemlst = ('a', 1, 2, 3) This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited. Note that these items must have int, float or str values assigned to them. The dialog will force these types to be retained. String values that are blank are marked as invalid. ''' def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], title='Edit items',header='',size=(300,250), CopyButton=False, ASCIIonly=False, minvals=[],maxvals=[],sizevals=[], checkdictlst=[], checkelemlst=[], checklabel=""): if len(dictlst) != len(elemlst): raise Exception("ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))) if len(checkdictlst) != len(checkelemlst): raise Exception("ScrolledMultiEditor error: len(checkdictlst) != len(checkelemlst) "+str(len(checkdictlst))+" != "+str(len(checkelemlst))) wx.Dialog.__init__( # create dialog & sizer self,parent,wx.ID_ANY,title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) mainSizer = wx.BoxSizer(wx.VERTICAL) self.orig = [] self.dictlst = dictlst self.elemlst = elemlst self.checkdictlst = checkdictlst self.checkelemlst = checkelemlst self.StartCheckValues = [checkdictlst[i][checkelemlst[i]] for i in range(len(checkdictlst))] self.ButtonIndex = {} for d,i in zip(dictlst,elemlst): self.orig.append(d[i]) # add a header if supplied if header: subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) subSizer.Add(wx.StaticText(self,wx.ID_ANY,header)) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND,0) # make OK button now, because we will need it for validation self.OKbtn = wx.Button(self, wx.ID_OK) self.OKbtn.SetDefault() # create scrolled panel and sizer panel = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=size, style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER) cols = 4 if CopyButton: cols += 1 subSizer = wx.FlexGridSizer(cols=cols,hgap=2,vgap=2) self.ValidatedControlsList = [] # make list of TextCtrls self.CheckControlsList = [] # make list of CheckBoxes for i,(d,k) in enumerate(zip(dictlst,elemlst)): if i >= len(prelbl): # label before TextCtrl, or put in a blank subSizer.Add((-1,-1)) else: subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i]))) kargs = {} if i < len(minvals): if minvals[i] is not None: kargs['xmin']=minvals[i] if i < len(maxvals): if maxvals[i] is not None: kargs['xmax']=maxvals[i] if i < len(sizevals): if sizevals[i]: kargs['size']=sizevals[i] if CopyButton: if i+1 == len(dictlst): but = (-1,-1) else: import wx.lib.colourselect as wscs # is there a way to test? but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP parent=panel,colour=(255,255,200),size=wx.Size(30,23), style=wx.RAISED_BORDER) but.Bind(wx.EVT_BUTTON, self._OnCopyButton) if 'phoenix' in wx.version(): but.SetToolTip('Press to copy adjacent value to all rows below') else: but.SetToolTipString('Press to copy adjacent value to all rows below') self.ButtonIndex[but] = i subSizer.Add(but) # create the validated TextCrtl, store it and add it to the sizer ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,ASCIIonly=ASCIIonly, **kargs) self.ValidatedControlsList.append(ctrl) subSizer.Add(ctrl) if i < len(postlbl): # label after TextCtrl, or put in a blank subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i]))) else: subSizer.Add((-1,-1)) if i < len(checkdictlst): ch = G2CheckBox(panel,checklabel,checkdictlst[i],checkelemlst[i]) self.CheckControlsList.append(ch) subSizer.Add(ch) else: subSizer.Add((-1,-1)) # finish up ScrolledPanel panel.SetSizer(subSizer) panel.SetAutoLayout(1) panel.SetupScrolling() # 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: panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1])) mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1) # Sizer for OK/Close buttons. N.B. on Close changes are discarded # by restoring the initial values btnsizer = wx.BoxSizer(wx.HORIZONTAL) btnsizer.Add(self.OKbtn) self.OKbtn.Bind(wx.EVT_BUTTON,lambda event: self.EndModal(wx.ID_OK)) btn = wx.Button(self, wx.ID_CLOSE,"Cancel") btn.Bind(wx.EVT_BUTTON,self._onClose) btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) # size out the window. Set it to be enlarged but not made smaller self.SetSizer(mainSizer) mainSizer.Fit(self) self.SetMinSize(self.GetSize()) def _OnCopyButton(self,event): 'Implements the copy down functionality' but = event.GetEventObject() n = self.ButtonIndex.get(but) if n is None: return for i,(d,k,ctrl) in enumerate(zip(self.dictlst,self.elemlst,self.ValidatedControlsList)): if i < n: continue if i == n: val = d[k] continue d[k] = val ctrl.SetValue(val) for i in range(len(self.checkdictlst)): if i < n: continue self.checkdictlst[i][self.checkelemlst[i]] = self.checkdictlst[n][self.checkelemlst[n]] self.CheckControlsList[i].SetValue(self.checkdictlst[i][self.checkelemlst[i]]) def _onClose(self,event): 'Used on Cancel: Restore original values & close the window' for d,i,v in zip(self.dictlst,self.elemlst,self.orig): d[i] = v for i in range(len(self.checkdictlst)): self.checkdictlst[i][self.checkelemlst[i]] = self.StartCheckValues[i] self.EndModal(wx.ID_CANCEL)
[docs] def ControlOKButton(self,setvalue): '''Enable or Disable the OK button for the dialog. Note that this is passed into the ValidatedTxtCtrl for use by validators. :param bool setvalue: if True, all entries in the dialog are checked for validity. if False then the OK button is disabled. ''' if setvalue: # turn button on, do only if all controls show as valid for ctrl in self.ValidatedControlsList: if ctrl.invalid: self.OKbtn.Disable() return else: self.OKbtn.Enable() else: self.OKbtn.Disable()
############################################### 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 ShowScrolledInfo(parent,txt,width=600,height=400,header='Warning info', buttonlist=None): '''Simple code to display possibly extensive error or warning text in a scrolled window. :param wx.Frame parent: parent window for :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 :param list buttonlist: list of button Ids to show. The default is None which places a single "Close" button and returns wx.ID_CANCEL :returns: the wx Id for the selected button ''' 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) txtSizer = wx.BoxSizer(wx.VERTICAL) txt = wx.StaticText(spanel,wx.ID_ANY,txt) txt.Wrap(600) txt.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) txtSizer.Add(txt,1,wx.ALL|wx.EXPAND,1) spanel.SetSizer(txtSizer) btnsizer = wx.BoxSizer(wx.HORIZONTAL) if buttonlist is None: btn = wx.Button(dlg, wx.ID_CLOSE) btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) else: for b in buttonlist: btn = wx.Button(dlg, b) btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(event.Id)) 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() ans = dlg.ShowModal() dlg.Destroy() return ans
[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]def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300)): '''Display a scrolled table of information in a dialog window :param wx.Frame G2frame: parent for dialog :param str lbl: label for window :param str title: window title :param list tbl: list of lists where inner list is each row :param list colLbls: list of str with labels for each column :param list colTypes: Data types for each column (such as wg.GRID_VALUE_STRING,wg.GRID_VALUE_FLOAT) :param list maxSize: Maximum size for the table in points. Defaults to (600,300) Example:: row = ['item1',1.234,'description of item'] colTypes = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_FLOAT+':8,4',wg.GRID_VALUE_STRING] colLbls = ['item name','value','Description'] G2ScrolledGrid(frm,'window label','title',20*[row],colLbls,colTypes) ''' dlg = wx.Dialog(G2frame,title=title,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(wx.StaticText(dlg,label=lbl), 0,wx.ALIGN_CENTER_HORIZONTAL,0) sizer.Add((-1,15)) rowlbl = [str(i+1) for i in range(len(tbl))] wxtbl = Table(tbl,rowLabels=rowlbl,colLabels=colLbls,types=colTypes) scrGrid = wx.ScrolledWindow(dlg) wxGrid = GSGrid(scrGrid) wxGrid.SetTable(wxtbl, True) wxGrid.AutoSizeColumns(False) wxGrid.EnableEditing(False) gridSizer = wx.BoxSizer(wx.VERTICAL) gridSizer.Add(wxGrid,1,wx.EXPAND,1) gridSizer.Layout() Size = gridSizer.GetMinSize() Size[0] = min(Size[0]+25,maxSize[0]) Size[1] = min(Size[1]+25,maxSize[1]) scrGrid.SetSizer(gridSizer) scrGrid.SetMinSize(Size) scrGrid.SetScrollbars(10,10,int(Size[0]/10-4),int(Size[1]/10-1)) scrGrid.Scroll(0,0) sizer.Add(scrGrid,1,wx.EXPAND,1) btnsizer = wx.BoxSizer(wx.HORIZONTAL) btnsizer.Add((-1,-1),1,wx.EXPAND,1) btn = wx.Button(dlg, wx.ID_OK) btn.SetDefault() btn.Bind(wx.EVT_BUTTON, lambda x: dlg.EndModal(wx.ID_OK)) btnsizer.Add(btn) btnsizer.Add((-1,-1),1,wx.EXPAND,1) sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5) sizer.Layout() dlg.SetSizer(sizer) sizer.Fit(dlg) 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] def ControlOKButton(self,setvalue): '''Enable or Disable the OK button for the dialog. Note that this is passed into the ValidatedTxtCtrl for use by validators. :param bool setvalue: if True, all entries in the dialog are checked for validity. if False then the OK button is disabled. ''' if setvalue: # turn button on, do only if all controls show as valid self.OKbtn.Enable() else: self.OKbtn.Disable()
[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