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