]> git.saurik.com Git - wxWidgets.git/blame - wxPython/demo/wxGrid_MegaExample.py
Updated the description of wx.PopupTransientWindow
[wxWidgets.git] / wxPython / demo / wxGrid_MegaExample.py
CommitLineData
8fa876ca
RD
1# 11/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
2#
3# o Updated for wx namespace
4#
5
6import wx
7import wx.grid as Grid
1fded56b 8
8fa876ca
RD
9import images
10
11class MegaTable(Grid.PyGridTableBase):
1fded56b
RD
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*
8fa876ca 22 Grid.PyGridTableBase.__init__(self)
1fded56b
RD
23 self.data = data
24 self.colnames = colnames
25 self.plugins = plugins or {}
26 # XXX
8fa876ca 27 # we need to store the row length and column length to
1fded56b
RD
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 GetRowLabelValues(self, row):
42 return 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 """
8fa876ca 55 (Grid) -> Reset the grid view. Call this to
1fded56b
RD
56 update the grid if rows and columns have been added or deleted
57 """
58 grid.BeginBatch()
8fa876ca 59
1fded56b 60 for current, new, delmsg, addmsg in [
8fa876ca
RD
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),
1fded56b 63 ]:
8fa876ca 64
1fded56b 65 if new < current:
8fa876ca 66 msg = Grid.GridTableMessage(self,delmsg,new,current-new)
1fded56b
RD
67 grid.ProcessTableMessage(msg)
68 elif new > current:
8fa876ca 69 msg = Grid.GridTableMessage(self,addmsg,new-current)
1fded56b
RD
70 grid.ProcessTableMessage(msg)
71 self.UpdateValues(grid)
8fa876ca 72
1fded56b
RD
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
8fa876ca 88 msg = Grid.GridTableMessage(self, Grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
1fded56b
RD
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
8fa876ca 100
1fded56b 101 for colname in self.colnames:
8fa876ca 102 attr = Grid.GridCellAttr()
1fded56b
RD
103 if colname in self.plugins:
104 renderer = self.plugins[colname](self)
8fa876ca 105
1fded56b
RD
106 if renderer.colSize:
107 grid.SetColSize(col, renderer.colSize)
8fa876ca 108
1fded56b
RD
109 if renderer.rowSize:
110 grid.SetDefaultRowSize(renderer.rowSize)
8fa876ca 111
d14a1e28 112 attr.SetReadOnly(True)
1fded56b 113 attr.SetRenderer(renderer)
8fa876ca 114
1fded56b
RD
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):
8fa876ca 121 #print 'append'
1fded56b 122 entry = {}
8fa876ca 123
1fded56b
RD
124 for name in self.colnames:
125 entry[name] = "Appended_%i"%row
8fa876ca 126
1fded56b
RD
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()
8fa876ca 143
1fded56b
RD
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
8fa876ca 149
1fded56b
RD
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()
8fa876ca 161
1fded56b
RD
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 = []
8fa876ca 174
1fded56b
RD
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 = []
8fa876ca 181
1fded56b
RD
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
8fa876ca 192class MegaImageRenderer(Grid.PyGridCellRenderer):
1fded56b
RD
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 """
8fa876ca 199 Grid.PyGridCellRenderer.__init__(self)
1fded56b
RD
200 self.table = table
201 self._choices = [images.getSmilesBitmap,
202 images.getMondrianBitmap,
203 images.get_10s_Bitmap,
204 images.get_01c_Bitmap]
205
1fded56b
RD
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)]()
8fa876ca 212 image = wx.MemoryDC()
1fded56b
RD
213 image.SelectObject(bmp)
214
215 # clear the background
8fa876ca
RD
216 dc.SetBackgroundMode(wx.SOLID)
217
1fded56b 218 if isSelected:
8fa876ca
RD
219 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
220 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
1fded56b
RD
221 else:
222 dc.SetBrush(wxBrush(wxWHITE, wxSOLID))
223 dc.SetPen(wxPen(wxWHITE, 1, wxSOLID))
fbd5dd1d 224 dc.DrawRectangleRect(rect)
1fded56b 225
8fa876ca
RD
226 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
227
1fded56b
RD
228 # copy the image but only to the size of the grid cell
229 width, height = bmp.GetWidth(), bmp.GetHeight()
8fa876ca 230
1fded56b
RD
231 if width > rect.width-2:
232 width = rect.width-2
233
234 if height > rect.height-2:
235 height = rect.height-2
236
fbd5dd1d 237 dc.Blit((rect.x+1, rect.y+1), (width, height),
1fded56b 238 image,
fbd5dd1d 239 (0, 0), wxCOPY, True)
1fded56b
RD
240
241
8fa876ca 242class MegaFontRenderer(Grid.PyGridCellRenderer):
1fded56b
RD
243 def __init__(self, table, color="blue", font="ARIAL", fontsize=8):
244 """Render data in the specified color and font and fontsize"""
8fa876ca 245 Grid.PyGridCellRenderer.__init__(self)
1fded56b
RD
246 self.table = table
247 self.color = color
8fa876ca
RD
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)
1fded56b
RD
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
8fa876ca
RD
262 dc.SetBackgroundMode(wx.SOLID)
263
1fded56b 264 if isSelected:
8fa876ca
RD
265 dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
266 dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
1fded56b
RD
267 else:
268 dc.SetBrush(wxBrush(wxWHITE, wxSOLID))
269 dc.SetPen(wxPen(wxWHITE, 1, wxSOLID))
fbd5dd1d 270 dc.DrawRectangleRect(rect)
1fded56b 271
8fa876ca
RD
272 #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height))
273
1fded56b 274 text = self.table.GetValue(row, col)
8fa876ca 275 dc.SetBackgroundMode(wx.SOLID)
1fded56b
RD
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)
fbd5dd1d 288 dc.DrawText(text, (rect.x+1, rect.y+1))
1fded56b
RD
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)
8fa876ca 296
1fded56b
RD
297 if width > rect.width-2:
298 width, height = dc.GetTextExtent("...")
299 x = rect.x+1 + rect.width-2 - width
fbd5dd1d
RD
300 dc.DrawRectangle((x, rect.y+1), (width+1, height))
301 dc.DrawText("...", (x, rect.y+1))
1fded56b
RD
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
8fa876ca 310class MegaGrid(Grid.Grid):
1fded56b
RD
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*
8fa876ca 319 Grid.Grid.__init__(self, parent, -1)
1fded56b
RD
320 self._table = MegaTable(data, colnames, plugins)
321 self.SetTable(self._table)
322 self._plugins = plugins
323
8fa876ca 324 self.Bind(Grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClicked)
1fded56b
RD
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"""
8fa876ca
RD
339 appendID = wx.NewId()
340 deleteID = wx.NewId()
1fded56b 341 x = self.GetRowSize(row)/2
8fa876ca 342
1fded56b
RD
343 if not self.GetSelectedRows():
344 self.SelectRow(row)
8fa876ca
RD
345
346 menu = wx.Menu()
1fded56b
RD
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
8fa876ca
RD
360 self.Bind(wx.EVT_MENU, append, id=appendID)
361 self.Bind(wx.EVT_MENU, delete, id=deleteID)
362 self.PopupMenu(menu, (x, yo))
1fded56b 363 menu.Destroy()
8fa876ca
RD
364 return
365
1fded56b
RD
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
8fa876ca
RD
371 menu = wx.Menu()
372 id1 = wx.NewId()
373 sortID = wx.NewId()
1fded56b
RD
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
8fa876ca
RD
391 self.Bind(wx.EVT_MENU, delete, id=id1)
392
1fded56b 393 if len(cols) == 1:
8fa876ca
RD
394 self.Bind(wx.EVT_MENU, sort, id=sortID)
395
396 self.PopupMenu(menu, (xo, 0))
1fded56b 397 menu.Destroy()
8fa876ca 398 return
1fded56b
RD
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
407import random
408colnames = ["Row", "This", "Is", "A", "Test"]
409
410data = []
8fa876ca 411
1fded56b
RD
412for row in range(1000):
413 d = {}
414 for name in ["This", "Test", "Is"]:
415 d[name] = random.random()
8fa876ca 416
1fded56b
RD
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
423class 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
8fa876ca 441class TestFrame(wx.Frame):
1fded56b
RD
442 def __init__(self, parent, plugins={"This":MegaFontRendererFactory("red", "ARIAL", 8),
443 "A":MegaImageRenderer,
444 "Test":MegaFontRendererFactory("orange", "TIMES", 24),}):
8fa876ca 445 wx.Frame.__init__(self, parent, -1,
1fded56b
RD
446 "Test Frame", size=(640,480))
447
448 grid = MegaGrid(self, data, colnames, plugins)
449 grid.Reset()
450
451
452#---------------------------------------------------------------------------
453
454def runTest(frame, nb, log):
455 win = TestFrame(frame)
456 frame.otherWin = win
457 win.Show(True)
458
459
460
461overview = """Mega Grid Example
462
463This example attempts to show many examples and tricks of
464using a virtual grid object. Hopefully the source isn't too jumbled.
465
466Features:
8fa876ca
RD
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
1fded56b 471 resized)
8fa876ca 472 <li>Dynamic renderers. Renderers are plugins based on
1fded56b
RD
473 column header name. Shows a simple Font Renderer and
474 an Image Renderer.
8fa876ca 475</ol>
1fded56b 476
8fa876ca 477Look for 'XXX' in the code to indicate some workarounds for non-obvious
1fded56b
RD
478behavior and various hacks.
479
480"""
481
482
1fded56b
RD
483if __name__ == '__main__':
484 import sys,os
485 import run
486 run.main(['', os.path.basename(sys.argv[0])])
487