]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/mixins/listctrl.py
1.
[wxWidgets.git] / wxPython / wx / lib / mixins / listctrl.py
1 #----------------------------------------------------------------------------
2 # Name: wxPython.lib.mixins.listctrl
3 # Purpose: Helpful mix-in classes for wxListCtrl
4 #
5 # Author: Robin Dunn
6 #
7 # Created: 15-May-2001
8 # RCS-ID: $Id$
9 # Copyright: (c) 2001 by Total Control Software
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------------
12 # 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
13 #
14 # o 2.5 compatability update.
15 # o ListCtrlSelectionManagerMix untested.
16 #
17
18 import locale
19 import wx
20
21 #----------------------------------------------------------------------------
22
23 class wxColumnSorterMixin:
24 """
25 A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
26 the column header is clicked on.
27
28 There are a few requirments needed in order for this to work genericly:
29
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.
34
35 2. Items in the list control must have a unique data value set
36 with list.SetItemData.
37
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.
42
43 Interesting methods to override are GetColumnSorter,
44 GetSecondarySortValues, and GetSortImages. See below for details.
45 """
46
47 def __init__(self, numColumns):
48 self.SetColumnCount(numColumns)
49 list = self.GetListCtrl()
50 if not list:
51 raise ValueError, "No wx.ListCtrl available"
52 self.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColClick, list)
53
54
55 def SetColumnCount(self, newNumColumns):
56 self._colSortFlag = [0] * newNumColumns
57 self._col = -1
58
59
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."""
62 oldCol = self._col
63 if col != -1:
64 self._col = col
65 self._colSortFlag[col] = ascending
66 self.GetListCtrl().SortItems(self.GetColumnSorter())
67 self.__updateImages(oldCol)
68
69
70 def GetColumnWidths(self):
71 """
72 Returns a list of column widths. Can be used to help restore the current
73 view later.
74 """
75 list = self.GetListCtrl()
76 rv = []
77 for x in range(len(self._colSortFlag)):
78 rv.append(list.GetColumnWidth(x))
79 return rv
80
81
82 def GetSortImages(self):
83 """
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.
86 """
87 return (-1, -1) # (decending, ascending) image IDs
88
89
90 def GetColumnSorter(self):
91 """Returns a callable object to be used for comparing column values when sorting."""
92 return self.__ColumnSorter
93
94
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
98 item data values."""
99 return (key1, key2)
100
101
102 def __OnColClick(self, evt):
103 oldCol = self._col
104 self._col = col = evt.GetColumn()
105 self._colSortFlag[col] = not self._colSortFlag[col]
106 self.GetListCtrl().SortItems(self.GetColumnSorter())
107 self.__updateImages(oldCol)
108 evt.Skip()
109
110
111 def __ColumnSorter(self, key1, key2):
112 col = self._col
113 ascending = self._colSortFlag[col]
114 item1 = self.itemDataMap[key1][col]
115 item2 = self.itemDataMap[key2][col]
116
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))
120 else:
121 cmpVal = cmp(item1, item2)
122 #---
123
124 # If the items are equal then pick something else to make the sort value unique
125 if cmpVal == 0:
126 cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
127
128 if ascending:
129 return cmpVal
130 else:
131 return -cmpVal
132
133
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()
139 if oldCol != -1:
140 list.ClearColumnImage(oldCol)
141 list.SetColumnImage(self._col, img)
142
143
144 #----------------------------------------------------------------------------
145 #----------------------------------------------------------------------------
146
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.
150
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.
154
155 NOTE: This only works for report-style lists.
156
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.
160
161 This mix-in class was written by Erik Westra <ewestra@wave.co.nz>
162 """
163 def __init__(self):
164 """ Standard initialiser.
165 """
166 self._lastColMinWidth = None
167
168 self.Bind(wx.EVT_SIZE, self._onResize)
169 self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
170
171
172 def resizeLastColumn(self, minWidth):
173 """ Resize the last column appropriately.
174
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.
178
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
182 columns).
183
184 'minWidth' is the preferred minimum width for the last column.
185 """
186 self._lastColMinWidth = minWidth
187 self._doResize()
188
189 # =====================
190 # == Private Methods ==
191 # =====================
192
193 def _onResize(self, event):
194 """ Respond to the wx.ListCtrl being resized.
195
196 We automatically resize the last column in the list.
197 """
198 wx.CallAfter(self._doResize)
199 event.Skip()
200
201
202 def _doResize(self):
203 """ Resize the last column as appropriate.
204
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.
208
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.
213 """
214 numCols = self.GetColumnCount()
215 if numCols == 0: return # Nothing to resize.
216
217 if self._lastColMinWidth == None:
218 self._lastColMinWidth = self.GetColumnWidth(numCols - 1)
219
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
228
229 totColWidth = 0 # Width of all columns except last one.
230 for col in range(numCols-1):
231 totColWidth = totColWidth + self.GetColumnWidth(col)
232
233 lastColWidth = self.GetColumnWidth(numCols - 1)
234
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
238 # scrollbar to show.
239 self.SetColumnWidth(numCols-1, self._lastColMinWidth)
240 return
241
242 # Resize the last column to take up the remaining available space.
243
244 self.SetColumnWidth(numCols-1, listWidth - totColWidth)
245
246
247
248 #----------------------------------------------------------------------------
249
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.
253
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())
258 if n >= 0:
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)
265
266 def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
267 """ Returns list of item indexes of given state (selected by defaults) """
268 res = []
269 idx = -1
270 while 1:
271 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
272 if idx == -1:
273 break
274 res.append(idx)
275 return res
276
277 wxEVT_DOPOPUPMENU = wx.NewEventType()
278 EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
279
280 class ListCtrlSelectionManagerMix:
281 """Mixin that defines a platform independent selection policy
282
283 As selection single and multi-select list return the item index or a
284 list of item indexes respectively.
285 """
286 _menu = None
287
288 def __init__(self):
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)
292
293 def getPopupMenu(self):
294 """ Override to implement dynamic menus (create) """
295 return self._menu
296
297 def setPopupMenu(self, menu):
298 """ Must be set for default behaviour """
299 self._menu = menu
300
301 def afterPopupMenu(self, menu):
302 """ Override to implement dynamic menus (destroy) """
303 pass
304
305 def getSelection(self):
306 res = getListCtrlSelection(self)
307 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
308 if res:
309 return res[0]
310 else:
311 return -1
312 else:
313 return res
314
315 def OnLCSMRightDown(self, event):
316 selectBeforePopup(event)
317 event.Skip()
318 menu = self.getPopupMenu()
319 if menu:
320 evt = wx.PyEvent()
321 evt.SetEventType(wxEVT_DOPOPUPMENU)
322 evt.menu = menu
323 evt.pos = event.GetPosition()
324 wx.PostEvent(self, evt)
325
326 def OnLCSMDoPopup(self, event):
327 self.PopupMenu(event.menu, event.pos)
328 self.afterPopupMenu(event.menu)
329
330 #----------------------------------------------------------------------