7 #--------------------------------------------------------------------------- 
   9 class MegaTable(Grid
.PyGridTableBase
): 
  11     A custom wx.Grid Table using user supplied data 
  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 
  19         # The base class must be initialized *first* 
  20         Grid
.PyGridTableBase
.__init
__(self
) 
  22         self
.colnames 
= colnames
 
  23         self
.plugins 
= plugins 
or {} 
  25         # we need to store the row length and column length to 
  26         # see if the table has changed size 
  27         self
._rows 
= self
.GetNumberRows() 
  28         self
._cols 
= self
.GetNumberCols() 
  30     def GetNumberCols(self
): 
  31         return len(self
.colnames
) 
  33     def GetNumberRows(self
): 
  36     def GetColLabelValue(self
, col
): 
  37         return self
.colnames
[col
] 
  39     def GetRowLabelValue(self
, row
): 
  40         return "row %03d" % int(self
.data
[row
][0]) 
  42     def GetValue(self
, row
, col
): 
  43         return str(self
.data
[row
][1].get(self
.GetColLabelValue(col
), "")) 
  45     def GetRawValue(self
, row
, col
): 
  46         return self
.data
[row
][1].get(self
.GetColLabelValue(col
), "") 
  48     def SetValue(self
, row
, col
, value
): 
  49         self
.data
[row
][1][self
.GetColLabelValue(col
)] = value
 
  51     def ResetView(self
, grid
): 
  53         (Grid) -> Reset the grid view.   Call this to 
  54         update the grid if rows and columns have been added or deleted 
  58         for current
, new
, delmsg
, addmsg 
in [ 
  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
), 
  64                 msg 
= Grid
.GridTableMessage(self
,delmsg
,new
,current
-new
) 
  65                 grid
.ProcessTableMessage(msg
) 
  67                 msg 
= Grid
.GridTableMessage(self
,addmsg
,new
-current
) 
  68                 grid
.ProcessTableMessage(msg
) 
  69                 self
.UpdateValues(grid
) 
  73         self
._rows 
= self
.GetNumberRows() 
  74         self
._cols 
= self
.GetNumberCols() 
  75         # update the column rendering plugins 
  76         self
._updateColAttrs
(grid
) 
  78         # update the scrollbars and the displayed part of the grid 
  79         grid
.AdjustScrollbars() 
  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 
  86         msg 
= Grid
.GridTableMessage(self
, Grid
.GRIDTABLE_REQUEST_VIEW_GET_VALUES
) 
  87         grid
.ProcessTableMessage(msg
) 
  89     def _updateColAttrs(self
, grid
): 
  91         wx.Grid -> update the column attributes to add the 
  92         appropriate renderer given the column name.  (renderers 
  93         are stored in the self.plugins dictionary) 
  95         Otherwise default to the default renderer. 
  99         for colname 
in self
.colnames
: 
 100             attr 
= Grid
.GridCellAttr() 
 101             if colname 
in self
.plugins
: 
 102                 renderer 
= self
.plugins
[colname
](self
) 
 105                     grid
.SetColSize(col
, renderer
.colSize
) 
 108                     grid
.SetDefaultRowSize(renderer
.rowSize
) 
 110                 attr
.SetReadOnly(True) 
 111                 attr
.SetRenderer(renderer
) 
 113             grid
.SetColAttr(col
, attr
) 
 116     # ------------------------------------------------------ 
 117     # begin the added code to manipulate the table (non wx related) 
 118     def AppendRow(self
, row
): 
 122         for name 
in self
.colnames
: 
 123             entry
[name
] = "Appended_%i"%row
 
 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
]) 
 130     def DeleteCols(self
, cols
): 
 132         cols -> delete the columns from the dataset 
 133         cols hold the column indices 
 135         # we'll cheat here and just remove the name from the 
 136         # list of column names.  The data will remain but 
 143             self
.colnames
.pop(i
-deleteCount
) 
 144             # we need to advance the delete count 
 145             # to make sure we delete the right columns 
 148         if not len(self
.colnames
): 
 151     def DeleteRows(self
, rows
): 
 153         rows -> delete the rows from the dataset 
 154         rows hold the row indices 
 161             self
.data
.pop(i
-deleteCount
) 
 162             # we need to advance the delete count 
 163             # to make sure we delete the right rows 
 166     def SortColumn(self
, col
): 
 168         col -> sort the data based on the column indexed by col 
 170         name 
= self
.colnames
[col
] 
 173         for row 
in self
.data
: 
 175             _data
.append((entry
.get(name
, None), row
)) 
 180         for sortvalue
