# -*- coding: utf-8 -*- | |
import copy | |
import sys | |
import datetime | |
import locale | |
import time | |
import operator | |
import re | |
import warnings | |
from decimal import Decimal | |
from decimal import InvalidOperation | |
import wx | |
import wx.grid | |
from wx._core import PyAssertionError | |
import dabo | |
from dabo.ui import makeDynamicProperty | |
if __name__ == "__main__": | |
dabo.ui.loadUI("wx") | |
import dabo.dEvents as dEvents | |
import dabo.dException as dException | |
from dabo.dLocalize import _, n_ | |
from dabo.lib.utils import ustr | |
import dControlMixin as cm | |
import dKeys | |
import dUICursors | |
import dabo.biz | |
import dabo.dColors as dColors | |
from dabo.dObject import dObject | |
import dabo.lib.dates | |
from dabo.lib.utils import noneSortKey, caseInsensitiveSortKey | |
from dabo.dBug import loggit | |
# Make this locale-independent | |
# JK: We can‘t set this up on module load because locale | |
# is set not until dApp is completely setup. | |
decimalPoint = None | |
class dGridDataTable(wx.grid.PyGridTableBase): | |
def __init__(self, parent): | |
super(dGridDataTable, self).__init__() | |
self._clearCache() | |
self.grid = parent | |
self._initTable() | |
def _clearCache(self): | |
self.__cachedVals = {} | |
self.__cachedAttrs = {} | |
def _initTable(self): | |
self.colDefs = [] | |
self._oldRowCount = 0 | |
self.grid.setTableAttributes(self) | |
def GetAttr(self, row, col, kind=0): | |
col = self._convertWxColNumToDaboColNum(col) | |
if col is None: | |
# Empty grid so far, no biggie: | |
return self.grid._defaultGridColAttr.Clone() | |
cv = self.__cachedAttrs.get((row, col)) | |
if cv: | |
diff = time.time() - cv[1] | |
if diff < 10: ## if it‘s been less than this # of seconds. | |
return cv[0].Clone() | |
dcol = self.grid.Columns[col] | |
dcol._updateCellDynamicProps(row) | |
if dcol._gridCellAttrs: | |
attr = dcol._gridCellAttrs.get(row, dcol._gridColAttr).Clone() | |
else: | |
attr = dcol._gridColAttr.Clone() | |
## Now, override with a custom renderer for this row/col if applicable. | |
## Note that only the renderer is handled here, as we are segfaulting when | |
## handling the editor here. | |
r = dcol.getRendererClassForRow(row) | |
if r is not None: | |
rnd = r() | |
attr.SetRenderer(rnd) | |
if r in (dcol.floatRendererClass, dcol.decimalRendererClass): | |
rnd.SetPrecision(dcol.Precision) | |
# Now check for alternate row coloration | |
if self.alternateRowColoring: | |
attr.SetBackgroundColour((self.rowColorEven, self.rowColorOdd)[row % 2]) | |
# Prevents overwriting when a long cell has None in the one next to it. | |
attr.SetOverflow(False) | |
self.__cachedAttrs[(row, col)] = (attr.Clone(), time.time()) | |
return attr | |
def GetRowLabelValue(self, row): | |
try: | |
return self.grid.RowLabels[row] | |
except IndexError: | |
return "" | |
def GetColLabelValue(self, col): | |
return "" | |
def setColumns(self, colDefs): | |
"""Create columns based on passed list of column definitions.""" | |
if colDefs == self.colDefs: | |
# Already done, no need to take the time. | |
return | |
for idx, col in enumerate(colDefs): | |
nm = col.DataField | |
while not nm: | |
nm = ustr(idx) | |
idx += 1 | |
if nm in colDefs: | |
nm = "" | |
colName = "Column_%s" % nm | |
pos = col._getUserSetting("Order") | |
if pos is not None: | |
col.Order = pos | |
# If the data types are actual types and not strings, convert | |
# them to common strings. | |
if isinstance(col.DataType, type): | |
typeDict = { | |
str : "string", | |
unicode : "unicode", | |
bool : "bool", | |
int : "integer", | |
float : "float", | |
long : "long", | |
datetime.date : "date", | |
datetime.datetime : "datetime", | |
datetime.time : "time", | |
Decimal: "decimal"} | |
try: | |
col.DataType = typeDict[col.DataType] | |
except KeyError: | |
# Not one of the standard types. Extract it from | |
# the string version of the type | |
try: | |
col.DataType = ustr(col.DataType).split("‘")[1].lower() | |
except IndexError: | |
# Something‘s odd. Print an error message and move on. | |
dabo.log.error("Unknown data type found in setColumns(): %s" | |
% col.DataType) | |
col.DataType = ustr(col.DataType) | |
# Make sure that all cols have an Order set | |
for num in range(len(colDefs)): | |
col = colDefs[num] | |
if col.Order < 0: | |
col.Order = num | |
colDefs.sort(self.orderSort) | |
self.colDefs = copy.copy(colDefs) | |
def orderSort(self, col1, col2): | |
return cmp(col1.Order, col2.Order) | |
def convertType(self, typ): | |
""" | |
Convert common types, names and abbreviations for | |
data types into the constants needed by the wx.grid. | |
""" | |
# Default | |
ret = wx.grid.GRID_VALUE_STRING | |
if type(typ) == str: | |
lowtyp = typ.lower() | |
else: | |
lowtyp = typ | |
if typ is Decimal: | |
lowtyp = "decimal" | |
if lowtyp in (bool, "bool", "boolean", "logical", "l"): | |
ret = wx.grid.GRID_VALUE_BOOL | |
if lowtyp in (int, long, "int", "integer", "bigint", "i", "long"): | |
ret = wx.grid.GRID_VALUE_NUMBER | |
elif lowtyp in (str, unicode, "char", "varchar", "text", "c", "s"): | |
ret = wx.grid.GRID_VALUE_STRING | |
elif lowtyp in (float, "float", "f", "decimal"): | |
ret = wx.grid.GRID_VALUE_FLOAT | |
elif lowtyp in (datetime.date, datetime.datetime, datetime.time, | |
"date", "datetime", "time", "d", "t"): | |
ret = wx.grid.GRID_VALUE_DATETIME | |
return ret | |
def CanGetValueAs(self, row, col, typ): | |
col = self._convertWxColNumToDaboColNum(col) | |
if self.grid.useCustomGetValue: | |
return self.grid.customCanGetValueAs(row, col, typ) | |
else: | |
dcol = self.grid.Columns[col] | |
return typ == self.convertType(dcol.DataType) | |
def CanSetValueAs(self, row, col, typ): | |
col = self._convertWxColNumToDaboColNum(col) | |
if self.grid.useCustomSetValue: | |
return self.grid.customCanSetValueAs(row, col, typ) | |
else: | |
dcol = self.grid.Columns[col] | |
return typ == self.convertType(dcol.DataType) | |
def fillTable(self, force=False): | |
""" | |
Fill the grid‘s data table to match the data set. Returns the number | |
of rows in the table. | |
""" | |
_oldRowCount = self._oldRowCount | |
# Get the data from the grid. | |
bizobj = self.grid.getBizobj() | |
if bizobj: | |
dataSet = bizobj | |
_newRowCount = dataSet.RowCount | |
self._bizobj = bizobj | |
else: | |
self._bizobj = None | |
dataSet = self.grid.DataSet | |
if dataSet is None: | |
return 0 | |
_newRowCount = len(dataSet) | |
if _oldRowCount is None: | |
## still haven‘t tracked down why, but bizobj grids needed _oldRowCount | |
## to be initialized to None, or extra rows would be added. Since we | |
## aren‘t a bizobj grid, we need to change that None to 0 here so that | |
## the rows can get appended below. | |
_oldRowCount = 0 | |
if _oldRowCount == _newRowCount and not force: | |
return _newRowCount | |
self.grid._syncRowCount() | |
# Column widths come from multiple places. In decreasing precedence: | |
# 1) dApp user settings, | |
# 2) col.Width (as set by the Width prop or by the fieldspecs) | |
# 3) have the grid autosize | |
for idx, col in enumerate(self.grid._columns): | |
gridCol = idx | |
# 1) Try to get the column width from the saved user settings: | |
width = col._getUserSetting("Width") | |
if width is None: | |
# 2) Try to get the column width from the column definition: | |
width = col.Width | |
if width is None or (width < 0): | |
# 3) Have the grid autosize: | |
self.grid.autoSizeCol(gridCol) | |
else: | |
col.Width = width | |
# Show the row labels, if any | |
for idx, label in enumerate(self.grid.RowLabels): | |
self.SetRowLabelValue(idx, label) | |
self._oldRowCount = _newRowCount | |
return _newRowCount | |
# The following methods are required by the grid, to find out certain | |
# important details about the underlying table. | |
# def GetNumberRows(self): | |
# bizobj = self.grid.getBizobj() | |
# if bizobj: | |
# return bizobj.RowCount | |
# try: | |
# num = len(self.grid.DataSet) | |
# except: | |
# num = 0 | |
# return num | |
# def GetNumberCols(self, useNative=False): | |
# if useNative: | |
# return super(dGridDataTable, self).GetNumberCols() | |
# else: | |
# return self.grid.ColumnCount | |
# def IsEmptyCell(self, row, col): | |
# if row >= self.grid.RowCount: | |
# return True | |
# return False | |
def GetValue(self, row, col, useCache=True, convertNoneToString=True, | |
dynamicUpdate=True, _fromGridEditor=False): | |
col = self._convertWxColNumToDaboColNum(col) | |
if useCache and not _fromGridEditor: | |
cv = self.__cachedVals.get((row, col)) | |
if cv: | |
diff = time.time() - cv[1] | |
if diff < 10: ## if it‘s been less than this # of seconds. | |
return cv[0] | |
if col is None: | |
# No corresponding Dabo column for this column; must be not visible. | |
return "" | |
bizobj = self.grid.getBizobj() | |
col_obj = self.grid.Columns[col] | |
field = col_obj.DataField | |
if dynamicUpdate: | |
col_obj._updateDynamicProps() | |
col_obj._updateCellDynamicProps(row) | |
ret = "" | |
if bizobj: | |
if field and (row < bizobj.RowCount): | |
try: | |
ret = bizobj.getFieldVal(field, row) | |
except dException.FieldNotFoundException: | |
pass | |
if not _fromGridEditor: | |
ret = self.getStringValue(ret) | |
else: | |
try: | |
ret = self.grid.DataSet[row][field] | |
except (TypeError, IndexError, KeyError): | |
pass | |
if ret is None and convertNoneToString: | |
ret = self.grid.NoneDisplay | |
if not _fromGridEditor: | |
self.__cachedVals[(row, col)] = (ret, time.time()) | |
return ret | |
def getStringValue(self, val): | |
"""Get the string value to display in the grid.""" | |
if isinstance(val, datetime.datetime): | |
return dabo.lib.dates.getStringFromDateTime(val) | |
elif isinstance(val, datetime.date): | |
return dabo.lib.dates.getStringFromDate(val) | |
return val | |
def SetValue(self, row, col, value, _fromGridEditor=False): | |
col = self._convertWxColNumToDaboColNum(col) | |
self.grid._setCellValue(row, col, value) | |
if not _fromGridEditor: | |
# Update the cache | |
self.__cachedVals[(row, col)] = (value, time.time()) | |
self.grid.afterCellEdit(row, col) | |
def _convertWxColNumToDaboColNum(self, wxCol): | |
return self.grid._convertWxColNumToDaboColNum(wxCol) | |
class GridListEditor(wx.grid.GridCellChoiceEditor): | |
def __init__(self, *args, **kwargs): | |
dabo.log.info("GridListEditor: Init ") | |
dabo.log.info(ustr(args)) | |
dabo.log.info(ustr(kwargs)) | |
super(GridListEditor, self).__init__(*args, **kwargs) | |
def Create(self, parent, id, evtHandler, *args, **kwargs): | |
dabo.log.info("GridListEditor: Create") | |
dabo.log.info(ustr(args)) | |
dabo.log.info(ustr(kwargs)) | |
self.control = dabo.ui.dDropdownList(parent=parent, id=id, | |
ValueMode="String") | |
self.SetControl(self.control) | |
if evtHandler: | |
self.control.PushEventHandler(evtHandler) | |
# super(GridListEditor, self).Create(parent, id, evtHandler) | |
def Clone(self): | |
return self.__class__() | |
def SetParameters(self, paramStr): | |
dabo.log.info("GridListEditor: SetParameters: %s" % paramStr) | |
self.control.Choices = eval(paramStr) | |
def BeginEdit(self, row, col, grid): | |
dabo.log.info("GridListEditor: BeginEdit (%d,%d)" % (row, col)) | |
self.value = grid.GetTable().GetValue(row, col) | |
dabo.log.info("GridListEditor: Value=%s" % self.value) | |
dabo.log.info("GridListEditor: Choices=%s" % self.control.Choices) | |
try: | |
self.control.Value = self.value | |
except ValueError: | |
dabo.log.info("GridListEditor: Value not in Choices") | |
self.control.SetFocus() | |
def EndEdit(self, row, col, grid): | |
dabo.log.info("GridListEditor: EndEdit (%d,%d)" % (row, col)) | |
changed = False | |
v = self.control.Value | |
if v != self.value: | |
changed = True | |
if changed: | |
grid.GetTable().SetValue(row, col, value) | |
self.value = "" | |
self.control.Value = self.value | |
return changed | |
def Reset(self): | |
dabo.log.info("GridListEditor: Reset") | |
self.control.Value = self.value | |
# def SetSize(self, rectorig): | |
# dabo.log.info("GridListEditor: SetSize: %s" % rectorig) | |
# dabo.log.info("GridListEditor: type of rectorig: %s" % type(rectorig)) | |
# # rect = wx.Rect(rectorig) | |
# # dabo.log.info("GridListEditor RECT: %s" % rect) | |
# super(GridListEditor, self).SetSize(rectorig) | |
def IsAcceptedKey(self, key): | |
dabo.log.info("GridListEditor: check key: %d" % (key)) | |
return true | |
class dColumn(dabo.ui.dPemMixinBase.dPemMixinBase): | |
""" | |
These aren‘t the actual columns that appear in the grid; rather, | |
they provide a way to interact with the underlying grid table in a more | |
straightforward manner. | |
""" | |
_call_beforeInit, _call_afterInit, _call_initProperties = False, True, True | |
def __init__(self, parent, properties=None, attProperties=None, | |
*args, **kwargs): | |
self._isConstructed = False | |
self._dynamic = {} | |
# Initialize the attributes for DataField and DataType | |
self._dataField = "" | |
self._dataType = "" | |
self._expand = False | |
# Default to 2 decimal places | |
self._precision = 2 | |
# Do text columns wrap their long text? | |
self._wordWrap = False | |
# Is the column shown? | |
self._visible = True | |
# Holds the default renderer class for the column | |
self._rendererClass = None | |
# Custom editors/renderers | |
self._customRenderers = {} | |
self._customEditors = {} | |
#Declare Internal Header Attributes | |
self._headerVerticalAlignment = "Center" | |
self._headerHorizontalAlignment = "Center" | |
self._headerForeColor = None | |
self._headerBackColor = None | |
dataFieldSent = "DataField" in kwargs | |
dataTypeSent = "DataType" in kwargs | |
precisionSent = "Precision" in kwargs | |
self._beforeInit() | |
kwargs["Parent"] = parent | |
# dColumn maintains one attr object that the grid table will use for | |
# setting properties such as ForeColor and Font on the entire column. | |
att = self._gridColAttr = parent._defaultGridColAttr.Clone() | |
att.SetFont(self._getDefaultFont()._nativeFont) | |
self._gridCellAttrs = {} | |
super(dColumn, self).__init__(properties=properties, attProperties=attProperties, | |
*args, **kwargs) | |
self._baseClass = dColumn | |
if dataFieldSent and not dataTypeSent: | |
implicitPrecision = not precisionSent | |
self._setDataTypeFromDataField(implicitPrecision) | |
def _beforeInit(self): | |
# Define the cell renderer and editor classes | |
import gridRenderers | |
self.stringRendererClass = wx.grid.GridCellStringRenderer | |
self.wrapStringRendererClass = wx.grid.GridCellAutoWrapStringRenderer | |
self.boolRendererClass = gridRenderers.BoolRenderer | |
self.intRendererClass = wx.grid.GridCellNumberRenderer | |
self.longRendererClass = wx.grid.GridCellNumberRenderer | |
self.decimalRendererClass = wx.grid.GridCellFloatRenderer | |
self.floatRendererClass = wx.grid.GridCellFloatRenderer | |
self.listRendererClass = wx.grid.GridCellStringRenderer | |
self.imageRendererClass = gridRenderers.ImageRenderer | |
self.stringEditorClass = wx.grid.GridCellTextEditor | |
self.wrapStringEditorClass = wx.grid.GridCellAutoWrapStringEditor | |
self.boolEditorClass = wx.grid.GridCellBoolEditor | |
self.intEditorClass = wx.grid.GridCellNumberEditor | |
self.longEditorClass = wx.grid.GridCellNumberEditor | |
self.decimalEditorClass = wx.grid.GridCellFloatEditor | |
self.floatEditorClass = wx.grid.GridCellFloatEditor | |
self.listEditorClass = wx.grid.GridCellChoiceEditor | |
# self.listEditorClass = GridListEditor | |
self.defaultRenderers = { | |
"str" : self.stringRendererClass, | |
"string" : self.stringRendererClass, | |
"date" : self.stringRendererClass, | |
"datetime" : self.stringRendererClass, | |
"bool" : self.boolRendererClass, | |
"int" : self.intRendererClass, | |
"long" : self.longRendererClass, | |
"decimal" : self.decimalRendererClass, | |
"float" : self.floatRendererClass, | |
"list" : self.listRendererClass, | |
str : self.stringRendererClass, | |
unicode : self.stringRendererClass, | |
datetime.date : self.stringRendererClass, | |
datetime.datetime : self.stringRendererClass, | |
bool : self.boolRendererClass, | |
int : self.intRendererClass, | |
long : self.longRendererClass, | |
float : self.floatRendererClass, | |
Decimal: self.decimalRendererClass, | |
list : self.listRendererClass} | |
self.defaultEditors = { | |
"str" : self.stringEditorClass, | |
"string" : self.stringEditorClass, | |
"date" : self.stringEditorClass, | |
"datetime" : self.stringEditorClass, | |
"bool" : self.boolEditorClass, | |
"int" : self.intEditorClass, | |
"integer" : self.intEditorClass, | |
"long" : self.longEditorClass, | |
"decimal" : self.decimalEditorClass, | |
"float" : self.floatEditorClass, | |
"list" : self.listEditorClass, | |
str : self.stringEditorClass, | |
unicode : self.stringEditorClass, | |
datetime.date : self.stringEditorClass, | |
datetime.datetime : self.stringEditorClass, | |
bool : self.boolEditorClass, | |
int : self.intEditorClass, | |
long : self.longEditorClass, | |
float : self.floatEditorClass, | |
Decimal: self.decimalEditorClass, | |
list : self.listEditorClass} | |
# Default to string renderer | |
self._rendererClass = self.stringRendererClass | |
super(dColumn, self)._beforeInit() | |
def _afterInit(self): | |
self._isConstructed = True | |
super(dColumn, self)._afterInit() | |
dabo.ui.callAfter(self._restoreFontZoom) | |
def getDataTypeForColumn(self): | |
try: | |
typ = self.DataType | |
except (dException.FieldNotFoundException, dException.NoRecordsException): | |
typ = None | |
return typ | |
def _setRenderer(self): | |
self._setDataTypeFromDataField() | |
custom = self.CustomRendererClass | |
if custom: | |
self._rendererClass = custom | |
else: | |
typ = self.getDataTypeForColumn() | |
self._rendererClass = self.defaultRenderers.get(typ, self.stringRendererClass) | |
@dabo.ui.deadCheck | |
def _updateDynamicProps(self): | |
for prop, func in self._dynamic.items(): | |
if prop[:4] != "Cell": | |
if isinstance(func, tuple): | |
args = func[1:] | |
func = func[0] | |
else: | |
args = () | |
setattr(self, prop, func(*args)) | |
def _updateCellDynamicProps(self, row): | |
kwargs = {"row": row} | |
self._cellDynamicRow = row | |
for prop, func in self._dynamic.items(): | |
if prop[:4] == "Cell": | |
if isinstance(func, tuple): | |
args = func[1:] | |
func = func[0] | |
else: | |
args = () | |
setattr(self, prop, func(*args, **kwargs)) | |
del self._cellDynamicRow | |
def _restoreFontZoom(self): | |
if self.Form and self.Form.SaveRestorePosition: | |
super(dColumn, self)._restoreFontZoom() | |
def _getDefaultFont(self): | |
ret = dabo.ui.dFont(Size=10, Bold=False, Italic=False, | |
Underline=False) | |
if sys.platform.startswith("win"): | |
# The wx default is quite ugly | |
try: | |
ret.Face = "Arial" | |
ret.Size = 9 | |
except dException.FontNotFoundException: | |
# I had this happen to a customer running Win XP. No idea why Arial | |
# would be missing. --pkm 2009-10-24 | |
pass | |
return ret | |
def _constructed(self): | |
return self._isConstructed | |
def release(self): | |
""" | |
Usually don‘t need this, but it helps to keep this in | |
line with other Dabo objects. | |
""" | |
try: | |
self.Parent.removeColumn(self) | |
except ValueError: | |
# Will happen when the column has already been removed | |
pass | |
def _setAbsoluteFontZoom(self, newZoom): | |
origFontSize = self._origFontSize = getattr(self, "_origFontSize", self.FontSize) | |
origHeaderFontSize = self._origHeaderFontSize = getattr(self, "_origHeaderFontSize", self.HeaderFontSize) | |
fontSize = origFontSize + newZoom | |
headerFontSize = origHeaderFontSize + newZoom | |
self._currFontZoom = newZoom | |
if fontSize > 1: | |
self.FontSize = fontSize | |
if headerFontSize > 1: | |
self.HeaderFontSize = headerFontSize | |
if self.Form is not None: | |
dabo.ui.callAfterInterval(200, self.Form.layout) | |
def _setEditor(self, row): | |
""" | |
Set the editor for the entire column based on the editor for this row. | |
This is a workaround to a problem that is preventing us from setting the | |
editor for a specific cell at the time the grid needs it. | |
""" | |
edClass = self.getEditorClassForRow(row) | |
attr = self._gridColAttr.Clone() | |
if edClass: | |
kwargs = {} | |
if edClass in (wx.grid.GridCellChoiceEditor,): | |
kwargs["choices"] = self.getListEditorChoicesForRow(row) | |
elif edClass in (wx.grid.GridCellFloatEditor,): | |
kwargs["precision"] = self.Precision | |
editor = edClass(**kwargs) | |
attr.SetEditor(editor) | |
# if edClass is self.floatEditorClass: | |
# editor.SetPrecision(self.Precision) | |
self._gridColAttr = attr | |
def getListEditorChoicesForRow(self, row): | |
"""Return the list of choices for the list editor for the given row.""" | |
return self.CustomListEditorChoices.get(row, self.ListEditorChoices) | |
def getEditorClassForRow(self, row): | |
"""Return the cell editor class for the passed row.""" | |
return self.CustomEditors.get(row, self.EditorClass) | |
def _getValueForRow(self, row): | |
if self.Parent: | |
return self.Parent.getColumnValueByRow(self, row) | |
def getRendererClassForRow(self, row): | |
"""Return the cell renderer class for the passed row.""" | |
if self._getValueForRow(row) == self.Parent.NoneDisplay: | |
# Null values in the data should be rendered as strings, | |
# no matter what type the column is. | |
return self.stringRendererClass | |
return self._customRenderers.get(row, self._rendererClass) | |
def _getHeaderRect(self): | |
"""Return the rect of this header in the header window.""" | |
grid = self.Parent | |
height = self.Parent.HeaderHeight | |
width = self.Width | |
top = 0 | |
# Thanks Roger Binns: | |
left = -grid.GetViewStart()[0] * grid.GetScrollPixelsPerUnit()[0] | |
for col in range(self.Parent.ColumnCount): | |
colObj = self.Parent.Columns[col] | |
if not colObj.Visible: | |
continue | |
if colObj == self: | |
break | |
left += colObj.Width | |
return wx.Rect(left, top, width, height) | |
def _refreshHeader(self): | |
"""Refresh just this column‘s header.""" | |
if self.Parent: | |
# This will trigger wx to query GetColLabelValue(), which will in turn | |
# call paintHeader() on just this column. It‘s roundabout, but gives the | |
# best overall results, but risks relying on wx implementation details. | |
# Other options, in case this starts to fail, are: | |
# self.Parent.Header.Refresh() | |
# self.Parent._paintHeader(self._GridColumnIndex) | |
self.Parent.SetColLabelValue(self.ColumnIndex, "") | |
def _refreshGrid(self): | |
"""Refresh the grid region, not the header region.""" | |
if self.Parent: | |
gw = self.Parent.GetGridWindow() | |
gw.Refresh() | |
def _persist(self, prop): | |
"""Persist the current prop setting to the user settings table.""" | |
self._setUserSetting(prop, getattr(self, prop)) | |
def _setDataTypeFromDataField(self, implicitPrecision=True): | |
""" | |
When a column has its DataField changed, we need to set the | |
correct DataType based on the new value. | |
""" | |
if self.Parent: | |
currDT = self.DataType | |
dt = self.Parent.typeFromDataField(self.DataField, self) | |
if dt not in (None, type(None)) and (dt != currDT): | |
self.DataType = dt | |
if dt is Decimal and implicitPrecision: | |
self.Precision = self.Parent.precisionFromDataField(self.DataField) | |
def _getUserSetting(self, prop): | |
"""Get the property value from the user settings table.""" | |
app = self.Application | |
grid = self.Parent | |
form = grid.Form | |
colName = "column_%s" % self.DataField | |
if app is not None and form is not None \ | |
and not hasattr(grid, "isDesignerControl"): | |
settingName = "%s.%s.%s.%s" % (form.Name, grid.Name, colName, prop) | |
return app.getUserSetting(settingName) | |
return None | |
def _setUserSetting(self, prop, val): | |
"""Set the property value to the user settings table.""" | |
app = self.Application | |
grid = self.Parent | |
form = grid.Form | |
colName = "column_%s" % self.DataField | |
if app is not None and form is not None \ | |
and not hasattr(grid, "isDesignerControl"): | |
settingName = "%s.%s.%s.%s" % (form.Name, grid.Name, colName, prop) | |
app.setUserSetting(settingName, val) | |
def _getColumnIndex(self): | |
"""Return our column index in the grid, or -1.""" | |
try: | |
return self.Parent.Columns.index(self) | |
except (ValueError, AttributeError): | |
return -1 | |
def _updateEditor(self): | |
"""The Field, DataType, or CustomEditor has changed: set in the attr""" | |
editorClass = self.EditorClass | |
if editorClass is None: | |
editor = None | |
else: | |
kwargs = {} | |
if editorClass in (wx.grid.GridCellChoiceEditor,): | |
kwargs["choices"] = self.ListEditorChoices | |
# Fix for editor precision issue. | |
elif editorClass in (wx.grid.GridCellFloatEditor,): | |
kwargs["precision"] = self.Precision | |
editor = editorClass(**kwargs) | |
self._gridColAttr.SetEditor(editor) | |
def _updateRenderer(self): | |
"""The Field, DataType, or CustomRenderer has changed: set in the attr""" | |
self._setRenderer() | |
rendClass = self.CustomRendererClass or self.RendererClass | |
if rendClass is None: | |
renderer = None | |
else: | |
renderer = rendClass() | |
self._gridColAttr.SetRenderer(renderer) | |
def _onFontPropsChanged(self, evt): | |
# Sent by the dFont object when any props changed. Wx needs to be notified: | |
self._gridColAttr.SetFont(self.Font._nativeFont) | |
self._refreshGrid() | |
def _onHeaderFontPropsChanged(self, evt): | |
# Sent by the dFont object when any props changed. Wx needs to be notified: | |
self._refreshHeader() | |
def _setCellProp(self, wxPropName, *args, **kwargs): | |
"""Called from all of the Cell property setters.""" | |
## dynamic prop uses cellDynamicRow; reg prop uses self.CurrentRow | |
try: | |
row = getattr(self, "_cellDynamicRow", self.Parent.CurrentRow) | |
except dabo.ui.deadObjectException: | |
# @dabo.ui.deadCheck didn‘t seem to work... | |
return | |
cellAttr = obj = self._gridCellAttrs.get(row, self._gridColAttr.Clone()) | |
if "." in wxPropName: | |
# For instance, Font.SetWeight | |
wxPropName, subObject = wxPropName.split(".") | |
obj = getattr(cellAttr, wxPropName) | |
getattr(obj, subObject)(*args, **kwargs) | |
setattr(cellAttr, wxPropName, obj) | |
else: | |
getattr(cellAttr, wxPropName)(*args, **kwargs) | |
self._gridCellAttrs[row] = cellAttr | |
def _getBackColor(self): | |
return self._gridColAttr.GetBackgroundColour() | |
def _setBackColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._gridColAttr.SetBackgroundColour(val) | |
self._refreshGrid() | |
else: | |
self._properties["BackColor"] = val | |
def _getCaption(self): | |
try: | |
v = self._caption | |
except AttributeError: | |
v = self._caption = "Column" | |
return v | |
def _setCaption(self, val): | |
if self._constructed(): | |
self._caption = val | |
self._refreshHeader() | |
else: | |
self._properties["Caption"] = val | |
def _getCellBackColor(self): | |
row = self.Parent.CurrentRow | |
cellAttr = self._gridCellAttrs.get(row, False) | |
if cellAttr: | |
return cellAttr.GetBackgroundColour() | |
return self.BackColor | |
def _setCellBackColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._setCellProp("SetBackgroundColour", val) | |
else: | |
self._properties["CellBackColor"] = val | |
def _getCellFontBold(self): | |
row = self.Parent.CurrentRow | |
cellAttr = self._gridCellAttrs.get(row, False) | |
if cellAttr: | |
return cellAttr.GetFont().GetWeight() == wx.BOLD | |
return self.FontBold | |
def _setCellFontBold(self, val): | |
if self._constructed(): | |
if val: | |
val = wx.FONTWEIGHT_BOLD | |
else: | |
val = wx.FONTWEIGHT_NORMAL | |
self._setCellProp("Font.SetWeight", val) | |
else: | |
self._properties["CellFontBold"] = val | |
def _getCellForeColor(self): | |
row = self.Parent.CurrentRow | |
cellAttr = self._gridCellAttrs.get(row, False) | |
if cellAttr: | |
return cellAttr.GetTextColour() | |
return self.ForeColor | |
def _setCellForeColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._setCellProp("SetTextColour", val) | |
else: | |
self._properties["CellForeColor"] = val | |
def _getCustomEditorClass(self): | |
try: | |
v = self._customEditorClass | |
except AttributeError: | |
v = self._customEditorClass = None | |
return v | |
def _setCustomEditorClass(self, val): | |
if self._constructed(): | |
self._customEditorClass = val | |
self._updateEditor() | |
else: | |
self._properties["CustomEditorClass"] = val | |
def _getCustomEditors(self): | |
try: | |
v = self._customEditors | |
except AttributeError: | |
v = self._customEditors = {} | |
return v | |
def _setCustomEditors(self, val): | |
self._customEditors = val | |
def _getCustomListEditorChoices(self): | |
try: | |
v = self._customListEditorChoices | |
except AttributeError: | |
v = self._customListEditorChoices = {} | |
return v | |
def _setCustomListEditorChoices(self, val): | |
self._customListEditorChoices = val | |
def _getCustomRendererClass(self): | |
try: | |
v = self._customRendererClass | |
except AttributeError: | |
v = self._customRendererClass = None | |
return v | |
def _setCustomRendererClass(self, val): | |
if self._constructed(): | |
self._customRendererClass = val | |
self._updateRenderer() | |
else: | |
self._properties["CustomRendererClass"] = val | |
def _getCustomRenderers(self): | |
try: | |
v = self._customRenderers | |
except AttributeError: | |
v = self._customRenderers = {} | |
return v | |
def _setCustomRenderers(self, val): | |
self._customRenderers = val | |
def _getDataType(self): | |
try: | |
v = self._dataType | |
except AttributeError: | |
v = self._dataType = "str" | |
return v | |
def _setDataType(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
if val.lower().strip() in ("str", "string", "char", "varchar", ""): | |
val = "str" | |
if self._dataType == val: | |
return | |
self._dataType = val | |
if "Automatic" in self.HorizontalAlignment: | |
self._setAutoHorizontalAlignment() | |
self._updateRenderer() | |
self._updateEditor() | |
else: | |
self._properties["DataType"] = val | |
def _getEditable(self): | |
return not self._gridColAttr.IsReadOnly() | |
def _setEditable(self, val): | |
if self._constructed(): | |
self._gridColAttr.SetReadOnly(not val) | |
if self.Parent: | |
self.Parent.refresh() | |
else: | |
self._properties["Editable"] = val | |
def _getEditorClass(self): | |
v = self.CustomEditorClass | |
if v is None: | |
v = self.defaultEditors.get(self.DataType) | |
return v | |
def _getExpand(self): | |
return self._expand | |
def _setExpand(self, val): | |
if self._constructed(): | |
self._expand = val | |
else: | |
self._properties["Expand"] = val | |
def _getDataField(self): | |
try: | |
v = self._dataField | |
except AttributeError: | |
v = self._dataField = "" | |
return v | |
def _setDataField(self, val): | |
if self._constructed(): | |
if self._dataField: | |
# Use a callAfter, since the parent may not be finished instantiating yet. | |
dabo.ui.callAfter(self._setDataTypeFromDataField) | |
self._dataField = val | |
if not self.Name or self.Name == "?": | |
self._name = _("col_%s") % val | |
self._updateRenderer() | |
self._updateEditor() | |
else: | |
self._properties["DataField"] = val | |
def _getFont(self): | |
if hasattr(self, "_font"): | |
v = self._font | |
else: | |
v = self.Font = dabo.ui.dFont(_nativeFont=self._gridColAttr.GetFont()) | |
return v | |
def _setFont(self, val): | |
assert isinstance(val, dabo.ui.dFont) | |
if self._constructed(): | |
self._font = val | |
self._gridColAttr.SetFont(val._nativeFont) | |
val.bindEvent(dEvents.FontPropertiesChanged, self._onFontPropsChanged) | |
self._refreshGrid() | |
else: | |
self._properties["Font"] = val | |
def _getFontBold(self): | |
return self.Font.Bold | |
def _setFontBold(self, val): | |
if self._constructed(): | |
self.Font.Bold = val | |
else: | |
self._properties["FontBold"] = val | |
def _getFontDescription(self): | |
return self.Font.Description | |
def _getFontInfo(self): | |
return self.Font._nativeFont.GetNativeFontInfoDesc() | |
def _getFontItalic(self): | |
return self.Font.Italic | |
def _setFontItalic(self, val): | |
if self._constructed(): | |
self.Font.Italic = val | |
else: | |
self._properties["FontItalic"] = val | |
def _getFontFace(self): | |
return self.Font.Face | |
def _setFontFace(self, val): | |
if self._constructed(): | |
self.Font.Face = val | |
else: | |
self._properties["FontFace"] = val | |
def _getFontSize(self): | |
return self.Font.Size | |
def _setFontSize(self, val): | |
if self._constructed(): | |
self.Font.Size = val | |
else: | |
self._properties["FontSize"] = val | |
def _getFontUnderline(self): | |
return self.Font.Underline | |
def _setFontUnderline(self, val): | |
if self._constructed(): | |
self.Font.Underline = val | |
else: | |
self._properties["FontUnderline"] = val | |
def _getForeColor(self): | |
try: | |
return self._gridColAttr.GetTextColour() | |
except wx.PyAssertionError: | |
# Getting the color failed on Mac and win: "no default attr" | |
default = dColors.colorTupleFromName("black") | |
self._gridColAttr.SetTextColour(default) | |
return default | |
def _setForeColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._gridColAttr.SetTextColour(val) | |
self._refreshGrid() | |
else: | |
self._properties["ForeColor"] = val | |
def _getHeaderFont(self): | |
if hasattr(self, "_headerFont"): | |
v = self._headerFont | |
else: | |
v = self.HeaderFont = self._getDefaultFont() | |
v.Bold = True | |
return v | |
def _setHeaderFont(self, val): | |
assert isinstance(val, dabo.ui.dFont) | |
if self._constructed(): | |
self._headerFont = val | |
val.bindEvent(dEvents.FontPropertiesChanged, self._onHeaderFontPropsChanged) | |
else: | |
self._properties["HeaderFont"] = val | |
def _getHeaderFontBold(self): | |
return self.HeaderFont.Bold | |
def _setHeaderFontBold(self, val): | |
if self._constructed(): | |
self.HeaderFont.Bold = val | |
else: | |
self._properties["HeaderFontBold"] = val | |
def _getHeaderFontDescription(self): | |
return self.HeaderFont.Description | |
def _getHeaderFontInfo(self): | |
return self.HeaderFont._nativeFont.GetNativeFontInfoDesc() | |
def _getHeaderFontItalic(self): | |
return self.HeaderFont.Italic | |
def _setHeaderFontItalic(self, val): | |
if self._constructed(): | |
self.HeaderFont.Italic = val | |
else: | |
self._properties["HeaderFontItalic"] = val | |
def _getHeaderFontFace(self): | |
return self.HeaderFont.Face | |
def _setHeaderFontFace(self, val): | |
if self._constructed(): | |
self.HeaderFont.Face = val | |
else: | |
self._properties["HeaderFontFace"] = val | |
def _getHeaderFontSize(self): | |
return self.HeaderFont.Size | |
def _setHeaderFontSize(self, val): | |
if self._constructed(): | |
self.HeaderFont.Size = val | |
else: | |
self._properties["HeaderFontSize"] = val | |
def _getHeaderFontUnderline(self): | |
return self.HeaderFont.Underline | |
def _setHeaderFontUnderline(self, val): | |
if self._constructed(): | |
self.HeaderFont.Underline = val | |
else: | |
self._properties["HeaderFontUnderline"] = val | |
def _getHeaderBackColor(self): | |
try: | |
v = self._headerBackColor | |
except AttributeError: | |
v = self._headerBackColor = None | |
return v | |
def _setHeaderBackColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._headerBackColor = val | |
self._refreshHeader() | |
else: | |
self._properties["HeaderBackColor"] = val | |
def _getHeaderForeColor(self): | |
try: | |
v = self._headerForeColor | |
except AttributeError: | |
v = self._headerForeColor = None | |
return v | |
def _setHeaderForeColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._headerForeColor = val | |
self._refreshHeader() | |
else: | |
self._properties["HeaderForeColor"] = val | |
def _getHeaderHorizontalAlignment(self): | |
try: | |
val = self._headerHorizontalAlignment | |
except AttributeError: | |
val = self._headerHorizontalAlignment = None | |
return val | |
def _setHeaderHorizontalAlignment(self, val): | |
if self._constructed(): | |
v = self._expandPropStringValue(val, ("Left", "Right", "Center", None)) | |
self._headerHorizontalAlignment = v | |
self._refreshHeader() | |
else: | |
self._properties["HeaderHorizontalAlignment"] = val | |
def _getHeaderVerticalAlignment(self): | |
try: | |
val = self._headerVerticalAlignment | |
except AttributeError: | |
val = self._headerVerticalAlignment = None | |
return val | |
def _setHeaderVerticalAlignment(self, val): | |
if self._constructed(): | |
v = self._expandPropStringValue(val, ("Top", "Bottom", "Center", None)) | |
self._headerVerticalAlignment = v | |
self._refreshHeader() | |
else: | |
self._properties["HeaderVerticalAlignment"] = val | |
def _getHorizontalAlignment(self): | |
try: | |
auto = self._autoHorizontalAlignment | |
except AttributeError: | |
auto = self._autoHorizontalAlignment = True | |
mapping = {wx.ALIGN_LEFT: "Left", wx.ALIGN_RIGHT: "Right", | |
wx.ALIGN_CENTRE: "Center"} | |
wxAlignment = self._gridColAttr.GetAlignment()[0] | |
try: | |
val = mapping[wxAlignment] | |
except KeyError: | |
val = "Left" | |
if auto: | |
val = "%s (Automatic)" % val | |
return val | |
def _setAutoHorizontalAlignment(self): | |
dt = self.DataType | |
if isinstance(dt, basestring): | |
if dt in ("decimal", "float", "long", "integer"): | |
self._setHorizontalAlignment("Right", _autoAlign=True) | |
def _setHorizontalAlignment(self, val, _autoAlign=False): | |
if self._constructed(): | |
val = self._expandPropStringValue(val, ("Automatic", "Left", "Right", "Center")) | |
if val == "Automatic" and not _autoAlign: | |
self._autoHorizontalAlignment = True | |
self._setAutoHorizontalAlignment() | |
return | |
if val != "Automatic" and not _autoAlign: | |
self._autoHorizontalAlignment = False | |
mapping = {"Left": wx.ALIGN_LEFT, "Right": wx.ALIGN_RIGHT, | |
"Center": wx.ALIGN_CENTRE} | |
try: | |
wxHorAlign = mapping[val] | |
except KeyError: | |
wxHorAlign = mapping["Left"] | |
val = "Left" | |
wxVertAlign = self._gridColAttr.GetAlignment()[1] | |
self._gridColAttr.SetAlignment(wxHorAlign, wxVertAlign) | |
self._refreshGrid() | |
else: | |
self._properties["HorizontalAlignment"] = val | |
def _getListEditorChoices(self): | |
try: | |
v = self._listEditorChoices | |
except AttributeError: | |
v = [] | |
return v | |
def _setListEditorChoices(self, val): | |
if self._constructed(): | |
self._listEditorChoices = val | |
else: | |
self._properties["ListEditorChoices"] = val | |
def _getMovable(self): | |
return getattr(self, "_movable", True) | |
def _setMovable(self, val): | |
self._movable = bool(val) | |
def _getOrder(self): | |
try: | |
v = self._order | |
except AttributeError: | |
v = self._order = -1 | |
return v | |
def _setOrder(self, val): | |
if self._constructed(): | |
self._order = val | |
else: | |
self._properties["Order"] = val | |
def _getPrecision(self): | |
return self._precision | |
def _setPrecision(self, val): | |
if self._constructed(): | |
self._precision = val | |
if self.Parent: | |
dabo.ui.callAfterInterval(50, self.Parent.refresh) | |
else: | |
self._properties["Precision"] = val | |
def _getRendererClass(self): | |
return self._rendererClass | |
def _getResizable(self): | |
return getattr(self, "_resizable", True) | |
def _setResizable(self, val): | |
self._resizable = bool(val) | |
def _getSearchable(self): | |
try: | |
v = self._searchable | |
except AttributeError: | |
v = self._searchable = True | |
return v | |
def _setSearchable(self, val): | |
if self._constructed(): | |
self._searchable = bool(val) | |
else: | |
self._properties["Searchable"] = val | |
def _getSortable(self): | |
try: | |
v = self._sortable | |
except AttributeError: | |
v = self._sortable = True | |
return v | |
def _setSortable(self, val): | |
if self._constructed(): | |
self._sortable = bool(val) | |
else: | |
self._properties["Sortable"] = val | |
def _getValue(self): | |
grid = self.Parent | |
if grid is None: | |
return None | |
biz = grid.getBizobj() | |
if self.DataField: | |
if biz and (grid.CurrentRow < biz.RowCount): | |
return biz.getFieldVal(self.DataField) | |
if grid.DataSet: | |
return grid.DataSet[grid.CurrentRow][self.DataField] | |
return None | |
def _getVerticalAlignment(self): | |
mapping = {wx.ALIGN_TOP: "Top", wx.ALIGN_BOTTOM: "Bottom", | |
wx.ALIGN_CENTRE: "Center"} | |
wxAlignment = self._gridColAttr.GetAlignment()[1] | |
try: | |
val = mapping[wxAlignment] | |
except KeyError: | |
val = "Top" | |
return val | |
def _setVerticalAlignment(self, val): | |
if self._constructed(): | |
val = self._expandPropStringValue(val, ("Top", "Bottom", "Center")) | |
mapping = {"Top": wx.ALIGN_TOP, "Bottom": wx.ALIGN_BOTTOM, | |
"Center": wx.ALIGN_CENTRE} | |
try: | |
wxVertAlign = mapping[val] | |
except KeyError: | |
wxVertAlign = mapping["Top"] | |
val = "Top" | |
wxHorAlign = self._gridColAttr.GetAlignment()[0] | |
self._gridColAttr.SetAlignment(wxHorAlign, wxVertAlign) | |
self._refreshGrid() | |
else: | |
self._properties["VerticalAlignment"] = val | |
def _getVisible(self): | |
return self._visible | |
def _setVisible(self, val): | |
if self._constructed(): | |
self._visible = val | |
self.Parent.showColumn(self, val) | |
else: | |
self._properties["Visible"] = val | |
def _getWidth(self): | |
try: | |
v = self._width | |
except AttributeError: | |
v = self._width = 150 | |
if self.Parent: | |
idx = self.Parent._convertDaboColNumToWxColNum(self.ColumnIndex) | |
if idx is not None: | |
# Make sure the grid is in sync: | |
try: | |
self.Parent.SetColSize(idx, v) | |
except wx.PyAssertionError: | |
# The grid may still be in the process of being created, so pass. | |
pass | |
return v | |
def _setWidth(self, val): | |
if self._constructed(): | |
try: | |
if val == self._width: | |
return | |
except AttributeError: | |
pass | |
self._width = val | |
grd = self.Parent | |
if grd: | |
grd._syncColumnCount() | |
idx = grd._convertDaboColNumToWxColNum(self.ColumnIndex) | |
if idx is not None: | |
# Change the size in the wx grid: | |
grd.SetColSize(idx, val) | |
else: | |
self._properties["Width"] = val | |
def _getWordWrap(self): | |
return self._wordWrap | |
def _setWordWrap(self, val): | |
if self._constructed(): | |
if val != self._wordWrap: | |
self._wordWrap = val | |
if val: | |
for typ in (unicode, "str", "string"): | |
self.defaultRenderers[typ] = self.wrapStringRendererClass | |
self.defaultEditors[typ] = self.wrapStringEditorClass | |
else: | |
for typ in (unicode, "str", "string"): | |
self.defaultRenderers[typ] = self.stringRendererClass | |
self.defaultEditors[typ] = self.stringEditorClass | |
self._updateEditor() | |
self._updateRenderer() | |
self._refreshGrid() | |
else: | |
self._properties["WordWrap"] = val | |
BackColor = property(_getBackColor, _setBackColor, None, | |
_("Color for the background of each cell in the column.")) | |
Caption = property(_getCaption, _setCaption, None, | |
_("Specifies the caption displayed in this column‘s header.") ) | |
ColumnIndex = property(_getColumnIndex, None, | |
_("Returns the index of this column in the parent grid.")) | |
CellBackColor = property(_getCellBackColor, _setCellBackColor, None, | |
_("Color for the background of the current cell in the column.")) | |
CellFontBold = property(_getCellFontBold, _setCellFontBold, None, | |
_("Specifies whether the current cell‘s font is bold-faced.")) | |
CellForeColor = property(_getCellForeColor, _setCellForeColor, None, | |
_("Color for the foreground (text) of the current cell in the column.")) | |
CustomEditorClass = property(_getCustomEditorClass, | |
_setCustomEditorClass, None, | |
_("""Custom Editor class for this column. Default: None. | |
Set this to override the default editor class, which Dabo will | |
select based on the data type of the field.""")) | |
CustomEditors = property(_getCustomEditors, _setCustomEditors, None, | |
_("""Dictionary of custom editors for this column. Default: {}. | |
Set this to override the default editor class on a row-by-row basis. | |
If there is no custom editor class for a given row in CustomEditors, | |
the CustomEditor property setting will apply.""")) | |
CustomListEditorChoices = property(_getCustomListEditorChoices, | |
_setCustomListEditorChoices, None, | |
_("""Dictionary of custom list choices for this column. Default: {}. | |
Set this to override the default list choices on a row-by-row basis. | |
If there is no custom entry for a given row in CustomListEditorChoices, | |
the ListEditorChoices property setting will apply.""")) | |
CustomRendererClass = property(_getCustomRendererClass, | |
_setCustomRendererClass, None, | |
_("""Custom Renderer class for this column. Default: None. | |
Set this to override the default renderer class, which Dabo will select based | |
on the data type of the field.""")) | |
CustomRenderers = property(_getCustomRenderers, _setCustomRenderers, None, | |
_("""Dictionary of custom renderers for this column. Default: {}. | |
Set this to override the default renderer class on a row-by-row basis. | |
If there is no custom renderer for a given row in CustomRenderers, the | |
CustomRendererClass property setting will apply.""")) | |
DataType = property(_getDataType, _setDataType, None, | |
_("Description of the data type for this column (str)") ) | |
Editable = property(_getEditable, _setEditable, None, | |
_("""If True, and if the grid is set as Editable, the cell values in this | |
column are editable by the user. If False, the cells in this column | |
cannot be edited no matter what the grid setting is. When editable, | |
incremental searching will not be enabled, regardless of the | |
Searchable property setting. (bool)""") ) | |
EditorClass = property(_getEditorClass, None, None, | |
_("""Returns the editor class used for cells in the column. This | |
will be self.CustomEditorClass if set, or the default editor for the | |
datatype of the field. (varies)""")) | |
Expand = property(_getExpand, _setExpand, None, | |
_("""Does this column expand/shrink as the grid width changes? | |
Default=False (bool)""")) | |
DataField = property(_getDataField, _setDataField, None, | |
_("Field key in the data set to which this column is bound. (str)") ) | |
Font = property(_getFont, _setFont, None, | |
_("The font properties of the column‘s cells. (dFont)") ) | |
FontBold = property(_getFontBold, _setFontBold, None, | |
_("Specifies if the cell font (for all cells in the column) is bold-faced. (bool)") ) | |
FontDescription = property(_getFontDescription, None, None, | |
_("Human-readable description of the column‘s cell font settings. (str)") ) | |
FontFace = property(_getFontFace, _setFontFace, None, | |
_("Specifies the font face for the column cells. (str)") ) | |
FontInfo = property(_getFontInfo, None, None, | |
_("Specifies the platform-native font info string for the column cells. Read-only. (str)") ) | |
FontItalic = property(_getFontItalic, _setFontItalic, None, | |
_("Specifies whether the column‘s cell font is italicized. (bool)") ) | |
FontSize = property(_getFontSize, _setFontSize, None, | |
_("Specifies the point size of the column‘s cell font. (int)") ) | |
FontUnderline = property(_getFontUnderline, _setFontUnderline, None, | |
_("Specifies whether cell text is underlined. (bool)") ) | |
ForeColor = property(_getForeColor, _setForeColor, None, | |
_("Color for the foreground (text) of each cell in the column.")) | |
HeaderBackColor = property(_getHeaderBackColor, _setHeaderBackColor, None, | |
_("Optional color for the background of the column header (str)") ) | |
HeaderFont = property(_getHeaderFont, _setHeaderFont, None, | |
_("The font properties of the column‘s header. (dFont)") ) | |
HeaderFontBold = property(_getHeaderFontBold, _setHeaderFontBold, None, | |
_("Specifies if the header font is bold-faced. (bool)") ) | |
HeaderFontDescription = property(_getHeaderFontDescription, None, None, | |
_("Human-readable description of the current header font settings. (str)") ) | |
HeaderFontFace = property(_getHeaderFontFace, _setHeaderFontFace, None, | |
_("Specifies the font face for the column header. (str)") ) | |
HeaderFontInfo = property(_getHeaderFontInfo, None, None, | |
_("Specifies the platform-native font info string for the column header. Read-only. (str)") ) | |
HeaderFontItalic = property(_getHeaderFontItalic, _setHeaderFontItalic, None, | |
_("Specifies whether the header font is italicized. (bool)") ) | |
HeaderFontSize = property(_getHeaderFontSize, _setHeaderFontSize, None, | |
_("Specifies the point size of the header font. (int)") ) | |
HeaderFontUnderline = property(_getHeaderFontUnderline, _setHeaderFontUnderline, None, | |
_("Specifies whether column header text is underlined. (bool)") ) | |
HeaderForeColor = property(_getHeaderForeColor, _setHeaderForeColor, None, | |
_("Optional color for the foreground (text) of the column header (str)") ) | |
HeaderHorizontalAlignment = property(_getHeaderHorizontalAlignment, _setHeaderHorizontalAlignment, None, | |
_("Specifies the horizontal alignment of the header caption. (‘Left‘, ‘Center‘, ‘Right‘)")) | |
HeaderVerticalAlignment = property(_getHeaderVerticalAlignment, _setHeaderVerticalAlignment, None, | |
_("Specifies the vertical alignment of the header caption. (‘Top‘, ‘Center‘, ‘Bottom‘)")) | |
HorizontalAlignment = property(_getHorizontalAlignment, _setHorizontalAlignment, None, | |
_("""Horizontal alignment for all cells in this column. (str) | |
Acceptable values are: | |
‘Automatic‘: The cell‘s contents will align right for numeric data, left for text. (default) | |
‘Left‘ | |
‘Center‘ | |
‘Right‘ """)) | |
ListEditorChoices = property(_getListEditorChoices, _setListEditorChoices, None, | |
_("""Specifies the list of choices that will appear in the list. Only applies | |
if the DataType is set as "list". (list)""")) | |
Movable = property(_getMovable, _setMovable, None, | |
_("""Specifies whether this column is movable by the user. | |
Note also the dGrid.MovableColumns property - if that is set | |
to False, columns will not be movable even if their Movable | |
property is set to True.""")) | |
Order = property(_getOrder, _setOrder, None, | |
_("""Order of this column. Columns in the grid are arranged according | |
to their relative Order. (int)""") ) | |
Precision = property(_getPrecision, _setPrecision, None, | |
_("Number of decimal places to display for float and decimal values (int)")) | |
RendererClass = property(_getRendererClass, None, None, | |
_("""Returns the renderer class used for cells in the column. This will be | |
self.CustomRendererClass if set, or the default renderer class for the | |
datatype of the field. (varies)""")) | |
Resizable = property(_getResizable, _setResizable, None, | |
_("""Specifies whether this column is resizable by the user. | |
Note also the dGrid.ResizableColumns property - if that is set | |
to False, columns will not be resizable even if their Resizable | |
property is set to True.""")) | |
Searchable = property(_getSearchable, _setSearchable, None, | |
_("""Specifies whether this column‘s incremental search is enabled. | |
Default: True. The grid‘s Searchable property will override this setting. | |
(bool)""")) | |
Sortable = property(_getSortable, _setSortable, None, | |
_("""Specifies whether this column can be sorted. Default: True. The grid‘s | |
Sortable property will override this setting. (bool)""")) | |
Value = property(_getValue, None, None, | |
_("""Returns the current value of the column from the underlying dataset or bizobj.""")) | |
VerticalAlignment = property(_getVerticalAlignment, _setVerticalAlignment, None, | |
_("""Vertical alignment for all cells in this column. Acceptable values | |
are ‘Top‘, ‘Center‘, and ‘Bottom‘. (str)""")) | |
Visible = property(_getVisible, _setVisible, None, | |
_("Controls whether the column is shown or not (bool)")) | |
Width = property(_getWidth, _setWidth, None, | |
_("Width of this column (int)") ) | |
WordWrap = property(_getWordWrap, _setWordWrap, None, | |
_("When True, text longer than the column width will wrap to the next line (bool)")) | |
# Dynamic Property Declarations | |
DynamicBackColor = makeDynamicProperty(BackColor) | |
DynamicCaption = makeDynamicProperty(Caption) | |
DynamicCellBackColor = makeDynamicProperty(CellBackColor) | |
DynamicCellFontBold = makeDynamicProperty(CellFontBold) | |
DynamicCellForeColor = makeDynamicProperty(CellForeColor) | |
DynamicCustomEditorClass = makeDynamicProperty(CustomEditorClass) | |
DynamicCustomEditors = makeDynamicProperty(CustomEditors) | |
DynamicCustomListEditorChoices = makeDynamicProperty(CustomListEditorChoices) | |
DynamicCustomRendererClass = makeDynamicProperty(CustomRendererClass) | |
DynamicCustomRenderers = makeDynamicProperty(CustomRenderers) | |
DynamicDataField = makeDynamicProperty(DataField) | |
DynamicDataType = makeDynamicProperty(DataType) | |
DynamicEditable = makeDynamicProperty(Editable) | |
DynamicFont = makeDynamicProperty(Font) | |
DynamicFontBold = makeDynamicProperty(FontBold) | |
DynamicFontFace = makeDynamicProperty(FontFace) | |
DynamicFontItalic = makeDynamicProperty(FontItalic) | |
DynamicFontSize = makeDynamicProperty(FontSize) | |
DynamicFontUnderline = makeDynamicProperty(FontUnderline) | |
DynamicForeColor = makeDynamicProperty(ForeColor) | |
DynamicHeaderBackColor = makeDynamicProperty(HeaderBackColor) | |
DynamicHeaderFont = makeDynamicProperty(HeaderFont) | |
DynamicHeaderFontBold = makeDynamicProperty(HeaderFontBold) | |
DynamicHeaderFontFace = makeDynamicProperty(HeaderFontFace) | |
DynamicHeaderFontItalic = makeDynamicProperty(HeaderFontItalic) | |
DynamicHeaderFontSize = makeDynamicProperty(HeaderFontSize) | |
DynamicHeaderFontUnderline = makeDynamicProperty(HeaderFontUnderline) | |
DynamicHeaderForeColor = makeDynamicProperty(HeaderForeColor) | |
DynamicHeaderHorizontalAlignment = makeDynamicProperty(HeaderHorizontalAlignment) | |
DynamicHeaderVerticalAlignment = makeDynamicProperty(HeaderVerticalAlignment) | |
DynamicHorizontalAlignment = makeDynamicProperty(HorizontalAlignment) | |
DynamicListEditorChoices = makeDynamicProperty(ListEditorChoices) | |
DynamicOrder = makeDynamicProperty(Order) | |
DynamicSearchable = makeDynamicProperty(Searchable) | |
DynamicSortable = makeDynamicProperty(Sortable) | |
DynamicVerticalAlignment = makeDynamicProperty(VerticalAlignment) | |
DynamicVisible = makeDynamicProperty(Visible) | |
DynamicWidth = makeDynamicProperty(Width) | |
class dGrid(cm.dControlMixin, wx.grid.Grid): | |
""" | |
Creates a grid, with rows and columns to represent records and fields. | |
Grids are powerful controls for allowing reading and writing of data. A | |
grid can have any number of dColumns, which themselves have lots of properties | |
to manipulate. The grid is virtual, meaning that large amounts of data can | |
be accessed efficiently: only the data that needs to be shown on the current | |
screen is copied and displayed. | |
""" | |
USE_DATASOURCE_BEING_SET_HACK = False | |
def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): | |
# Update global decimalPoint attribute. | |
global decimalPoint | |
if decimalPoint is None: | |
decimalPoint = locale.localeconv()["decimal_point"] | |
# Get scrollbar size from system metrics. | |
self._scrollBarSize = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X) | |
self._baseClass = dGrid | |
preClass = wx.grid.Grid | |
# Internal flag indicates update invoked by grid itself. | |
self._inUpdate = False | |
# Internal flag indicates header repaint invoked by the header itself. | |
self._inHeaderPaint = False | |
# Internal flag to determine if the prior sort order needs to be restored. | |
self._sortRestored = False | |
# Internal flag to determine if the resorting is the result of the DataSet property. | |
self._settingDataSetFromSort = False | |
# Internal flag to determine if refresh should be called after sorting. | |
self._refreshAfterSort = True | |
# Local count of rows in the data table | |
self._tableRows = 0 | |
# List of visible columns | |
self._daboVisibleColumns = [] | |
# When user selects new row, does the form have responsibility for making the change? | |
self._mediateRowNumberThroughForm = True | |
# Used to provide ‘data‘ when the DataSet is empty. | |
self.emptyRowsToAdd = 0 | |
# dColumn maintains its own cell attribute object, but this is the default: | |
self._defaultGridColAttr = self._getDefaultGridColAttr() | |
# Some applications (I‘m thinking the UI Designer here) need to be able | |
# to set Editing = True, but still disallow editing. This attribute does that. | |
self._vetoAllEditing = False | |
# Can the user move the columns around? | |
self._movableColumns = True | |
# Can the user re-size the columns or rows? | |
self._resizableColumns = True | |
self._resizableRows = True | |
# Flag to indicate we are auto-sizing all columns | |
self._inAutoSizeLoop = False | |
# Flag to indicate we are in a range selection event | |
self._inRangeSelect = False | |
# Flag to indicate we are in a selection update event | |
self._inUpdateSelection = False | |
# Flag to avoid record pointer movement during DataSource setting. Only | |
# applies if dGrid.USE_DATASOURCE_BEING_SET_HACK is True (default False) | |
self._dataSourceBeingSet = False | |
# Do we show row or column labels? | |
self._showHeaders = True | |
self._showRowLabels = False | |
# Declare Internal Row Attributes | |
self._rowLabels = [] | |
self._sameSizeRows = True | |
# Declare Internal Column Attributes | |
self._columnClass = dColumn | |
self._columns = [] | |
#Declare Internal Search And Sort Attributes | |
self._searchable = True | |
self._searchDelay = None | |
self._sortable = True | |
#Declare Internal Header Attributes | |
self._headerVerticalAlignment = "Center" | |
self._headerHorizontalAlignment = "Center" | |
self._headerForeColor = None | |
self._headerBackColor = (232, 232, 232) | |
self._verticalHeaders = False | |
self._autoAdjustHeaderHeight = False | |
self._headerMaxTextHeight = 0 | |
self._columnMetrics = [(0, 0)] | |
# What color/size should the little sort indicator arrow be? | |
self._sortIndicatorColor = "yellow" | |
self._sortIndicatorSize = 8 | |
#Set NoneDisplay attributes | |
if self.Application: | |
self.__noneDisplayDefault = self.Application.NoneDisplay | |
else: | |
self.__noneDisplayDefault = _("< None >") | |
self._noneDisplay = self.__noneDisplayDefault | |
# These hold the values that affect row/col hiliting | |
self._selectionForeColor = "black" | |
self._selectionBackColor = "yellow" | |
self._selectionMode = "Cell" | |
self._modeSet = False | |
self._multipleSelection = True | |
# Track the last row and col selected | |
self._lastRow = self._lastCol = None | |
self._alternateRowColoring = False | |
self._rowColorEven = "white" | |
self._rowColorOdd = (212, 255, 212) # very light green | |
cm.dControlMixin.__init__(self, preClass, parent, properties=properties, | |
attProperties=attProperties, *args, **kwargs) | |
# Reduces grid flickering on Windows platform. | |
self._enableDoubleBuffering() | |
# Need to sync the size reported by wx to the size reported by Dabo: | |
self.RowHeight = self.RowHeight | |
self.ShowRowLabels = self.ShowRowLabels | |
# Set reasonable minimum size, as the default of (-1,-1) results in something | |
# in wx calculating the effective minsize based on how much space we need to | |
# show all the rows: | |
self.SetMinSize((100, 100)) | |
def _afterInit(self): | |
# When doing an incremental search, do we stop | |
# at the nearest matching value? | |
self.searchNearest = True | |
# Do we do case-sensitive incremental searches? | |
self.searchCaseSensitive = False | |
# How many characters of strings do we display? | |
self.stringDisplayLen = 64 | |
self.currSearchStr = "" | |
self.incSearchTimer = dabo.ui.dTimer(self) | |
self.incSearchTimer.bindEvent(dEvents.Hit, self.onIncSearchTimer) | |
# By default, row labels are not shown. They can be displayed | |
# if desired by setting ShowRowLabels = True, and their size | |
# can be adjusted by setting RowLabelWidth = <width> | |
self.SetRowLabelSize(self.RowLabelWidth) | |
self.EnableEditing(self.Editable and not self._vetoAllEditing) | |
# These need to be set to True, and custom methods provided, | |
# if a grid with variable types in a single column is used. | |
self.useCustomGetValue = False | |
self.useCustomSetValue = False | |
# flags used by mouse motion event handlers: | |
self._headerDragging = False | |
self._headerDragFrom = 0 | |
self._headerDragTo = 0 | |
self._headerSizing = False | |
self.sortedColumn = None | |
self.sortOrder = None | |
self.caseSensitiveSorting = False | |
# If there is a custom sort method, set this to True | |
self.customSort = False | |
super(dGrid, self)._afterInit() | |
# Set the header props/events | |
self.initHeader() | |
# Make sure that the columns are sized properly | |
dabo.ui.callAfter(self._updateColumnWidths) | |
@dabo.ui.deadCheck | |
def _afterInitAll(self): | |
super(dGrid, self)._afterInitAll() | |
for col in self.Columns: | |
col._setRenderer() | |
def _initEvents(self): | |
## pkm: Don‘t do the grid_cell mouse events, because we handle it manually and it | |
## would result in doubling up the events. | |
#self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__onWxGridCellMouseLeftDoubleClick) | |
#self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.__onWxGridCellMouseLeftClick) | |
#self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.__onWxGridCellMouseRightClick) | |
self.Bind(wx.grid.EVT_GRID_ROW_SIZE, self.__onWxGridRowSize) | |
self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.__onWxGridSelectCell) | |
self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.__onWxGridColSize) | |
self.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.__onWxGridEditorCreated) | |
self.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.__onWxGridEditorShown) | |
self.Bind(wx.grid.EVT_GRID_EDITOR_HIDDEN, self.__onWxGridEditorHidden) | |
self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.__onWxGridCellChange) | |
self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.__onWxGridRangeSelect) | |
self.Bind(wx.EVT_SCROLLWIN, self.__onWxScrollWin) | |
# Testing bool cell renderer/editor single-click-toggle: | |
self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.__onGridCellLeftClick_toggleCB) | |
gridWindow = self.GetGridWindow() | |
gridWindow.Bind(wx.EVT_MOTION, self.__onWxMouseMotion) | |
gridWindow.Bind(wx.EVT_LEFT_DCLICK, self.__onWxMouseLeftDoubleClick) | |
gridWindow.Bind(wx.EVT_LEFT_DOWN, self.__onWxMouseLeftDown) | |
gridWindow.Bind(wx.EVT_LEFT_UP, self.__onWxMouseLeftUp) | |
gridWindow.Bind(wx.EVT_RIGHT_DOWN, self.__onWxMouseRightDown) | |
gridWindow.Bind(wx.EVT_RIGHT_UP, self.__onWxMouseRightUp) | |
gridWindow.Bind(wx.EVT_CONTEXT_MENU, self.__onWxContextMenu) | |
self.bindEvent(dEvents.KeyDown, self._onKeyDown) | |
self.bindEvent(dEvents.KeyChar, self._onKeyChar) | |
self.bindEvent(dEvents.GridRowSize, self._onGridRowSize) | |
self.bindEvent(dEvents.GridCellSelected, self._onGridCellSelected) | |
self.bindEvent(dEvents.GridColSize, self._onGridColSize) | |
self.bindEvent(dEvents.GridCellEdited, self._onGridCellEdited) | |
self.bindEvent(dEvents.GridMouseLeftClick, self._onGridMouseLeftClick) | |
self.bindEvent(dEvents.MouseWheel, self._onGridMouseWheel) | |
## wx.EVT_CONTEXT_MENU doesn‘t appear to be working for dGrid yet: | |
# self.bindEvent(dEvents.GridContextMenu, self._onContextMenu) | |
self.bindEvent(dEvents.GridMouseRightClick, self._onGridMouseRightClick) | |
self.bindEvent(dEvents.Resize, self._onGridResize) | |
self.bindEvent(dEvents.Create, self._onCreate) | |
self.bindEvent(dEvents.Destroy, self._onDestroy) | |
super(dGrid, self)._initEvents() | |
def initHeader(self): | |
"""Initialize behavior for the grid header region.""" | |
header = self._getWxHeader() | |
self.defaultHdrCursor = header.GetCursor() | |
self._headerNeedsRedraw = False | |
self._lastHeaderMousePosition = None | |
self._headerMouseLeftDown, self._headerMouseRightDown = False, False | |
header.Bind(wx.EVT_LEFT_DCLICK, self.__onWxHeaderMouseLeftDoubleClick) | |
header.Bind(wx.EVT_LEFT_DOWN, self.__onWxHeaderMouseLeftDown) | |
header.Bind(wx.EVT_LEFT_UP, self.__onWxHeaderMouseLeftUp) | |
header.Bind(wx.EVT_RIGHT_DOWN, self.__onWxHeaderMouseRightDown) | |
header.Bind(wx.EVT_RIGHT_UP, self.__onWxHeaderMouseRightUp) | |
header.Bind(wx.EVT_MOTION, self.__onWxHeaderMouseMotion) | |
header.Bind(wx.EVT_PAINT, self.__onWxHeaderPaint) | |
header.Bind(wx.EVT_CONTEXT_MENU, self.__onWxHeaderContextMenu) | |
header.Bind(wx.EVT_ENTER_WINDOW, self.__onWxHeaderMouseEnter) | |
header.Bind(wx.EVT_LEAVE_WINDOW, self.__onWxHeaderMouseLeave) | |
header.Bind(wx.EVT_IDLE, self.__onWxHeaderIdle) | |
self.bindEvent(dEvents.GridHeaderMouseLeftDown, self._onGridHeaderMouseLeftDown) | |
self.bindEvent(dEvents.GridHeaderMouseMove, self._onGridHeaderMouseMove) | |
self.bindEvent(dEvents.GridHeaderMouseLeftUp, self._onGridHeaderMouseLeftUp) | |
self.bindEvent(dEvents.GridHeaderMouseRightUp, self._onGridHeaderMouseRightUp) | |
self.bindEvent(dEvents.GridHeaderMouseRightClick, self._onGridHeaderMouseRightClick) | |
def update(self): | |
""" | |
Call this when your datasource or dataset has changed to get the grid showing | |
the proper number of rows with current data. | |
""" | |
# We never call the superclass update, because we don‘t need/want that behavior. | |
last = getattr(self, "_lastCellSelectedTime", 0) | |
cur = time.time() | |
if cur - last < .5: | |
return | |
self._syncRowCount() | |
self._syncCurrentRow() | |
self.refresh() ## to clear the cache and repaint the cells | |
def _syncAll(self): | |
self._syncRowCount() | |
self._syncColumnCount() | |
self._syncCurrentRow() | |
def refresh(self): | |
"""Repaint the grid.""" | |
if getattr(self, "__inRefresh", False): | |
return | |
self.__inRefresh = True | |
self._Table._clearCache() ## Make sure the proper values are filled into the cells | |
# Force invisible column dynamic properties to update (possible to make Visible again): | |
invisible_cols = [c._updateDynamicProps() for c in self.Columns if not c.Visible] | |
super(dGrid, self).refresh() | |
self.__inRefresh = False | |
def _refreshHeader(self): | |
self._getWxHeader().Refresh() | |
def GetCellValue(self, row, col, useCache=True): | |
try: | |
ret = self._Table.GetValue(row, col, useCache=useCache) | |
except AttributeError: | |
ret = super(dGrid, self).GetCellValue(row, col) | |
return ret | |
def GetValue(self, row, col, dynamicUpdate=True): | |
try: | |
ret = self._Table.GetValue(row, col, dynamicUpdate=dynamicUpdate) | |
except (AttributeError, TypeError): | |
ret = super(dGrid, self).GetValue(row, col) | |
return ret | |
def SetValue(self, row, col, val): | |
try: | |
self._Table.SetValue(row, col, val) | |
except StandardError, e: | |
super(dGrid, self).SetCellValue(row, col, val) | |
# Update the main data source | |
self._setCellValue(row, col, val) | |
def _setCellValue(self, row, col, val): | |
try: | |
column = self.Columns[col] | |
fld = column.DataField | |
biz = self.getBizobj() | |
if isinstance(val, float) and column.DataType == "decimal": | |
val = Decimal(ustr(val)) | |
if biz: | |
biz.RowNumber = row | |
biz.setFieldVal(fld, val) | |
else: | |
self.DataSet[row][fld] = val | |
except StandardError, e: | |
dabo.log.error("Cannot update data set: %s" % e) | |
# Wrapper methods to Dabo-ize these calls. | |
def getValue(self, row=None, col=None): | |
""" | |
Returns the value of the specified row and column. | |
If no row/col is specified, the current row/col will be used. | |
""" | |
if row is None: | |
row = self.CurrentRow | |
if col is None: | |
col = self.CurrentColumn | |
ret = self.GetValue(row, col, dynamicUpdate=False) | |
if isinstance(ret, str): | |
ret = ret.decode(self.Encoding) | |
return ret | |
def setValue(self, row, col, val): | |
return self.SetValue(row, col, val) | |
# These two methods need to be customized if a grid has columns | |
# with more than one type of data in them. | |
def customCanGetValueAs(self, row, col, typ): pass | |
def customCanSetValueAs(self, row, col, typ): pass | |
# Wrap the native wx methods | |
def setEditorForCell(self, row, col, edt): | |
## dColumn maintains a dict of overriding editor mappings, but keep this | |
## function for convenience. | |
dcol = self.Columns[col] | |
dcol.CustomEditors[row] = edt | |
#self.SetCellEditor(row, col, edt) | |
def setRendererForCell(self, row, col, rnd): | |
## dColumn maintains a dict of overriding renderer mappings, but keep this | |
## function for convenience. | |
dcol = self.Columns[col] | |
dcol.CustomRenderers[row] = rnd | |
#self.SetCellRenderer(row, col, rnd) | |
def typeFromDataField(self, df, col=None): | |
""" | |
When the DataField is set for a column, it needs to set the corresponding | |
value of its DataType property. Will return the Python data type, or None if | |
there is no bizobj, or no DataStructure info available in the bizobj. | |
""" | |
biz = self.getBizobj() | |
if biz is None: | |
if col is not None: | |
return col.getDataTypeForColumn() | |
else: | |
return None | |
try: | |
pyType = biz.getDataTypeForField(df) | |
except ValueError, e: | |
dabo.log.error(e) | |
return None | |
return pyType | |
def precisionFromDataField(self, df): | |
""" | |
Return the decimal precision for the passed data field, or the default | |
precision if this isn‘t a decimal field or it isn‘t specified in the | |
bizobj. | |
""" | |
default = 2 | |
biz = self.getBizobj() | |
if biz is not None: | |
ret = biz.getPrecisionForField(df) | |
if ret is not None: | |
return ret | |
return default | |
def getTableClass(cls): | |
""" | |
We don‘t expose the underlying table class to the ui namespace, as it‘s a | |
wx-specific implementation detail, but for cases where you need to subclass | |
the table, this classmethod will return the class reference. | |
""" | |
return dGridDataTable | |
getTableClass = classmethod(getTableClass) | |
def setTableAttributes(self, tbl=None): | |
"""Set the attributes for table display""" | |
if tbl is None: | |
try: | |
tbl = self._Table | |
except TypeError: | |
tbl = None | |
if tbl is None: | |
# Still not fully constructed | |
dabo.ui.callAfter(self.setTableAttributes) | |
return | |
tbl.alternateRowColoring = self.AlternateRowColoring | |
tbl.rowColorOdd = self._getWxColour(self.RowColorOdd) | |
tbl.rowColorEven = self._getWxColour(self.RowColorEven) | |
def afterCellEdit(self, row, col): | |
"""Called after a cell has been edited by the user.""" | |
pass | |
def fillGrid(self, force=False): | |
"""Refresh the grid to match the data in the data set.""" | |
# Get the default row size from dApp‘s user settings | |
rowSize = self._getUserSetting("RowSize") | |
if rowSize: | |
self.SetDefaultRowSize(rowSize) | |
tbl = self._Table | |
if self.emptyRowsToAdd and self.Columns: | |
# Used for display purposes when no data is present. | |
self._addEmptyRows() | |
tbl.setColumns(self.Columns) | |
self._tableRows = tbl.fillTable(force) | |
if not self._sortRestored: | |
dabo.ui.callAfter(self._restoreSort) | |
self._sortRestored = True | |
# This will make sure that the current selection mode is activated. | |
# We can‘t do it until after the first time the grid is filled. | |
if not self._modeSet: | |
self._modeSet = True | |
self.SelectionMode = self.SelectionMode | |
# I‘ve found that both refresh calls are needed sometimes, especially | |
# on Linux when manually moving a column header with the mouse. | |
dabo.ui.callAfterInterval(200, self.refresh) | |
self.refresh() | |
def _updateDaboVisibleColumns(self): | |
try: | |
self._daboVisibleColumns = [e[0] for e in enumerate(self._columns) if e[1].Visible] | |
except wx._core.PyAssertionError, e: | |
# Can happen when an editor is active and columns resize | |
vis = [] | |
for pos, col in enumerate(self._columns): | |
if col.Visible: | |
vis.append(pos) | |
self._daboVisibleColumns = vis | |
def _convertWxColNumToDaboColNum(self, wxCol): | |
""" | |
For the Visible property to work, we need to convert the column number | |
wx sends to the actual column index in grid.Columns. | |
Returns None if there is no corresponding dabo column. | |
""" | |
try: | |
return self._daboVisibleColumns[wxCol] | |
except IndexError: | |
return None | |
def _convertDaboColNumToWxColNum(self, daboCol): | |
""" | |
For the Visible property to work, we need to convert the column number | |
dabo uses in grid.Columns to the wx column. | |
Returns None if there is no corresponding wx column. | |
""" | |
try: | |
return self._daboVisibleColumns.index(daboCol) | |
except ValueError: | |
return None | |
def _restoreSort(self): | |
if not self.Sortable: | |
return | |
self.sortedColumn = self._getUserSetting("sortedColumn") | |
self.sortOrder = self._getUserSetting("sortOrder") | |
if self.sortedColumn is not None: | |
sortCol = None | |
for idx, col in enumerate(self.Columns): | |
if col.DataField == self.sortedColumn: | |
sortCol = idx | |
break | |
if sortCol is not None and col.Sortable: | |
if self.RowCount > 0: | |
self.processSort(sortCol, toggleSort=False) | |
def _addEmptyRows(self): | |
""" | |
Adds blank rows of data to the grid. Used mostly by | |
the Designer to display a grid that actually looks like a grid. | |
""" | |
# First, get the type and field name for each column, and | |
# add an empty value to a dict. | |
colDict = {} | |
for col in self.Columns: | |
val = " " * 10 | |
dt = col.DataType | |
if dt is "bool": | |
val = False | |
elif dt in ("int", "long"): | |
val = 0 | |
elif dt in ("float", "decimal"): | |
val = 0.00 | |
colDict[col.DataField] = val | |
# Now add as many rows as specified | |
ds = [] | |
for cnt in xrange(self.emptyRowsToAdd): | |
ds.append(colDict) | |
self.emptyRowsToAdd = 0 | |
self.DataSet = ds | |
def buildFromDataSet(self, ds, keyCaption=None, | |
includeFields=None, colOrder=None, colWidths=None, colTypes=None, | |
autoSizeCols=True): | |
""" | |
Add columns with properties set based on the passed dataset. | |
A dataset is defined as one of: | |
+ a sequence of dicts, containing fieldname/fieldvalue pairs. | |
+ a string, which maps to a bizobj on the form. | |
The columns will be taken from the first record of the dataset, with each | |
column header caption being set to the field name, unless the optional | |
keyCaption parameter is passed. This parameter is a 1:1 dict containing | |
the data set keys as its keys, and the desired caption as the | |
corresponding value. | |
If the includeFields parameter is a sequence, the only columns added will | |
be the fieldnames included in the includeFields sequence. If the | |
includeFields parameter is None, all fields will be added to the grid. | |
The columns will be in the order returned by ds.keys(), unless the | |
optional colOrder parameter is passed. Like the keyCaption property, | |
this is a 1:1 dict containing key:order. | |
""" | |
if not ds: | |
return False | |
if colOrder is None: | |
colOrder = {} | |
if colWidths is None: | |
colWidths = {} | |
if colTypes is None: | |
colTypes = {} | |
if isinstance(ds, basestring) or isinstance(ds, dabo.biz.dBizobj): | |
# Assume it is a bizobj datasource. | |
if self.DataSource != ds: | |
self.DataSource = ds | |
else: | |
self.DataSource = None | |
self.DataSet = ds | |
bizobj = self.getBizobj() | |
if bizobj: | |
data = bizobj.getDataSet(rows=1) | |
if data: | |
firstRec = data[0] | |
else: | |
# Ok, the bizobj doesn‘t have any records, yet we still want to build | |
# the grid. We can get enough info from getDataStructureFromDescription(): | |
try: | |
structure = bizobj.getDataStructureFromDescription() | |
except TypeError: | |
# Well, that call failed... seems that sqlite doesn‘t define a cursor | |
# description? I need to test this out. For now, fall back to the old | |
# code that gets the data structure by executing "select * from table | |
# where 1=0". The downside to this is that no derived fields will be | |
# included in the structure. | |
structure = bizobj.getDataStructure() | |
firstRec = {} | |
for field in structure: | |
firstRec[field[0]] = None | |
if field[0] not in colTypes: | |
colTypes[field[0]] = field[1] | |
else: | |
# not a bizobj datasource | |
firstRec = ds[0] | |
colKeys = [key for key in firstRec.keys() | |
if (includeFields is None or key in includeFields)] | |
# Add the columns | |
for colKey in colKeys: | |
# Use the keyCaption values, if possible | |
try: | |
cap = keyCaption[colKey] | |
except (KeyError, TypeError): | |
cap = colKey | |
col = self.addColumn(inBatch=True) | |
col.Caption = cap | |
col.DataField = colKey | |
## pkm: Get the datatype from what is specified in fieldspecs, not from | |
## the actual type of the record. | |
try: | |
dt = colTypes[colKey] | |
except KeyError: | |
# But if it didn‘t exist in the fieldspecs, use the actual type: | |
dt = type(firstRec[colKey]) | |
if dt is type(None): | |
if bizobj: | |
for idx in range(bizobj.RowCount)[1:]: | |
val = bizobj.getFieldVal(colKey, idx) | |
if val is not None: | |
dt = type(val) | |
break | |
else: | |
for rec in ds[1:]: | |
val = rec[colKey] | |
if val is not None: | |
dt = type(val) | |
break | |
col.DataType = dt | |
if dt is type(None): | |
# Default to string type | |
dt = col.DataType = str | |
# See if any order was specified | |
if colKey in colOrder: | |
col.Order = colOrder[colKey] | |
# See if any width was specified | |
if colKey in colWidths: | |
col.Width = colWidths[colKey] | |
else: | |
# Use a default width | |
col.Width = -1 | |
# Populate the grid | |
self.fillGrid(True) | |
if autoSizeCols: | |
self.autoSizeCol("all", True) | |
return True | |
def _onCreate(self, evt): | |
self.restoreDataSet() | |
def _onDestroy(self, evt): | |
self.saveDataSet() | |
def _onGridResize(self, evt): | |
# Prevent unnecessary event processing. | |
try: | |
updCol = (self._lastSize != evt._uiEvent.Size) | |
except AttributeError: | |
updCol = True | |
if updCol: | |
self._lastSize = evt._uiEvent.Size | |
dabo.ui.callAfter(self._updateColumnWidths) | |
def _totalContentWidth(self, addScrollBar=False): | |
ret = sum([col.Width for col in self.Columns]) | |
if self.ShowRowLabels: | |
ret += self.RowLabelWidth | |
if addScrollBar and self.isScrollBarVisible("v"): | |
ret += self._scrollBarSize | |
return ret | |
def _totalContentHeight(self, addScrollBar=False): | |
if self.SameSizeRows: | |
ret = self.RowHeight * self.RowCount | |
else: | |
ret = sum([self.GetRowSize(r) for r in xrange(self.RowCount)]) | |
if self.ShowHeaders: | |
ret += self.HeaderHeight | |
if addScrollBar and self.isScrollBarVisible("h"): | |
ret += self._scrollBarSize | |
return ret | |
def isScrollBarVisible(self, which): | |
whichSide = {"h": wx.HORIZONTAL, "v": wx.VERTICAL}[which[0].lower()] | |
sr = self.GetScrollRange(whichSide) | |
if self.Application.Platform in ("Win", "GTK"): | |
# For some reason, GetScrollRange() returns either 1 or 101 when the scrollbar | |
# is not visible under Windows or GTK. Under OS X, it returns 0 as expected. | |
return sr not in (1, 101) | |
return bool(sr) | |
@dabo.ui.deadCheck | |
def _updateColumnWidths(self): | |
""" | |
See if there are any dynamically-sized columns, and resize them | |
accordingly. | |
""" | |
try: | |
if self._inColWidthUpdate: | |
return | |
except AttributeError: | |
pass | |
self._inColWidthUpdate = False | |
if [col for col in self.Columns if col.Expand]: | |
dabo.ui.callAfterInterval(10, self._delayedUpdateColumnWidths) | |
def _delayedUpdateColumnWidths(self, redo=False): | |
def _setFlag(): | |
self._inColWidthUpdate = True | |
self.BeginBatch() | |
def _clearFlag(): | |
self._inColWidthUpdate = False | |
self.EndBatch() | |
if self._inColWidthUpdate: | |
return | |
_setFlag() | |
dynCols = [col for col in self.Columns | |
if col.Expand] | |
dynColCnt = len(dynCols) | |
colWd = self._totalContentWidth(addScrollBar=True) | |
rowHt = self._totalContentHeight() | |
grdWd = self.Width | |
# Subtract extra pixels to avoid triggering the scroll bar. Again, this | |
# will probably be OS-dependent | |
diff = grdWd - colWd - 10 | |
if redo and not diff: | |
diff = -10 | |
if not diff: | |
dabo.ui.callAfterInterval(5, _clearFlag) | |
return | |
if not redo and (diff == self._scrollBarSize): | |
# This can cause infinite loops as we adjust constantly | |
diff -= 1 | |
adj = diff/ dynColCnt | |
mod = diff % dynColCnt | |
for col in dynCols: | |
if mod: | |
newWidth = col.Width + (adj+1) | |
mod -= 1 | |
else: | |
newWidth = col.Width + adj | |
# Don‘t allow the Expand columns to shrink below 24px wide. | |
col.Width = max(24, newWidth) | |
# Check to see if we need a further adjustment | |
adjWd = self._totalContentWidth() | |
if self.isScrollBarVisible("h") and (adjWd < grdWd): | |
_clearFlag() | |
self._delayedUpdateColumnWidths(redo=True) | |
else: | |
dabo.ui.callAfter(_clearFlag) | |
def autoSizeCol(self, colNum, persist=False): | |
""" | |
Set the column to the minimum width necessary to display its data. | |
Set colNum=‘all‘ to auto-size all columns. Set persist=True to persist the | |
new width to the user settings table. | |
""" | |
if isinstance(colNum, basestring) and colNum.lower() == "all": | |
self.BeginBatch() | |
self._inAutoSizeLoop = True | |
for ii in range(len(self.Columns)): | |
self.autoSizeCol(ii, persist=persist) | |
self._updateColumnWidths() | |
self.EndBatch() | |
self._inAutoSizeLoop = False | |
return | |
maxWidth = 250 ## limit the width of the column to something reasonable | |
if not self._inAutoSizeLoop: | |
# lock the screen | |
self.lockDisplay() | |
## This function will get used in both if/elif below: | |
def _setColSize(idx): | |
sortIconSize = self.SortIndicatorSize | |
sortIconBuffer = sortIconSize / 2 | |
## breathing room around header caption: | |
capBuffer = 5 | |
## add additional room to account for possible sort indicator: | |
capBuffer += ((2 * sortIconSize) + (2 * sortIconBuffer)) | |
colObj = self.Columns[idx] | |
if not colObj.Visible: | |
## wx knows nothing about Dabo‘s invisible columns | |
return | |
idx = self._convertDaboColNumToWxColNum(idx) | |
autoWidth = self.GetColSize(idx) | |
# Account for the width of the header caption: | |
cw = dabo.ui.fontMetricFromFont(colObj.Caption, | |
colObj.HeaderFont._nativeFont)[0] + capBuffer | |
w = max(autoWidth, cw) | |
w = min(w, maxWidth) | |
colObj.Width = w | |
if persist: | |
colObj._persist("Width") | |
try: | |
self.AutoSizeColumn(self._convertDaboColNumToWxColNum(colNum), setAsMin=False) | |
except (TypeError, wx.PyAssertionError): | |
pass | |
if colNum > -1: | |
_setColSize(colNum) | |
if not self._inAutoSizeLoop: | |
self.refresh() | |
self.unlockDisplay() | |
self._updateColumnWidths() | |
def _paintHeader(self, updateBox=None): | |
""" | |
This method handles all of the display for the header, including writing | |
the Captions along with any sort indicators. | |
""" | |
if self._inHeaderPaint: | |
return | |
self._inHeaderPaint = True | |
w = self._getWxHeader() | |
w.SetBackgroundColour((255, 255, 255)) | |
if updateBox is None: | |
updateBox = w.GetClientRect() | |
try: | |
# When called from OnPaint event, there should be PaintDC context. | |
dc = wx.PaintDC(w) | |
except wx.PyAssertionError: | |
dc = wx.ClientDC(w) | |
textAngle = {True: 90, False: 0}[self.VerticalHeaders] | |
self._columnMetrics = [] | |
for idx, col in enumerate(self._columns): | |
headerRect = col._getHeaderRect() | |
intersect = wx.IntersectRect(updateBox, headerRect) | |
if intersect is None: | |
# column isn‘t visible | |
continue | |
headerRect[0] -= 1 | |
headerRect[2] += 1 | |
sortIndicator = False | |
colObj = self.getColByX(intersect[0]) | |
if not colObj: | |
# Grid is probably being created or destroyed, so just skip it | |
continue | |
dc.SetClippingRegion(*headerRect) | |
holdBrush = dc.GetBrush() | |
holdPen = dc.GetPen() | |
fcolor = colObj.HeaderForeColor | |
if fcolor is None: | |
fcolor = self.HeaderForeColor | |
if fcolor is None: | |
fcolor = (0,0,0) | |
bcolor = colObj.HeaderBackColor | |
if bcolor is None: | |
bcolor = self.HeaderBackColor | |
dc.SetTextForeground(fcolor) | |
wxNativeFont = colObj.HeaderFont._nativeFont | |
# draw the col. header background: | |
if bcolor is not None: | |
dc.SetBrush(wx.Brush(bcolor, wx.SOLID)) | |
dc.SetPen(wx.Pen(fcolor, width=0)) | |
dc.DrawRectangle(*headerRect) | |
# draw the col. border: | |
dc.SetBrush(wx.TRANSPARENT_BRUSH) | |
dc.SetPen(self.GetDefaultGridLinePen()) | |
dc.DrawRectangle(*headerRect) | |
dc.SetPen(holdPen) | |
dc.SetBrush(holdBrush) | |
if colObj.DataField == self.sortedColumn: | |
sortIndicator = True | |
sortIconSize = self.SortIndicatorSize | |
sortIconBuffer = sortIconSize / 2 | |
# draw a triangle, pointed up or down, at the top left | |
# of the column. TODO: Perhaps replace with prettier icons | |
left = headerRect[0] + sortIconBuffer | |
top = headerRect[1] + sortIconBuffer | |
brushColor = self.SortIndicatorColor | |
if isinstance(brushColor, basestring): | |
brushColor = dColors.colorTupleFromName(brushColor) | |
dc.SetBrush(wx.Brush(brushColor, wx.SOLID)) | |
if self.sortOrder == "DESC": | |
# Down arrow | |
dc.DrawPolygon([(left, top), (left + sortIconSize, top), | |
(left + sortIconBuffer, top + sortIconSize)]) | |
elif self.sortOrder == "ASC": | |
# Up arrow | |
dc.DrawPolygon([(left + sortIconBuffer, top), | |
(left + sortIconSize, top + sortIconSize), | |
(left, top + sortIconSize)]) | |
else: | |
# Column is not sorted, so don‘t draw. | |
sortIndicator = False | |
dc.SetFont(wxNativeFont) | |
ah = colObj.HeaderHorizontalAlignment | |
av = colObj.HeaderVerticalAlignment | |
if ah is None: | |
ah = self.HeaderHorizontalAlignment | |
if av is None: | |
av = self.HeaderVerticalAlignment | |
if ah is None: | |
ah = "Center" | |
if av is None: | |
av = "Bottom" | |
wxah = {"Center": wx.ALIGN_CENTRE_HORIZONTAL, | |
"Left": wx.ALIGN_LEFT, | |
"Right": wx.ALIGN_RIGHT}[ah] | |
wxav = {"Center": wx.ALIGN_CENTRE_VERTICAL, | |
"Top": wx.ALIGN_TOP, | |
"Bottom": wx.ALIGN_BOTTOM}[av] | |
# Give some more space around the rect - some platforms use a 3d look | |
# and anyway it looks better if left/right aligned text isn‘t right on | |
# the line. | |
horBuffer = 3 | |
vertBuffer = 2 | |
sortBuffer = horBuffer | |
if sortIndicator: | |
# If there‘s a sort indicator, we‘ll nudge the caption over | |
sortBuffer += (sortIconSize + sortIconBuffer) | |
trect = list(headerRect) | |
trect[0] = trect[0] + sortBuffer | |
trect[1] = trect[1] + vertBuffer | |
if ah == "Center": | |
trect[2] = trect[2] - (2 * sortBuffer) | |
else: | |
trect[2] = trect[2] - (horBuffer + sortBuffer) | |
trect[3] = trect[3] - (2 * vertBuffer) | |
trect = wx.Rect(*trect) | |
twd, tht = dabo.ui.fontMetricFromDC(dc, colObj.Caption) | |
if self.VerticalHeaders: | |
# Note that when rotating 90 degrees, the width affect height, | |
# and vice-versa | |
twd, tht = tht, twd | |
self._columnMetrics.append((twd, tht)) | |
# Figure out the x,y coordinates to start the text drawing. | |
left, top, wd, ht = trect | |
x = left | |
if ah == "Center": | |
x += (wd / 2) - (twd / 2) | |
elif ah == "Right": | |
x += wd - twd | |
# Note that we need to adjust for text height when angle is 0. | |
yadj = 0 | |
if textAngle == 0: | |
yadj = tht | |
y = top + ht - yadj | |
if av == "Top": | |
y = top + tht + 2 - yadj | |
elif av == "Center": | |
y = top + (ht / 2) + (tht / 2) - yadj | |
txt = self.drawText("%s" % colObj.Caption, x, y, angle=textAngle, | |
persist=False, dc=dc, useDefaults=True) | |
dc.DestroyClippingRegion() | |
if self.AutoAdjustHeaderHeight: | |
self.fitHeaderHeight() | |
self._inHeaderPaint = False | |
def fitHeaderHeight(self): | |
""" | |
Sizes the HeaderHeight to comfortably fit the captions. Primarily used for | |
vertical captions or multi-line captions. | |
""" | |
self._paintHeader() | |
if self._columnMetrics: | |
self._headerMaxTextHeight = max([cht for cwd, cht in self._columnMetrics]) | |
else: | |
self._headerMaxTextHeight = 0 | |
diff = (self._headerMaxTextHeight + 20) - self.HeaderHeight | |
if diff: | |
self.HeaderHeight += diff | |
dabo.ui.callAfter(self.refresh) | |
def showColumn(self, col, visible): | |
""" | |
If the column is not shown and visible=True, show it. Likewise | |
but opposite if visible=False. | |
""" | |
col = self._resolveColumn(col, logOnly=True) | |
if col is None: | |
# Invalid ‘col‘ passed | |
return | |
col._visible = visible | |
self._syncColumnCount() | |
if getattr(self.Parent, "__inRefresh", False): | |
self.refresh() | |
def moveColumn(self, colNum, toNum): | |
"""Move the column to a new position.""" | |
oldCol = self.Columns[colNum] | |
self.Columns.remove(oldCol) | |
if toNum > colNum: | |
self.Columns.insert(toNum-1, oldCol) | |
else: | |
self.Columns.insert(toNum, oldCol) | |
for col in self.Columns: | |
col.Order = self.Columns.index(col) * 10 | |
col._persist("Order") | |
self.fillGrid(True) | |
def getColumnValueByRow(self, col, row): | |
"""Returns the value in the given column and row.""" | |
if isinstance(col, dColumn): | |
colnum = self.Columns.index(col) | |
else: | |
colnum = col | |
return self.GetValue(row, colnum) | |
def sizeToColumns(self, scrollBarFudge=True): | |
""" | |
Set the width of the grid equal to the sum of the widths of the columns. | |
If scrollBarFudge is True, additional space will be added to account for | |
the width of the vertical scrollbar. | |
""" | |
fudge = 5 | |
if scrollBarFudge: | |
fudge = 18 | |
self.Width = reduce(operator.add, [col.Width for col in self.Columns]) + fudge | |
def sizeToRows(self, maxHeight=500, scrollBarFudge=True): | |
""" | |
Set the height of the grid equal to the sum of the heights of the rows. | |
This is intended to be used only when the number of rows is expected to be | |
low. Set maxHeight to whatever you want the maximum height to be. | |
""" | |
fudge = 5 | |
if scrollBarFudge: | |
fudge = 18 | |
self.Height = min(self.RowHeight * self.RowCount, maxHeight) + fudge | |
def onIncSearchTimer(self, evt): | |
""" | |
Occurs when the incremental search timer reaches its interval. | |
It is time to run the search, if there is any search in the buffer. | |
""" | |
if self.currSearchStr not in ("", "\n", "\r", "\r\n"): | |
self.runIncSearch() | |
else: | |
self.incSearchTimer.stop() | |
##----------------------------------------------------------## | |
## begin: user hook methods ## | |
##----------------------------------------------------------## | |
def fillContextMenu(self, menu): | |
""" | |
User hook called just before showing the context menu. | |
User code can append menu items, or replace/remove the menu entirely. | |
Return a dMenu or None from this hook. Default: no context menu. | |
""" | |
return menu | |
def fillHeaderContextMenu(self, menu): | |
""" | |
User hook called just before showing the context menu for the header. | |
User code can append menu items, or replace/remove the menu entirely. | |
Return a dMenu or None from this hook. The default menu includes an | |
option to autosize the column. | |
""" | |
return menu | |
##----------------------------------------------------------## | |
## end: user hook methods ## | |
##----------------------------------------------------------## | |
def sort(self): | |
"""Hook method used in subclasses for custom sorting.""" | |
pass | |
def processSort(self, gridCol=None, toggleSort=True): | |
""" | |
Sort the grid column. | |
Toggle between ascending and descending. If the grid column index isn‘t | |
passed, the currently active grid column will be sorted. | |
""" | |
if gridCol is None: | |
gridCol = self.CurrentColumn | |
colObj = self._resolveColumn(gridCol) | |
canSort = (self.Sortable and colObj.Sortable) | |
columnToSort = colObj.DataField | |
sortCol = self.Columns.index(colObj) | |
dataType = self.Columns[sortCol].DataType | |
if not canSort: | |
# Some columns, especially those with mixed values, | |
# should not be sorted. | |
return | |
sortOrder="ASC" | |
if columnToSort == self.sortedColumn: | |
sortOrder = self.sortOrder | |
if toggleSort: | |
if sortOrder == "ASC": | |
sortOrder = "DESC" | |
elif sortOrder == "DESC": | |
columnToSort = None | |
sortOrder = "ASC" | |
else: | |
sortOrder = "ASC" | |
self.sortOrder = sortOrder | |
self.sortedColumn = columnToSort | |
eventData = {"column": colObj, "sortOrder": sortOrder} | |
self.raiseEvent(dEvents.GridBeforeSort, eventObject=self, | |
eventData=eventData) | |
biz = self.getBizobj() | |
if columnToSort is not None: | |
if self.customSort: | |
# Grids tied to bizobj cursors may want to use their own sorting. | |
self.sort() | |
elif biz: | |
# Use the default sort() in the bizobj: | |
try: | |
biz.sort(columnToSort, sortOrder, self.caseSensitiveSorting) | |
except dException.NoRecordsException: | |
# no records to sort: who cares. | |
pass | |
else: | |
# Create the list to hold the rows for sorting | |
caseSensitive = self.caseSensitiveSorting | |
sortList = [] | |
rowNum = 0 | |
rowlabels = self.RowLabels | |
if self.DataSet: | |
for row in self.DataSet: | |
if rowlabels: | |
sortList.append([row[columnToSort], row, rowlabels[rowNum]]) | |
rowNum += 1 | |
else: | |
sortList.append([row[columnToSort], row]) | |
# At this point we have a list consisting of lists. Each of these member | |
# lists contain the sort value in the zeroth element, and the row as | |
# the first element. | |
# First, see if we are comparing strings | |
if dataType is None: | |
f = sortList[0][0] | |
if f is None: | |
# We are just poking around, trying to glean the datatype, which is prone | |
# to error. The record we just checked is None, so try the last record and | |
# then give up. | |
f = sortList[-1][0] | |
#pkm: I think grid column DataType properties should store raw python | |
# types, not string renditions of them. But for now, convert to | |
# string renditions. I also think that this codeblock should be | |
# obsolete once all dabo grids use dColumn objects. | |
if isinstance(f, datetime.date): | |
dataType = "date" | |
elif isinstance(f, datetime.datetime): | |
dataType = "datetime" | |
elif isinstance(f, unicode): | |
dataType = "unicode" | |
elif isinstance(f, str): | |
dataType = "string" | |
elif isinstance(f, long): | |
dataType = "long" | |
elif isinstance(f, int): | |
dataType = "int" | |
elif isinstance(f, Decimal): | |
dataType = "decimal" | |
else: | |
dataType = None | |
sortingStrings = isinstance(sortList[0][0], basestring) | |
else: | |
sortingStrings = dataType in ("unicode", "string") | |
if sortingStrings and not caseSensitive: | |
sortKey = caseInsensitiveSortKey | |
elif dataType in ("date", "datetime"): | |
# can‘t compare NoneType to these types: | |
sortKey = noneSortKey | |
else: | |
sortKey = None | |
sortList.sort(key=sortKey, reverse=(sortOrder == "DESC")) | |
# Extract the rows into a new list, then set the dataSet to the new list | |
newRows = [] | |
newLabels = [] | |
for elem in sortList: | |
newRows.append(elem[1]) | |
if self.RowLabels: | |
newLabels.append(elem[2]) | |
self.RowLabels = newLabels | |
# Set this to avoid infinite loops | |
self._settingDataSetFromSort = True | |
self.DataSet = newRows | |
self._settingDataSetFromSort = False | |
if biz: | |
dabo.ui.setAfter(self, "CurrentRow", biz.RowNumber) | |
if self._refreshAfterSort: | |
self.refresh() | |
self._setUserSetting("sortedColumn", columnToSort) | |
self._setUserSetting("sortOrder", sortOrder) | |
self.raiseEvent(dEvents.GridAfterSort, eventObject=self, | |
eventData=eventData) | |
dabo.ui.callAfterInterval(200, self.Form.update) ## rownum in status bar | |
def restoreDataSet(self): | |
if self.SaveRestoreDataSet: | |
ds = self.Application.getUserSetting("%s.DataSet" | |
% self.getAbsoluteName()) | |
if ds is not None: | |
self.DataSet = ds | |
def saveDataSet(self): | |
if self.SaveRestoreDataSet: | |
self.Application.setUserSetting("%s.DataSet" | |
% self.getAbsoluteName(), self.DataSet) | |
def runIncSearch(self): | |
"""Run the incremental search.""" | |
gridCol = self.CurrentColumn | |
if gridCol < 0: | |
gridCol = 0 | |
fld = self.Columns[gridCol].DataField | |
if self.RowCount <= 0: | |
# Nothing to seek within! | |
return | |
if not (self.Searchable and self.Columns[gridCol].Searchable): | |
# Doesn‘t apply to this column. | |
self.currSearchStr = "" | |
return | |
newRow = self.CurrentRow | |
biz = self.getBizobj() | |
srchVal = origSrchStr = self.currSearchStr | |
self.currSearchStr = "" | |
near = self.searchNearest | |
caseSensitive = self.searchCaseSensitive | |
# Copy the specified field vals and their row numbers to a list, and | |
# add those lists to the sort list | |
sortList = [] | |
for i in range(0, self.RowCount): | |
if biz: | |
val = biz.getFieldVal(fld, i, _forceNoCallback=True) | |
else: | |
val = self.DataSet[i][fld] | |
sortList.append( [val, i] ) | |
# Determine if we are seeking string values | |
compString = False | |
for row in sortList: | |
if row[0] is not None: | |
compString = isinstance(row[0], basestring) | |
break | |
if not compString: | |
# coerce srchVal to be the same type as the field type | |
listval = sortList[0][0] | |
if isinstance(listval, int): | |
try: | |
srchVal = int(srchVal) | |
except ValueError: | |
srchVal = int(0) | |
elif isinstance(listval, long): | |
try: | |
srchVal = long(srchVal) | |
except ValueError: | |
srchVal = long(0) | |
elif isinstance(listval, float): | |
try: | |
srchVal = float(srchVal) | |
except ValueError: | |
srchVal = float(0) | |
elif isinstance(listval, (datetime.datetime, datetime.date, datetime.time)): | |
# We need to convert the sort vals into strings | |
sortList = [(ustr(vv), i) for vv, i in sortList] | |
compString = True | |
# Now iterate through the list to find the matching value. I know that | |
# there are more efficient search algorithms, but for this purpose, we‘ll | |
# just use brute force | |
if compString: | |
if caseSensitive: | |
mtchs = [vv for vv in sortList | |
if isinstance(vv[0], basestring) and vv[0].startswith(srchVal)] | |
else: | |
srchVal = srchVal.lower() | |
mtchs = [vv for vv in sortList | |
if isinstance(vv[0], basestring) and vv[0].lower().startswith(srchVal)] | |
else: | |
mtchs = [vv for vv in sortList | |
if vv[0] == srchVal] | |
if mtchs: | |
# The row num is the second element. We want the first row in | |
# the list, since it will still be sorted. | |
newRow = mtchs[0][1] | |
else: | |
for fldval, row in sortList: | |
if not compString or caseSensitive: | |
match = (fldval == srchVal) | |
else: | |
# Case-insensitive string search. | |
match = (isinstance(fldval, basestring) and fldval.lower() == srchVal) | |
if match: | |
newRow = row | |
break | |
else: | |
if near: | |
newRow = row | |
# If we are doing a near search, see if the row is less than the | |
# requested matching value. If so, update the value of ‘ret‘. If not, | |
# we have passed the matching value, so there‘s no point in | |
# continuing the search, but we mu | |
if compString and not caseSensitive and isinstance(fldval, basestring): | |
toofar = fldval.lower() > srchVal | |
else: | |
toofar = fldval > srchVal | |
if toofar: | |
break | |
self.CurrentRow = newRow | |
if self.Form is not None: | |
# Add a ‘.‘ to the status bar to signify that the search is | |
# done, and clear the search string for next time. | |
currAutoUpdate = self.Form.AutoUpdateStatusText | |
self.Form.AutoUpdateStatusText = False | |
if currAutoUpdate: | |
dabo.ui.setAfterInterval(1000, self.Form, "AutoUpdateStatusText", True) | |
self.Form.setStatusText("Search: ‘%s‘." % origSrchStr) | |
self.currSearchStr = "" | |
def addToSearchStr(self, key): | |
""" | |
Add a character to the current incremental search. | |
Called by KeyDown when the user pressed an alphanumeric key. Add the | |
key to the current search and start the timer. | |
""" | |
app = self.Application | |
searchDelay = self.SearchDelay | |
if searchDelay is None: | |
if app is not None: | |
searchDelay = self.Application.SearchDelay | |
else: | |
# use a default | |
searchDelay = 500 | |
self.incSearchTimer.stop() | |
self.currSearchStr = "".join((self.currSearchStr, key)) | |
if self.Form is not None: | |
self.Form.setStatusText("Search: ‘%s‘" % self.currSearchStr) | |
self.incSearchTimer.start(searchDelay) | |
def findReplace(self, action, findString, replaceString, downwardSearch, | |
wholeWord, matchCase): | |
"""Called from the ‘Find‘ dialog.""" | |
ret = False | |
rowcol = currRow, currCol = (self.CurrentRow, self.CurrentColumn) | |
if downwardSearch: | |
op = operator.gt | |
else: | |
op = operator.lt | |
if wholeWord: | |
if matchCase: | |
srch = r"\b%s\b" % findString | |
findGen = ((r,c) for r in xrange(self.RowCount) for c in xrange(self.ColumnCount) | |
if op((r,c), rowcol) | |
and re.search(srch, ustr(self.GetValue(r, c)))) | |
else: | |
srch = r"\b%s\b" % findString.lower() | |
findGen = ((r,c) for r in xrange(self.RowCount) for c in xrange(self.ColumnCount) | |
if op((r,c), rowcol) | |
and re.search(srch, ustr(self.GetValue(r, c)).lower())) | |
else: | |
if matchCase: | |
findGen = ((r,c) for r in xrange(self.RowCount) for c in xrange(self.ColumnCount) | |
if op((r,c), rowcol) | |
and findString in ustr(self.GetValue(r, c))) | |
else: | |
findGen = ((r,c) for r in xrange(self.RowCount) for c in xrange(self.ColumnCount) | |
if op((r,c), rowcol) | |
and findString.lower() in ustr(self.GetValue(r, c)).lower()) | |
if action == "Find": | |
try: | |
while True: | |
newR, newC = findGen.next() | |
targetVal = self.GetValue(newR, newC) | |
targetString = ustr(targetVal) | |
if isinstance(targetVal, (basestring, datetime.datetime, datetime.date)): | |
# Values can be inexact matches | |
break | |
else: | |
# Needs to be an exact match | |
if findString == targetString: | |
break | |
ret = True | |
self._lastRow, self._lastCol = newR, newC | |
self.CurrentRow, self.CurrentColumn = newR, newC | |
except StopIteration: | |
ret = False | |
elif action == "Replace": | |
val = self.GetValue(currRow, currCol) | |
if isinstance(val, basestring): | |
self.SetValue(currRow, currCol, val.replace(findString, replaceString)) | |
ret = True | |
elif isinstance(val, bool): | |
if replaceString.lower() in ("true", "t", "false", "f", "1", "0", "yes", "y", "no", "n"): | |
newval = replaceString.lower() in ("true", "t", "1", "yes", "y") | |
self.SetValue(currRow, currCol, newval) | |
ret = True | |
else: | |
dabo.log.error(_("Invalid boolean replacement value: %s") % replaceString) | |
ret = False | |
else: | |
# Try the numeric types | |
typFunc = type(val) | |
if typFunc(findString) == val: | |
# We can replace if replaceString can be the correct type | |
errors = (ValueError, InvalidOperation) | |
try: | |
newval = typFunc(replaceString) | |
self.SetValue(currRow, currCol, newval) | |
ret = True | |
except errors: | |
dabo.log.error(_("Invalid replacement value: %s") % replaceString) | |
ret = False | |
if ret: | |
self.ForceRefresh() | |
return ret | |
def getColNumByX(self, x): | |
"""Given the x-coordinate, return the column index in self.Columns.""" | |
col = self.XToCol(x + (self.GetViewStart()[0]*self.GetScrollPixelsPerUnit()[0])) | |
if col == wx.NOT_FOUND: | |
col = -1 | |
else: | |
col = self._convertWxColNumToDaboColNum(col) | |
return col | |
def getRowNumByY(self, y): | |
"""Given the y-coordinate, return the row number.""" | |
row = self.YToRow(y + (self.GetViewStart()[1]*self.GetScrollPixelsPerUnit()[1])) | |
if row == wx.NOT_FOUND: | |
row = -1 | |
return row | |
def getColByX(self, x): | |
"""Given the x-coordinate, return the column object.""" | |
colNum = self.getColNumByX(x) | |
if (colNum < 0) or (colNum > self.ColumnCount-1): | |
return None | |
else: | |
return self.Columns[colNum] | |
def getColByDataField(self, df): | |
"""Given a DataField value, return the corresponding column.""" | |
try: | |
ret = [col for col in self.Columns | |
if col.DataField == df][0] | |
except IndexError: | |
ret = None | |
return ret | |
def maxColOrder(self): | |
"""Returns the highest value of Order for all columns.""" | |
ret = -1 | |
if len(self.Columns) > 0: | |
ret = max([cc.Order for cc in self.Columns]) | |
return ret | |
def addColumns(self, *columns): | |
""" | |
Adds a set of columns to the grid. | |
Each column in the set should be a dColumn instance. | |
""" | |
columns = self._resolveColumns(columns) | |
for column in columns: | |
self.addColumn(column, inBatch=True) | |
self._syncColumnCount() | |
self.fillGrid(True) | |
def addColumn(self, col=None, inBatch=False, *args, **kwargs): | |
"""Adds a column to the grid. | |
If no col (class or instance) is passed, a blank dColumn is added, which | |
can be customized later. Any extra keyword arguments are passed to the | |
constructor of the new dColumn. | |
""" | |
if col is None: | |
col = self.ColumnClass(self, *args, **kwargs) | |
else: | |
if not isinstance(col, dColumn): | |
if issubclass(col, dabo.ui.dColumn): | |
col = col(self, *args, **kwargs) | |
else: | |
raise ValueError(_("col must be a dColumn subclass or instance")) | |
else: | |
col.setProperties(**kwargs) | |
col.Parent = self | |
if col.Order == -1: | |
maxOrd = self.maxColOrder() | |
if maxOrd < 0: | |
newOrd = 0 | |
else: | |
newOrd = maxOrd + 10 | |
col.Order = newOrd | |
self.Columns.append(col) | |
if not inBatch: | |
self._syncColumnCount() | |
self.fillGrid(force=True) | |
try: | |
## Set the Width property last, otherwise it won‘t stick: | |
if not col.Width: | |
col.Width = 75 | |
else: | |
## If Width was specified in the dColumn subclass or in the constructor, | |
## it‘s been set as the property but because it wasn‘t part of the grid | |
## yet it hasn‘t yet taken effect: force it. | |
col.Width = col.Width | |
except (wx.PyAssertionError, wx.core.PyAssertionError, wx._core.PyAssertionError): | |
# If the underlying wx grid doesn‘t yet know about the column, such | |
# as when adding columns with inBatch=True, this can throw an error | |
if not inBatch: | |
# For now, just log it | |
dabo.log.info(_("Cannot set width of column %s") % col.Order) | |
return col | |
def _resolveColumns(self, columns): | |
if len(columns) == 1 and isinstance(columns[0], (list, tuple, set)): | |
columns = columns[0] | |
return [self._resolveColumn(col) for col in columns] | |
def _resolveColumn(self, colOrIdx, returnColumn=True, logOnly=False): | |
""" | |
Accepts either a column object or a column index, and returns a column | |
object. If you need the column‘s index instead, pass False to the | |
‘returnColumn‘ parameter. | |
Used for cases where a method can accept either type of reference, but | |
needs to work with the actual column. | |
If anything other than a column reference or an integer is passed, a | |
ValueError will be raised. If you prefer to simply log the error without | |
raising an exception, pass True to the logOnly parameter (default=False). | |
""" | |
if isinstance(colOrIdx, (int, long)): | |
return self.Columns[colOrIdx] if returnColumn else colOrIdx | |
elif isinstance(colOrIdx, dColumn): | |
return colOrIdx if returnColumn else self.Columns.index(colOrIdx) | |
else: | |
typcoi = type(colOrIdx) | |
msg = _("Values must be a dColumn or an int; received ‘%(colOrIdx)s‘ " | |
"(%(typcoi)s)") % locals() | |
if logOnly: | |
dabo.log.error(msg) | |
return None | |
else: | |
raise ValueError(msg) | |
def removeColumns(self, *columns): | |
""" | |
Removes a set of columns from the grid. | |
The passed columns can be indexes or dColumn instances, or both. | |
""" | |
columns = self._resolveColumns(columns) | |
for col in columns: | |
self.removeColumn(col, inBatch=True) | |
self._syncColumnCount() | |
self.fillGrid(True) | |
def removeColumn(self, col=None, inBatch=False): | |
""" | |
Removes a column from the grid. | |
If no column is passed, the last column is removed. The col argument can | |
be either a column index or a dColumn instance. | |
""" | |
if col is None: | |
colNum = self.ColumnCount - 1 | |
else: | |
colNum = self._resolveColumn(col, returnColumn=False, logOnly=True) | |
del self.Columns[colNum] | |
if not inBatch: | |
self._syncColumnCount() | |
self.fillGrid(True) | |
def cell(self, row, col): | |
class GridCell(object): | |
def __init__(self, parent, row, col): | |
self.parent = parent | |
self.row = row | |
self.col = col | |
def _getVal(self): | |
return self.parent.GetValue(self.row, self.col) | |
def _setVal(self, val): | |
self.parent.SetValue(self.row, self.col, val) | |
Value = property(_getVal, _setVal) | |
return GridCell(self, row, col) | |
def copy(self): | |
valSep = dabo.copyValueSeparator | |
strSep = dabo.copyStringSeparator | |
lnSep = dabo.copyLineSeparator | |
def valEscape(val): | |
if isinstance(val, basestring): | |
# Need to escape tabs and newlines | |
escval = val.replace("\t", "\\t").replace("\n", "\\n") | |
if strSep: | |
# Also escape the string separator | |
escval = escval.replace(strSep, "\\%s" % strSep) | |
return "%s%s%s" % (strSep, escval, strSep) | |
else: | |
ret = str(val) | |
if isinstance(val, (Decimal, float)): | |
# We need to convert decimal point accordingly to the locale. | |
ret = ret.replace(".", decimalPoint) | |
return ret | |
def valuesForRange(rowrange, colrange): | |
allvals = [] | |
for row in rowrange: | |
rowvals = [] | |
for col in colrange: | |
val = self.getValue(row, col) | |
rowvals.append(valEscape(val)) | |
allvals.append(valSep.join(rowvals)) | |
return lnSep.join(allvals) | |
sel = self.Selection | |
if not sel: | |
return None | |
selmode = self.SelectionMode | |
copied = [] | |
txtToCopy = "" | |
if selmode == "Cell": | |
copySections = [] | |
for rangeTuple in sel: | |
zrow, zcol = zip(*rangeTuple) | |
rowrange = range(zrow[0], zrow[1] + 1) | |
colrange = range(zcol[0], zcol[1] + 1) | |
copySections.append(valuesForRange(rowrange, colrange)) | |
txtToCopy = lnSep.join(copySections) | |
else: | |
if selmode == "Row": | |
rowrange = sel | |
colrange = range(0, self.ColumnCount) | |
else: | |
rowrange = range(0, self.RowCount) | |
colrange = sel | |
txtToCopy = valuesForRange(rowrange, colrange) | |
self.Application.copyToClipboard(txtToCopy) | |
def getBizobj(self): | |
""" | |
Get the bizobj that is controlling this grid. | |
Either there was an explicitly-set bizobj reference in | |
self.DataSource, in which case that is returned, or self.DataSource | |
is a string, in which case the form hierarchy is walked finding the | |
first bizobj with the correct DataSource. | |
Return None if no bizobj can be located. | |
""" | |
ds = self.DataSource | |
if isinstance(ds, dabo.biz.dBizobj): | |
return ds | |
if isinstance(ds, basestring) and self.Form is not None: | |
form = self.Form | |
while form is not None: | |
if hasattr(form, "getBizobj"): | |
biz = form.getBizobj(ds) | |
if isinstance(biz, dabo.biz.dBizobj): | |
return biz | |
form = form.Form | |
return None | |
def setRowHeight(self, row, ht): | |
"""Explicitly set the height of a specific row in the grid. If | |
SameSizeRows is True, all rows will be affected. | |
""" | |
if self.SameSizeRows: | |
self.RowHeight = ht | |
else: | |
if row >= self.RowCount: | |
rcm = self.RowCount - 1 | |
dabo.log.error(_("Specified row is out of range for setRowHeight(). " | |
"Attempted: %(row)s; max row: %(rcm)s") % locals()) | |
return | |
self.SetRowSize(row, ht) | |
def _getWxHeader(self): | |
"""Return the wx grid header window.""" | |
return self.GetGridColLabelWindow() | |
def _syncCurrentRow(self): | |
""" | |
Sync the CurrentRow of the grid to the RowNumber of the bizobj. | |
Has no effect if the grid‘s DataSource isn‘t a link to a bizobj. | |
""" | |
try: | |
self.CurrentRow = self.getBizobj().RowNumber | |
except AttributeError: | |
pass | |
# On Win, when row is deleted, active row remains unselected. | |
if self.SelectionMode == "Row": | |
row = self.CurrentRow | |
if row not in self.Selection: | |
self.SelectRow(row) | |
def _syncColumnCount(self): | |
"""Sync wx‘s rendition of column count with our self.ColumnCount""" | |
msg = None | |
wxColumnCount = self.GetNumberCols() | |
daboColumnCount = len([col for col in self.Columns if col.Visible]) | |
diff = daboColumnCount - wxColumnCount | |
self.BeginBatch() | |
if diff < 0: | |
msg = wx.grid.GridTableMessage(self._Table, | |
wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, | |
0, abs(diff)) | |
elif diff > 0: | |
msg = wx.grid.GridTableMessage(self._Table, | |
wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, | |
diff) | |
if msg: | |
self.ProcessTableMessage(msg) | |
self.EndBatch() | |
# Update the visible columns attribute | |
self._updateDaboVisibleColumns() | |
# We need to adjust the Width of visible columns here, in case any | |
# columns have Visible = False. | |
for daboCol, colObj in enumerate(self._columns): | |
wxCol = self._convertDaboColNumToWxColNum(daboCol) | |
if wxCol is not None: | |
self.SetColSize(wxCol, colObj.Width) | |
def _syncRowCount(self): | |
"""Sync wx‘s rendition of row count with our self.RowCount""" | |
msg = None | |
wxRowCount = self.GetNumberRows() | |
daboRowCount = self.RowCount | |
diff = daboRowCount - wxRowCount | |
if diff < 0: | |
msg = wx.grid.GridTableMessage(self._Table, | |
wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, | |
0, abs(diff)) | |
elif diff > 0: | |
msg = wx.grid.GridTableMessage(self._Table, | |
wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, | |
diff) | |
if msg: | |
self.ProcessTableMessage(msg) | |
def _getDefaultGridColAttr(self): | |
"""Return the GridCellAttr that will be used for all columns by default.""" | |
attr = wx.grid.GridCellAttr() | |
attr.SetAlignment(wx.ALIGN_TOP, wx.ALIGN_LEFT) | |
attr.SetReadOnly(True) | |
return attr | |
def _getUserSetting(self, prop): | |
"""Get the value of prop from the user settings table.""" | |
app = self.Application | |
form = self.Form | |
ret = None | |
if app is not None and form is not None \ | |
and not hasattr(self, "isDesignerControl"): | |
settingName = "%s.%s.%s" % (form.Name, self.Name, prop) | |
ret = app.getUserSetting(settingName) | |
return ret | |
def _setUserSetting(self, prop, val): | |
"""Persist the value of prop to the user settings table.""" | |
app = self.Application | |
form = self.Form | |
if app is not None and form is not None \ | |
and not hasattr(self, "isDesignerControl"): | |
settingName = "%s.%s.%s" % (form.Name, self.Name, prop) | |
app.setUserSetting(settingName, val) | |
def _enableDoubleBuffering(self): | |
for win in (self.GetGridWindow(), self.GetGridColLabelWindow()): | |
if not win.IsDoubleBuffered(): | |
win.SetDoubleBuffered(True) | |
def _disableDoubleBuffering(self): | |
for win in (self.GetGridWindow(), self.GetGridColLabelWindow()): | |
if win.IsDoubleBuffered(): | |
win.SetDoubleBuffered(False) | |
##----------------------------------------------------------## | |
## begin: dEvent callbacks for internal use ## | |
##----------------------------------------------------------## | |
def _onGridCellEdited(self, evt): | |
## force cache to update after an edit: | |
row, col = evt.EventData["row"], evt.EventData["col"] | |
self.GetCellValue(row, col, useCache=False) | |
def _onGridColSize(self, evt): | |
"Occurs when the user resizes the width of the column." | |
colNum = evt.EventData["col"] | |
col = self.Columns[colNum] | |
colName = "Column_%s" % col.DataField | |
# Sync our column object up with what the grid is reporting, and because | |
# the user made this change, save to the userSettings: | |
col.Width = self.GetColSize(self._convertDaboColNumToWxColNum(colNum)) | |
col._persist("Width") | |
self._disableDoubleBuffering() | |
self._enableDoubleBuffering() | |
dabo.ui.callAfterInterval(20, self._updateColumnWidths) | |
def _onGridHeaderMouseMove(self, evt): | |
curMousePosition = evt.EventData["mousePosition"] | |
headerIsDragging = self._headerDragging | |
headerIsSizing = self._headerSizing | |
dragging = evt.EventData["mouseDown"] and (curMousePosition != self._lastHeaderMousePosition) | |
header = self._getWxHeader() | |
if dragging: | |
self._lastHeaderMousePosition = evt.EventData["mousePosition"] | |
x,y = self._lastHeaderMousePosition | |
if not headerIsSizing and (self.getColNumByX(x) == self.getColNumByX(x-5) == self.getColNumByX(x+5)): | |
if not headerIsDragging: | |
curCol = self.getColByX(x) | |
if self.MovableColumns and curCol and curCol.Movable: | |
# A header reposition is beginning | |
self._headerDragging = True | |
self._headerDragFrom = (x,y) | |
else: | |
# already dragging. | |
begCol = self.getColNumByX(self._headerDragFrom[0]) | |
curCol = self.getColNumByX(x) | |
# The visual indicators (changing the mouse cursor) isn‘t currently | |
# working. It would work without the evt.Skip() below, but that is | |
# needed for when the column is resized. | |
uic = dUICursors | |
if begCol == curCol: | |
# Give visual indication that a move is initiated | |
header.SetCursor(uic.getStockCursor(uic.Cursor_Size_WE)) | |
else: | |
# Give visual indication that this is an acceptable drop target | |
header.SetCursor(uic.getStockCursor(uic.Cursor_Bullseye)) | |
else: | |
# A size action is happening | |
self._headerSizing = True | |
def _onGridHeaderMouseLeftUp(self, evt): | |
""" | |
Occurs when the left mouse button is released in the grid header. | |
Basically, this comes down to two possibilities: the end of a drag | |
operation, or a single-click operation. If we were dragging, then | |
it is possible a column needs to change position. If we were clicking, | |
then it is a sort operation. | |
""" | |
x,y = evt.EventData["mousePosition"] | |
if self._headerDragging: | |
# A drag action is ending | |
self._headerDragTo = (x,y) | |
begCol = self.getColNumByX(self._headerDragFrom[0]) | |
curCol = self.getColNumByX(x) | |
if begCol != curCol: | |
if curCol > begCol: | |
curCol += 1 | |
self.moveColumn(begCol, curCol) | |
self._getWxHeader().SetCursor(self.defaultHdrCursor) | |
elif self._headerSizing: | |
pass | |
else: | |
# we weren‘t dragging, and the mouse was just released. | |
# Find out the column we are in based on the x-coord, and | |
# do a processSort() on that column. | |
col = self.getColNumByX(x) | |
self.processSort(col) | |
self._headerDragging = False | |
self._headerSizing = False | |
## pkm: commented out the evt.Continue=False because it doesn‘t appear | |
## to be needed, and it prevents the native UI from responding. | |
#evt.Continue = False | |
def _onGridHeaderMouseRightClick(self, evt): | |
dabo.ui.callAfter(self._showHeaderContextMenu) | |
def _showHeaderContextMenu(self): | |
# Make the popup menu appear in the location that was clicked. We init | |
# the menu here, then call the user hook method to optionally fill the | |
# menu. If we get a menu back from the user hook, we display it. | |
menu = dabo.ui.dMenu() | |
# Fill the default menu item(s): | |
def _autosizeColumn(evt): | |
self.autoSizeCol(self.getColNumByX(self.getMousePosition()[0]), persist=True) | |
def _autosizeAllColumns(evt): | |
self.autoSizeCol("All") | |
if self.ResizableColumns: | |
menu.append(_("&Autosize Column"), OnHit=_autosizeColumn, | |
help=_("Autosize the column based on the data in the column.")) | |
menu.append(_("&Autosize All Columns"), OnHit=_autosizeAllColumns, | |
help=_("Autosize all columns in the grid.")) | |
menu = self.fillHeaderContextMenu(menu) | |
if menu is not None and len(menu.Children) > 0: | |
self.showContextMenu(menu) | |
def _onGridMouseWheel(self, evt): | |
## Override the default implementation which scrolls too slowly. | |
evt.stop() | |
lastWheelTime = getattr(self, "_lastWheelTime", 0) | |
thisWheelTime = self._lastWheelTime = time.time() | |
ui = evt._uiEvent | |
mult = 1 | |
if ui.GetWheelRotation() > 0: | |
mult = -1 | |
linesPerAction = ui.GetLinesPerAction() | |
scrollAmt = mult * linesPerAction | |
if thisWheelTime - lastWheelTime > .5: | |
## Run the first wheel scroll to occur immediately: | |
self._scrollLines(scrollAmt) | |
return | |
## Throttle subsequent rapid-fire wheel scrolls through callAfterInterval, | |
## otherwise the events pile up resulting in poor performance. | |
_accumulatedWheelScroll = getattr(self, "_accumulatedWheelScroll", None) | |
if _accumulatedWheelScroll is None: | |
dabo.ui.callAfterInterval(50, self._scrollAccumulatedLines) | |
_accumulatedWheelScroll = 0 | |
self._accumulatedWheelScroll = _accumulatedWheelScroll + scrollAmt | |
self._wheelScrollLines = linesPerAction | |
def _scrollAccumulatedLines(self): | |
scrollAmt = self._accumulatedWheelScroll | |
if sys.platform.startswith("win") and scrollAmt > self._wheelScrollLines: | |
# I guess Windows doesn‘t receive as many wheel events per timeslice | |
# as Gtk does. This attempts to compensate. | |
scrollAmt *= (scrollAmt * .5) | |
self._scrollLines(scrollAmt) | |
self._accumulatedWheelScroll = None | |
def _scrollLines(self, scrollAmt): | |
## Without the Freeze/Thaw, performance sucks on Windows as it tries to do | |
## it smoothly. | |
self.Freeze() | |
self.ScrollLines(scrollAmt) | |
self.Thaw() | |
def _onGridHeaderMouseRightUp(self, evt): | |
"""Occurs when the right mouse button goes up in the grid header.""" | |
pass | |
_onGridHeaderContextMenu = _onGridHeaderMouseRightUp | |
def _onGridMouseRightClick(self, evt): | |
# Make the popup menu appear in the location that was clicked. We init | |
# the menu here, then call the user hook method to optionally fill the | |
# menu. If we get a menu back from the user hook, we display it. | |
# First though, make the cell the user right-clicked on the current cell: | |
if self.MultipleSelection: | |
# Don‘t erase the multiple selection if the user clicks on a valid | |
# row or column: | |
if "row" in self.SelectionMode.lower() and evt.row not in self.Selection: | |
self.CurrentRow = evt.row | |
elif "col" in self.SelectionMode.lower() and evt.col not in self.Selection: | |
self.CurrentCol = evt.col | |
elif "cel" in self.SelectionMode.lower(): | |
self.CurrentRow = evt.row | |
self.CurrentCol = evt.col | |
else: | |
self.CurrentRow = evt.row | |
self.CurrentColumn = evt.col | |
menu = dabo.ui.dMenu() | |
menu = self.fillContextMenu(menu) | |
if menu is not None and len(menu.Children) > 0: | |
self.showContextMenu(menu) | |
_onContextMenu = _onGridMouseRightClick | |
def _onGridHeaderMouseLeftDown(self, evt): | |
# We need to eat this event, because the native wx grid will select all | |
# rows in the column, which is a spreadsheet-like behavior, not a data- | |
# aware grid-like behavior. However, let‘s keep our eyes out for a better | |
# way to handle this, because eating events could cause some hard-to-debug | |
# problems later (there could be other, more critical code, that isn‘t | |
# being allowed to run). | |
self._lastHeaderMousePosition = evt.EventData["mousePosition"] | |
self._headerDragging = False | |
self._headerSizing = False | |
evt.Continue = False | |
def _onGridMouseLeftClick(self, evt): | |
self.ShowCellEditControl() | |
def _onGridRowSize(self, evt): | |
""" | |
Occurs when the user sizes the height of the row. If the | |
property ‘SameSizeRows‘ is True, Dabo overrides the wxPython | |
default and applies that size change to all rows, not just the row | |
the user sized. | |
""" | |
row = evt.EventData["row"] | |
if row is None or row < 0 or row > self.RowCount: | |
# pkm: This has happened but I don‘t know why. Treat as spurious. | |
return | |
if self.SameSizeRows: | |
try: | |
self.RowHeight = self.GetRowSize(row) | |
except wx._core.PyAssertionError: | |
# pkm: I don‘t understand how it could have gotten this far, but | |
# I got an error report that the c++ assertion row>=0 && row<m_numrows failed. | |
pass | |
def _onGridCellSelected(self, evt): | |
"""Occurs when the grid‘s cell focus has changed.""" | |
threshold = .2 | |
last = getattr(self, "_lastCellSelectedTime", 0) | |
cur = self._lastCellSelectedTime = time.time() | |
#self._gridCellSelectedNewRowCol = (evt.EventData["row"], evt.EventData["col"]) | |
if cur - last > threshold: | |
# Update immediately: | |
self._gridCellSelectedOldRow = self.CurrentRow | |
self._updateCellSelection((evt.EventData["row"], evt.EventData["col"])) | |
return | |
# Let the grid scroll as fast as possible while rapid-fire keyboard navigation is | |
# occurring, but <threshold> seconds later, sync up the bizobj and update the selection: | |
if getattr(self, "_gridCellSelectedOldRow", None) is None: | |
self._gridCellSelectedOldRow = self.CurrentRow | |
dabo.ui.callAfterInterval(threshold*1000, self._updateCellSelection) | |
def _updateCellSelection(self, newRowCol=None): | |
if self._inUpdateSelection: | |
return | |
oldRow = self._gridCellSelectedOldRow | |
self._gridCellSelectedOldRow = None | |
if newRowCol is None: | |
newRowCol = (self.CurrentRow, self.CurrentColumn) | |
newRow = newRowCol[0] | |
newCol = self._convertWxColNumToDaboColNum(newRowCol[1]) | |
try: | |
col = self.Columns[newCol] | |
except (IndexError, TypeError): | |
col = None | |
if col and col.Editable and self.Editable: | |
return ## segfault avoidance | |
## pkm 2005-09-28: This works around a nasty segfault: | |
self.HideCellEditControl() | |
## but periodically test it. My current version: 2.6.1.1pre | |
if col: | |
## pkm 2005-09-28: Part of the editor segfault workaround. This sets the | |
## editor for the entire column, at a point in time before | |
## the grid is actually asking for the editor, and in a | |
## fashion that ensures the editor instance doesn‘t go | |
## out of scope prematurely. | |
col._setEditor(newRow) | |
if col and (self.Editable and col.Editable and not self._vetoAllEditing | |
and self.ActivateEditorOnSelect): | |
dabo.ui.callAfter(self.EnableCellEditControl) | |
if oldRow != newRow: | |
bizobj = self.getBizobj() | |
if bizobj and not self._dataSourceBeingSet: | |
# Don‘t run any of this code if this is the initial setting of the DataSource | |
if bizobj.RowCount > newRow and bizobj.RowNumber != newRow: | |
if self._mediateRowNumberThroughForm and isinstance(self.Form, dabo.ui.dForm): | |
# run it through the form: | |
if not self.Form.moveToRowNumber(newRow, bizobj): | |
dabo.ui.callAfter(self.refresh) | |
else: | |
# run it through the bizobj directly: | |
try: | |
bizobj.RowNumber = newRow | |
self.Form.update() | |
except dException.BusinessRuleViolation, e: | |
dabo.ui.stop(e) | |
dabo.ui.callAfter(self.refresh) | |
else: | |
# We are probably trying to select row 0 when there are no records | |
# in the bizobj. | |
##pkm: the following call causes an assertion on Mac, and appears to be | |
## unneccesary. | |
#self.SetGridCursor(0,0) | |
pass | |
self._dataSourceBeingSet = False | |
dabo.ui.callAfterInterval(50, self._updateSelection) | |
def _updateSelection(self): | |
if self._inUpdateSelection or self.SelectionMode =="Cell": | |
return | |
self._inUpdateSelection = True | |
self.Freeze() | |
self.ClearSelection() | |
fnc = {"Row": self.SelectRow, "Col": self.SelectCol}[self.SelectionMode] | |
for num in self.Selection: | |
fnc(num, True) | |
self.Thaw() | |
self._inUpdateSelection = False | |
def _checkSelectionType(self): | |
""" | |
When the SelectionMode or MultipleSelection properties change, | |
we want to make sure that the selection reflects those settings. | |
""" | |
mode = self.SelectionMode | |
if mode == "Row": | |
self.SelectRow(self.CurrentRow) | |
elif mode == "Col": | |
self.SelectCol(self.CurrentColumn) | |
else: | |
self.SelectBlock(self.CurrentRow, self.CurrentColumn, | |
self.CurrentRow, self.CurrentColumn) | |
self.refresh() | |
def _onKeyDown(self, evt): | |
keycode = evt.EventData["keyCode"] | |
if keycode == 27: | |
# esc pressed. Grid will eat it by default. But if we are in a dialog with | |
# a cancel button, let‘s runCancel() since that‘s what the user likely wants: | |
if hasattr(self.Form, "runCancel"): | |
self.Form.runCancel() | |
if keycode == 9 and self.TabNavigates: | |
evt.stop() | |
self.Navigate(not evt.EventData["shiftDown"]) | |
def _onKeyChar(self, evt): | |
"""Occurs when the user presses a key inside the grid.""" | |
columns = self.Columns | |
current_col = self.CurrentColumn | |
if not columns or (self.Editable and columns[current_col].Editable | |
and not self._vetoAllEditing): | |
# Can‘t search and edit at the same time | |
return | |
keyCode = evt.EventData["unicodeKey"] | |
try: | |
char = unichr(keyCode) | |
except ValueError: | |
# keycode not in ascii range | |
return | |
if keyCode in (dKeys.key_Left, dKeys.key_Right, | |
dKeys.key_Up, dKeys.key_Down, dKeys.key_Pageup, dKeys.key_Pagedown, | |
dKeys.key_Home, dKeys.key_End, dKeys.key_Prior, dKeys.key_Next) \ | |
or evt.EventData["hasModifiers"]: | |
# Enter, Tab, and Arrow Keys shouldn‘t be searched on. | |
return | |
if (self.Searchable and columns[current_col].Searchable): | |
self.addToSearchStr(char) | |
# For some reason, without this the key happens twice | |
evt.stop() | |
##----------------------------------------------------------## | |
## end: dEvent callbacks for internal use ## | |
##----------------------------------------------------------## | |
def _calcRanges(self, seq, rowOrCol): | |
startPoints = [] | |
nextVal = -1 | |
maxIdx = len(seq)-1 | |
for idx,pt in enumerate(seq): | |
if idx == 0: | |
startPoints.append(pt) | |
nextVal = pt+1 | |
else: | |
if pt == nextVal: | |
nextVal += 1 | |
else: | |
startPoints.append(pt) | |
nextVal = pt+1 | |
endPoints = [] | |
for pt in startPoints: | |
idx = seq.index(pt) | |
if idx == maxIdx: | |
endPoints.append(pt) | |
else: | |
found = False | |
while idx < maxIdx: | |
if seq[idx+1] == pt + 1: | |
idx += 1 | |
pt += 1 | |
else: | |
endPoints.append(pt) | |
found = True | |
break | |
if not found: | |
endPoints.append(pt) | |
typ = rowOrCol.lower()[:3] | |
if typ == "row": | |
cols = self.ColumnCount | |
rangeStart = [(r, 0) for r in startPoints] | |
rangeEnd = [(r, cols) for r in endPoints] | |
elif typ == "col": | |
rows = self.RowCount | |
rangeStart = [(0, c) for c in startPoints] | |
rangeEnd = [(rows, c) for c in endPoints] | |
return zip(rangeStart, rangeEnd) | |
##----------------------------------------------------------## | |
## begin: wx callbacks to re-route to dEvents ## | |
##----------------------------------------------------------## | |
## dGrid has to reimplement all of this to augment what dPemMixin does, | |
## to offer separate events in the grid versus the header region. | |
def __onWxContextMenu(self, evt): | |
self.raiseEvent(dEvents.GridContextMenu, evt) | |
evt.Skip() | |
def __onWxGridColSize(self, evt): | |
daboCol = self._convertWxColNumToDaboColNum(evt.GetRowOrCol()) | |
colObj = self.Columns[daboCol] | |
if self.ResizableColumns and colObj.Resizable: | |
self.raiseEvent(dEvents.GridColSize, col=daboCol) | |
else: | |
# need to reference the Width property for some reason: | |
colObj.Width | |
evt.Veto() | |
self._refreshHeader() | |
def __onWxGridSelectCell(self, evt): | |
if getattr(self, "_inSelect", False) or getattr(self, "_inUpdateSelection", False): | |
# Avoid recursion | |
return | |
if self.ColumnCount == 0: | |
# Grid is not fully constructed yet | |
return | |
col = self.Columns[evt.GetCol()] | |
if col.Editable and col.RendererClass == col.boolRendererClass: | |
# user is clicking on a checkbox | |
wx.CallAfter(self.EnableCellEditControl) | |
self._inSelect = True | |
if evt.Selecting(): | |
self._updateWxSelection(evt) | |
self.raiseEvent(dEvents.GridCellSelected, evt) | |
self._lastRow, self._lastCol = evt.GetRow(), evt.GetCol() | |
if not sys.platform.startswith("win"): | |
evt.Skip() | |
self._inSelect = False | |
def __onWxGridRangeSelect(self, evt): | |
if self._inRangeSelect: | |
# avoid recursive events | |
return | |
self._inRangeSelect = True | |
if evt.Selecting(): | |
self._updateWxSelection(evt) | |
self.raiseEvent(dEvents.GridRangeSelected, evt) | |
evt.Skip() | |
self._inRangeSelect = False | |
def __onWxScrollWin(self, evt): | |
evtClass = dabo.ui.getScrollWinEventClass(evt) | |
self.raiseEvent(evtClass, evt) | |
evt.Skip() | |
def _updateWxSelection(self, evt): | |
if self.MultipleSelection: | |
# Nothing to do | |
return | |
origRow, origCol = self.CurrentRow, self.CurrentColumn | |
mode = self.GetSelectionMode() | |
try: | |
top, bott = evt.GetTopRow(), evt.GetBottomRow() | |
except AttributeError: | |
top = bott = evt.GetRow() | |
try: | |
left, right = evt.GetLeftCol(), evt.GetRightCol() | |
except AttributeError: | |
left = right = evt.GetCol() | |
if mode == wx.grid.Grid.wxGridSelectRows: | |
if (top != bott) or (top != origCol): | |
# Attempting to select a range | |
if top == origRow: | |
row = bott | |
else: | |
row = top | |
if self._lastCol is not None: | |
self.SetGridCursor(row, self._lastCol) | |
self.SelectRow(row) | |
elif mode == wx.grid.Grid.wxGridSelectColumns: | |
if (left != right) or (left != origCol): | |
# Attempting to select a range | |
if left == origCol: | |
col = right | |
else: | |
col = left | |
self.SetGridCursor(self._lastRow, col) | |
self.SelectCol(col) | |
else: | |
# Cells | |
chg = False | |
row, col = origRow, origCol | |
if top != bott: | |
chg = True | |
if top == origRow: | |
row = bott | |
else: | |
row = top | |
elif top != origRow: | |
# New row | |
chg = True | |
row = top | |
if left != right: | |
chg = True | |
if left == origCol: | |
col = right | |
else: | |
col = left | |
elif left != origCol: | |
# New col | |
chg = True | |
col = left | |
if chg: | |
self.SetGridCursor(row, col) | |
self.SelectBlock(row, col, row, col) | |
def __onWxGridEditorShown(self, evt): | |
self.raiseEvent(dEvents.GridCellEditBegin, evt) | |
evt.Skip() | |
def __onWxGridEditorHidden(self, evt): | |
self.raiseEvent(dEvents.GridCellEditEnd, evt) | |
evt.Skip() | |
def _toggleCheckBox(self): | |
ed = getattr(self, "_activeEditorControl", None) | |
if ed: | |
ed.SetValue(not ed.GetValue()) | |
self._checkBoxToggled(ed) | |
def _checkBoxToggled(self, obj): | |
# Force the flushing of the value immediately, instead of waiting for the | |
# editor to lose focus (where the flush will happen a second time). | |
self._Table.SetValue(self.CurrentRow, self.CurrentColumn, obj.GetValue()) | |
self.raiseEvent(dEvents.GridCellEditorHit) | |
def __onGridCellLeftClick_toggleCB(self, evt): | |
col = self.Columns[evt.GetCol()] | |
if col.RendererClass == col.boolRendererClass: | |
dabo.ui.callAfterInterval(100, self._toggleCheckBox) | |
evt.Skip() | |
def __onWxGridEditorCreated(self, evt): | |
"""Bind the kill focus event to the newly instantiated cell editor """ | |
editor = evt.GetControl() | |
editor.Bind(wx.EVT_KILL_FOCUS, self.__onWxGridCellEditorKillFocus) | |
col = self.Columns[evt.GetCol()] | |
if col.RendererClass == col.boolRendererClass: | |
def onKeyDown(evt): | |
if evt.KeyCode == wx.WXK_UP: | |
if self.GetGridCursorRow() > 0: | |
self.DisableCellEditControl() | |
self.MoveCursorUp(False) | |
elif evt.KeyCode == wx.WXK_DOWN: | |
if self.GetGridCursorRow() < (self.GetNumberRows() - 1): | |
self.DisableCellEditControl() | |
self.MoveCursorDown(False) | |
elif evt.KeyCode == wx.WXK_LEFT: | |
if self.GetGridCursorCol() > 0: | |
self.DisableCellEditControl() | |
self.MoveCursorLeft(False) | |
elif evt.KeyCode == wx.WXK_RIGHT: | |
if self.GetGridCursorCol() < (self.GetNumberCols() - 1): | |
self.DisableCellEditControl() | |
self.MoveCursorRight(False) | |
else: | |
evt.Skip() | |
def onHit(evt): | |
self._checkBoxToggled(editor) | |
ed = self._activeEditorControl = evt.GetControl() | |
style = ed.GetWindowStyle() | |
style |= wx.WANTS_CHARS | |
ed.SetWindowStyle(style) | |
ed.Bind(wx.EVT_KEY_DOWN, onKeyDown) | |
ed.Bind(wx.EVT_CHECKBOX, onHit) | |
evt.Skip() | |
def __onWxGridCellEditorKillFocus(self, evt): | |
# Cell editor‘s grandparent, the grid GridWindow‘s parent, is the grid. | |
self.SaveEditControlValue() | |
self.HideCellEditControl() | |
evt.Skip() | |
def __onWxGridCellChange(self, evt): | |
self.raiseEvent(dEvents.GridCellEdited, evt) | |
evt.Skip() | |
def __onWxGridRowSize(self, evt): | |
self.raiseEvent(dEvents.GridRowSize, evt) | |
evt.Skip() | |
def __onWxHeaderContextMenu(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderContextMenu, evt, col=col) | |
evt.Skip() | |
def __onWxHeaderIdle(self, evt): | |
self.raiseEvent(dEvents.GridHeaderIdle, evt) | |
evt.Skip() | |
def __onWxHeaderMouseEnter(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseEnter, evt, col=col) | |
evt.Skip() | |
def __onWxHeaderMouseLeave(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self._headerMouseLeftDown, self._headerMouseRightDown = False, False | |
self.raiseEvent(dEvents.GridHeaderMouseLeave, evt, col=col) | |
evt.Skip() | |
def __onWxHeaderMouseLeftDoubleClick(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseLeftDoubleClick, evt, col=col) | |
evt.Skip() | |
def __onWxHeaderMouseLeftDown(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseLeftDown, evt, col=col) | |
self._headerMouseLeftDown = True | |
#evt.Skip() #- don‘t skip or all the rows will be selected. | |
def __onWxHeaderMouseLeftUp(self, evt): | |
dabo.ui.callAfter(self._enableDoubleBuffering) | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseLeftUp, evt, col=col) | |
if self._headerMouseLeftDown: | |
# mouse went down and up in the header: send a click: | |
self.raiseEvent(dEvents.GridHeaderMouseLeftClick, evt, col=col) | |
self._headerMouseLeftDown = False | |
evt.Skip() | |
def __onWxHeaderMouseMotion(self, evt): | |
if dabo.ui.isMouseLeftDown(): | |
self._disableDoubleBuffering() | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseMove, evt, col=col) | |
evt.Skip() | |
def __onWxHeaderMouseRightDown(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseRightDown, evt, col=col) | |
self._headerMouseRightDown = True | |
evt.Skip() | |
def __onWxHeaderMouseRightUp(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridHeaderMouseRightUp, evt, col=col) | |
if self._headerMouseRightDown: | |
# mouse went down and up in the header: send a click: | |
self.raiseEvent(dEvents.GridHeaderMouseRightClick, evt) | |
self._headerMouseRightDown = False | |
evt.Skip() | |
def __onWxHeaderPaint(self, evt): | |
updateBox = self._getWxHeader().GetUpdateRegion().GetBox() | |
self._paintHeader(updateBox) | |
def _getColRowForPosition(self, pos): | |
"""Used in the mouse event handlers to stuff the col, row into EventData.""" | |
col = self.getColNumByX(pos[0]) | |
row = self.getRowNumByY(pos[1]) | |
if col < 0 or row < 0: | |
# click was outside grid cell area | |
col, row = None, None | |
return col, row | |
def __onWxMouseLeftDoubleClick(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseLeftDoubleClick, evt, col=col, row=row) | |
evt.Skip() | |
def __onWxMouseLeftDown(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseLeftDown, evt, col=col, row=row) | |
self._mouseLeftDown = (col, row) | |
evt.Skip() | |
def __onWxMouseLeftUp(self, evt): | |
dabo.ui.callAfter(self._enableDoubleBuffering) | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseLeftUp, evt, col=col, row=row) | |
if getattr(self, "_mouseLeftDown", (None, None)) == (col, row): | |
# mouse went down and up in this cell: send a click: | |
self.raiseEvent(dEvents.GridMouseLeftClick, evt, col=col, row=row) | |
self._mouseLeftDown = (None, None) | |
evt.Skip() | |
def __onWxMouseMotion(self, evt): | |
if dabo.ui.isMouseLeftDown(): | |
self._disableDoubleBuffering() | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseMove, evt, col=col, row=row) | |
evt.Skip() | |
def __onWxMouseRightDown(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseRightDown, evt, col=col, row=row) | |
self._mouseRightDown = (col, row) | |
evt.Skip() | |
def __onWxMouseRightUp(self, evt): | |
col, row = self._getColRowForPosition(evt.GetPosition()) | |
self.raiseEvent(dEvents.GridMouseRightUp, evt, col=col, row=row) | |
if getattr(self, "_mouseRightDown", (None, None)) == (col, row): | |
# mouse went down and up in this cell: send a click: | |
self.raiseEvent(dEvents.GridMouseRightClick, evt, col=col, row=row) | |
self._mouseRightDown = (None, None) | |
evt.Skip() | |
##----------------------------------------------------------## | |
## end: wx callbacks to re-route to dEvents ## | |
##----------------------------------------------------------## | |
##----------------------------------------------------------## | |
## begin: property definitions ## | |
##----------------------------------------------------------## | |
def _getActivateEditorOnSelect(self): | |
try: | |
v = self._activateEditorOnSelect | |
except AttributeError: | |
v = self._activateEditorOnSelect = True | |
return v | |
def _setActivateEditorOnSelect(self, val): | |
self._activateEditorOnSelect = bool(val) | |
def _getAlternateRowColoring(self): | |
return self._alternateRowColoring | |
def _setAlternateRowColoring(self, val): | |
if self._constructed(): | |
self._alternateRowColoring = val | |
self.setTableAttributes(self._Table) | |
self.Refresh() | |
else: | |
self._properties["AlternateRowColoring"] = val | |
def _getAutoAdjustHeaderHeight(self): | |
return self._autoAdjustHeaderHeight | |
def _setAutoAdjustHeaderHeight(self, val): | |
if self._constructed(): | |
self._autoAdjustHeaderHeight = val | |
self.refresh() | |
if val: | |
self.fitHeaderHeight() | |
else: | |
self._properties["AutoAdjustHeaderHeight"] = val | |
def _getCellHighlightWidth(self): | |
return self.GetCellHighlightPenWidth() | |
def _setCellHighlightWidth(self, val): | |
if self._constructed(): | |
self.SetCellHighlightPenWidth(val) | |
self.SetCellHighlightROPenWidth(val) | |
else: | |
self._properties["CellHighlightWidth"] = val | |
def _getColumns(self): | |
return self._columns | |
def _getColumnClass(self): | |
return self._columnClass | |
def _setColumnClass(self, val): | |
self._columnClass = val | |
def _getColumnCount(self): | |
return len(self.Columns) | |
def _setColumnCount(self, val): | |
if self._constructed(): | |
if val > -1: | |
colChange = val - self.ColumnCount | |
if colChange == 0: | |
# No change | |
return | |
elif colChange < 0: | |
while self.ColumnCount > val: | |
self.Columns.remove(self.Columns[-1]) | |
else: | |
for cc in range(colChange): | |
self.addColumn(inBatch=True) | |
self._syncColumnCount() | |
self.fillGrid(True) | |
else: | |
self._properties["ColumnCount"] = val | |
def _getCurrCellVal(self): | |
return self.GetValue(self.GetGridCursorRow(), self.GetGridCursorCol()) | |
def _setCurrCellVal(self, val): | |
self.SetValue(self.GetGridCursorRow(), self.GetGridCursorCol(), val) | |
self.refresh() | |
def _getCurrentColumn(self): | |
return self.GetGridCursorCol() | |
def _setCurrentColumn(self, val): | |
if self._constructed(): | |
if val > -1: | |
val = min(val, self.ColumnCount) | |
rn = self.CurrentRow | |
self.SetGridCursor(rn, val) | |
self.MakeCellVisible(rn, val) | |
else: | |
self._properties["CurrentColumn"] = val | |
def _getCurrentField(self): | |
return self.Columns[self.GetGridCursorCol()].DataField | |
def _setCurrentField(self, val): | |
if self._constructed(): | |
for ii in range(len(self.Columns)): | |
if self.Columns[ii].DataField == val: | |
self.CurrentColumn = ii | |
break | |
else: | |
self._properties["CurrentField"] = val | |
def _getCurrentRow(self): | |
return self.GetGridCursorRow() | |
def _setCurrentRow(self, val): | |
if self._constructed(): | |
curr = self.GetGridCursorRow() | |
if val >= self.RowCount: | |
val = self.RowCount - 1 | |
if val < 0: | |
val = 0 | |
cn = self.CurrentColumn | |
if curr != val: | |
# The row is being changed | |
val = max(0, val) | |
cn = max(0, cn) | |
self.SetGridCursor(val, cn) | |
self.MakeCellVisible(val, cn) | |
else: | |
self._properties["CurrentRow"] = val | |
def _getDataSet(self): | |
if self.DataSource is not None: | |
ret = None | |
bo = self.getBizobj() | |
try: | |
ret = bo.getDataSet() | |
except AttributeError: | |
# See if the DataSource is a reference | |
try: | |
ret = eval(self.DataSource) | |
except StandardError: | |
# If it fails for any reason, bail. | |
pass | |
self._dataSet = ret | |
else: | |
try: | |
ret = self._dataSet | |
except AttributeError: | |
ret = self._dataSet = None | |
return ret | |
def _setDataSet(self, val): | |
if self._constructed(): | |
if (self.DataSource is not None) and not hasattr(self, "isDesignerControl"): | |
raise ValueError("Cannot set DataSet: DataSource defined.") | |
# We must make sure the grid‘s table is initialized first: | |
self._Table | |
if not isinstance(val, dabo.db.dDataSet): | |
val = dabo.db.dDataSet(val) | |
self._dataSet = val | |
self.fillGrid() | |
self._syncAll() | |
if not self._settingDataSetFromSort: | |
# Force the grid to maintain its current sort order | |
self._restoreSort() | |
dabo.ui.callAfter(self.refresh) | |
else: | |
self._properties["DataSet"] = val | |
def _getDataSource(self): | |
try: | |
v = self._dataSource | |
except AttributeError: | |
v = self._dataSource = None | |
return v | |
def _setDataSource(self, val): | |
if self._constructed(): | |
# We must make sure the grid‘s table is initialized first: | |
self._Table | |
self._dataSet = None | |
self._dataSource = val | |
self.fillGrid(True) | |
biz = self.getBizobj() | |
if self.USE_DATASOURCE_BEING_SET_HACK: | |
self._dataSourceBeingSet = True | |
if biz: | |
dabo.ui.setAfter(self, "CurrentRow", biz.RowNumber) | |
else: | |
self._properties["DataSource"] = val | |
def _getEditable(self): | |
return self.IsEditable() | |
def _setEditable(self, val): | |
if self._constructed(): | |
self.EnableEditing(val) | |
else: | |
self._properties["Editable"] = val | |
def _getEncoding(self): | |
try: | |
ret = self.getBizobj().Encoding | |
except AttributeError: | |
ret = dabo.getEncoding() | |
return ret | |
def _getHeaderBackColor(self): | |
return self._headerBackColor | |
def _setHeaderBackColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._headerBackColor = val | |
self.refresh() | |
else: | |
self._properties["HeaderBackColor"] = val | |
def _getHeaderForeColor(self): | |
return self._headerForeColor | |
def _setHeaderForeColor(self, val): | |
if self._constructed(): | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self._headerForeColor = val | |
self.refresh() | |
else: | |
self._properties["HeaderForeColor"] = val | |
def _getHeaderHeight(self): | |
return self.GetColLabelSize() | |
def _setHeaderHeight(self, val): | |
if self._constructed(): | |
if val <= 0: | |
self._lastPositiveHeaderHeight = self.GetColLabelSize() | |
self.SetColLabelSize(val) | |
else: | |
self._properties["HeaderHeight"] = val | |
def _getHeaderHorizontalAlignment(self): | |
return self._headerHorizontalAlignment | |
def _setHeaderHorizontalAlignment(self, val): | |
if self._constructed(): | |
v = self._expandPropStringValue(val, ("Left", "Right", "Center")) | |
self._headerHorizontalAlignment = v | |
self.refresh() | |
else: | |
self._properties["HeaderHorizontalAlignment"] = val | |
def _getHeaderVerticalAlignment(self): | |
return self._headerVerticalAlignment | |
def _setHeaderVerticalAlignment(self, val): | |
if self._constructed(): | |
v = self._expandPropStringValue(val, ("Top", "Bottom", "Center")) | |
self._headerVerticalAlignment = v | |
self.refresh() | |
else: | |
self._properties["HeaderVerticalAlignment"] = val | |
def _getHorizontalScrolling(self): | |
return self.GetScrollPixelsPerUnit()[0] > 0 | |
def _setHorizontalScrolling(self, val): | |
if self._constructed(): | |
if val: | |
self.SetScrollRate(20, self.GetScrollPixelsPerUnit()[1]) | |
else: | |
self.SetScrollRate(0, self.GetScrollPixelsPerUnit()[1]) | |
self.refresh() | |
else: | |
self._properties["HorizontalScrolling"] = val | |
def _getMovableColumns(self): | |
return self._movableColumns | |
def _setMovableColumns(self, val): | |
self._movableColumns = val | |
def _getMultipleSelection(self): | |
return self._multipleSelection | |
def _setMultipleSelection(self, val): | |
if self._constructed(): | |
if val != self._multipleSelection: | |
self._multipleSelection = val | |
self._checkSelectionType() | |
else: | |
self._properties["MultipleSelection"] = val | |
def _getNoneDisplay(self): | |
return self._noneDisplay | |
def _setNoneDisplay(self, val): | |
if val is None: | |
self._noneDisplay = self.__noneDisplayDefault | |
else: | |
assert isinstance(val, basestring) | |
self._noneDisplay = val | |
def _getResizableColumns(self): | |
return self._resizableColumns | |
def _setResizableColumns(self, val): | |
self._resizableColumns = val | |
def _getResizableRows(self): | |
return self._resizableRows | |
def _setResizableRows(self, val): | |
if self._constructed(): | |
self._resizableRows = val | |
if val: | |
self.EnableDragRowSize() | |
else: | |
self.DisableDragRowSize() | |
else: | |
self._properties["ResizableRows"] = val | |
def _getRowColorEven(self): | |
return self._rowColorEven | |
def _setRowColorEven(self, val): | |
self._rowColorEven = val | |
self.setTableAttributes(self._Table) | |
def _getRowColorOdd(self): | |
return self._rowColorOdd | |
def _setRowColorOdd(self, val): | |
self._rowColorOdd = val | |
self.setTableAttributes(self._Table) | |
def _getRowCount(self): | |
try: | |
self._tableRows = self.getBizobj().RowCount | |
except AttributeError: | |
pass | |
return self._tableRows | |
def _getRowHeight(self): | |
try: | |
v = self._rowHeight | |
except AttributeError: | |
v = self._rowHeight = self.GetDefaultRowSize() | |
return v | |
def _setRowHeight(self, val): | |
if self._constructed(): | |
try: | |
rh = self._rowHeight | |
except AttributeError: | |
rh = self._rowHeight = self.GetDefaultRowSize() | |
if val != rh: | |
self._rowHeight = val | |
self.SetDefaultRowSize(val, True) | |
self.ForceRefresh() | |
# Persist the new size: | |
self._setUserSetting("RowSize", val) | |
else: | |
self._properties["RowHeight"] = val | |
def _getRowLabels(self): | |
return self._rowLabels | |
def _setRowLabels(self, val): | |
self._rowLabels = val | |
self.fillGrid() | |
def _getRowLabelWidth(self): | |
try: | |
v = self._rowLabelWidth | |
except AttributeError: | |
v = self._rowLabelWidth = self.GetDefaultRowLabelSize() | |
return v | |
def _setRowLabelWidth(self, val): | |
if self._constructed(): | |
self._rowLabelWidth = val | |
if self.ShowRowLabels: | |
self.SetRowLabelSize(val) | |
else: | |
self._properties["RowLabelWidth"] = val | |
def _getSameSizeRows(self): | |
return self._sameSizeRows | |
def _setSameSizeRows(self, val): | |
self._sameSizeRows = bool(val) | |
def _getSaveRestoreDataSet(self): | |
return getattr(self, "_saveRestoreDataSet", False) | |
def _setSaveRestoreDataSet(self, val): | |
self._saveRestoreDataSet = bool(val) | |
def _getSearchable(self): | |
return self._searchable | |
def _setSearchable(self, val): | |
self._searchable = bool(val) | |
def _getSearchDelay(self): | |
return self._searchDelay | |
def _setSearchDelay(self, val): | |
self._searchDelay = val | |
def _getSelection(self): | |
ret = [] | |
sm = self._selectionMode | |
tl = self.GetSelectionBlockTopLeft() | |
br = self.GetSelectionBlockBottomRight() | |
cols = self.GetSelectedCols() | |
rows = self.GetSelectedRows() | |
cells = self.GetSelectedCells() | |
if sm == "Row": | |
ret = rows | |
# See if anything is returned by the block functions | |
if tl and br: | |
for tlz, brz in zip(tl, br): | |
r1 = tlz[0] | |
r2 = brz[0] | |
ret += range(r1, r2+1) | |
if not ret: | |
# Only a single cell selected | |
ret = [self.GetGridCursorRow()] | |
elif sm == "Col": | |
ret = cols | |
# See if anything is returned by the block functions | |
if tl and br: | |
for tlz, brz in zip(tl, br): | |
c1 = tlz[1] | |
c2 = brz[1] | |
ret += range(c1, c2+1) | |
if not ret: | |
# Only a single cell selected | |
ret = [self.GetGridCursorCol()] | |
else: | |
# Cell selection mode | |
if tl and br: | |
ret = zip(tl, br) | |
# Add any selected rows | |
if rows: | |
ret += self._calcRanges(rows, "Rows") | |
# Add any selected columns | |
if cols: | |
ret += self._calcRanges(cols, "Cols") | |
# Add any selected cells | |
if cells: | |
ret += [(val, val) for val in cells] | |
if not ret: | |
cell = (self.GetGridCursorRow(), self.GetGridCursorCol()) | |
ret = [(cell, cell)] | |
ret.sort() | |
return ret | |
def _getSelectionBackColor(self): | |
return self._selectionBackColor | |
def _setSelectionBackColor(self, val): | |
if self._constructed(): | |
self._selectionBackColor = val | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self.SetSelectionBackground(val) | |
else: | |
self._properties["SelectionBackColor"] = val | |
def _getSelectionForeColor(self): | |
return self._selectionForeColor | |
def _setSelectionForeColor(self, val): | |
if self._constructed(): | |
self._selectionForeColor = val | |
if isinstance(val, basestring): | |
val = dColors.colorTupleFromName(val) | |
self.SetSelectionForeground(val) | |
else: | |
self._properties["SelectionForeColor"] = val | |
def _getSelectionMode(self): | |
return self._selectionMode | |
def _setSelectionMode(self, val): | |
if self._constructed(): | |
orig = self._selectionMode | |
val2 = val.lower().strip()[:2] | |
if val2 == "ro": | |
try: | |
self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) | |
self._selectionMode = "Row" | |
except wx.PyAssertionError: | |
dabo.ui.callAfter(self._setSelectionMode, val) | |
elif val2 == "co": | |
try: | |
self.SetSelectionMode(wx.grid.Grid.wxGridSelectColumns) | |
self._selectionMode = "Col" | |
except wx.PyAssertionError: | |
dabo.ui.callAfter(self._setSelectionMode, val) | |
else: | |
try: | |
self.SetSelectionMode(wx.grid.Grid.wxGridSelectCells) | |
self._selectionMode = "Cell" | |
except wx.PyAssertionError: | |
dabo.ui.callAfter(self._setSelectionMode, val) | |
if self._selectionMode != orig: | |
self._checkSelectionType() | |
else: | |
self._properties["SelectionMode"] = val | |
def _getShowCellBorders(self): | |
return self.GridLinesEnabled() | |
def _setShowCellBorders(self, val): | |
if self._constructed(): | |
self.EnableGridLines(val) | |
else: | |
self._properties["ShowCellBorders"] = val | |
def _getShowColumnLabels(self): | |
warnings.warn(_("ShowColumnLabels is deprecated. Use ShowHeaders instead"), DeprecationWarning) | |
return self._showHeaders | |
def _setShowColumnLabels(self, val): | |
if self._constructed(): | |
warnings.warn(_("ShowColumnLabels is deprecated. Use ShowHeaders instead"), DeprecationWarning) | |
self._showHeaders = val | |
if val: | |
self.SetColLabelSize(self.HeaderHeight) | |
else: | |
self.SetColLabelSize(0) | |
else: | |
self._properties["ShowColumnLabels"] = val | |
def _getShowHeaders(self): | |
return self._showHeaders | |
def _setShowHeaders(self, val): | |
if self._constructed(): | |
self._showHeaders = val | |
if val: | |
hh = getattr(self, "_lastPositiveHeaderHeight", None) | |
if not hh: | |
# Use current if already positive: | |
hh = self.GetColLabelSize() | |
if not hh: | |
# Set a reasonable default (should never happen) | |
hh = 32 | |
self.SetColLabelSize(hh) | |
else: | |
curr = self.GetColLabelSize() | |
if curr > 0: | |
self._lastPositiveHeaderHeight = curr | |
self.SetColLabelSize(0) | |
else: | |
self._properties["ShowHeaders"] = val | |
def _getShowRowLabels(self): | |
return self._showRowLabels | |
def _setShowRowLabels(self, val): | |
if self._constructed(): | |
self._showRowLabels = val | |
if val: | |
self.SetRowLabelSize(self.RowLabelWidth) | |
else: | |
self.SetRowLabelSize(0) | |
else: | |
self._properties["ShowRowLabels"] = val | |
def _getSortable(self): | |
return self._sortable | |
def _setSortable(self, val): | |
self._sortable = bool(val) | |
def _getSortIndicatorColor(self): | |
return self._sortIndicatorColor | |
def _setSortIndicatorColor(self, val): | |
if self._constructed(): | |
self._sortIndicatorColor = val | |
else: | |
self._properties["SortIndicatorColor"] = val | |
def _getSortIndicatorSize(self): | |
return self._sortIndicatorSize | |
def _setSortIndicatorSize(self, val): | |
if self._constructed(): | |
self._sortIndicatorSize = val | |
else: | |
self._properties["SortIndicatorSize"] = val | |
def _getTabNavigates(self): | |
return getattr(self, "_tabNavigates", True) | |
def _setTabNavigates(self, val): | |
self._tabNavigates = bool(val) | |
def _getVerticalHeaders(self): | |
return self._verticalHeaders | |
def _setVerticalHeaders(self, val): | |
if self._constructed(): | |
if val != self._verticalHeaders: | |
self._verticalHeaders = val | |
self.refresh() | |
if self.AutoAdjustHeaderHeight: | |
dabo.ui.callAfter(self.fitHeaderHeight) | |
else: | |
self._properties["VerticalHeaders"] = val | |
def _getVerticalScrolling(self): | |
return self.GetScrollPixelsPerUnit()[1] > 0 | |
def _setVerticalScrolling(self, val): | |
if self._constructed(): | |
if val: | |
self.SetScrollRate(self.GetScrollPixelsPerUnit()[0], 20) | |
else: | |
self.SetScrollRate(self.GetScrollPixelsPerUnit()[0], 0) | |
self.refresh() | |
else: | |
self._properties["VerticalScrolling"] = val | |
def _getTable(self): | |
## pkm: we can‘t call this until after the grid is fully constructed. Need to fix. | |
try: | |
tbl = self.GetTable() | |
except TypeError: | |
tbl = None | |
if not tbl: | |
try: | |
tbl = dGridDataTable(self) | |
self.SetTable(tbl, False) | |
except TypeError: | |
tbl = None | |
return tbl | |
def _setTable(self, tbl): | |
if self._constructed(): | |
self.SetTable(tbl, True) | |
else: | |
self._properties["Table"] = value | |
ActivateEditorOnSelect = property( | |
_getActivateEditorOnSelect, _setActivateEditorOnSelect, None, | |
_("Specifies whether the cell editor, if any, is activated upon cell selection.")) | |
AlternateRowColoring = property(_getAlternateRowColoring, _setAlternateRowColoring, None, | |
_("""When True, alternate rows of the grid are colored according to | |
the RowColorOdd and RowColorEven properties (bool)""")) | |
AutoAdjustHeaderHeight = property(_getAutoAdjustHeaderHeight, | |
_setAutoAdjustHeaderHeight, None, | |
_("""When True, changing the VerticalHeaders property will adjust the HeaderHeight | |
to accommodate the rotated labels. Default=False. (bool)""")) | |
CellHighlightWidth = property(_getCellHighlightWidth, _setCellHighlightWidth, None, | |
_("Specifies the width of the cell highlight box.")) | |
Children = property(_getColumns, None, None, | |
_("List of dColumns, same as self.Columns. (list)")) | |
Columns = property(_getColumns, None, None, | |
_("List of dColumns. (list)")) | |
ColumnClass = property(_getColumnClass, _setColumnClass, None, | |
_("""Class to instantiate when a change to ColumnCount requires | |
additional columns to be created. Default=dColumn. (dColumn subclass)""") ) | |
ColumnCount = property(_getColumnCount, _setColumnCount, None, | |
_("Number of columns in the grid. (int)") ) | |
CurrentCellValue = property(_getCurrCellVal, _setCurrCellVal, None, | |
_("Value of the currently selected grid cell (varies)") ) | |
CurrentColumn = property(_getCurrentColumn, _setCurrentColumn, None, | |
_("Currently selected column index. (int)") ) | |
CurrentField = property(_getCurrentField, _setCurrentField, None, | |
_("Field for the currently selected column (str)") ) | |
CurrentRow = property(_getCurrentRow, _setCurrentRow, None, | |
_("Currently selected row (int)") ) | |
DataSet = property(_getDataSet, _setDataSet, None, | |
_("""The set of data displayed in the grid. (set of dicts) | |
When DataSource isn‘t defined, setting DataSet to a set of dicts, | |
such as what you get from calling dBizobj.getDataSet(), will | |
define the source of the data that the grid displays. | |
If DataSource is defined, DataSet is read-only and returns the dataSet | |
from the bizobj.""")) | |
DataSource = property(_getDataSource, _setDataSource, None, | |
_("""The source of the data to display in the grid. (str) | |
This corresponds to a bizobj with a matching DataSource on the form, | |
and setting this makes it impossible to set DataSet.""")) | |
Editable = property(_getEditable, _setEditable, None, | |
_("""This setting enables/disables cell editing globally. (bool) | |
When False, no cells will be editable by the user. When True, cells in | |
columns set as Editable will be editable by the user. Note that grids | |
and columns are both set with Editable=False by default, so to enable | |
cell editing you need to turn it on in the appropriate column as well | |
as in the grid.""") ) | |
Encoding = property(_getEncoding, None, None, | |
_("Name of encoding to use for unicode (str)") ) | |
HeaderBackColor = property(_getHeaderBackColor, _setHeaderBackColor, None, | |
_("""Optional color for the background of the column headers. (str or None) | |
This is only the default: setting the corresponding dColumn property will | |
override.""") ) | |
HeaderForeColor = property(_getHeaderForeColor, _setHeaderForeColor, None, | |
_("""Optional color for the foreground (text) of the column headers. (str or None) | |
This is only the default: setting the corresponding dColumn property will | |
override.""") ) | |
HeaderHeight = property(_getHeaderHeight, _setHeaderHeight, None, | |
_("Height of the column headers. (int)") ) | |
HeaderHorizontalAlignment = property(_getHeaderHorizontalAlignment, _setHeaderHorizontalAlignment, None, | |
_("""The horizontal alignment of the header captions. (‘Left‘, ‘Center‘, ‘Right‘) | |
This is only the default: setting the corresponding dColumn property will | |
override.""") ) | |
HeaderVerticalAlignment = property(_getHeaderVerticalAlignment, _setHeaderVerticalAlignment, None, | |
_("""The vertical alignment of the header captions. (‘Top‘, ‘Center‘, ‘Bottom‘) | |
This is only the default: setting the corresponding dColumn property will | |
override.""") ) | |
HorizontalScrolling = property(_getHorizontalScrolling, _setHorizontalScrolling, None, | |
_("Is scrolling enabled in the horizontal direction? (bool)")) | |
MovableColumns = property(_getMovableColumns, _setMovableColumns, None, | |
_("When False, the user cannot re-order the columns by dragging the headers (bool)")) | |
MultipleSelection = property(_getMultipleSelection, _setMultipleSelection, None, | |
_("When True (default), more than one cell/row/col can be selected at once (bool)")) | |
NoneDisplay = property(_getNoneDisplay, _setNoneDisplay, None, | |
_("Text to display for null (None) values. (str)") ) | |
ResizableColumns = property(_getResizableColumns, _setResizableColumns, None, | |
_("When False, the user cannot resize the columns (bool)")) | |
ResizableRows = property(_getResizableRows, _setResizableRows, None, | |
_("When False, the user cannot resize the rows (bool)")) | |
RowColorEven = property(_getRowColorEven, _setRowColorEven, None, | |
_("""When alternate row coloring is active, controls the color | |
of the even rows (str or tuple)""")) | |
RowColorOdd = property(_getRowColorOdd, _setRowColorOdd, None, | |
_("""When alternate row coloring is active, controls the color | |
of the odd rows (str or tuple)""")) | |
RowCount = property(_getRowCount, None, None, | |
_("Number of rows in the grid. (int)") ) | |
RowHeight = property(_getRowHeight, _setRowHeight, None, | |
_("Row Height for all rows of the grid (int)")) | |
RowLabels = property(_getRowLabels, _setRowLabels, None, | |
_("List of the row labels. (list)") ) | |
RowLabelWidth = property(_getRowLabelWidth, _setRowLabelWidth, None, | |
_("""Width of the label on the left side of the rows. This only changes | |
the grid if ShowRowLabels is True. (int)""")) | |
SameSizeRows = property(_getSameSizeRows, _setSameSizeRows, None, | |
_("""Is every row the same height? (bool)""")) | |
SaveRestoreDataSet = property(_getSaveRestoreDataSet, _setSaveRestoreDataSet, None, | |
_("""Specifies whether the DataSet is persisted to preferences (bool). | |
This allows you to build a grid to capture user input of some form, and | |
instead of saving the row and field values to a database, to save the | |
entire dataset to a single key in the prefs table. | |
Use this sparingly for grids that won‘t grow too large. | |
The default is False.""")) | |
SaveRestoreDataSet = property(_getSaveRestoreDataSet, _setSaveRestoreDataSet, None, | |
_("""Specifies whether the DataSet is persisted to preferences (bool). | |
This allows you to build a grid to capture user input of some form, and | |
instead of saving the row and field values to a database, to save the | |
entire dataset to a single key in the prefs table. | |
Use this sparingly for grids that won‘t grow too large. | |
The default is False.""")) | |
Searchable = property(_getSearchable, _setSearchable, None, | |
_("""Specifies whether the columns can be searched. (bool) | |
If True, columns that have their Searchable properties set to True | |
will be searchable. | |
Default: True""")) | |
SearchDelay = property(_getSearchDelay, _setSearchDelay, None, | |
_("""Specifies the delay before incrementeal searching begins. (int or None) | |
As the user types, the search string is modified. If the time between | |
keystrokes exceeds SearchDelay (milliseconds), the search will run and | |
the search string will be cleared. | |
If SearchDelay is set to None (the default), Application.SearchDelay will | |
be used.""") ) | |
Selection = property(_getSelection, None, None, | |
_("""Returns either a list of row/column numbers if SelectionMode is set to | |
either ‘Row‘ or ‘Column‘. If SelectionMode is ‘Cell‘, returns a list of 2-tuples, | |
where each 2-tuple represents a selected range of cells: the top-left and | |
bottom-right coordinates for a given range. If only a single cell is selected, | |
there will be only one 2-tuple in the list, with both values being the same. | |
If a continuous block of cells is selected, there will be only one 2-tuple in the | |
list, but the values will differ. If more than one discontinuous range is selected, | |
there will be as many 2-tuples as there are range blocks. (list)""")) | |
SelectionBackColor = property(_getSelectionBackColor, _setSelectionBackColor, None, | |
_("BackColor of selected cells (str or RGB tuple)")) | |
SelectionForeColor = property(_getSelectionForeColor, _setSelectionForeColor, None, | |
_("ForeColor of selected cells (str or RGB tuple)")) | |
SelectionMode = property(_getSelectionMode, _setSelectionMode, None, | |
_("""Determines how the grid displays selections. (str) | |
Options are: | |
Cells/Plain/None - no row/col highlighting (default) | |
Row - the row of the selected cell is highlighted | |
Column - the column of the selected cell is highlighted | |
The highlight color is determined by the SelectionBackColor and | |
SelectionForeColor properties. | |
""")) | |
ShowCellBorders = property(_getShowCellBorders, _setShowCellBorders, None, | |
_("Are borders around cells shown? (bool)") ) | |
ShowColumnLabels = property(_getShowColumnLabels, _setShowColumnLabels, None, | |
_("""Are column labels shown? (bool) | |
DEPRECATED: Use ShowHeaders instead.""") ) | |
ShowHeaders = property(_getShowHeaders, _setShowHeaders, None, | |
_("""Are grid column headers shown? (bool)""") ) | |
ShowRowLabels = property(_getShowRowLabels, _setShowRowLabels, None, | |
_("Are row labels shown? (bool)") ) | |
Sortable = property(_getSortable, _setSortable, None, | |
_("""Specifies whether the columns can be sorted. If True, | |
and if the column‘s Sortable property is True, the column | |
will be sortable. Default: True (bool)""")) | |
SortIndicatorColor = property(_getSortIndicatorColor, _setSortIndicatorColor, | |
None, _("""Color of the icon is that identifies a column as being sorted. | |
Default="yellow". (str or color tuple)""")) | |
SortIndicatorSize = property(_getSortIndicatorSize, _setSortIndicatorSize, | |
None, _("""Determines how large the icon is that identifies a column as | |
being sorted. Default=8. (int)""")) | |
TabNavigates = property(_getTabNavigates, _setTabNavigates, None, | |
_("""Specifies whether Tab navigates to the next control (True, the default), | |
or if Tab moves to the next column in the grid (False).""")) | |
VerticalHeaders = property(_getVerticalHeaders, _setVerticalHeaders, None, | |
_("""When True, the column headers‘ Captions are written vertically. | |
Default=False. (bool)""")) | |
VerticalScrolling = property(_getVerticalScrolling, _setVerticalScrolling, None, | |
_("Is scrolling enabled in the vertical direction? (bool)")) | |
_Table = property(_getTable, _setTable, None, | |
_("Reference to the internal table class (dGridDataTable)") ) | |
# Dynamic Property Declarations | |
DynamicActivateEditorOnSelect = makeDynamicProperty(ActivateEditorOnSelect) | |
DynamicAlternateRowColoring = makeDynamicProperty(AlternateRowColoring) | |
DynamicCellHighlightWidth = makeDynamicProperty(CellHighlightWidth) | |
DynamicColumnClass = makeDynamicProperty(ColumnClass) | |
DynamicColumnCount = makeDynamicProperty(ColumnCount) | |
DynamicCurrentColumn = makeDynamicProperty(CurrentColumn) | |
DynamicCurrentField = makeDynamicProperty(CurrentField) | |
DynamicCurrentRow = makeDynamicProperty(CurrentRow) | |
DynamicDataSet = makeDynamicProperty(DataSet) | |
DynamicDataSource = makeDynamicProperty(DataSource) | |
DynamicEditable = makeDynamicProperty(Editable) | |
DynamicHeaderBackColor = makeDynamicProperty(HeaderBackColor) | |
DynamicHeaderForeColor = makeDynamicProperty(HeaderForeColor) | |
DynamicHeaderHeight = makeDynamicProperty(HeaderHeight) | |
DynamicHeaderHorizontalAlignment = makeDynamicProperty(HeaderHorizontalAlignment) | |
DynamicHeaderVerticalAlignment = makeDynamicProperty(HeaderVerticalAlignment) | |
DynamicHorizontalScrolling = makeDynamicProperty(HorizontalScrolling) | |
DynamicRowColorEven = makeDynamicProperty(RowColorEven) | |
DynamicRowColorOdd = makeDynamicProperty(RowColorOdd) | |
DynamicRowHeight = makeDynamicProperty(RowHeight) | |
DynamicRowLabels = makeDynamicProperty(RowLabels) | |
DynamicRowLabelWidth = makeDynamicProperty(RowLabelWidth) | |
DynamicSameSizeRows = makeDynamicProperty(SameSizeRows) | |
DynamicSearchable = makeDynamicProperty(Searchable) | |
DynamicSearchDelay = makeDynamicProperty(SearchDelay) | |
DynamicSelectionBackColor = makeDynamicProperty(SelectionBackColor) | |
DynamicSelectionForeColor = makeDynamicProperty(SelectionForeColor) | |
DynamicSelectionMode = makeDynamicProperty(SelectionMode) | |
DynamicShowCellBorders = makeDynamicProperty(ShowCellBorders) | |
DynamicShowColumnLabels = makeDynamicProperty(ShowColumnLabels) | |
DynamicShowHeaders = makeDynamicProperty(ShowHeaders) | |
DynamicShowRowLabels = makeDynamicProperty(ShowRowLabels) | |
DynamicSortable = makeDynamicProperty(Sortable) | |
DynamicTabNavigates = makeDynamicProperty(TabNavigates) | |
DynamicVerticalScrolling = makeDynamicProperty(VerticalScrolling) | |
DynamicVerticalHeaders = makeDynamicProperty(VerticalHeaders) | |
##----------------------------------------------------------## | |
## end: property definitions ## | |
##----------------------------------------------------------## | |
class _dGrid_test(dGrid): | |
def initProperties(self): | |
thisYear = datetime.datetime.now().year | |
ds = [ | |
{"name" : "Ed Leafe", "age" : thisYear - 1957, "coder" : True, "color": "cornsilk"}, | |
{"name" : "Paul McNett", "age" : thisYear - 1969, "coder" : True, "color": "wheat"}, | |
{"name" : "Ted Roche", "age" : thisYear - 1958, "coder" : True, "color": "goldenrod"}, | |
{"name" : "Derek Jeter", "age": thisYear - 1974, "coder" : False, "color": "white"}, | |
{"name" : "Halle Berry", "age" : thisYear - 1966, "coder" : False, "color": "orange"}, | |
{"name" : "Steve Wozniak", "age" : thisYear - 1950, "coder" : True, "color": "yellow"}, | |
{"name" : "LeBron James", "age" : thisYear - 1984, "coder" : False, "color": "gold"}, | |
{"name" : "Madeline Albright", "age" : thisYear - 1937, "coder" : False, "color": "red"}] | |
for row in range(len(ds)): | |
for i in range(20): | |
ds[row]["i_%s" % i] = "sss%s" % i | |
self.DataSet = ds | |
self.TabNavigates = False | |
self.Width = 360 | |
self.Height = 150 | |
self.Editable = False | |
#self.Sortable = False | |
#self.Searchable = False | |
def afterInit(self): | |
super(_dGrid_test, self).afterInit() | |
self.addColumn(Name="Geek", DataField="coder", Caption="Geek?", | |
Order=10, DataType="bool", Width=60, Sortable=False, | |
Searchable=False, Editable=True, HeaderFontBold=False, | |
HorizontalAlignment="Center", VerticalAlignment="Center", | |
Resizable=False) | |
col = dColumn(self, Name="Person", Order=20, DataField="name", | |
DataType="string", Width=200, Caption="Celebrity Name", | |
Sortable=True, Searchable=True, Editable=True, Expand=False) | |
self.addColumn(col) | |
col.HeaderFontItalic = True | |
col.HeaderBackColor = "peachpuff" | |
col.HeaderVerticalAlignment = "Top" | |
col.HeaderHorizontalAlignment = "Left" | |
# Let‘s make a custom editor for the name | |
class ColoredText(dabo.ui.dTextBox): | |
def initProperties(self): | |
self.ForeColor = "blue" | |
self.FontItalic = True | |
self.FontSize = 24 | |
def onKeyChar(self, evt): | |
self.ForeColor = dColors.randomColor() | |
self.FontItalic = not self.FontItalic | |
# Since we‘re using a big font, set a minimum height for the editor | |
col.CustomEditorClass = dabo.ui.makeGridEditor(ColoredText, minHeight=40) | |
self.addColumn(Name="Age", Order=30, DataField="age", | |
DataType="integer", Width=40, Caption="Age", | |
Sortable=True, Searchable=True, Editable=True) | |
col = dColumn(self, Name="Color", Order=40, DataField="color", | |
DataType="string", Width=40, Caption="Favorite Color", | |
Sortable=True, Searchable=True, Editable=True, Expand=False) | |
self.addColumn(col) | |
col.ListEditorChoices = dColors.colors | |
col.CustomEditorClass = col.listEditorClass | |
col.HeaderVerticalAlignment = "Bottom" | |
col.HeaderHorizontalAlignment = "Right" | |
col.HeaderForeColor = "brown" | |
for i in range(1): | |
# Can‘t test Expand with so many columns! Just add one. | |
self.addColumn(DataField="i_%s" % i, Caption="i_%s" % i) | |
def onScrollLineUp(self, evt): | |
print "LINE UP orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
def onScrollLineDown(self, evt): | |
print "LINE DOWN orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
def onScrollPageUp(self, evt): | |
print "PAGE UP orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
def onScrollPageDown(self, evt): | |
print "PAGE DOWN orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
def onScrollThumbDrag(self, evt): | |
print "DRAG orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
def onScrollThumbRelease(self, evt): | |
print "THUMB RELEASE orientation =", evt.orientation, " scrollpos =", evt.scrollpos | |
if __name__ == ‘__main__‘: | |
from dabo.dApp import dApp | |
class TestForm(dabo.ui.dForm): | |
def afterInit(self): | |
self.BackColor = "khaki" | |
g = self.grid = _dGrid_test(self, RegID="sampleGrid") | |
self.Sizer.append(g, 1, "x", border=0, borderSides="all") | |
self.Sizer.appendSpacer(10) | |
gsz = dabo.ui.dGridSizer(HGap=50) | |
chk = dabo.ui.dCheckBox(self, Caption="Allow Editing?", RegID="gridEdit", | |
DataSource=self.grid, DataField="Editable") | |
chk.update() | |
gsz.append(chk, row=0, col=0) | |
chk = dabo.ui.dCheckBox(self, Caption="Show Headers", | |
RegID="showHeaders", DataSource=self.grid, | |
DataField="ShowHeaders") | |
gsz.append(chk, row=1, col=0) | |
chk.update() | |
chk = dabo.ui.dCheckBox(self, Caption="Allow Multiple Selection", | |
RegID="multiSelect", DataSource=self.grid, | |
DataField="MultipleSelection") | |
chk.update() | |
gsz.append(chk, row=2, col=0) | |
chk = dabo.ui.dCheckBox(self, Caption="Vertical Headers", | |
RegID="verticalHeaders", DataSource=self.grid, | |
DataField="VerticalHeaders") | |
chk.update() | |
gsz.append(chk, row=3, col=0) | |
chk = dabo.ui.dCheckBox(self, Caption="Auto-adjust Header Height", | |
RegID="autoAdjust", DataSource=self.grid, | |
DataField="AutoAdjustHeaderHeight") | |
chk.update() | |
gsz.append(chk, row=4, col=0) | |
radSelect = dabo.ui.dRadioList(self, Choices=["Row", "Col", "Cell"], | |
ValueMode="string", Caption="Sel Mode", BackColor=self.BackColor, | |
DataSource=self.grid, DataField="SelectionMode", RegID="radSelect") | |
radSelect.refresh() | |
gsz.append(radSelect, row=0, col=1, rowSpan=3) | |
def setVisible(evt): | |
col = g.getColByDataField("name") | |
but = evt.EventObject | |
col.Visible = not col.Visible | |
if col.Visible: | |
but.Caption = "Make Celebrity Invisible" | |
else: | |
but.Caption = "Make Celebrity Visible" | |
butVisible = dabo.ui.dButton(self, Caption="Toggle Celebrity Visibility", | |
OnHit=setVisible) | |
gsz.append(butVisible, row=5, col=0) | |
self.Sizer.append(gsz, halign="Center", border=10) | |
gsz.setColExpand(True, 1) | |
self.layout() | |
self.fitToSizer(20, 20) | |
app = dApp(MainFormClass=TestForm) | |
app.setup() | |
app.MainForm.radSelect.setFocus() | |
app.start() |
github n addabaji-ci/dabo-0.9.13/dabo-0.9.13/dabo/ui/uiwx/dGrid.py
原文:http://www.cnblogs.com/chengxuyuan326260/p/6391579.html