- 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.
- 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
