Title: [commits] (grant) [15958] [APP] table changes for summary view support
Revision
15958
Author
grant
Date
2007-11-29 08:39:45 -0800 (Thu, 29 Nov 2007)

Log Message

[APP] table changes for summary view support
- Support for sorting by column
- Added an IconRenderer, analagous to IconAttributeEditor, for displaying
icon states in a table column.

Modified Paths

Diff

Modified: branches/rearchitecture/Chandler-Platform/ocap/wxui/table.py (15957 => 15958)

--- branches/rearchitecture/Chandler-Platform/ocap/wxui/table.py	2007-11-29 16:31:49 UTC (rev 15957)
+++ branches/rearchitecture/Chandler-Platform/ocap/wxui/table.py	2007-11-29 16:39:45 UTC (rev 15958)
@@ -19,11 +19,13 @@
 import wx.grid as wxGrid
 
 import peak.events.trellis as trellis
-import peak.events.collections as collections
 
 #import Styles
 #import DragAndDrop
 #import PimBlocks
+import ocap.wxui.multi_state as multi_state
+import ocap.wxui.drawing as drawing
+from ocap.wxui.image import get_image, get_raw_image
 
 logger = logging.getLogger(__name__)
 
@@ -53,7 +55,7 @@
         type = "String",
         attributeName = None,
         attributeSourceName = None,
-        indexName = None,
+        sortKey = None,
     
         icon = None,
         format = None,
@@ -66,6 +68,17 @@
         defaultSort = False,
         collapsedSections = None,
     )
+    
+    @trellis.rule
+    def sortKey(self):
+        return lambda item: getattr(item, self.attributeName)
+    
+    @trellis.rule
+    def bitmap(self):
+        if self.icon is None:
+            return None
+        image = get_raw_image(self.icon + ".png")
+        return wx.BitmapFromImage(image)
 
     def isSectionCollapsed(self, sectionValue):
         return str(sectionValue) in self.collapsedSections
@@ -82,14 +95,45 @@
     def getWidth(self):
         return self.width
 
+def GetRectFromOffsets(rect, offsets):
+    def GetEdge(rect, offset):
+        if offset >= 0:
+            edge = rect.GetLeft()
+        else:
+            edge = rect.GetRight()
+        return edge + offset
+
+    top = rect.GetTop()
+    height = offsets[2]
+    if height == 0:
+        height = rect.GetHeight()
+    else:
+        top = top + (rect.GetHeight() - height) / 2.0
+    left = GetEdge(rect, offsets[0])
+    return wx.Rect(left,
+                    top,
+                    GetEdge(rect, offsets[1]) - left,
+                    height)
+
+
 class TablePresentation(trellis.Component, wxGrid.PyGridTableBase):
     trellis.values(
         listModel = None,
         columns = None,
+        activeColumn = None,
     )
     
     @trellis.action
-    def updateGridRows(self):
+    def switchColumn(self):
+        if (self.activeColumn is not None and
+            self.activeColumn.sortKey is not None):
+            self.listModel.sort_key = self.activeColumn.sortKey
+            
+        self.View.SetUseColSortArrows(self.activeColumn is not None and
+                                      self.activeColumn.useSortArrows)
+    
+    @trellis.action
+    def updateGrid(self):
         view = self.GetView()
         
         view.BeginBatch()
@@ -117,35 +161,14 @@
                                       start, newLen))
         view.EndBatch()
 
-        
-        viewSelection = view.GetSelectedRows()
+        for row in view.GetSelectedRows():
+            if not self.listModel[row] in self.listModel.selected:
+                view.SelectBlock(index, 0, index, 1, False)
 
         for row, item in enumerate(self.listModel):
-            if item in self.listModel.selected and not row in viewSelection:
-                view.SelectRow(row, True)
-                
-        for row in viewSelection:
-            if not self.listModel[row] in self.listModel.selected:
-                view.DeselectRow(row)
-
-    @trellis.rule
-    def observing(self):
-        return collections.Observing(
-            lookup_func=lambda index: self.GetValue(*index))
+            if item in self.listModel.selected:
+                view.SelectBlock(row, 0, row, 1, True)
     
-    @trellis.action
-    def updateObservedRows(self):
-        numRows = len(self.listModel)
-        numCols = len(self.columns)
-        self.observing.keys.update((row, col) for row in xrange(numRows)
-                                              for col in xrange(numCols))
-
-    @trellis.action
-    def observerChanges(self):
-        for rowCol, oldNew in self.observing.changes.iteritems():
-            if oldNew[0] != oldNew[1]:
-                self.View.RefreshRect(self.View.CellToRect(*rowCol))
-    
     defaultRWAttribute = wxGrid.GridCellAttr()
     defaultROAttribute = wxGrid.GridCellAttr()
     defaultROAttribute.SetReadOnly(True)
@@ -160,14 +183,18 @@
             table = Table(widget)
             
         self.listModel = listModel
+        table.SetTable(self, selmode=wxGrid.Grid.SelectRows)
         self.SetView(table)
