]> git.saurik.com Git - wxWidgets.git/blame - wxPython/demo/Grid_MegaExample.py
wxUSE_STL/wxArrayString fixes.
[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 222
1fded56b
RD
223 # copy the image but only to the size of the grid cell
224 width, height = bmp.GetWidth(), bmp.GetHeight()
8fa876ca 225
1fded56b
RD
226 if width > rect.width-2:
227 width = rect.width-2
228
229 if height > rect.height-2:
230 height = rect.height-2
231
d7403ad2 232 dc.Blit(rect.x+1, rect.y+1, width, height,
1fded56b 233 image,
d7403ad2 234 0, 0, wx.COPY, True)
1fded56b
RD
235
236
8fa876ca 237class MegaFontRenderer(Grid.PyGridCellRenderer):
1fded56b
RD
238 def __init__(self, table, color="blue", font="ARIAL", fontsize=8):
239 """Render data in the specified color and font and fontsize"""
8fa876ca 240 Grid.PyGridCellRenderer.__init__(self)
1fded56b
RD
241 self.table = table
242 self.color = color
8fa876ca
RD
243 self.font = wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, font)
244 self.selectedBrush = wx.Brush("blue", wx.SOLID)
245 self.normalBrush = wx.Brush(wx.WHITE, wx.SOLID)
1fded56b
RD
246 self.colSize = None
247 self.rowSize = 50
248
249 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
250 # Here we draw text in a grid cell using various fonts
251 # and colors. We have to set the clipping region on
252 # the grid's DC, otherwise the text will spill over
253 # to the next cell
254 dc.SetClippingRect(rect)
255
256 # clear the background
8fa876ca
RD
257 dc.SetBackgroundMode(wx.SOLID)
258
1fded56b 259 if isSelected:
8fa876ca
RD
260 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
261 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
1fded56b 262 else:
372bde9b
RD
263 dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
264 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
fbd5dd1d 265 dc.DrawRectangleRect(rect)
1fded56b
RD
266
267 text = self.table.GetValue(row, col)
8fa876ca 268 dc.SetBackgroundMode(wx.SOLID)
1fded56b
RD
269
270 # change the text background based on whether the grid is selected
271 # or not
272 if isSelected:
273 dc.SetBrush(self.selectedBrush)
274 dc.SetTextBackground("blue")
275 else:
276 dc.SetBrush(self.normalBrush)
277 dc.SetTextBackground("white")
278
279 dc.SetTextForeground(self.color)
280 dc.SetFont(self.font)
d7403ad2 281 dc.DrawText(text, rect.x+1, rect.y+1)
1fded56b
RD
282
283 # Okay, now for the advanced class :)
284 # Let's add three dots "..."
285 # to indicate that that there is more text to be read
286 # when the text is larger than the grid cell
287
288 width, height = dc.GetTextExtent(text)
8fa876ca 289
1fded56b
RD
290 if width > rect.width-2:
291 width, height = dc.GetTextExtent("...")
292 x = rect.x+1 + rect.width-2 - width
d7403ad2
RD
293 dc.DrawRectangle(x, rect.y+1, width+1, height)
294 dc.DrawText("...", x, rect.y+1)
1fded56b
RD
295
296 dc.DestroyClippingRegion()
297
298
299# --------------------------------------------------------------------
300# Sample Grid using a specialized table and renderers that can
301# be plugged in based on column names
302
8fa876ca 303class MegaGrid(Grid.Grid):
1fded56b
RD
304 def __init__(self, parent, data, colnames, plugins=None):
305 """parent, data, colnames, plugins=None
306 Initialize a grid using the data defined in data and colnames
307 (see MegaTable for a description of the data format)
308 plugins is a dictionary of columnName -> column renderers.
309 """
310
311 # The base class must be initialized *first*
8fa876ca 312 Grid.Grid.__init__(self, parent, -1)
1fded56b
RD
313 self._table = MegaTable(data, colnames, plugins)
314 self.SetTable(self._table)
315 self._plugins = plugins
316
8fa876ca 317 self.Bind(Grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClicked)
1fded56b
RD
318
319 def Reset(self):
320 """reset the view based on the data in the table. Call
321 this when rows are added or destroyed"""
322 self._table.ResetView(self)
323
324 def OnLabelRightClicked(self, evt):
325 # Did we click on a row or a column?
326 row, col = evt.GetRow(), evt.GetCol()
327 if row == -1: self.colPopup(col, evt)
328 elif col == -1: self.rowPopup(row, evt)
329
330 def rowPopup(self, row, evt):
331 """(row, evt) -> display a popup menu when a row label is right clicked"""
8fa876ca
RD
332 appendID = wx.NewId()
333 deleteID = wx.NewId()
1fded56b 334 x = self.GetRowSize(row)/2
8fa876ca 335
1fded56b
RD
336 if not self.GetSelectedRows():
337 self.SelectRow(row)
8fa876ca
RD
338
339 menu = wx.Menu()
1fded56b
RD
340 xo, yo = evt.GetPosition()
341 menu.Append(appendID, "Append Row")
342 menu.Append(deleteID, "Delete Row(s)")
343
344 def append(event, self=self, row=row):
345 self._table.AppendRow(row)
346 self.Reset()
347
348 def delete(event, self=self, row=row):
349 rows = self.GetSelectedRows()
350 self._table.DeleteRows(rows)
351 self.Reset()
352
8fa876ca
RD
353 self.Bind(wx.EVT_MENU, append, id=appendID)
354 self.Bind(wx.EVT_MENU, delete, id=deleteID)
355 self.PopupMenu(menu, (x, yo))
1fded56b 356 menu.Destroy()
8fa876ca
RD
357 return
358
1fded56b
RD
359
360 def colPopup(self, col, evt):
361 """(col, evt) -> display a popup menu when a column label is
362 right clicked"""
363 x = self.GetColSize(col)/2
8fa876ca
RD
364 menu = wx.Menu()
365 id1 = wx.NewId()
366 sortID = wx.NewId()
1fded56b
RD
367
368 xo, yo = evt.GetPosition()
369 self.SelectCol(col)
370 cols = self.GetSelectedCols()
371 self.Refresh()
372 menu.Append(id1, "Delete Col(s)")
373 menu.Append(sortID, "Sort Column")
374
375 def delete(event, self=self, col=col):
376 cols = self.GetSelectedCols()
377 self._table.DeleteCols(cols)
378 self.Reset()
379
380 def sort(event, self=self, col=col):
381 self._table.SortColumn(col)
382 self.Reset()
383
8fa876ca
RD
384 self.Bind(wx.EVT_MENU, delete, id=id1)
385
1fded56b 386 if len(cols) == 1:
8fa876ca
RD
387 self.Bind(wx.EVT_MENU, sort, id=sortID)
388
389 self.PopupMenu(menu, (xo, 0))
1fded56b 390 menu.Destroy()
8fa876ca 391 return
1fded56b
RD
392
393# -----------------------------------------------------------------
394# Test data
395# data is in the form
396# [rowname, dictionary]
397# where dictionary.get(colname, None) -> returns the value for the cell
398#
399# the colname must also be supplied
400import random
401colnames = ["Row", "This", "Is", "A", "Test"]
402
403data = []
8fa876ca 404
1fded56b
RD
405for row in range(1000):
406 d = {}
407 for name in ["This", "Test", "Is"]:
408 d[name] = random.random()
8fa876ca 409
1fded56b
RD
410 d["Row"] = len(data)
411 # XXX
412 # the "A" column can only be between one and 4
413 d["A"] = random.choice(range(4))
414 data.append((str(row), d))
415
416class MegaFontRendererFactory:
417 def __init__(self, color, font, fontsize):
418 """
419 (color, font, fontsize) -> set of a factory to generate
420 renderers when called.
421 func = MegaFontRenderFactory(color, font, fontsize)
422 renderer = func(table)
423 """
424 self.color = color
425 self.font = font
426 self.fontsize = fontsize
427
428 def __call__(self, table):
429 return MegaFontRenderer(table, self.color, self.font, self.fontsize)
430
431
432#---------------------------------------------------------------------------
433
8fa876ca 434class TestFrame(wx.Frame):
1fded56b
RD
435 def __init__(self, parent, plugins={"This":MegaFontRendererFactory("red", "ARIAL", 8),
436 "A":MegaImageRenderer,
437 "Test":MegaFontRendererFactory("orange", "TIMES", 24),}):
8fa876ca 438 wx.Frame.__init__(self, parent, -1,
1fded56b
RD
439 "Test Frame", size=(640,480))
440
441 grid = MegaGrid(self, data, colnames, plugins)
442 grid.Reset()
443
444
445#---------------------------------------------------------------------------
446
447def runTest(frame, nb, log):
448 win = TestFrame(frame)
449 frame.otherWin = win
450 win.Show(True)
451
452
453
454overview = """Mega Grid Example
455
456This example attempts to show many examples and tricks of
457using a virtual grid object. Hopefully the source isn't too jumbled.
458
459Features:
8fa876ca
RD
460<ol>
461 <li>Uses a virtual grid
462 <li>Columns and rows have popup menus (right click on labels)
463 <li>Columns and rows can be deleted (i.e. table can be
1fded56b 464 resized)
8fa876ca 465 <li>Dynamic renderers. Renderers are plugins based on
1fded56b
RD
466 column header name. Shows a simple Font Renderer and
467 an Image Renderer.
8fa876ca 468</ol>
1fded56b 469
8fa876ca 470Look for 'XXX' in the code to indicate some workarounds for non-obvious
1fded56b
RD
471behavior and various hacks.
472
473"""
474
475
1fded56b
RD
476if __name__ == '__main__':
477 import sys,os
478 import run
8eca4fef 479 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
1fded56b 480