]> git.saurik.com Git - wxWidgets.git/blob - wxPython/demo/Grid_MegaExample.py
Reorder the controls so the RadioButtons work right on Windows
[wxWidgets.git] / wxPython / demo / Grid_MegaExample.py
1
2 import wx
3 import wx.grid as Grid
4
5 import images
6
7 class MegaTable(Grid.PyGridTableBase):
8 """
9 A custom wx.Grid Table using user supplied data
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*
18 Grid.PyGridTableBase.__init__(self)
19 self.data = data
20 self.colnames = colnames
21 self.plugins = plugins or {}
22 # XXX
23 # we need to store the row length and column length to
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
37 def GetRowLabelValue(self, row):
38 return "row %03d" % int(self.data[row][0])
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 """
51 (Grid) -> Reset the grid view. Call this to
52 update the grid if rows and columns have been added or deleted
53 """
54 grid.BeginBatch()
55
56 for current, new, delmsg, addmsg in [
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),
59 ]:
60
61 if new < current:
62 msg = Grid.GridTableMessage(self,delmsg,new,current-new)
63 grid.ProcessTableMessage(msg)
64 elif new > current:
65 msg = Grid.GridTableMessage(self,addmsg,new-current)
66 grid.ProcessTableMessage(msg)
67 self.UpdateValues(grid)
68
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
84 msg = Grid.GridTableMessage(self, Grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
85 grid.ProcessTableMessage(msg)
86
87 def _updateColAttrs(self, grid):
88 """
89 wx.Grid -> update the column attributes to add the
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
96
97 for colname in self.colnames:
98 attr = Grid.GridCellAttr()
99 if colname in self.plugins:
100 renderer = self.plugins[colname](self)
101
102 if renderer.colSize:
103 grid.SetColSize(col, renderer.colSize)
104
105 if renderer.rowSize:
106 grid.SetDefaultRowSize(renderer.rowSize)
107
108 attr.SetReadOnly(True)
109 attr.SetRenderer(renderer)
110
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):
117 #print 'append'
118 entry = {}
119
120 for name in self.colnames:
121 entry[name] = "Appended_%i"%row
122
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()
139
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
145
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()
157
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 = []
170
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 = []
177
178 for sortvalue, row in _data:
179 self.data.append(row)
180
181 # end table manipulation code
182 # ----------------------------------------------------------
183
184
185 # --------------------------------------------------------------------
186 # Sample wx.Grid renderers
187
188 class MegaImageRenderer(Grid.PyGridCellRenderer):
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 """
195 Grid.PyGridCellRenderer.__init__(self)
196 self.table = table
197 self._choices = [images.getSmilesBitmap,
198 images.getMondrianBitmap,
199 images.get_10s_Bitmap,
200 images.get_01c_Bitmap]
201
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)]()
208 image = wx.MemoryDC()
209 image.SelectObject(bmp)
210
211 # clear the background
212 dc.SetBackgroundMode(wx.SOLID)
213
214 if isSelected:
215 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
216 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
217 else:
218 dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
219 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
220 dc.DrawRectangleRect(rect)
221
222 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
223
224 # copy the image but only to the size of the grid cell
225 width, height = bmp.GetWidth(), bmp.GetHeight()
226
227 if width > rect.width-2:
228 width = rect.width-2
229
230 if height > rect.height-2:
231 height = rect.height-2
232
233 dc.Blit((rect.x+1, rect.y+1), (width, height),
234 image,
235 (0, 0), wx.COPY, True)
236
237
238 class MegaFontRenderer(Grid.PyGridCellRenderer):
239 def __init__(self, table, color="blue", font="ARIAL", fontsize=8):
240 """Render data in the specified color and font and fontsize"""
241 Grid.PyGridCellRenderer.__init__(self)
242 self.table = table
243 self.color = color
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)
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
258 dc.SetBackgroundMode(wx.SOLID)
259
260 if isSelected:
261 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
262 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
263 else:
264 dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
265 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
266 dc.DrawRectangleRect(rect)
267
268 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
269
270 text = self.table.GetValue(row, col)
271 dc.SetBackgroundMode(wx.SOLID)
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)
284 dc.DrawText(text, (rect.x+1, rect.y+1))
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)
292
293 if width > rect.width-2:
294 width, height = dc.GetTextExtent("...")
295 x = rect.x+1 + rect.width-2 - width
296 dc.DrawRectangle((x, rect.y+1), (width+1, height))
297 dc.DrawText("...", (x, rect.y+1))
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
306 class MegaGrid(Grid.Grid):
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*
315 Grid.Grid.__init__(self, parent, -1)
316 self._table = MegaTable(data, colnames, plugins)
317 self.SetTable(self._table)
318 self._plugins = plugins
319
320 self.Bind(Grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClicked)
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"""
335 appendID = wx.NewId()
336 deleteID = wx.NewId()
337 x = self.GetRowSize(row)/2
338
339 if not self.GetSelectedRows():
340 self.SelectRow(row)
341
342 menu = wx.Menu()
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
356 self.Bind(wx.EVT_MENU, append, id=appendID)
357 self.Bind(wx.EVT_MENU, delete, id=deleteID)
358 self.PopupMenu(menu, (x, yo))
359 menu.Destroy()
360 return
361
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
367 menu = wx.Menu()
368 id1 = wx.NewId()
369 sortID = wx.NewId()
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
387 self.Bind(wx.EVT_MENU, delete, id=id1)
388
389 if len(cols) == 1:
390 self.Bind(wx.EVT_MENU, sort, id=sortID)
391
392 self.PopupMenu(menu, (xo, 0))
393 menu.Destroy()
394 return
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
403 import random
404 colnames = ["Row", "This", "Is", "A", "Test"]
405
406 data = []
407
408 for row in range(1000):
409 d = {}
410 for name in ["This", "Test", "Is"]:
411 d[name] = random.random()
412
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
419 class 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
437 class TestFrame(wx.Frame):
438 def __init__(self, parent, plugins={"This":MegaFontRendererFactory("red", "ARIAL", 8),
439 "A":MegaImageRenderer,
440 "Test":MegaFontRendererFactory("orange", "TIMES", 24),}):
441 wx.Frame.__init__(self, parent, -1,
442 "Test Frame", size=(640,480))
443
444 grid = MegaGrid(self, data, colnames, plugins)
445 grid.Reset()
446
447
448 #---------------------------------------------------------------------------
449
450 def runTest(frame, nb, log):
451 win = TestFrame(frame)
452 frame.otherWin = win
453 win.Show(True)
454
455
456
457 overview = """Mega Grid Example
458
459 This example attempts to show many examples and tricks of
460 using a virtual grid object. Hopefully the source isn't too jumbled.
461
462 Features:
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
467 resized)
468 <li>Dynamic renderers. Renderers are plugins based on
469 column header name. Shows a simple Font Renderer and
470 an Image Renderer.
471 </ol>
472
473 Look for 'XXX' in the code to indicate some workarounds for non-obvious
474 behavior and various hacks.
475
476 """
477
478
479 if __name__ == '__main__':
480 import sys,os
481 import run
482 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
483