-        table.SetTable(self, selmode=wxGrid.Grid.SelectRows)
 
         for index, column in enumerate(self.columns or ()):
+            table.SetColLabelValue(index, column.heading)
             table.SetColSize(index, column.width)
             table.ScaleColumn(index, column.scaleColumn)
+            if column == self.activeColumn:
+                table.SetSelectedCol(index)
 
         table.Bind(wxGrid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
+        table.Bind(wxGrid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClicked)
 
     def GetNumberRows(self):
         return len(self.listModel)
@@ -215,24 +242,9 @@
     def GetColLabelValue(self, col):
         return self.columns[col].heading
 
-    def GetRowLabelValue(self, row):
-        pass
+    def GetColLabelBitmap(self, col):
+        return self.columns[col].bitmap
 
-    def GetColLabelBitmap(self, column):
-        column = self.columns[column]
-
-        if column.icon is not None:
-            # we'll cache the bitmap so we're not constantly loading
-            # it from disk
-            bitmap = getattr(self, '_colbitmap%s' % column, None)
-            if bitmap is None:
-                bitmap = wx.GetApp().GetImage(column.icon + ".png")
-                setattr(self, '_colbitmap%s' % column, bitmap)
-            return bitmap
-        
-        else:
-            return None
-    
     def OnRangeSelect(self, event):
         """
         Synchronize the grid's selection back into the ListModel
@@ -285,6 +297,16 @@
         if lastRow is not None:
             yield lastRow, self.IndexToRow(len(self.listModel) - 1)
 
+    def OnLabelLeftClicked(self, event):
+        assert (event.GetRow() == -1) # Currently Table only supports column headers
+        column = self.columns[event.GetCol()]
+        
+        if column is self.activeColumn:
+            self.listModel.reverse = not self.listModel.reverse
+        else:
+            self.activeColumn = column
+
+
     def GetTypeName(self, row, column):
         return self.columns[column].type
 
@@ -334,7 +356,6 @@
         # is what the following binding is for
         #self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
         self.Bind(wxGrid.EVT_GRID_CELL_BEGIN_DRAG, self.OnItemDrag)
-        self.Bind(wxGrid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClicked)
 
         gridWindow = self.GetGridWindow()
         # wxSidebar is subclassed from wxTable and depends on the binding of
@@ -386,14 +407,6 @@
         self.SetLightSelectionBackground()
         self.InvalidateSelection()
 
-    def OnLabelLeftClicked(self, event):
-        assert (event.GetRow() == -1) # Currently Table only supports column headers
-        column = self.columns[event.GetCol()]
-        # @@@ [grant] Reversing a column
-        indexName = column.indexName
-        sameColumn = getattr(self.listModel, 'indexName', None) == indexName
-        self.listModel.setCollectionIndex(indexName, toggleDescending=sameColumn)
-
     def OnKeyDown(self, event):
 
         # default grid behavior is to move to the "next" cell,
@@ -420,8 +433,8 @@
         
         for rowStart, rowEnd in self.Table.SelectedRowRanges():
             dirtyRect = wx.Rect()
-            dirtyRect.SetTopLeft(self.CellToRect(rowStart, 0).GetTopLeft())
-            dirtyRect.SetBottomRight(self.CellToRect(rowEnd,
+            dirtyRect.SetTopLeft(self.GetCellRect(rowStart, 0).GetTopLeft())
+            dirtyRect.SetBottomRight(self.GetCellRect(rowEnd,
                                                      lastRow).GetBottomRight())
             dirtyRect.OffsetXY(self.GetRowLabelSize(), self.GetColLabelSize())
             self.RefreshRect(dirtyRect)
@@ -441,8 +454,8 @@
 
         self.EnableGridLines(self.hasGridLines)
 
-    def CalculateCellRect(self, cell):
-        cellRect = self.CellToRect(cell[1], cell[0])
+    def GetCellRect(self, row, col):
+        cellRect = self.CellToRect(row, col)
         cellRect.OffsetXY(self.GetRowLabelSize(), self.GetColLabelSize())
         left, top = self.CalcScrolledPosition(cellRect.GetLeft(), cellRect.GetTop())
         cellRect.SetLeft(left)
@@ -486,7 +499,7 @@
             if self.Table.CanClick(row, col):
                 self.clickRowCol = self.overRowCol = row, col
                 event.Skip(False) # Gobble the event
-                self.RefreshRect(self.CellToRect(*self.clickRowCol))
+                self.RefreshRect(self.GetCellRect(*self.clickRowCol))
                 self.SetFocus()
             else:
                 self.overRowCol = None
@@ -495,7 +508,7 @@
             if self.clickRowCol == (row, col):
                 self.Table.OnClick(row, col)
             if self.clickRowCol is not None:
-                self.RefreshRect(self.CellToRect(*self.clickRowCol))
+                self.RefreshRect(self.GetCellRect(*self.clickRowCol))
             self.overRowCol = None
 
         elif event.LeftDClick():
@@ -531,7 +544,7 @@
 
         if self.overRowCol != oldOverRowCol:
             if oldOverRowCol is not None:
-                self.RefreshRect(self.CellToRect(*oldOverRowCol))
+                self.RefreshRect(self.GetCellRect(*oldOverRowCol))
 
             if self.overRowCol is None:
                 if (gridWindow.HasCapture()):
@@ -539,9 +552,9 @@
                 if self.clickRowCol is not None:
                     clickRowCol = self.clickRowCol
                     self.clickRowCol = None
-                    self.RefreshRect(self.CellToRect(*clickRowCol))
+                    self.RefreshRect(self.GetCellRect(*clickRowCol))
             else:
-                self.RefreshRect(self.CellToRect(*self.overRowCol))
+                self.RefreshRect(self.GetCellRect(*self.overRowCol))
             
 
     def OnMouseCaptureLost(self, event):
@@ -575,3 +588,103 @@
             # not sure when the -1 case would happen?
             if -1 not in (topRow, bottomRow):
                 yield (topRow, bottomRow)
+
+class IconRenderer(wxGrid.PyGridCellRenderer):
+    """
+    Utility class for displaying an icon in a table column. It's
+    loosely based on the old IconAttributeEditor.
+    """
+    bitmapCache = None
+    bitmapProvider = staticmethod(get_image)
+    
+    def __init__(self):
+        super(IconRenderer, self).__init__()
+        if self.bitmapCache is None:
+            cls = type(self)
+            cls.bitmapCache = multi_state.MultiStateBitmapCache()
+    
+            cls.bitmapCache.AddStates(multibitmaps=list(self.getStateInfos()),
+                                      bitmapProvider=self.bitmapProvider)
+
+    ROLLED_OVER = 1
+    SELECTED = 2
+    MOUSE_DOWN = 4
+    READ_ONLY = 8
+    VARIATION_MAP = {
+        0 : 'normal',
+        ROLLED_OVER: 'rollover',
+        SELECTED: 'selected',
+        SELECTED | ROLLED_OVER: 'rolloverselected',
+        MOUSE_DOWN: 'normal', # note, this is the not-rollover case: mouse out
+        MOUSE_DOWN | ROLLED_OVER: 'mousedown', # mouse in
+        MOUSE_DOWN | SELECTED: 'selected',
+        MOUSE_DOWN | SELECTED | ROLLED_OVER: 'mousedownselected',
+        # @@@ Change these if we need special read/only icons
+        READ_ONLY: "normal",
+        READ_ONLY | ROLLED_OVER: "normal",
+        READ_ONLY | SELECTED: "selected",
+        READ_ONLY | SELECTED | ROLLED_OVER: 'selected',
+        READ_ONLY | MOUSE_DOWN: 'normal', 
+        READ_ONLY | MOUSE_DOWN | ROLLED_OVER: 'normal',
+        READ_ONLY | MOUSE_DOWN | SELECTED: 'selected',
+        READ_ONLY | MOUSE_DOWN | SELECTED | ROLLED_OVER: 'selected',
+    }
+
+    def getStateInfos(self):
+        """
+        Return an iterable of all the multi_state.BitmapInfos you
+        want to use for your icon states.
+        """
+        return ()
+
+    def getVariation(self, readOnly, isSelected, mouseDown, mouseOver):
+        bits = ((self.READ_ONLY if readOnly else 0) |
+                (self.MOUSE_DOWN if mouseDown else 0) |
+                (self.ROLLED_OVER if mouseOver else 0) |
+                (self.SELECTED if isSelected else 0))
+        return self.VARIATION_MAP[bits]
+
+    def mapValueToIconState(self, value):
+        """
+        """
+        # By default, we use the value as the icon state as-is.
+        return value
+    
+    def advanceValue(self, value):
+        return value
+
+    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
+        drawing.SetTextColorsAndFont(grid, attr, dc, isSelected)
+
+        dc.SetBackgroundMode(wx.SOLID)
+        dc.SetPen(wx.TRANSPARENT_PEN)
+
+        dc.DrawRectangleRect(rect)
+
+        dc.SetBackgroundMode(wx.TRANSPARENT)
+        
+        value = grid.Table.GetValue(row, col)
+        
+        mouseDown = (row, col) == grid.clickRowCol
+        if mouseDown:
+            value = self.advanceValue(value)
+
+        state = self.mapValueToIconState(value)
+            
+        mouseOver = mouseDown or (row, col) == grid.overRowCol
+        
+        
+        variation = self.getVariation(False, isSelected, mouseDown, mouseOver)
+
+        imageSet = self.bitmapCache[state]
+        
+        image = getattr(imageSet, variation, None)
+
+        assert image is not None, "Bogus image for state %s variation %s" % (
+                                  taskness, variation)
+
+        x, y, w, h = rect.Get()
+        x += (w - image.GetWidth()) / 2
+        y += (h - image.GetHeight()) / 2
+        dc.DrawBitmap(image, x, y, True)
+        




_______________________________________________
Commits mailing list
[email protected]
http://lists.osafoundation.org/mailman/listinfo/commits

Reply via email to