"""Classes and functions to facilitate general wx actions."""
from abc import ABCMeta, abstractmethod
import textwrap
import sys
import wx
from wx.lib.wordwrap import wordwrap as wxwrap
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
from wx.lib.mixins.listctrl import TextEditMixin
from src.gui import images as img
_LIST_BOX_OPTS = (wx.LC_ALIGN_LEFT | wx.LC_REPORT | wx.LC_NO_HEADER |
wx.LC_SINGLE_SEL)
_MIN_WIDTH = 500
#===============================================================================
#
# CUSTOM CONTROLS
#
#===============================================================================
class ListBox(wx.ListCtrl, ListCtrlAutoWidthMixin):
"""A simple, single-column list control.
This class is basically a wxListBox, except that it process more events.
Parameters
----------
parent : wxWindow
The panel or frame which contains this control.
wxId : int
The ID of the control.
items : list of str
The initial contents of the control.
"""
def __init__(self, parent, wxId=wx.ID_ANY, items=None,
style=_LIST_BOX_OPTS):
super(ListBox, self).__init__(parent, wxId, style=style)
ListCtrlAutoWidthMixin.__init__(self)
if items is not None:
self.setItems(items)
self.InsertColumn(0, '')
def setItems(self, items):
"""Set the items in the list.
Parameters
----------
items : list of str
The strings which should make up the list.
"""
self.DeleteAllItems()
for index, item in enumerate(items):
self.InsertStringItem(index, item)
def setSelection(self, index):
"""Select an item.
Parameters
----------
index : int
The index of the row to select.
"""
self.Focus(index)
self.Select(index)
def getSelection(self):
"""Get the index of the selected item.
Returns
-------
int
The index of the selected item.
"""
return self.GetFocusedItem()
class StaticListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
"""An auto-sizing table.
Parameters
----------
parent : wxWindow
The panel or frame which contains this control.
wxId : int
The ID of the control.
headers : list of str
A list of strings to serve as the headers for the columns (the length
of the list determines the number of columns). If `None` (the default),
the table will contain a single, unlabeled column.
listFormat : int or list of int
Flags to indicate the formatting of the columns. If the input is a
list, each element will be applied to a different column.
"""
def __init__(self, parent, wxId=wx.ID_ANY, headers=None,
listFormat=wx.LIST_FORMAT_RIGHT):
wx.ListCtrl.__init__(self, parent, wxId,
style=wx.LC_REPORT|wx.BORDER_SIMPLE)
ListCtrlAutoWidthMixin.__init__(self)
if headers is None:
self.__headers = ['']
else:
self.__headers = headers
self.__widthFactor = 0.97/len(self.__headers)
if isinstance(listFormat, list):
self.__format = listFormat
else:
self.__format = [listFormat]*len(self.__headers)
self._createColumns()
self.InsertStringItem(0, '')
self.Bind(wx.EVT_SIZE, self._onSize)
def _createColumns(self):
"""Create the column headers."""
for index, info in enumerate(zip(self.__headers, self.__format)):
title, columnFormat = info
self.InsertColumn(index+1, title, columnFormat)
def setValues(self, data):
"""Set the data stored in the table.
Parameters
----------
data : list of tuple of str
A list of tuples of strings. Each tuple gets a row,
and each string goes into its own column within the row.
"""
self.ClearAll()
self._createColumns()
for row in data:
self.Append(row)
def getData(self):
"""Get the data stored in the table.
Returns
-------
list of tuple of str
A list of tuples of strings. Each tuple represents a single row.
"""
dat = []
for row in xrange(self.GetItemCount()):
currentRow = []
for col in xrange(len(self.__headers)):
currentRow.append(self.GetItem(row, col).GetText())
dat.append(tuple(currentRow))
return dat
def moveRowUp(self, index):
"""Move the indexed row up one position.
Parameters
----------
index : int
The index of the row to move.
"""
if index <= 0:
return
movingData = self.removeRow(index)
self.InsertStringItem(index-1, movingData[0])
for columnIndex, item in enumerate(movingData[1:]):
self.SetStringItem(index-1, columnIndex+1, item)
def moveRowDown(self, index):
"""Move the indicated row down one position.
Parameters
----------
index : int
The position of the row which should be moved down.
"""
numItems = self.GetItemCount()
if not 0 <= index < numItems - 1:
return
movingData = self.removeRow(index)
self.InsertStringItem(index+1, movingData[0])
for columnIndex, item in enumerate(movingData[1:]):
self.SetStringItem(index+1, columnIndex+1, item)
def addRow(self, newData):
"""Add a new item to the end of the table.
Parameters
----------
newData : tuple of str
A tuple with one element for each column to be added to the end of
the list.
Returns
-------
int
The number of rows after the addition has taken place.
"""
self.Append(newData)
return self.GetItemCount()
def insertRow(self, index, newData):
"""Insert an item at the specified index.
Parameters
----------
index : int
The position at which to insert the new row.
newData : tuple of str
The row data to insert into the table.
Returns
-------
int
The number of rows after the addition has taken place.
"""
self.InsertStringItem(index, newData[0])
for column, item in enumerate(newData[1:]):
self.SetStringItem(index, column + 1, item)
return self.GetItemCount()
def removeRow(self, index):
"""Remove the selected row from the table.
Parameters
----------
index
The position of the row to remove.
Returns
-------
list of str
The data from the row which was removed.
"""
rowData = []
for columnIndex in xrange(len(self.__headers)):
rowData.append(self.GetItem(index, columnIndex).GetText())
self.DeleteItem(index)
return rowData
def getRowData(self, index):
"""Get the data from one row of the table.
Parameters
----------
index : int
The index of the row whose data should be returned.
Returns
-------
list of str
A list of strings containing the data from the specified row.
"""
output = []
for col in xrange(self.GetColumnCount()):
output.append(self.GetItem(index, col).GetText())
return output
def _onSize(self, event):
"""On resize events, automatically adjust all column widths."""
colwidth = event.GetSize()[0]*self.__widthFactor
for index in xrange(len(self.__headers)):
self.SetColumnWidth(index, colwidth)
event.Skip()
class ListCtrl(StaticListCtrl, TextEditMixin):
"""An editable, auto-sizing table.
Parameters
----------
parent : wxWindow
The panel or frame which contains this control.
wxId : int
The ID of the control.
headers : list of str
A list of strings to serve as the headers for the columns (the length
of the list determines the number of columns). If `None` (the default),
the table will contain a single, unlabeled column.
listFormat : int
Flags to indicate the formatting of the columns.
"""
def __init__(self, parent, wxId=wx.ID_ANY, headers=None,
listFormat=wx.LIST_FORMAT_RIGHT):
StaticListCtrl.__init__(self, parent, wxId, headers, listFormat)
TextEditMixin.__init__(self)
[docs]class KeyValListCtrl(StaticListCtrl):
"""A list control for managing typed key-value pairs.
Parameters
----------
parent : wxWindow
The panel or frame which contains this control.
"""
def __init__(self, parent):
StaticListCtrl.__init__(self, parent, wx.ID_ANY,
headers=['Name', 'Default value', 'Type'],
style=wx.LIST_FORMAT_LEFT)
#===============================================================================
#
# ABSTRACT BASE DIALOG
#
#===============================================================================
[docs]class BaseDialog(wx.Dialog):
"""A base class to be extended by dialogs.
Note: it is important to call this class's `finish` method after all
components have been added.
Parameters
----------
parent : wxWindow
The panel or frame which spawns this dialog.
wxId : int
The wxPython identification number for this dialog.
title : str
The title to display in the dialog's title bar.
info : tuple of str
A tuple of strings. The first is the title of the info bar, and the
second is the text of the info bar. The info bar will not be displayed
if `infoBar` is `None`.
"""
__metaclass__ = ABCMeta
def __init__(self, parent, wxId=wx.ID_ANY, title='Dialog', info=None,
minWidth=None):
super(BaseDialog, self).__init__(parent, wxId)
self.SetTitle(title)
self._info = info
if minWidth is None:
self.minWidth = _MIN_WIDTH
else:
self.minWidth = minWidth
self.__sizer = wx.BoxSizer(wx.VERTICAL)
if self._info is not None:
self.__infoBar = wx.StaticText(self)
self.__sizer.Add(self.__infoBar, 0, wx.EXPAND|wx.ALL, 5)
self.__sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
[docs] def setPanel(self, panel, proportion=1, flags=wx.EXPAND|wx.ALL, border=5):
"""Finish laying out the panel."""
self.__sizer.Add(panel, proportion, flags, border)
buttonpanel = wx.Panel(self, wx.ID_ANY)
buttonsizer = wx.BoxSizer(wx.HORIZONTAL)
buttonpanel.SetSizer(buttonsizer)
okButton = wx.Button(buttonpanel, wx.ID_OK, 'OK')
cancelButton = wx.Button(buttonpanel, wx.ID_CANCEL, 'Cancel')
buttonsizer.Add(cancelButton, 0, wx.ALL, 2)
buttonsizer.Add(okButton, 0, wx.ALL, 2)
okButton.Bind(wx.EVT_BUTTON, self._onClose)
cancelButton.Bind(wx.EVT_BUTTON, self._onClose)
self.Bind(wx.EVT_CLOSE, self._onClose)
self.__sizer.Add(buttonpanel, 0, wx.ALL|wx.ALIGN_RIGHT, 4)
self.SetSizeHints(self.minWidth, -1)
self.SetSizer(self.__sizer)
self.Layout()
self.Fit()
if self._info is not None:
newText = wxwrap(self._info[1], self.GetSizeTuple()[0]-5,
wx.ClientDC(self.__infoBar), True)
self.__infoBar.SetLabel(newText)
self.Fit()
@abstractmethod
[docs] def update(self):
"""Update whatever objects this dialog is supposed to modify.
Returns
-------
bool
Whether the attempt to update the appropriate objects was
successful.
"""
return True
def _onClose(self, event):
"""Update objects, and, if successful, close the dialog."""
result = event.GetId()
if result == wx.ID_OK:
if self.update():
self.EndModal(result)
self.Destroy()
else:
self.EndModal(result)
self.Destroy()
# class InfoBar(wx.Panel):
# """A bar for displaying information about the purpose of the dialog.
#
# Parameters
# ----------
# parent : wxWindow
# The panel or frame which contains this bar.
# title : str
# The title of the information bar.
# string : str
# The information string to display.
# icon : wxIcon
# The icon to display to the left of the string.
# """
#
# leftBorder = 20
# rightBorder = 30
#
# def __init__(self, parent, title, string, minWidth=None):
# super(InfoBar, self).__init__(parent, wx.ID_ANY)
# titleFont = wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_ITALIC,
# wx.FONTWEIGHT_BOLD)
# infoFont = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
# wx.FONTWEIGHT_NORMAL)
# self.info = string
# if minWidth is None:
# self.minWidth = _MIN_WIDTH
# else:
# self.minWidth = minWidth
# self.SetSizeHints(self.minWidth-5, -1, self.minWidth-5, -1)
#
# self.SetBackgroundColour(wx.WHITE)
#
# sizer = wx.BoxSizer(wx.HORIZONTAL)
#
# rightpanel = wx.Panel(self, wx.ID_ANY)
# rightsizer = wx.BoxSizer(wx.VERTICAL)
# rightpanel.SetSizer(rightsizer)
# rightpanel.SetBackgroundColour(wx.WHITE)
#
# titleText = wx.StaticText(rightpanel, wx.ID_ANY, label=title)
# titleText.SetFont(titleFont)
#
# self.infopanel = wx.Panel(rightpanel, wx.ID_ANY)
# infosizer = wx.BoxSizer(wx.HORIZONTAL)
# self.infopanel.SetSizer(infosizer)
# self.infopanel.SetBackgroundColour(wx.WHITE)
#
# self.infoText = StaticWrapText(self.infopanel, label=string)
# infosizer.AddSpacer(10)
# infosizer.Add(self.infoText, 1, wx.EXPAND|wx.ALL, 5)
#
# self.infoText.setFont(infoFont)
#
# rightsizer.AddSpacer(5)
# rightsizer.Add(titleText, 0, wx.EXPAND|wx.LEFT, 5)
# rightsizer.AddSpacer(5)
# rightsizer.Add(self.infopanel, 0, wx.EXPAND|wx.LEFT, InfoBar.leftBorder)
# rightsizer.AddSpacer(5)
#
# sizer.Add(rightpanel, 1, wx.EXPAND)
#
# self.SetSizer(sizer)
# sizer.Fit(self)
#
# class StaticWrapText(wx.PyControl):
# """An auto-word-wrapping text label."""
#
# def __init__(self, parent, wxId=wx.ID_ANY, label='', pos=wx.DefaultPosition,
# size=wx.DefaultSize, style=wx.NO_BORDER):
# super(StaticWrapText, self).__init__(parent, wxId, pos, size, style)
# self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
# self.wraplabel = label
# self.SetBackgroundColour(wx.WHITE)
#
# def wrap(self):
# """Wrap the label text based on the size of the control."""
# self.Freeze()
# self.statictext.SetLabel(self.wraplabel)
# self.statictext.Wrap(self.GetSize().width)
# self.Thaw()
#
# # pylint: disable=W0221
# def DoGetBestSize(self):
# """Get the best size."""
# self.wrap()
# self.SetSize(self.statictext.GetSize())
# return self.GetSize()
# # pylint: enable=W0221
#
# def setFont(self, newFont):
# """Set the font of the label."""
# self.statictext.SetFont(newFont)
#===============================================================================
#
# SPECIAL PANELS
#
#===============================================================================
# General-purpose panel --------------------------------------------------------
[docs]class Panel(wx.Panel):
"""A panel subclass which builds in sizer support.
Parameter
---------
parent : wxWindow
The panel or frame which contains this panel.
sizerType : str
The type of the sizer for the panel. It may be 'vertical' (the default)
or 'horizontal', in which case a wxBoxSizer will be used, or 'flex_grid'
or 'grid'.
label : str
The string to go in the panel's static box frame. If `None`, no
static box will be displayed.
"""
labelOptions = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL
def __init__(self, parent, sizerType='vertical', label=None,
*args, **kwargs):
"""Create a new panel."""
super(Panel, self).__init__(parent, wx.ID_ANY)
if 'extraBorder' in kwargs:
extraBorder = kwargs.pop('extraBorder')
else:
extraBorder = 0
if 'panelStyle' in kwargs:
panelStyle = kwargs.pop('panelStyle')
else:
panelStyle = 0
if 'scrolling' in kwargs:
scrolling = kwargs.pop('scrolling')
self.panel = wx.ScrolledWindow(self, wx.ID_ANY, style=panelStyle)
if scrolling:
self.panel.SetScrollbars(1, 1, 1, 1)
else:
self.panel = wx.Panel(self, wx.ID_ANY, style=panelStyle)
self.sizer = self.__setSizer(sizerType, *args, **kwargs)
if label is None:
mainsizer = wx.BoxSizer(wx.VERTICAL)
mainsizer.Add(self.panel, 1, wx.EXPAND | wx.ALL, extraBorder)
self.SetSizer(mainsizer)
else:
self.staticpanel = wx.StaticBox(self, wx.ID_ANY, label)
staticsizer = wx.StaticBoxSizer(self.staticpanel, wx.VERTICAL)
staticsizer.Add(self.panel, 1, wx.EXPAND | wx.ALL, extraBorder)
self.SetSizer(staticsizer)
def __setSizer(self, sizerType, *args, **kwargs):
"""Create the appropriate sizer
Parameters
----------
sizerType : str
The type of the sizer for the panel. It may be 'vertical' (the
default) or 'horizontal', in which case a wxBoxSizer will be used,
or 'flex_grid' or 'grid', which are self-explanatory.
"""
if sizerType == 'horizontal':
sizer = wx.BoxSizer(wx.HORIZONTAL, *args, **kwargs)
elif sizerType == 'flex_grid':
sizer = wx.FlexGridSizer(*args, **kwargs)
elif sizerType == 'grid':
sizer = wx.GridSizer(*args, **kwargs)
else:
sizer = wx.BoxSizer(wx.VERTICAL, *args, **kwargs)
self.panel.SetSizer(sizer)
return sizer
[docs] def setLabel(self, newLabel):
"""Set the label for the border.
Parameters
----------
newLabel : str
The new label for the panel's border.
"""
try:
self.staticpanel.SetLabel(newLabel)
except AttributeError:
pass
[docs] def add(self, item, *args, **kwargs):
"""Add an item to the panel's sizer.
All extra arguments are passed to the `sizer.Add` method.
Parameters
----------
item : wxWindow
The control to add to the panel.
"""
item.Reparent(self.panel)
self.sizer.Add(item, *args, **kwargs)
[docs] def addLabel(self, text, border=3, flag=None):
"""Add a text label to the control using default options.
Parameters
----------
text : str
The text of the label to add.
border : int
The width in pixels of the border which will surround the new
text label.
Returns
-------
wxStaticText
The added text label.
"""
if flag is None:
flag = Panel.labelOptions
staticText = wx.StaticText(self.panel, wx.ID_ANY, label=text)
self.sizer.Add(staticText, 0, flag, border)
return staticText
[docs] def addLabeledText(self, label, initialValue=None, border=0, style=0,
defocusHandler=None):
"""Add a label and a text control.
Parameters
----------
label : str
The text to label the text control.
initialValue : str
The initial value of the text control.
border : int
The width in pixels of the border which will surround both the
new label and the new text control.
style : int
The style (in terms of `wx` constants) of the text control.
defocusHandler : method
The method to execute when the text control loses focus.
Returns
-------
wxTextCtrl
The text control which has been created and added to the panel.
"""
staticText = wx.StaticText(self.panel, wx.ID_ANY, label)
textCtrl = wx.TextCtrl(self.panel, wx.ID_ANY, initialValue,
style=style)
if defocusHandler is not None:
textCtrl.Bind(wx.EVT_KILL_FOCUS, defocusHandler)
self.sizer.Add(staticText, 0, Panel.labelOptions, border)
self.sizer.Add(textCtrl, 1, wx.EXPAND | wx.ALL, border)
return textCtrl
[docs] def addLabeledComboBox(self, label, initialValue='', choices=None, border=0,
style=0, defocusHandler=None, valueHandler=None,
proportion=1):
"""Add a label and a text control.
Parameters
----------
label : str
The text to label the combo box.
initialValue : str
The initial value of the combo box.
choices : list of str
The accepted values for the combo box.
border : int
The width in pixels of the border which will surround both the
new label and the new combo box.
style : int
The style (in terms of `wx` constants) of the combo box.
defocusHandler : method
The method to execute when the combo box loses focus.
valueHandler : method
The method to execute when the selection of the combo box changes.
proportion : int
The weighting factor for sizing the combo box.
"""
if choices is None:
choices = []
staticText = wx.StaticText(self.panel, wx.ID_ANY, label)
comboBox = wx.ComboBox(self.panel, wx.ID_ANY, initialValue,
choices=choices, style=style)
if defocusHandler is not None:
comboBox.Bind(wx.EVT_KILL_FOCUS, defocusHandler)
if valueHandler is not None:
comboBox.Bind(wx.EVT_COMBOBOX, valueHandler)
self.add(staticText, 0, Panel.labelOptions, border)
self.add(comboBox, proportion, wx.EXPAND | wx.ALL, border)
return comboBox
[docs] def addLabeledMultiCtrl(self, label, initialValue=None, allowed=None,
border=0, style=0, defocusHandler=None):
"""Add a label and text controls to the panel.
The number of controls to be added will be determined by
`initialValue`. There will be one item for each value of `initialValue`,
If the value of `allowed` at the same position as a value of
`initialValue` is a list, the created control will be a `wxComboBox`
with the listed choices available.
Parameters
----------
label : str
The text to label the text control.
initialValue : list of str
The initial values of the text controls.
allowed : list of list of str
A list of lists of strings. Each element in the outer list
corresponds to a single control. The strings in the inner
list correspond to the allowed values for the control. If
any element of the outer list is `None`, that list item will
create a `wxTextCtrl` instead of a `wxComboBox`.
border : int
The width in pixels of the border which will surround both the
new label and the new text control.
style : int
The style (in terms of `wx` constants) of the text control.
defocusHandler : method or list of method
The method to execute when the text control loses focus. If it is
a list, the handlers will be applied sequentially to the controls
created for each element in `initialValue`. If it is a single
method, the same method will be bound to all controls.
Returns
-------
list of (wxTextCtrl or wxComboBox)
The list of text controls and combo box controls which have been
created and added to the panel.
"""
staticText = wx.StaticText(self.panel, wx.ID_ANY, label)
if not isinstance(defocusHandler, list):
defocusHandler = [defocusHandler]*len(initialValue)
if not isinstance(allowed, list):
allowed = [allowed]*len(initialValue)
self.sizer.Add(staticText, 0, Panel.labelOptions, border)
ctrls = []
for initial, handler, choices in zip(initialValue, defocusHandler,
allowed):
if choices is not None:
ctrl = wx.ComboBox(self.panel, wx.ID_ANY, initial,
choices=choices, style=style)
else:
ctrl = wx.TextCtrl(self.panel, wx.ID_ANY, initial,
style=style)
ctrls.append(ctrl)
if handler is not None:
ctrl.Bind(wx.EVT_KILL_FOCUS, handler)
self.sizer.Add(ctrl, 1, wx.EXPAND | wx.ALL, border)
return ctrls
[docs] def addGrowableColumn(self, index, proportion=1):
"""Make a column growable in a flexible sizer.
Parameters
----------
index : int
The index of the column to allow to expand.
proportion : int
The weighting factor for expansion.
"""
try:
function = getattr(self.sizer, 'AddGrowableCol')
function(index, proportion)
except AttributeError:
pass
[docs] def addStretchSpacer(self, proportion):
"""Add a stretchable spacer to the panel.
Parameters
----------
proportion : int
The weighting factor for expansion.
"""
self.sizer.AddStretchSpacer(proportion)
# Scan configuration panel -----------------------------------------------------
[docs]class ScanPanel(wx.Panel):
"""A scan panel with extra options.
Parameters
----------
parent : wxWindow
The panel or frame which contains this panel.
wxId : int
The ID of this panel.
initialData : list of tuple of float
The data the table should contain initially. Each tuple is one row
and should be of the form (initial point, final point, step size),
where all are in the same units.
formatString : str
The string used to format the floats into appropriate strings.
buttonIcons : bool
Whether to use icons for the editing buttons. If `False`, text will
be used instead.
label : str
The label to surround the panel. If `None`, the panel will not have
either a label or a border.
"""
def __init__(self, parent, wxId=wx.ID_ANY, initialData=None,
formatString='%.3f', buttonIcons=True, label=None):
super(ScanPanel, self).__init__(parent, wxId)
self.__formatString = formatString
if label is not None:
staticbox = wx.StaticBox(self, wx.ID_ANY, label)
mainsizer = wx.StaticBoxSizer(staticbox, wx.HORIZONTAL)
else:
mainsizer = wx.BoxSizer(wx.HORIZONTAL)
listpanel = wx.Panel(self, wx.ID_ANY)
listsizer = wx.BoxSizer(wx.VERTICAL)
listpanel.SetSizer(listsizer)
self.__list = ListCtrl(listpanel, wx.ID_ANY,
headers=('Initial', 'Final', 'Step'))
listsizer.Add(self.__list, 1, wx.EXPAND|wx.ALL)
if initialData is not None:
self.setData(initialData)
navpanel = wx.Panel(self, wx.ID_ANY)
navsizer = wx.BoxSizer(wx.VERTICAL)
navpanel.SetSizer(navsizer)
if buttonIcons:
self.navUp = createButton(navpanel, navsizer, wx.ID_ANY,
img.getScanUpBitmap(),
tooltip='Move up',
handler=self.onMoveUp)
self.navDown = createButton(navpanel, navsizer, wx.ID_ANY,
img.getScanDownBitmap(),
tooltip='Move down',
handler=self.onMoveDown)
self.navAdd = createButton(navpanel, navsizer, wx.ID_ANY,
img.getScanAddBitmap(),
tooltip='Add item',
handler=self.onAdd)
self.navInsert = createButton(navpanel, navsizer, wx.ID_ANY,
img.getScanInsertBitmap(),
tooltip='Insert item',
handler=self.onInsert)
self.navRemove = createButton(navpanel, navsizer, wx.ID_ANY,
img.getScanRemoveBitmap(),
tooltip='Remove item',
handler=self.onRemove)
else:
self.navUp = createButton(navpanel, navsizer, wx.ID_ANY,
label='Up',
tooltip='Move up',
handler=self.onMoveUp)
self.navDown = createButton(navpanel, navsizer, wx.ID_ANY,
label='Down',
tooltip='Move down',
handler=self.onMoveDown)
self.navAdd = createButton(navpanel, navsizer, wx.ID_ANY,
label='Add',
tooltip='Add item',
handler=self.onAdd)
self.navInsert = createButton(navpanel, navsizer, wx.ID_ANY,
label='Insert',
tooltip='Insert item',
handler=self.onInsert)
self.navRemove = createButton(navpanel, navsizer, wx.ID_ANY,
label='Remove',
tooltip='Remove item',
handler=self.onRemove)
mainsizer.Add(listpanel, 1, wx.ALL|wx.EXPAND, 3)
mainsizer.Add(navpanel, 0, wx.ALL, 3)
if self.__formatString is not None and self.__formatString != '%s':
self.__list.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._verifyData)
self.SetSizerAndFit(mainsizer)
[docs] def onMoveUp(self, event):
"""Move the currently selected item up."""
cpos = self.__list.GetFocusedItem()
if cpos <= 0:
return
self.__list.Select(cpos, False)
self.__list.moveRowUp(cpos)
self.__list.Select(cpos - 1)
self.__list.Focus(cpos - 1)
[docs] def onMoveDown(self, event):
"""Move the selected item down."""
cpos = self.__list.GetFocusedItem()
if not 0 <= cpos < self.__list.GetItemCount() - 1:
return
self.__list.Select(cpos, False)
self.__list.moveRowDown(cpos)
self.__list.Select(cpos + 1)
self.__list.Focus(cpos + 1)
[docs] def onAdd(self, event):
"""Add a new item."""
self.__list.Select(self.__list.GetFocusedItem(), False)
numItems = self.__list.GetItemCount()
if numItems > 0:
previousData = self.getRowData(numItems-1)
else:
tempData = []
for _ in xrange(self.__list.GetColumnCount()):
tempData.append(0)
previousData = (0, 0, 0)
newData = list(previousData)
newData[0] = previousData[1]
newData[1] = 0
self.__list.addRow(self._formatData([newData])[0])
self.__list.Select(numItems)
self.__list.Focus(numItems)
[docs] def onInsert(self, event):
"""Insert an item at the current position"""
cpos = self.__list.GetFocusedItem()
self.__list.Select(cpos, False)
if cpos > 0:
previousData = self.getRowData(cpos-1)
else:
previousData = (0, 0, 0)
newData = list(previousData)
newData[0] = previousData[1]
newData[1] = 0
self.__list.insertRow(cpos, self._formatData([newData])[0])
self.__list.Focus(cpos)
self.__list.Select(cpos)
[docs] def onRemove(self, event):
"""Remove the selected item."""
cpos = self.__list.GetFocusedItem()
if cpos >= 0:
self.__list.Select(cpos, False)
self.__list.removeRow(cpos)
if self.__list.GetItemCount() > 0:
self.__list.Select(0, True)
[docs] def getRowData(self, index):
"""Get the data from one row of the table.
Parameters
----------
index : int
The index of the row whose data should be returned.
Returns
-------
list of str
A list of strings containing the data from the specified row.
"""
data = self.__list.getRowData(index)
return self._formatData([data])[0]
def _formatData(self, data):
"""Format the data using the format string.
Parameters
----------
data : list of tuple
The data to go into the table. Each tuple represents a single
row. The data type of the elements may be `str`, `int`, or `float`,
but it must be castable to `float`.
Returns
-------
list of tuple of str
The same data as was entered, but formatted according to the
specified format string.
"""
if self.__formatString is not None and self.__formatString != '%s':
formattedData = []
for row in data:
currentRow = []
for col in row:
try:
newValue = self.__formatString % float(col)
except (ValueError, TypeError):
newValue = self.__formatString % 0.0
currentRow.append(newValue)
formattedData.append(tuple(currentRow))
return formattedData
return data
[docs] def setData(self, data, formatData=True):
"""Set the data values in the table control.
Parameters
----------
data : list of tuple
A list of tuples, where each tuple contains the data for one row.
The data type of the elements in the tuples can be `str`, `int`, or
`float`, as long as it can be cast to `float`.
formatData : bool
Whether to format the data before passing it to the table control.
"""
if formatData:
data = self._formatData(data)
self.__list.setValues(data)
[docs] def getData(self, formatData=False):
"""Get the data values from the table control.
Parameters
----------
formatData : bool
Whether to format the data before returning it from the table.
Returns
-------
list of tuple of str
The data from the list control. Each tuple represents one row.
"""
if formatData:
return self._formatData(self.__list.getData())
return self.__list.getData()
def _verifyData(self, event):
"""Make sure all inputs are floats."""
self.formatData()
self.Layout()
#===============================================================================
#
# MULTIPURPOSE CONTROL CONSTRUCTORS
#
#===============================================================================
[docs]def createLabeledTextControl(parent, sizer, wxId=wx.ID_ANY, label='Input:',
initialValue='', border=0, style=0,
defocusHandler=None):
"""Create a label and a text control and add them to the sizer.
Parameters
----------
parent : wxWindow
The panel or frame which should serve as the parent for the label
and control.
sizer : wxSizer
The sizer to which the label and text control should be added. Usually,
it should be a wxFlexGridSizer or wxGridSizer.
wxId : int
The ID for the text control (default: wx.ID_ANY).
label : str
The text that goes in the label (default: "Input:").
initialValue : str
The initial value for the control (default: "").
border : int
The border thickness in pixels (default: 0). The same border is applied
around both the label and the control.
style : int
The style (normally, use wx constants) to use for the text control
(default: 0).
defocusHandler : method
A method to be called when the text control loses focus (default:
`None`).
Returns
-------
wxTextCtrl
The text control created by this function.
"""
staticText = wx.StaticText(parent, wx.ID_ANY, label=label)
textControl = wx.TextCtrl(parent, wxId, value=initialValue, style=style)
sizer.Add(staticText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL,
border)
sizer.Add(textControl, 1, wx.EXPAND|wx.ALL, border)
if defocusHandler is not None:
textControl.Bind(wx.EVT_KILL_FOCUS, defocusHandler)
return textControl
[docs]def createLabeledComboBox(parent, sizer, wxId=wx.ID_ANY, label='Input:',
initialValue='', choices=None, border=0,
style=wx.CB_DROPDOWN, defocusHandler=None,
valueHandler=None):
"""Create a label and a combo box and add them to the sizer.
Parameters
----------
parent : wxWindow
The panel or frame which should serve as the parent for the label
and control.
sizer : wxSizer
The sizer to which the label and combo box should be added. Usually,
it should be a wxFlexGridSizer or wxGridSizer.
wxId : int
The ID for the combo box (default: wx.ID_ANY).
label : str
The text that goes in the label (default: "Input:").
initialValue : str
The initial value for the control (default: "").
choices : list of str
A list of strings representing the values the combo box offers. If
`None` (the default), no options will be available initially. (If
`None`, make sure that `style` is not `wx.CB_READONLY`, or the combo
box will be entirely useless.)
border : int
The border thickness in pixels (default: 0). The same border is applied
around both the label and the control.
style : int
The style (normally, use wx constants) to use for the combo box
(default: `wx.CB_DROPDOWN`; the other typical value is
`wx.CB_READONLY`).
defocusHandler : method
A method to be called when the text control loses focus (default:
`None`).
valueHandler : method
A method to be called when the value selection in the combo box
changes (default: `None`)
Returns
-------
wxComboBox
The combo box created by this function.
"""
if choices is None:
choices = []
staticText = wx.StaticText(parent, wx.ID_ANY, label=label)
comboBox = wx.ComboBox(parent, wxId, initialValue,
choices=choices, style=style)
sizer.Add(staticText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL,
border)
sizer.Add(comboBox, 1, wx.EXPAND|wx.ALL, border)
if defocusHandler is not None:
comboBox.Bind(wx.EVT_KILL_FOCUS, defocusHandler)
if valueHandler is not None:
comboBox.Bind(wx.EVT_COMBOBOX, valueHandler)
return comboBox
[docs]class ErrorDialog(wx.Dialog):
"""A dialog for displaying general sequence errors.
Parameters
----------
parent : wxWindow
The panel or frame which spawns this dialog.
error : GeneralExperimentError
The exception whose contents should be displayed in the dialog.
"""
def __init__(self, parent, error):
super(ErrorDialog, self).__init__(parent)
self.error = error
imageList = wx.ImageList(16, 16, True)
iconError = imageList.Add(wx.ArtProvider.GetBitmap(wx.ART_ERROR,
size=(16, 16)))
iconWarning = imageList.Add(wx.ArtProvider.GetBitmap(wx.ART_WARNING,
size=(16, 16)))
self.list = ListBox(self, wx.ID_ANY, style=wx.LC_REPORT|wx.LC_NO_HEADER|
wx.BORDER_SIMPLE)
self.list.AssignImageList(imageList, wx.IMAGE_LIST_SMALL)
self.list.InsertColumn(0, '')
self.list.Bind(wx.EVT_LIST_ITEM_FOCUSED, self._onSelection)
for kind, text in error.items:
if kind == 'error':
imageIndex = iconError
else:
imageIndex = iconWarning
index = self.list.InsertStringItem(sys.maxint, text)
self.list.SetItemImage(index, imageIndex, imageIndex)
buttonpanel = wx.Panel(self)
buttonsizer = wx.BoxSizer(wx.HORIZONTAL)
buttonpanel.SetSizer(buttonsizer)
self.buttonCancel = wx.Button(buttonpanel, wx.ID_CANCEL, label='Cancel')
self.buttonCancel.Bind(wx.EVT_BUTTON, self._onClose)
self.buttonProceed = wx.Button(buttonpanel, wx.ID_OK, label='Proceed')
self.buttonProceed.Bind(wx.EVT_BUTTON, self._onClose)
if error.errorCount > 0:
self.buttonProceed.Enable(False)
label = wx.StaticText(self, label='There are errors in the ' +
'sequence. Cannot proceed.')
self.SetIcon(wx.ArtProvider.GetIcon(wx.ART_ERROR, size=(16, 16)))
else:
label = wx.StaticText(self, label='There are problems with the ' +
'sequence. Are you sure you want to ' +
'proceed?')
self.SetIcon(wx.ArtProvider.GetIcon(wx.ART_WARNING, size=(16, 16)))
self.SetTitle('Sequence Errors Detected')
self.infobox = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.infobox.SetEditable(False)
buttonsizer.Add(self.buttonCancel, 0, wx.ALL, 2)
buttonsizer.Add(self.buttonProceed, 0, wx.ALL, 2)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(label, 0, wx.ALIGN_LEFT|wx.ALL, 5)
sizer.Add(self.list, 2, wx.ALL|wx.EXPAND, 5)
sizer.Add(self.infobox, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(buttonpanel, 0, wx.ALL|wx.ALIGN_RIGHT, 5)
self.Bind(wx.EVT_CLOSE, self._onClose)
sizer.SetMinSize((400, 300))
self.SetSizer(sizer)
self.Layout()
self.Fit()
self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
def _onClose(self, event):
"""Respond to a close event."""
buttonId = event.GetId()
self.EndModal(buttonId)
self.Destroy()
def _onSelection(self, event):
"""Respond to selection of items."""
index = self.list.getSelection()
message = self.error.items[index][1]
self.infobox.SetValue(message)
#--------------------------------------------------------------------- Prompters
[docs]class ComboBoxPrompter(object):
"""An object for prompting for user input from a combo box dialog."""
def __init__(self, parent=None, width=60):
self._parent = parent
self._width = width
def prompt(self, prompt, options):
dialog = wx.SingleChoiceDialog(self._parent,
textwrap.fill(prompt, self._width),
'Enter a selection',
options,
style=wx.CHOICEDLG_STYLE&~wx.CANCEL)
dialog.ShowModal()
response = dialog.GetSelection()
dialog.Destroy()
return response