, row 
in _data
: 
 181             self
.data
.append(row
) 
 183     # end table manipulation code 
 184     # ---------------------------------------------------------- 
 187 # -------------------------------------------------------------------- 
 188 # Sample wx.Grid renderers 
 190 class MegaImageRenderer(Grid
.PyGridCellRenderer
): 
 191     def __init__(self
, table
): 
 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] 
 197         Grid
.PyGridCellRenderer
.__init
__(self
) 
 199         self
._choices 
= [images
.getSmilesBitmap
, 
 200                          images
.getMondrianBitmap
, 
 201                          images
.getWXPdemoBitmap
, 
 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
)]() 
 210         image 
= wx
.MemoryDC() 
 211         image
.SelectObject(bmp
) 
 213         # clear the background 
 214         dc
.SetBackgroundMode(wx
.SOLID
) 
 217             dc
.SetBrush(wx
.Brush(wx
.BLUE
, wx
.SOLID
)) 
 218             dc
.SetPen(wx
.Pen(wx
.BLUE
, 1, wx
.SOLID
)) 
 220             dc
.SetBrush(wx
.Brush(wx
.WHITE
, wx
.SOLID
)) 
 221             dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)) 
 222         dc
.DrawRectangleRect(rect
) 
 225         # copy the image but only to the size of the grid cell 
 226         width
, height 
= bmp
.GetWidth(), bmp
.GetHeight() 
 228         if width 
> rect
.width
-2: 
 231         if height 
> rect
.height
-2: 
 232             height 
= rect
.height
-2 
 234         dc
.Blit(rect
.x
+1, rect
.y
+1, width
, height
, 
 239 class MegaFontRenderer(Grid
.PyGridCellRenderer
): 
 240     def __init__(self
, table
, color
="blue", font
="ARIAL", fontsize
=8): 
 241         """Render data in the specified color and font and fontsize""" 
 242         Grid
.PyGridCellRenderer
.__init
__(self
) 
 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
) 
 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 
 256         dc
.SetClippingRect(rect
) 
 258         # clear the background 
 259         dc
.SetBackgroundMode(wx
.SOLID
) 
 262             dc
.SetBrush(wx
.Brush(wx
.BLUE
, wx
.SOLID
)) 
 263             dc
.SetPen(wx
.Pen(wx
.BLUE
, 1, wx
.SOLID
)) 
 265             dc
.SetBrush(wx
.Brush(wx
.WHITE
, wx
.SOLID
)) 
 266             dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)) 
 267         dc
.DrawRectangleRect(rect
) 
 269         text 
= self
.table
.GetValue(row
, col
) 
 270         dc
.SetBackgroundMode(wx
.SOLID
) 
 272         # change the text background based on whether the grid is selected 
 275             dc
.SetBrush(self
.selectedBrush
) 
 276             dc
.SetTextBackground("blue") 
 278             dc
.SetBrush(self
.normalBrush
) 
 279             dc
.SetTextBackground("white") 
 281         dc
.SetTextForeground(self
.color
) 
 282         dc
.SetFont(self
.font
) 
 283         dc
.DrawText(text
, rect
.x
+1, rect
.y
+1) 
 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 
 290         width
, height 
= dc
.GetTextExtent(text
) 
 292         if width 
> rect
.width
-2: 
 293             width
, height 
= dc
.GetTextExtent("...") 
 294             x 
= rect
.x
+1 + rect
.width
-2 - width
 
 295             dc
.DrawRectangle(x
, rect
.y
+1, width
+1, height
) 
 296             dc
.DrawText("...", x
, rect
.y
+1) 
 298         dc
.DestroyClippingRegion() 
 301 # -------------------------------------------------------------------- 
 302 # Sample Grid using a specialized table and renderers that can 
 303 # be plugged in based on column names 
 305 class MegaGrid(Grid
.Grid
): 
 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. 
 313         # The base class must be initialized *first* 
 314         Grid
.Grid
.__init
__(self
, parent
, -1) 
 315         self
._table 
= MegaTable(data
, colnames
, plugins
) 
 316         self
.SetTable(self
._table
) 
 317         self
._plugins 
= plugins
 
 319         self
.Bind(Grid
.EVT_GRID_LABEL_RIGHT_CLICK
, self
.OnLabelRightClicked
) 
 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
) 
 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
) 
 332     def rowPopup(self
, row
, evt
): 
 333         """(row, evt) -> display a popup menu when a row label is right clicked""" 
 334         appendID 
= wx
.NewId() 
 335         deleteID 
= wx
.NewId() 
 336         x 
