]> git.saurik.com Git - wxWidgets.git/blame - wxPython/demo/Grid_MegaExample.py
Don't bind events to the return value of SetDefault, D'oh!
[wxWidgets.git] / wxPython / demo / Grid_MegaExample.py
CommitLineData
8fa876ca
RD
1
2import wx
3import wx.grid as Grid
1fded56b 4
8fa876ca
RD
5import images
6
7class MegaTable(Grid.PyGridTableBase):
1fded56b 8 """
95bfd958 9 A custom wx.Grid Table using user supplied data
1fded56b
RD
10 """
11 def __init__(self, data, colnames, plugins):
12 """data is a list of the form
13 [(rowname, dictionary),
14 dictionary.get(colname, None) returns the data for column
15 colname
16 """
17 # The base class must be initialized *first*
8fa876ca 18 Grid.PyGridTableBase.__init__(self)
1fded56b
RD
19 self.data = data
20 self.colnames = colnames
21 self.plugins = plugins or {}
22 # XXX
8fa876ca 23 # we need to store the row length and column length to
1fded56b
RD
24 # see if the table has changed size
25 self._rows = self.GetNumberRows()
26 self._cols = self.GetNumberCols()
27
28 def GetNumberCols(self):
29 return len(self.colnames)
30
31 def GetNumberRows(self):
32 return len(self.data)
33
34 def GetColLabelValue(self, col):
35 return self.colnames[col]
36
e6132c30
RD
37 def GetRowLabelValue(self, row):
38 return "row %03d" % int(self.data[row][0])
1fded56b
RD
39
40 def GetValue(self, row, col):
41 return str(self.data[row][1].get(self.GetColLabelValue(col), ""))
42
43 def GetRawValue(self, row, col):
44 return self.data[row][1].get(self.GetColLabelValue(col), "")
45
46 def SetValue(self, row, col, value):
47 self.data[row][1][self.GetColLabelValue(col)] = value
48
49 def ResetView(self, grid):
50 """
8fa876ca 51 (Grid) -> Reset the grid view. Call this to
1fded56b
RD
52 update the grid if rows and columns have been added or deleted
53 """
54 grid.BeginBatch()
8fa876ca 55
1fded56b 56 for current, new, delmsg, addmsg in [
8fa876ca
RD
57 (self._rows, self.GetNumberRows(), Grid.GRIDTABLE_NOTIFY_ROWS_DELETED, Grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
58 (self._cols, self.GetNumberCols(), Grid.GRIDTABLE_NOTIFY_COLS_DELETED, Grid.GRIDTABLE_NOTIFY_COLS_APPENDED),
1fded56b 59 ]:
8fa876ca 60
1fded56b 61 if new < current:
8fa876ca 62 msg = Grid.GridTableMessage(self,delmsg,new,current-new)
1fded56b
RD
63 grid.ProcessTableMessage(msg)
64 elif new > current:
8fa876ca 65 msg = Grid.GridTableMessage(self,addmsg,new-current)
1fded56b
RD
66 grid.ProcessTableMessage(msg)
67 self.UpdateValues(grid)
8fa876ca 68
1fded56b
RD
69 grid.EndBatch()
70
71 self._rows = self.GetNumberRows()
72 self._cols = self.GetNumberCols()
73 # update the column rendering plugins
74 self._updateColAttrs(grid)
75
76 # update the scrollbars and the displayed part of the grid
77 grid.AdjustScrollbars()
78 grid.ForceRefresh()
79
80
81 def UpdateValues(self, grid):
82 """Update all displayed values"""
83 # This sends an event to the grid table to update all of the values
8fa876ca 84 msg = Grid.GridTableMessage(self, Grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
1fded56b
RD
85 grid.ProcessTableMessage(msg)
86
87 def _updateColAttrs(self, grid):
88 """
95bfd958 89 wx.Grid -> update the column attributes to add the
1fded56b
RD
90 appropriate renderer given the column name. (renderers
91 are stored in the self.plugins dictionary)
92
93 Otherwise default to the default renderer.
94 """
95 col = 0
8fa876ca 96
1fded56b 97 for colname in self.colnames:
8fa876ca 98 attr = Grid.GridCellAttr()
1fded56b
RD
99 if colname in self.plugins:
100 renderer = self.plugins[colname](self)
8fa876ca 101
1fded56b
RD
102 if renderer.colSize:
103 grid.SetColSize(col, renderer.colSize)
8fa876ca 104
1fded56b
RD
105 if renderer.rowSize:
106 grid.SetDefaultRowSize(renderer.rowSize)
8fa876ca 107
d14a1e28 108 attr.SetReadOnly(True)
1fded56b 109 attr.SetRenderer(renderer)
8fa876ca 110
1fded56b
RD
111 grid.SetColAttr(col, attr)
112 col += 1
113
114 # ------------------------------------------------------
115 # begin the added code to manipulate the table (non wx related)
116 def AppendRow(self, row):
8fa876ca 117 #print 'append'
1fded56b 118 entry = {}
8fa876ca 119
1fded56b
RD
120 for name in self.colnames:
121 entry[name] = "Appended_%i"%row
8fa876ca 122
1fded56b
RD
123 # XXX Hack
124 # entry["A"] can only be between 1..4
125 entry["A"] = random.choice(range(4))
126 self.data.insert(row, ["Append_%i"%row, entry])
127
128 def DeleteCols(self, cols):
129 """
130 cols -> delete the columns from the dataset
131 cols hold the column indices
132 """
133 # we'll cheat here and just remove the name from the
134 # list of column names. The data will remain but
135 # it won't be shown
136 deleteCount = 0
137 cols = cols[:]
138 cols.sort()
8fa876ca 139
1fded56b
RD
140 for i in cols:
141 self.colnames.pop(i-deleteCount)
142 # we need to advance the delete count
143 # to make sure we delete the right columns
144 deleteCount += 1
8fa876ca 145
1fded56b
RD
146 if not len(self.colnames):
147 self.data = []
148
149 def DeleteRows(self, rows):
150 """
151 rows -> delete the rows from the dataset
152 rows hold the row indices
153 """
154 deleteCount = 0
155 rows = rows[:]
156 rows.sort()
8fa876ca 157
1fded56b
RD
158 for i in rows:
159 self.data.pop(i-deleteCount)
160 # we need to advance the delete count
161 # to make sure we delete the right rows
162 deleteCount += 1
163
164 def SortColumn(self, col):
165 """
166 col -> sort the data based on the column indexed by col
167 """
168 name = self.colnames[col]
169 _data = []
8fa876ca 170
1fded56b
RD
171 for row in self.data:
172 rowname, entry = row
173 _data.append((entry.get(name, None), row))
174
175 _data.sort()
176 self.data = []
8fa876ca 177
1fded56b
RD
178 for sortvalue, row in _data:
179 self.data.append(row)
180
181 # end table manipulation code
182 # ----------------------------------------------------------
183
184
185# --------------------------------------------------------------------
95bfd958 186# Sample wx.Grid renderers
1fded56b 187
8fa876ca 188class MegaImageRenderer(Grid.PyGridCellRenderer):
1fded56b
RD
189 def __init__(self, table):
190 """
191 Image Renderer Test. This just places an image in a cell
192 based on the row index. There are N choices and the
193 choice is made by choice[row%N]
194 """
8fa876ca 195 Grid.PyGridCellRenderer.__init__(self)
1fded56b
RD
196 self.table = table
197 self._choices = [images.getSmilesBitmap,
198 images.getMondrianBitmap,
199 images.get_10s_Bitmap,
200 images.get_01c_Bitmap]
201
1fded56b
RD
202 self.colSize = None
203 self.rowSize = None
204
205 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
206 choice = self.table.GetRawValue(row, col)
207 bmp = self._choices[ choice % len(self._choices)]()
8fa876ca 208 image = wx.MemoryDC()
1fded56b
RD
209 image.SelectObject(bmp)
210
211 # clear the background
8fa876ca
RD
212 dc.SetBackgroundMode(wx.SOLID)
213
1fded56b 214 if isSelected:
8fa876ca
RD
215 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
216 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
1fded56b 217 else:
372bde9b
RD
218 dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
219 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
fbd5dd1d 220 dc.DrawRectangleRect(rect)
1fded56b 221
8fa876ca
RD
222 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
223
1fded56b
RD
224 # copy the image but only to the size of the grid cell
225 width, height = bmp.GetWidth(), bmp.GetHeight()
8fa876ca 226
1fded56b
RD
227 if width > rect.width-2:
228 width = rect.width-2
229
230 if height > rect.height-2:
231 height = rect.height-2
232
fbd5dd1d 233 dc.Blit((rect.x+1, rect.y+1), (width, height),
1fded56b 234 image,
372bde9b 235 (0, 0), wx.COPY, True)
1fded56b
RD
236
237
8fa876ca 238class MegaFontRenderer(Grid.PyGridCellRenderer):
1fded56b
RD
239 def __init__(self, table, color="blue", font="ARIAL", fontsize=8):
240 """Render data in the specified color and font and fontsize"""
8fa876ca 241 Grid.PyGridCellRenderer.__init__(self)
1fded56b
RD
242 self.table = table
243 self.color = color
8fa876ca
RD
244 self.font = wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, font)
245 self.selectedBrush = wx.Brush("blue", wx.SOLID)
246 self.normalBrush = wx.Brush(wx.WHITE, wx.SOLID)
1fded56b
RD
247 self.colSize = None
248 self.rowSize = 50
249
250 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
251 # Here we draw text in a grid cell using various fonts
252 # and colors. We have to set the clipping region on
253 # the grid's DC, otherwise the text will spill over
254 # to the next cell
255 dc.SetClippingRect(rect)
256
257 # clear the background
8fa876ca
RD
258 dc.SetBackgroundMode(wx.SOLID)
259
1fded56b 260 if isSelected:
8fa876ca
RD
261 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
262 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
1fded56b 263 else:
372bde9b
RD
264 dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
265 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
fbd5dd1d 266 dc.DrawRectangleRect(rect)
1fded56b 267
8fa876ca
RD
268 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
269
1fded56b 270 text = self.table.GetValue(row, col)
8fa876ca 271 dc.SetBackgroundMode(wx.SOLID)
1fded56b
RD
272
273 # change the text background based on whether the grid is selected
274 # or not
275 if isSelected:
276 dc.SetBrush(self.selectedBrush)
277 dc.SetTextBackground("blue")
278 else:
279 dc.SetBrush(self.normalBrush)
280 dc.SetTextBackground("white")
281
282 dc.SetTextForeground(self.color)
283 dc.SetFont(self.font)
fbd5dd1d 284 dc.DrawText(text, (rect.x+1, rect.y+1))
1fded56b
RD
285
286 # Okay, now for the advanced class :)
287 # Let's add three dots "..."
288 # to indicate that that there is more text to be read
289 # when the text is larger than the grid cell
290
291 width, height = dc.GetTextExtent(text)
8fa876ca 292
1fded56b
RD
293 if width > rect.width-2:
294 width, height = dc.GetTextExtent("...")
295 x = rect.x+1 + rect.width-2 - width
fbd5dd1d
RD
296 dc.DrawRectangle((x, rect.y+1), (width+1, height))
297 dc.DrawText("...", (x, rect.y+1))
1fded56b
RD
298
299 dc.DestroyClippingRegion()
300
301
302# --------------------------------------------------------------------
303# Sample Grid using a specialized table and renderers that can
304# be plugged in based on column names
305
8fa876ca 306class MegaGrid(Grid.Grid):
1fded56b
RD
307 def __init__(self, parent, data, colnames, plugins=None):
308 """parent, data, colnames, plugins=None
309 Initialize a grid using the data defined in data and colnames
310 (see MegaTable for a description of the data format)
311 plugins is a dictionary of columnName -> column renderers.
312 """
313
314 # The base class must be initialized *first*
8fa876ca 315 Grid.Grid.__init__(self, parent, -1)
1fded56b
RD
316 self._table = MegaTable(data, colnames, plugins)
317 self.SetTable(self._table)
318 self._plugins = plugins
319
8fa876ca 320 self.Bind(Grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClicked)
1fded56b
RD
321
322 def Reset(self):
323 """reset the view based on the data in the table. Call
324 this when rows are added or destroyed"""
325 self._table.ResetView(self)
326
327 def OnLabelRightClicked(self, evt):
328 # Did we click on a row or a column?
329 row, col = evt.GetRow(), evt.GetCol()
330 if row == -1: self.colPopup(col, evt)
331 elif col == -1: self.rowPopup(row, evt)
332
333 def rowPopup(self, row, evt):
334 """(row, evt) -> display a popup menu when a row label is right clicked"""
8fa876ca
RD
335 appendID = wx.NewId()
336 deleteID = wx.NewId()
1fded56b 337 x = self.GetRowSize(row)/2
8fa876ca 338
1fded56b
RD
339 if not self.GetSelectedRows():
340 self.SelectRow(row)
8fa876ca
RD
341
342 menu = wx.Menu()
1fded56b
RD
343 xo, yo = evt.GetPosition()
344 menu.Append(appendID, "Append Row")
345 menu.Append(deleteID, "Delete Row(s)")
346
347 def append(event, self=self, row=row):
348 self._table.AppendRow(row)
349 self.Reset()
350
351 def delete(event, self=self, row=row):
352 rows = self.GetSelectedRows()
353 self._table.DeleteRows(rows)
354 self.Reset()
355
8fa876ca
RD
356 self.Bind(wx.EVT_MENU, append, id=appendID)
357 self.Bind(wx.EVT_MENU, delete, id=deleteID)
358 self.PopupMenu(menu, (x, yo))
1fded56b 359 menu.Destroy()
8fa876ca
RD
360 return
361
1fded56b
RD
362
363 def colPopup(self, col, evt):
364 """(col, evt) -> display a popup menu when a column label is
365 right clicked"""
366 x = self.GetColSize(col)/2
8fa876ca
RD
367 menu = wx.Menu()
368 id1 = wx.NewId()
369 sortID = wx.NewId()
1fded56b
RD
370
371 xo, yo = evt.GetPosition()
372 self.SelectCol(col)
373 cols = self.GetSelectedCols()
374 self.Refresh()
375 menu.Append(id1, "Delete Col(s)")
376 menu.Append(sortID, "Sort Column")
377
378 def delete(event, self=self, col=col):
379 cols = self.GetSelectedCols()
380 self._table.DeleteCols(cols)
381 self.Reset()
382
383 def sort(event, self=self, col=col):
384 self._table.SortColumn(col)
385 self.Reset()
386
8fa876ca
RD
387 self.Bind(wx.EVT_MENU, delete, id=id1)
388
1fded56b 389 if len(cols) == 1:
8fa876ca
RD
390 self.Bind(wx.EVT_MENU, sort, id=sortID)
391
392 self.PopupMenu(menu, (xo, 0))
1fded56b 393 menu.Destroy()
8fa876ca 394 return
1fded56b
RD
395
396# -----------------------------------------------------------------
397# Test data
398# data is in the form
399# [rowname, dictionary]
400# where dictionary.get(colname, None) -> returns the value for the cell
401#
402# the colname must also be supplied
403import random
404colnames = ["Row", "This", "Is", "A", "Test"]
405
406data = []
8fa876ca 407
1fded56b
RD
408for row in range(1000):
409 d = {}
410 for name in ["This", "Test", "Is"]:
411 d[name] = random.random()
8fa876ca 412
1fded56b
RD
413 d["Row"] = len(data)
414 # XXX
415 # the "A" column can only be between one and 4
416 d["A"] = random.choice(range(4))
417 data.append((str(row), d))
418
419class MegaFontRendererFactory:
420 def __init__(self, color, font, fontsize):
421 """
422 (color, font, fontsize) -> set of a factory to generate
423 renderers when called.
424 func = MegaFontRenderFactory(color, font, fontsize)
425 renderer = func(table)
426 """
427 self.color = color
428 self.font = font
429 self.fontsize = fontsize
430
431 def __call__(self, table):
432 return MegaFontRenderer(table, self.color, self.font, self.fontsize)
433
434
435#---------------------------------------------------------------------------
436
8fa876ca 437class TestFrame(wx.Frame):
1fded56b
RD
438 def __init__(self, parent, plugins={"This":MegaFontRendererFactory("red", "ARIAL", 8),
439 "A":MegaImageRenderer,
440 "Test":MegaFontRendererFactory("orange", "TIMES", 24),}):
8fa876ca 441 wx.Frame.__init__(self, parent, -1,
1fded56b
RD
442 "Test Frame", size=(640,480))
443
444 grid = MegaGrid(self, data, colnames, plugins)
445 grid.Reset()
446
447
448#---------------------------------------------------------------------------
449
450def runTest(frame, nb, log):
451 win = TestFrame(frame)
452 frame.otherWin = win
453 win.Show(True)
454
455
456
457overview = """Mega Grid Example
458
459This example attempts to show many examples and tricks of
460using a virtual grid object. Hopefully the source isn't too jumbled.
461
462Features:
8fa876ca
RD
463<ol>
464 <li>Uses a virtual grid
465 <li>Columns and rows have popup menus (right click on labels)
466 <li>Columns and rows can be deleted (i.e. table can be
1fded56b 467 resized)
8fa876ca 468 <li>Dynamic renderers. Renderers are plugins based on
1fded56b
RD
469 column header name. Shows a simple Font Renderer and
470 an Image Renderer.
8fa876ca 471</ol>
1fded56b 472
8fa876ca 473Look for 'XXX' in the code to indicate some workarounds for non-obvious
1fded56b
RD
474behavior and various hacks.
475
476"""
477
478
1fded56b
RD
479if __name__ == '__main__':
480 import sys,os
481 import run
8eca4fef 482 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
1fded56b 483