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