+#----------------------------------------------------------------------------
+# Name: wxPython.lib.mixins.listctrl
+# Purpose: Helpful mix-in classes for wxListCtrl
+#
+# Author: Robin Dunn
+#
+# Created: 15-May-2001
+# RCS-ID: $Id$
+# Copyright: (c) 2001 by Total Control Software
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+# 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o 2.5 compatability update.
+# o ListCtrlSelectionManagerMix untested.
+#
+# 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o wxColumnSorterMixin -> ColumnSorterMixin
+# o wxListCtrlAutoWidthMixin -> ListCtrlAutoWidthMixin
+#
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+import locale
+import wx
-from wx import _rename
-from wxPython.lib.mixins import listctrl
-_rename(globals(), listctrl.__dict__, modulename='lib.mixins.listctrl')
-del listctrl
-del _rename
+#----------------------------------------------------------------------------
+
+class ColumnSorterMixin:
+ """
+ A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
+ the column header is clicked on.
+
+ There are a few requirments needed in order for this to work genericly:
+
+ 1. The combined class must have a GetListCtrl method that
+ returns the wx.ListCtrl to be sorted, and the list control
+ must exist at the time the wx.ColumnSorterMixin.__init__
+ method is called because it uses GetListCtrl.
+
+ 2. Items in the list control must have a unique data value set
+ with list.SetItemData.
+
+ 3. The combined class must have an attribute named itemDataMap
+ that is a dictionary mapping the data values to a sequence of
+ objects representing the values in each column. These values
+ are compared in the column sorter to determine sort order.
+
+ Interesting methods to override are GetColumnSorter,
+ GetSecondarySortValues, and GetSortImages. See below for details.
+ """
+
+ def __init__(self, numColumns):
+ self.SetColumnCount(numColumns)
+ list = self.GetListCtrl()
+ if not list:
+ raise ValueError, "No wx.ListCtrl available"
+ self.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColClick, list)
+
+
+ def SetColumnCount(self, newNumColumns):
+ self._colSortFlag = [0] * newNumColumns
+ self._col = -1
+
+
+ def SortListItems(self, col=-1, ascending=1):
+ """Sort the list on demand. Can also be used to set the sort column and order."""
+ oldCol = self._col
+ if col != -1:
+ self._col = col
+ self._colSortFlag[col] = ascending
+ self.GetListCtrl().SortItems(self.GetColumnSorter())
+ self.__updateImages(oldCol)
+
+
+ def GetColumnWidths(self):
+ """
+ Returns a list of column widths. Can be used to help restore the current
+ view later.
+ """
+ list = self.GetListCtrl()
+ rv = []
+ for x in range(len(self._colSortFlag)):
+ rv.append(list.GetColumnWidth(x))
+ return rv
+
+
+ def GetSortImages(self):
+ """
+ Returns a tuple of image list indexesthe indexes in the image list for an image to be put on the column
+ header when sorting in descending order.
+ """
+ return (-1, -1) # (decending, ascending) image IDs
+
+
+ def GetColumnSorter(self):
+ """Returns a callable object to be used for comparing column values when sorting."""
+ return self.__ColumnSorter
+
+
+ def GetSecondarySortValues(self, col, key1, key2):
+ """Returns a tuple of 2 values to use for secondary sort values when the
+ items in the selected column match equal. The default just returns the
+ item data values."""
+ return (key1, key2)
+
+
+ def __OnColClick(self, evt):
+ oldCol = self._col
+ self._col = col = evt.GetColumn()
+ self._colSortFlag[col] = not self._colSortFlag[col]
+ self.GetListCtrl().SortItems(self.GetColumnSorter())
+ self.__updateImages(oldCol)
+ evt.Skip()
+
+
+ def __ColumnSorter(self, key1, key2):
+ col = self._col
+ ascending = self._colSortFlag[col]
+ item1 = self.itemDataMap[key1][col]
+ item2 = self.itemDataMap[key2][col]
+
+ #--- Internationalization of string sorting with locale module
+ if type(item1) == type('') or type(item2) == type(''):
+ cmpVal = locale.strcoll(str(item1), str(item2))
+ else:
+ cmpVal = cmp(item1, item2)
+ #---
+
+ # If the items are equal then pick something else to make the sort value unique
+ if cmpVal == 0:
+ cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
+
+ if ascending:
+ return cmpVal
+ else:
+ return -cmpVal
+
+
+ def __updateImages(self, oldCol):
+ sortImages = self.GetSortImages()
+ if self._col != -1 and sortImages[0] != -1:
+ img = sortImages[self._colSortFlag[self._col]]
+ list = self.GetListCtrl()
+ if oldCol != -1:
+ list.ClearColumnImage(oldCol)
+ list.SetColumnImage(self._col, img)
+
+
+#----------------------------------------------------------------------------
+#----------------------------------------------------------------------------
+
+class ListCtrlAutoWidthMixin:
+ """ A mix-in class that automatically resizes the last column to take up
+ the remaining width of the wx.ListCtrl.
+
+ This causes the wx.ListCtrl to automatically take up the full width of
+ the list, without either a horizontal scroll bar (unless absolutely
+ necessary) or empty space to the right of the last column.
+
+ NOTE: This only works for report-style lists.
+
+ WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
+ sure you call event.Skip() to ensure that the mixin's
+ _OnResize method is called.
+
+ This mix-in class was written by Erik Westra <ewestra@wave.co.nz>
+"""
+ def __init__(self):
+ """ Standard initialiser.
+ """
+ self._lastColMinWidth = None
+
+ self.Bind(wx.EVT_SIZE, self._onResize)
+ self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
+
+
+ def resizeLastColumn(self, minWidth):
+ """ Resize the last column appropriately.
+
+ If the list's columns are too wide to fit within the window, we use
+ a horizontal scrollbar. Otherwise, we expand the right-most column
+ to take up the remaining free space in the list.
+
+ This method is called automatically when the wx.ListCtrl is resized;
+ you can also call it yourself whenever you want the last column to
+ be resized appropriately (eg, when adding, removing or resizing
+ columns).
+
+ 'minWidth' is the preferred minimum width for the last column.
+ """
+ self._lastColMinWidth = minWidth
+ self._doResize()
+
+ # =====================
+ # == Private Methods ==
+ # =====================
+
+ def _onResize(self, event):
+ """ Respond to the wx.ListCtrl being resized.
+
+ We automatically resize the last column in the list.
+ """
+ wx.CallAfter(self._doResize)
+ event.Skip()
+
+
+ def _doResize(self):
+ """ Resize the last column as appropriate.
+
+ If the list's columns are too wide to fit within the window, we use
+ a horizontal scrollbar. Otherwise, we expand the right-most column
+ to take up the remaining free space in the list.
+
+ We remember the current size of the last column, before resizing,
+ as the preferred minimum width if we haven't previously been given
+ or calculated a minimum width. This ensure that repeated calls to
+ _doResize() don't cause the last column to size itself too large.
+ """
+ numCols = self.GetColumnCount()
+ if numCols == 0: return # Nothing to resize.
+
+ if self._lastColMinWidth == None:
+ self._lastColMinWidth = self.GetColumnWidth(numCols - 1)
+
+ # We're showing the vertical scrollbar -> allow for scrollbar width
+ # NOTE: on GTK, the scrollbar is included in the client size, but on
+ # Windows it is not included
+ listWidth = self.GetClientSize().width
+ if wx.Platform != '__WXMSW__':
+ if self.GetItemCount() > self.GetCountPerPage():
+ scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
+ listWidth = listWidth - scrollWidth
+
+ totColWidth = 0 # Width of all columns except last one.
+ for col in range(numCols-1):
+ totColWidth = totColWidth + self.GetColumnWidth(col)
+
+ lastColWidth = self.GetColumnWidth(numCols - 1)
+
+ if totColWidth + self._lastColMinWidth > listWidth:
+ # We haven't got the width to show the last column at its minimum
+ # width -> set it to its minimum width and allow the horizontal
+ # scrollbar to show.
+ self.SetColumnWidth(numCols-1, self._lastColMinWidth)
+ return
+
+ # Resize the last column to take up the remaining available space.
+
+ self.SetColumnWidth(numCols-1, listWidth - totColWidth)
+
+
+
+#----------------------------------------------------------------------------
+
+SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
+def selectBeforePopup(event):
+ """Ensures the item the mouse is pointing at is selected before a popup.
+
+ Works with both single-select and multi-select lists."""
+ ctrl = event.GetEventObject()
+ if isinstance(ctrl, wxListCtrl):
+ n, flags = ctrl.HitTest(event.GetPosition())
+ if n >= 0:
+ if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
+ for i in range(ctrl.GetItemCount()):
+ ctrl.SetItemState(i, 0, SEL_FOC)
+ #for i in getListCtrlSelection(ctrl, SEL_FOC):
+ # ctrl.SetItemState(i, 0, SEL_FOC)
+ ctrl.SetItemState(n, SEL_FOC, SEL_FOC)
+
+def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
+ """ Returns list of item indexes of given state (selected by defaults) """
+ res = []
+ idx = -1
+ while 1:
+ idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
+ if idx == -1:
+ break
+ res.append(idx)
+ return res
+
+wxEVT_DOPOPUPMENU = wx.NewEventType()
+EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
+
+class ListCtrlSelectionManagerMix:
+ """Mixin that defines a platform independent selection policy
+
+ As selection single and multi-select list return the item index or a
+ list of item indexes respectively.
+ """
+ _menu = None
+
+ def __init__(self):
+ self.Bind(wx.EVT_RIGHT_DOWN, self.OnLCSMRightDown)
+ self.Bind(EVT_DOPOPUPMENU, self.OnLCSMDoPopup)
+# self.Connect(-1, -1, self.wxEVT_DOPOPUPMENU, self.OnLCSMDoPopup)
+
+ def getPopupMenu(self):
+ """ Override to implement dynamic menus (create) """
+ return self._menu
+
+ def setPopupMenu(self, menu):
+ """ Must be set for default behaviour """
+ self._menu = menu
+
+ def afterPopupMenu(self, menu):
+ """ Override to implement dynamic menus (destroy) """
+ pass
+
+ def getSelection(self):
+ res = getListCtrlSelection(self)
+ if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
+ if res:
+ return res[0]
+ else:
+ return -1
+ else:
+ return res
+
+ def OnLCSMRightDown(self, event):
+ selectBeforePopup(event)
+ event.Skip()
+ menu = self.getPopupMenu()
+ if menu:
+ evt = wx.PyEvent()
+ evt.SetEventType(wxEVT_DOPOPUPMENU)
+ evt.menu = menu
+ evt.pos = event.GetPosition()
+ wx.PostEvent(self, evt)
+
+ def OnLCSMDoPopup(self, event):
+ self.PopupMenu(event.menu, event.pos)
+ self.afterPopupMenu(event.menu)
+
+#----------------------------------------------------------------------