= self
.GetRowSize(row
)/2 
 338         if not self
.GetSelectedRows(): 
 342         xo
, yo 
= evt
.GetPosition() 
 343         menu
.Append(appendID
, "Append Row") 
 344         menu
.Append(deleteID
, "Delete Row(s)") 
 346         def append(event
, self
=self
, row
=row
): 
 347             self
._table
.AppendRow(row
) 
 350         def delete(event
, self
=self
, row
=row
): 
 351             rows 
= self
.GetSelectedRows() 
 352             self
._table
.DeleteRows(rows
) 
 355         self
.Bind(wx
.EVT_MENU
, append
, id=appendID
) 
 356         self
.Bind(wx
.EVT_MENU
, delete
, id=deleteID
) 
 362     def colPopup(self
, col
, evt
): 
 363         """(col, evt) -> display a popup menu when a column label is 
 365         x 
= self
.GetColSize(col
)/2 
 370         xo
, yo 
= evt
.GetPosition() 
 372         cols 
= self
.GetSelectedCols() 
 374         menu
.Append(id1
, "Delete Col(s)") 
 375         menu
.Append(sortID
, "Sort Column") 
 377         def delete(event
, self
=self
, col
=col
): 
 378             cols 
= self
.GetSelectedCols() 
 379             self
._table
.DeleteCols(cols
) 
 382         def sort(event
, self
=self
, col
=col
): 
 383             self
._table
.SortColumn(col
) 
 386         self
.Bind(wx
.EVT_MENU
, delete
, id=id1
) 
 389             self
.Bind(wx
.EVT_MENU
, sort
, id=sortID
) 
 395 # ----------------------------------------------------------------- 
 397 # data is in the form 
 398 # [rowname, dictionary] 
 399 # where dictionary.get(colname, None) -> returns the value for the cell 
 401 # the colname must also be supplied 
 403 colnames 
= ["Row", "This", "Is", "A", "Test"] 
 407 for row 
in range(1000): 
 409     for name 
in ["This", "Test", "Is"]: 
 410         d
[name
] = random
.random() 
 414     # the "A" column can only be between one and 4 
 415     d
["A"] = random
.choice(range(4)) 
 416     data
.append((str(row
), d
)) 
 418 class MegaFontRendererFactory
: 
 419     def __init__(self
, color
, font
, fontsize
): 
 421         (color, font, fontsize) -> set of a factory to generate 
 422         renderers when called. 
 423         func = MegaFontRenderFactory(color, font, fontsize) 
 424         renderer = func(table) 
 428         self
.fontsize 
= fontsize
 
 430     def __call__(self
, table
): 
 431         return MegaFontRenderer(table
, self
.color
, self
.font
, self
.fontsize
) 
 434 #--------------------------------------------------------------------------- 
 436 class TestFrame(wx
.Frame
): 
 437     def __init__(self
, parent
, plugins
={"This":MegaFontRendererFactory("red", "ARIAL", 8), 
 438                                         "A":MegaImageRenderer
, 
 439                                         "Test":MegaFontRendererFactory("orange", "TIMES", 24),}): 
 440         wx
.Frame
.__init
__(self
, parent
, -1, 
 441                          "Test Frame", size
=(640,480)) 
 443         grid 
= MegaGrid(self
, data
, colnames
, plugins
) 
 447 #--------------------------------------------------------------------------- 
 449 class TestPanel(wx
.Panel
): 
 450     def __init__(self
, parent
, log
): 
 452         wx
.Panel
.__init
__(self
, parent
, -1) 
 454         b 
= wx
.Button(self
, -1, "Show the MegaGrid", (50,50)) 
 455         self
.Bind(wx
.EVT_BUTTON
, self
.OnButton
, b
) 
 458     def OnButton(self
, evt
): 
 459         win 
= TestFrame(self
) 
 462 #--------------------------------------------------------------------------- 
 465 def runTest(frame
, nb
, log
): 
 466     win 
= TestPanel(nb
, log
) 
 471 overview 
= """Mega Grid Example 
 473 This example attempts to show many examples and tricks of 
 474 using a virtual grid object.  Hopefully the source isn't too jumbled. 
 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 
 482    <li>Dynamic renderers.  Renderers are plugins based on 
 483       column header name.  Shows a simple Font Renderer and 
 487 Look for 'XXX' in the code to indicate some workarounds for non-obvious 
 488 behavior and various hacks. 
 493 if __name__ 
== '__main__': 
 496     run
.main(['', os
.path
.basename(sys
.argv
[0])] + sys
.argv
[1:])