| # -*- 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() |