| 1 |
"""Grid Editors |
|---|
| 2 |
|
|---|
| 3 |
20080131 - GNT |
|---|
| 4 |
""" |
|---|
| 5 |
|
|---|
| 6 |
import wx |
|---|
| 7 |
import dabo |
|---|
| 8 |
import datetime |
|---|
| 9 |
import time |
|---|
| 10 |
import locale |
|---|
| 11 |
import sys |
|---|
| 12 |
dabo.ui.loadUI("wx") |
|---|
| 13 |
from dabo.dBug import loggit |
|---|
| 14 |
|
|---|
| 15 |
# There is an issue with this renderer class where a black highlight remains around changed cells. This is caused |
|---|
| 16 |
# purely by the use of this renderer. |
|---|
| 17 |
|
|---|
| 18 |
# todo: sometimes the column is formated bold, not always |
|---|
| 19 |
# todo: if the row/column is selected and a diffent cell then the gDropdownList-Column, then the background is |
|---|
| 20 |
# not displayed properly. |
|---|
| 21 |
# todo: if in editmode and the header is click to sort, the dropdownlist is not updated |
|---|
| 22 |
# todo: if started, the keys are not found, i.e. Draw is called before getLookupDict |
|---|
| 23 |
# todo: sorts by keys, not values. |
|---|
| 24 |
|
|---|
| 25 |
# TODO: If this becomes included within dabo, it is recommended to remove the need for |
|---|
| 26 |
# getKeys, getChoices, getValueMode and set these arguements in the dGrid._updateEditor function as with the existing List Editor |
|---|
| 27 |
class gGridDropdownListRenderer(wx.grid.PyGridCellRenderer): |
|---|
| 28 |
"""Display the Lookup value of a combo or list box instead of the actual value. |
|---|
| 29 |
You must inherit this class and override the getKeys, getChoices and getValueMode Functions.""" |
|---|
| 30 |
def __init__(self, *args, **kwargs): |
|---|
| 31 |
self.__valueLookupDict = self.getLookupDict() |
|---|
| 32 |
super(gGridDropdownListRenderer, self).__init__(*args, **kwargs) |
|---|
| 33 |
|
|---|
| 34 |
# These functions must be overriden |
|---|
| 35 |
@loggit |
|---|
| 36 |
def getKeys(self): |
|---|
| 37 |
"""Override this function to return a list of stored keys stored in this datafield.""" |
|---|
| 38 |
return [2, 4, 6, 8] |
|---|
| 39 |
def getChoices(self): |
|---|
| 40 |
"""Override this function to return a list of values to be displayed for this datafield.""" |
|---|
| 41 |
return ["The Ants", "Go Marching", "Two by Two", "Hoorah, Hoorah"] |
|---|
| 42 |
def getValueMode(self): |
|---|
| 43 |
"""Override this Function to return the ValueMode for the Combo or List box.""" |
|---|
| 44 |
return 'Key' |
|---|
| 45 |
|
|---|
| 46 |
def getLookupDict(self): |
|---|
| 47 |
"""Override this function to return a dictionary of values to display. The dictionary key is the stored value.""" |
|---|
| 48 |
if self.getValueMode() == 'Key': |
|---|
| 49 |
keys = self.getKeys() |
|---|
| 50 |
choices = self.getChoices() |
|---|
| 51 |
return dict(zip(keys,choices)) |
|---|
| 52 |
|
|---|
| 53 |
# This is the function overriden from the parent class. |
|---|
| 54 |
def Draw(self, grid, attr, dc, rect, row, col, isSelected): |
|---|
| 55 |
"""Customisation Point: Draw the data from grid in the rectangle with attributes using the dc""" |
|---|
| 56 |
self.clip(dc, rect) |
|---|
| 57 |
# We use our custom attr, not the one wx passes: |
|---|
| 58 |
attr = grid._Table.GetAttr(row, col) |
|---|
| 59 |
try: |
|---|
| 60 |
txt = self.getValueText(grid, row, col) |
|---|
| 61 |
dc.Clear() |
|---|
| 62 |
super(gGridDropdownListRenderer, self).Draw(grid, attr, dc, rect, row, col, isSelected) |
|---|
| 63 |
self.drawText(txt, attr, dc, rect) |
|---|
| 64 |
#super(gGridDropdownListRenderer, self).Draw(grid, attr, dc, rect, row, col, isSelected) |
|---|
| 65 |
finally: |
|---|
| 66 |
self.unclip(dc) |
|---|
| 67 |
@loggit |
|---|
| 68 |
def getValueText(self, grid, row, col): |
|---|
| 69 |
"""Returns the text to display in the grid. If the key does not exist, then the |
|---|
| 70 |
key will be displayed.""" |
|---|
| 71 |
key = grid._Table.GetValue(row, col) |
|---|
| 72 |
return self.__valueLookupDict.get(key, "key not found") |
|---|
| 73 |
|
|---|
| 74 |
def drawText(self, txt, attr, dc, rect): |
|---|
| 75 |
dc.DrawText(txt, rect.x, rect.y) |
|---|
| 76 |
def clip(self, dc, rect): |
|---|
| 77 |
"""Setup the clipping rectangle""" |
|---|
| 78 |
dc.SetClippingRegion(rect.x, rect.y, rect.width, rect.height) |
|---|
| 79 |
def unclip(self, dc): |
|---|
| 80 |
"""Destroy the clipping rectangle""" |
|---|
| 81 |
dc.DestroyClippingRegion() |
|---|
| 82 |
|
|---|
| 83 |
class gGridDropdownListDebug(dabo.ui.dDropdownList): |
|---|
| 84 |
def onDestroy(self, evt): |
|---|
| 85 |
dabo.infoLog.write("gGridDropdownList: OnDestroy") |
|---|
| 86 |
#super(gGridDropdownListEditor, self).__init__(*args, **kwargs) |
|---|
| 87 |
def onCreate(self, evt): |
|---|
| 88 |
dabo.infoLog.write("gGridDropdownList: OnCreate ") |
|---|
| 89 |
def onMouseEvent(self, evt): |
|---|
| 90 |
dabo.infoLog.write("gGridDropdownList: MouseEvent ") |
|---|
| 91 |
def onGotFocus(self, evt): |
|---|
| 92 |
dabo.infoLog.write("gGridDropdownList: GotFocus") |
|---|
| 93 |
def onLostFocus(self, evt): |
|---|
| 94 |
dabo.infoLog.write("gGridDropdownList: LostFocus") |
|---|
| 95 |
def onHit(self, evt): |
|---|
| 96 |
dabo.infoLog.write("gGridDropdownList: Hit") |
|---|
| 97 |
evt.Continue = True |
|---|
| 98 |
#print "New Key, ", evt.EventData['index'] |
|---|
| 99 |
#print "New Choice, ", evt.EventObject |
|---|
| 100 |
#evt.EventObject.SetFocus() |
|---|
| 101 |
args = self.endEditArgs |
|---|
| 102 |
kwargs = self.endEditKwargs |
|---|
| 103 |
self.endEdit(*args, **kwargs) |
|---|
| 104 |
def onValueChanged(self, evt): |
|---|
| 105 |
dabo.infoLog.write("gGridDropdownList: ValueChanged") |
|---|
| 106 |
def setEndEdit(self, endEdit, *args, **kwargs): |
|---|
| 107 |
self.endEdit = endEdit |
|---|
| 108 |
self.endEditArgs = args |
|---|
| 109 |
self.endEditKwargs = kwargs |
|---|
| 110 |
|
|---|
| 111 |
class gGridDropdownList(gGridDropdownListDebug): |
|---|
| 112 |
"""Due to an issue with PyGridCellEditor calling EndEdit on control |
|---|
| 113 |
lostFocus (and the DropdownList/wx.Choice looses focus a little early), |
|---|
| 114 |
this class has been created with the intention of still capturing user input.""" |
|---|
| 115 |
# I suspect that wx.grid.GridCellChoiceEditor is designed specifically |
|---|
| 116 |
# not to loose focus during editing. It might be worth overriding |
|---|
| 117 |
# some of the functionality of dDropdownList / wx.Choice in a new subclass to behave in a similiar manner. |
|---|
| 118 |
def onHit(self, evt): |
|---|
| 119 |
args = self.endEditArgs |
|---|
| 120 |
kwargs = self.endEditKwargs |
|---|
| 121 |
self.endEdit(*args, **kwargs) |
|---|
| 122 |
|
|---|
| 123 |
def setEndEdit(self, endEdit, *args, **kwargs): |
|---|
| 124 |
self.endEdit = endEdit |
|---|
| 125 |
self.endEditArgs = args |
|---|
| 126 |
self.endEditKwargs = kwargs |
|---|
| 127 |
|
|---|
| 128 |
# TODO: If this becomes included within dabo, it is recommended to remove the need for |
|---|
| 129 |
# getKeys, getChoices, getValueMode and set these arguements in the dGrid._updateEditor function as with the existing List Editor |
|---|
| 130 |
class gGridDropdownListEditor(wx.grid.PyGridCellEditor): |
|---|
| 131 |
"""GridComboListEditor embeds a (dabo) combo box or (dabo) list box within a (dabo) grid. |
|---|
| 132 |
This class must be inherited and have the getKeys, getChoices and getValueMode methods overriden.""" |
|---|
| 133 |
|
|---|
| 134 |
# This function must be overriden |
|---|
| 135 |
def getKeys(self): |
|---|
| 136 |
"""Override this function to return a list of stored keys stored in this datafield.""" |
|---|
| 137 |
return [2, 4, 6, 8] |
|---|
| 138 |
def getChoices(self): |
|---|
| 139 |
"""Override this function to return a list of values to be displayed for this datafield.""" |
|---|
| 140 |
return ["The Ants", "Go Marching", "Two by Two", "Hoorah, Hoorah"] |
|---|
| 141 |
def getValueMode(self): |
|---|
| 142 |
"""Override this Function to return the ValueMode for the Combo or List box.""" |
|---|
| 143 |
return 'Key' |
|---|
| 144 |
|
|---|
| 145 |
def Create(self, parent, id, evtHandler, *args, **kwargs): |
|---|
| 146 |
control = gGridDropdownList(parent=parent, id=id, Keys=self.getKeys(), Choices=self.getChoices(), ValueMode=self.getValueMode()) |
|---|
| 147 |
self.control = control |
|---|
| 148 |
self.SetControl(self.control) |
|---|
| 149 |
if evtHandler: |
|---|
| 150 |
self.control.PushEventHandler(evtHandler) |
|---|
| 151 |
# This Control does not appear to be receiving mouse events properly... I had this problem with the DateTextBox |
|---|
| 152 |
# Once the drop down list is selected, the control looses focus and then the grid will no longer be updated. |
|---|
| 153 |
# This is because EndEdit is called after the object has lost focus (I think). Quick Fix is to catch event in |
|---|
| 154 |
# above class (gGridDropdownList) |
|---|
| 155 |
super(gGridDropdownListEditor, self).Create(parent, id, evtHandler) |
|---|
| 156 |
|
|---|
| 157 |
def Clone(self): |
|---|
| 158 |
return self.__class__() |
|---|
| 159 |
|
|---|
| 160 |
def SetParameters(self, paramStr): |
|---|
| 161 |
dabo.infoLog.write("gGridDropdownListEditor: SetParameters: %s" % paramStr) |
|---|
| 162 |
#self.control.Choices = eval(paramStr) |
|---|
| 163 |
|
|---|
| 164 |
def BeginEdit(self, row, col, grid): |
|---|
| 165 |
self.value = grid.GetTable().GetValue(row, col) |
|---|
| 166 |
# todo: adapt for choice mode |
|---|
| 167 |
if self.value in self.control.Keys: |
|---|
| 168 |
self.control.Value = self.value |
|---|
| 169 |
else: |
|---|
| 170 |
dabo.infoLog.write("gGridDropdownListEditor.BeginEdit: Value not in Choices") |
|---|
| 171 |
self.control.setEndEdit(self.EndEdit, row, col, grid) |
|---|
| 172 |
self.control.SetFocus() |
|---|
| 173 |
|
|---|
| 174 |
def EndEdit(self, row, col, grid): |
|---|
| 175 |
changed = False |
|---|
| 176 |
v = self.control.Value |
|---|
| 177 |
if v != self.value and v is not None: |
|---|
| 178 |
changed = True |
|---|
| 179 |
grid.GetTable().SetValue(row, col, v) |
|---|
| 180 |
self.value = "" |
|---|
| 181 |
self.control.Value = self.value |
|---|
| 182 |
return changed |
|---|
| 183 |
|
|---|
| 184 |
# def SetSize(self, rectorig): |
|---|
| 185 |
# dabo.infoLog.write("GridListEditor: SetSize: %s" % rectorig) |
|---|
| 186 |
# dabo.infoLog.write("GridListEditor: type of rectorig: %s" % type(rectorig)) |
|---|
| 187 |
# rect = wx.Rect(rectorig.X, rectorig.Y, rectorig.Width+10, rectorig.Height+10) |
|---|
| 188 |
# #rect = rectorig |
|---|
| 189 |
# dabo.infoLog.write("GridListEditor RECT: %s" % rect) |
|---|
| 190 |
# super(gGridDropdownListEditor, self).SetSize(rect) |
|---|
| 191 |
# #self.control.fitToSizer() |
|---|
| 192 |
|
|---|
| 193 |
def Reset(self): |
|---|
| 194 |
self.control.Value = self.value |
|---|
| 195 |
def IsAcceptedKey(self, key): |
|---|
| 196 |
return True |
|---|
| 197 |
|
|---|
| 198 |
if __name__ == '__main__': |
|---|
| 199 |
class _dGrid_test(dabo.ui.dGrid): |
|---|
| 200 |
def initProperties(self): |
|---|
| 201 |
self.DataSet = [ |
|---|
| 202 |
{"name" : "2", "age" : 49, "coder" : True, "color": "orange"}, |
|---|
| 203 |
{"name" : "4", "age" : 37, "coder" : True, "color": "yellow"}, |
|---|
| 204 |
{"name" : "6", "age" : 48, "coder" : True, "color": "red"}, |
|---|
| 205 |
{"name" : "8", "age": 32 , "coder" : False, "color": "white"}] |
|---|
| 206 |
self.Width = 360 |
|---|
| 207 |
self.Height = 150 |
|---|
| 208 |
self.Editable = True |
|---|
| 209 |
#self.Sortable = False |
|---|
| 210 |
#self.Searchable = False |
|---|
| 211 |
|
|---|
| 212 |
def afterInit(self): |
|---|
| 213 |
self.super() |
|---|
| 214 |
|
|---|
| 215 |
self.addColumn(Name="Geek", DataField="coder", Caption="Geek?", |
|---|
| 216 |
Order=10, DataType="bool", Width=60, Sortable=False, |
|---|
| 217 |
Searchable=False, Editable=True, HeaderFontBold=False) |
|---|
| 218 |
|
|---|
| 219 |
col = dabo.ui.dColumn(self, Name="Person", Order=20, DataField="name", |
|---|
| 220 |
DataType="string", Width=200, Caption="Celebrity Name", |
|---|
| 221 |
Sortable=True, Searchable=True, Editable=True, Expand=False) |
|---|
| 222 |
self.addColumn(col) |
|---|
| 223 |
|
|---|
| 224 |
col.HeaderFontItalic = True |
|---|
| 225 |
col.HeaderBackColor = "orange" |
|---|
| 226 |
col.HeaderVerticalAlignment = "Top" |
|---|
| 227 |
col.HeaderHorizontalAlignment = "Left" |
|---|
| 228 |
#col.ListEditorChoices = dabo.dColors.colors |
|---|
| 229 |
#col.CustomEditorClass = GridListEditor |
|---|
| 230 |
|
|---|
| 231 |
col.CustomEditorClass = gGridDropdownListEditor |
|---|
| 232 |
col.CustomRendererClass = gGridDropdownListRenderer #this calls getLookupDict |
|---|
| 233 |
#col.CustomRendererClass = wx.grid.GridCellStringRenderer |
|---|
| 234 |
|
|---|
| 235 |
self.addColumn(Name="Age", Order=30, DataField="age", |
|---|
| 236 |
DataType="integer", Width=40, Caption="Age", |
|---|
| 237 |
Sortable=True, Searchable=True, Editable=True) |
|---|
| 238 |
|
|---|
| 239 |
col = dabo.ui.dColumn(self, Name="Color", Order=40, DataField="color", |
|---|
| 240 |
DataType="string", Width=40, Caption="Favorite Color", |
|---|
| 241 |
Sortable=True, Searchable=True, Editable=True, Expand=False) |
|---|
| 242 |
self.addColumn(col) |
|---|
| 243 |
|
|---|
| 244 |
col.ListEditorChoices = dabo.dColors.colors |
|---|
| 245 |
col.CustomEditorClass = col.listEditorClass |
|---|
| 246 |
#col.CustomEditorClass = dabo.ui.dGrid.GridListEditor |
|---|
| 247 |
#col.CustomEditorClass = gGridDropdownListEditor |
|---|
| 248 |
|
|---|
| 249 |
col.HeaderVerticalAlignment = "Bottom" |
|---|
| 250 |
col.HeaderHorizontalAlignment = "Right" |
|---|
| 251 |
col.HeaderForeColor = "brown" |
|---|
| 252 |
|
|---|
| 253 |
self.RowLabels = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] |
|---|
| 254 |
#self.ShowRowLabels = True |
|---|
| 255 |
|
|---|
| 256 |
class TestForm(dabo.ui.dForm): |
|---|
| 257 |
def afterInit(self): |
|---|
| 258 |
self.BackColor = "khaki" |
|---|
| 259 |
g = self.grid = _dGrid_test(self, RegID="sampleGrid") |
|---|
| 260 |
self.Sizer.append(g, 1, "x", border=40, borderSides="all") |
|---|
| 261 |
self.Sizer.appendSpacer(10) |
|---|
| 262 |
gsz = dabo.ui.dGridSizer(HGap=50) |
|---|
| 263 |
|
|---|
| 264 |
chk = dabo.ui.dCheckBox(self, Caption="Edit Table", RegID="geekEdit", |
|---|
| 265 |
DataSource="sampleGrid", DataField="Editable") |
|---|
| 266 |
chk.refresh() |
|---|
| 267 |
gsz.append(chk, row=0, col=0) |
|---|
| 268 |
|
|---|
| 269 |
chk = dabo.ui.dCheckBox(self, Caption="Show Row Labels", |
|---|
| 270 |
RegID="showRowLabels", DataSource="sampleGrid", |
|---|
| 271 |
DataField="ShowRowLabels") |
|---|
| 272 |
gsz.append(chk, row=1, col=0) |
|---|
| 273 |
chk.refresh() |
|---|
| 274 |
|
|---|
| 275 |
chk = dabo.ui.dCheckBox(self, Caption="Allow Multiple Selection", |
|---|
| 276 |
RegID="multiSelect", DataSource="sampleGrid", |
|---|
| 277 |
DataField="MultipleSelection") |
|---|
| 278 |
chk.refresh() |
|---|
| 279 |
gsz.append(chk, row=2, col=0) |
|---|
| 280 |
|
|---|
| 281 |
radSelect = dabo.ui.dRadioList(self, Choices=["Row", "Col", "Cell"], |
|---|
| 282 |
ValueMode="string", Caption="Sel Mode", BackColor=self.BackColor, |
|---|
| 283 |
DataSource="sampleGrid", DataField="SelectionMode", RegID="radSelect") |
|---|
| 284 |
radSelect.refresh() |
|---|
| 285 |
gsz.append(radSelect, row=0, col=1, rowSpan=3) |
|---|
| 286 |
|
|---|
| 287 |
self.Sizer.append(gsz, halign="Center", border=10) |
|---|
| 288 |
gsz.setColExpand(True, 1) |
|---|
| 289 |
self.layout() |
|---|
| 290 |
|
|---|
| 291 |
self.fitToSizer(20,20) |
|---|
| 292 |
|
|---|
| 293 |
|
|---|
| 294 |
app = dabo.dApp(MainFormClass=TestForm) |
|---|
| 295 |
app.setup() |
|---|
| 296 |
app.MainForm.radSelect.setFocus() |
|---|
| 297 |
app.start() |
|---|
| 298 |
|
|---|
| 299 |
# GridCellChoice |
|---|