1 #----------------------------------------------------------------------------
2 # Name: wxPython.lib.mixins.listctrl
3 # Purpose: Helpful mix-in classes for wxListCtrl
9 # Copyright: (c) 2001 by Total Control Software
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------------
12 # 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14 # o 2.5 compatability update.
15 # o ListCtrlSelectionManagerMix untested.
21 #----------------------------------------------------------------------------
23 class wxColumnSorterMixin
:
25 A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
26 the column header is clicked on.
28 There are a few requirments needed in order for this to work genericly:
30 1. The combined class must have a GetListCtrl method that
31 returns the wx.ListCtrl to be sorted, and the list control
32 must exist at the time the wx.ColumnSorterMixin.__init__
33 method is called because it uses GetListCtrl.
35 2. Items in the list control must have a unique data value set
36 with list.SetItemData.
38 3. The combined class must have an attribute named itemDataMap
39 that is a dictionary mapping the data values to a sequence of
40 objects representing the values in each column. These values
41 are compared in the column sorter to determine sort order.
43 Interesting methods to override are GetColumnSorter,
44 GetSecondarySortValues, and GetSortImages. See below for details.
47 def __init__(self
, numColumns
):
48 self
.SetColumnCount(numColumns
)
49 list = self
.GetListCtrl()
51 raise ValueError, "No wx.ListCtrl available"
52 self
.Bind(wx
.EVT_LIST_COL_CLICK
, self
.__OnColClick
, list)
55 def SetColumnCount(self
, newNumColumns
):
56 self
._colSortFlag
= [0] * newNumColumns
60 def SortListItems(self
, col
=-1, ascending
=1):
61 """Sort the list on demand. Can also be used to set the sort column and order."""
65 self
._colSortFlag
[col
] = ascending
66 self
.GetListCtrl().SortItems(self
.GetColumnSorter())
67 self
.__updateImages
(oldCol
)
70 def GetColumnWidths(self
):
72 Returns a list of column widths. Can be used to help restore the current
75 list = self
.GetListCtrl()
77 for x
in range(len(self
._colSortFlag
)):
78 rv
.append(list.GetColumnWidth(x
))
82 def GetSortImages(self
):
84 Returns a tuple of image list indexesthe indexes in the image list for an image to be put on the column
85 header when sorting in descending order.
87 return (-1, -1) # (decending, ascending) image IDs
90 def GetColumnSorter(self
):
91 """Returns a callable object to be used for comparing column values when sorting."""
92 return self
.__ColumnSorter
95 def GetSecondarySortValues(self
, col
, key1
, key2
):
96 """Returns a tuple of 2 values to use for secondary sort values when the
97 items in the selected column match equal. The default just returns the
102 def __OnColClick(self
, evt
):
104 self
._col
= col
= evt
.GetColumn()
105 self
._colSortFlag
[col
] = not self
._colSortFlag
[col
]
106 self
.GetListCtrl().SortItems(self
.GetColumnSorter())
107 self
.__updateImages
(oldCol
)
111 def __ColumnSorter(self
, key1
, key2
):
113 ascending
= self
._colSortFlag
[col
]
114 item1
= self
.itemDataMap
[key1
][col
]
115 item2
= self
.itemDataMap
[key2
][col
]
117 #--- Internationalization of string sorting with locale module
118 if type(item1
) == type('') or type(item2
) == type(''):
119 cmpVal
= locale
.strcoll(str(item1
), str(item2
))
121 cmpVal
= cmp(item1
, item2
)
124 # If the items are equal then pick something else to make the sort value unique
126 cmpVal
= apply(cmp, self
.GetSecondarySortValues(col
, key1
, key2
))
134 def __updateImages(self
, oldCol
):
135 sortImages
= self
.GetSortImages()
136 if self
._col
!= -1 and sortImages
[0] != -1:
137 img
= sortImages
[self
._colSortFlag
[self
._col
]]
138 list = self
.GetListCtrl()
140 list.ClearColumnImage(oldCol
)
141 list.SetColumnImage(self
._col
, img
)
144 #----------------------------------------------------------------------------
145 #----------------------------------------------------------------------------
147 class wxListCtrlAutoWidthMixin
:
148 """ A mix-in class that automatically resizes the last column to take up
149 the remaining width of the wx.ListCtrl.
151 This causes the wx.ListCtrl to automatically take up the full width of
152 the list, without either a horizontal scroll bar (unless absolutely
153 necessary) or empty space to the right of the last column.
155 NOTE: This only works for report-style lists.
157 WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
158 sure you call event.Skip() to ensure that the mixin's
159 _OnResize method is called.
161 This mix-in class was written by Erik Westra <ewestra@wave.co.nz>
164 """ Standard initialiser.
166 self
._lastColMinWidth
= None
168 self
.Bind(wx
.EVT_SIZE
, self
._onResize
)
169 self
.Bind(wx
.EVT_LIST_COL_END_DRAG
, self
._onResize
, self
)
172 def resizeLastColumn(self
, minWidth
):
173 """ Resize the last column appropriately.
175 If the list's columns are too wide to fit within the window, we use
176 a horizontal scrollbar. Otherwise, we expand the right-most column
177 to take up the remaining free space in the list.
179 This method is called automatically when the wx.ListCtrl is resized;
180 you can also call it yourself whenever you want the last column to
181 be resized appropriately (eg, when adding, removing or resizing
184 'minWidth' is the preferred minimum width for the last column.
186 self
._lastColMinWidth
= minWidth
189 # =====================
190 # == Private Methods ==
191 # =====================
193 def _onResize(self
, event
):
194 """ Respond to the wx.ListCtrl being resized.
196 We automatically resize the last column in the list.
198 wx
.CallAfter(self
._doResize
)
203 """ Resize the last column as appropriate.
205 If the list's columns are too wide to fit within the window, we use
206 a horizontal scrollbar. Otherwise, we expand the right-most column
207 to take up the remaining free space in the list.
209 We remember the current size of the last column, before resizing,
210 as the preferred minimum width if we haven't previously been given
211 or calculated a minimum width. This ensure that repeated calls to
212 _doResize() don't cause the last column to size itself too large.
214 numCols
= self
.GetColumnCount()
215 if numCols
== 0: return # Nothing to resize.
217 if self
._lastColMinWidth
== None:
218 self
._lastColMinWidth
= self
.GetColumnWidth(numCols
- 1)
220 # We're showing the vertical scrollbar -> allow for scrollbar width
221 # NOTE: on GTK, the scrollbar is included in the client size, but on
222 # Windows it is not included
223 listWidth
= self
.GetClientSize().width
224 if wx
.Platform
!= '__WXMSW__':
225 if self
.GetItemCount() > self
.GetCountPerPage():
226 scrollWidth
= wx
.SystemSettings_GetMetric(wx
.SYS_VSCROLL_X
)
227 listWidth
= listWidth
- scrollWidth
229 totColWidth
= 0 # Width of all columns except last one.
230 for col
in range(numCols
-1):
231 totColWidth
= totColWidth
+ self
.GetColumnWidth(col
)
233 lastColWidth
= self
.GetColumnWidth(numCols
- 1)
235 if totColWidth
+ self
._lastColMinWidth
> listWidth
:
236 # We haven't got the width to show the last column at its minimum
237 # width -> set it to its minimum width and allow the horizontal
239 self
.SetColumnWidth(numCols
-1, self
._lastColMinWidth
)
242 # Resize the last column to take up the remaining available space.
244 self
.SetColumnWidth(numCols
-1, listWidth
- totColWidth
)
248 #----------------------------------------------------------------------------
250 SEL_FOC
= wx
.LIST_STATE_SELECTED | wx
.LIST_STATE_FOCUSED
251 def selectBeforePopup(event
):
252 """Ensures the item the mouse is pointing at is selected before a popup.
254 Works with both single-select and multi-select lists."""
255 ctrl
= event
.GetEventObject()
256 if isinstance(ctrl
, wxListCtrl
):
257 n
, flags
= ctrl
.HitTest(event
.GetPosition())
259 if not ctrl
.GetItemState(n
, wx
.LIST_STATE_SELECTED
):
260 for i
in range(ctrl
.GetItemCount()):
261 ctrl
.SetItemState(i
, 0, SEL_FOC
)
262 #for i in getListCtrlSelection(ctrl, SEL_FOC):
263 # ctrl.SetItemState(i, 0, SEL_FOC)
264 ctrl
.SetItemState(n
, SEL_FOC
, SEL_FOC
)
266 def getListCtrlSelection(listctrl
, state
=wx
.LIST_STATE_SELECTED
):
267 """ Returns list of item indexes of given state (selected by defaults) """
271 idx
= listctrl
.GetNextItem(idx
, wx
.LIST_NEXT_ALL
, state
)
277 wxEVT_DOPOPUPMENU
= wx
.NewEventType()
278 EVT_DOPOPUPMENU
= wx
.PyEventBinder(wxEVT_DOPOPUPMENU
, 0)
280 class ListCtrlSelectionManagerMix
:
281 """Mixin that defines a platform independent selection policy
283 As selection single and multi-select list return the item index or a
284 list of item indexes respectively.
289 self
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnLCSMRightDown
)
290 self
.Bind(EVT_DOPOPUPMENU
, self
.OnLCSMDoPopup
)
291 # self.Connect(-1, -1, self.wxEVT_DOPOPUPMENU, self.OnLCSMDoPopup)
293 def getPopupMenu(self
):
294 """ Override to implement dynamic menus (create) """
297 def setPopupMenu(self
, menu
):
298 """ Must be set for default behaviour """
301 def afterPopupMenu(self
, menu
):
302 """ Override to implement dynamic menus (destroy) """
305 def getSelection(self
):
306 res
= getListCtrlSelection(self
)
307 if self
.GetWindowStyleFlag() & wx
.LC_SINGLE_SEL
:
315 def OnLCSMRightDown(self
, event
):
316 selectBeforePopup(event
)
318 menu
= self
.getPopupMenu()
321 evt
.SetEventType(wxEVT_DOPOPUPMENU
)
323 evt
.pos
= event
.GetPosition()
324 wx
.PostEvent(self
, evt
)
326 def OnLCSMDoPopup(self
, event
):
327 self
.PopupMenu(event
.menu
, event
.pos
)
328 self
.afterPopupMenu(event
.menu
)
330 #----------------------------------------------------------------------