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