]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/mixins/listctrl.py
more informative assert message
[wxWidgets.git] / wxPython / wx / lib / mixins / listctrl.py
CommitLineData
d14a1e28
RD
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#----------------------------------------------------------------------------
b881fc78
RD
12# 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
13#
14# o 2.5 compatability update.
15# o ListCtrlSelectionManagerMix untested.
16#
d4b73b1b
RD
17# 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
18#
19# o wxColumnSorterMixin -> ColumnSorterMixin
15513a80
RD
20# o wxListCtrlAutoWidthMixin -> ListCtrlAutoWidthMixin
21# ...
22# 13/10/2004 - Pim Van Heuven (pim@think-wize.com)
23# o wxTextEditMixin: Support Horizontal scrolling when TAB is pressed on long
24# ListCtrls, support for WXK_DOWN, WXK_UP, performance improvements on
25# very long ListCtrls, Support for virtual ListCtrls
26#
27# 15-Oct-2004 - Robin Dunn
28# o wxTextEditMixin: Added Shift-TAB support
d4b73b1b 29#
1fded56b 30
b881fc78
RD
31import locale
32import wx
1fded56b 33
d14a1e28
RD
34#----------------------------------------------------------------------------
35
d4b73b1b 36class ColumnSorterMixin:
d14a1e28 37 """
b881fc78 38 A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
d14a1e28
RD
39 the column header is clicked on.
40
41 There are a few requirments needed in order for this to work genericly:
42
43 1. The combined class must have a GetListCtrl method that
b881fc78
RD
44 returns the wx.ListCtrl to be sorted, and the list control
45 must exist at the time the wx.ColumnSorterMixin.__init__
d14a1e28
RD
46 method is called because it uses GetListCtrl.
47
48 2. Items in the list control must have a unique data value set
49 with list.SetItemData.
50
51 3. The combined class must have an attribute named itemDataMap
52 that is a dictionary mapping the data values to a sequence of
53 objects representing the values in each column. These values
54 are compared in the column sorter to determine sort order.
55
56 Interesting methods to override are GetColumnSorter,
57 GetSecondarySortValues, and GetSortImages. See below for details.
58 """
59
60 def __init__(self, numColumns):
61 self.SetColumnCount(numColumns)
62 list = self.GetListCtrl()
63 if not list:
b881fc78 64 raise ValueError, "No wx.ListCtrl available"
438550d8 65 list.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColClick, list)
d14a1e28
RD
66
67
68 def SetColumnCount(self, newNumColumns):
69 self._colSortFlag = [0] * newNumColumns
70 self._col = -1
71
72
73 def SortListItems(self, col=-1, ascending=1):
74 """Sort the list on demand. Can also be used to set the sort column and order."""
75 oldCol = self._col
76 if col != -1:
77 self._col = col
78 self._colSortFlag[col] = ascending
79 self.GetListCtrl().SortItems(self.GetColumnSorter())
80 self.__updateImages(oldCol)
81
82
83 def GetColumnWidths(self):
84 """
85 Returns a list of column widths. Can be used to help restore the current
86 view later.
87 """
88 list = self.GetListCtrl()
89 rv = []
90 for x in range(len(self._colSortFlag)):
91 rv.append(list.GetColumnWidth(x))
92 return rv
93
94
95 def GetSortImages(self):
96 """
97 Returns a tuple of image list indexesthe indexes in the image list for an image to be put on the column
98 header when sorting in descending order.
99 """
100 return (-1, -1) # (decending, ascending) image IDs
101
102
103 def GetColumnSorter(self):
104 """Returns a callable object to be used for comparing column values when sorting."""
105 return self.__ColumnSorter
106
107
108 def GetSecondarySortValues(self, col, key1, key2):
109 """Returns a tuple of 2 values to use for secondary sort values when the
110 items in the selected column match equal. The default just returns the
111 item data values."""
112 return (key1, key2)
113
114
115 def __OnColClick(self, evt):
116 oldCol = self._col
117 self._col = col = evt.GetColumn()
069eeb17 118 self._colSortFlag[col] = int(not self._colSortFlag[col])
d14a1e28
RD
119 self.GetListCtrl().SortItems(self.GetColumnSorter())
120 self.__updateImages(oldCol)
121 evt.Skip()
122
123
124 def __ColumnSorter(self, key1, key2):
125 col = self._col
126 ascending = self._colSortFlag[col]
127 item1 = self.itemDataMap[key1][col]
128 item2 = self.itemDataMap[key2][col]
129
130 #--- Internationalization of string sorting with locale module
131 if type(item1) == type('') or type(item2) == type(''):
132 cmpVal = locale.strcoll(str(item1), str(item2))
133 else:
134 cmpVal = cmp(item1, item2)
135 #---
136
137 # If the items are equal then pick something else to make the sort value unique
138 if cmpVal == 0:
139 cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
140
141 if ascending:
142 return cmpVal
143 else:
144 return -cmpVal
145
146
147 def __updateImages(self, oldCol):
148 sortImages = self.GetSortImages()
149 if self._col != -1 and sortImages[0] != -1:
150 img = sortImages[self._colSortFlag[self._col]]
151 list = self.GetListCtrl()
152 if oldCol != -1:
153 list.ClearColumnImage(oldCol)
154 list.SetColumnImage(self._col, img)
155
156
157#----------------------------------------------------------------------------
158#----------------------------------------------------------------------------
159
d4b73b1b 160class ListCtrlAutoWidthMixin:
d14a1e28 161 """ A mix-in class that automatically resizes the last column to take up
b881fc78 162 the remaining width of the wx.ListCtrl.
d14a1e28 163
b881fc78 164 This causes the wx.ListCtrl to automatically take up the full width of
d14a1e28
RD
165 the list, without either a horizontal scroll bar (unless absolutely
166 necessary) or empty space to the right of the last column.
167
168 NOTE: This only works for report-style lists.
169
b881fc78 170 WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
d14a1e28
RD
171 sure you call event.Skip() to ensure that the mixin's
172 _OnResize method is called.
173
174 This mix-in class was written by Erik Westra <ewestra@wave.co.nz>
5841276a 175 """
d14a1e28
RD
176 def __init__(self):
177 """ Standard initialiser.
178 """
27ed367c
RD
179 self._resizeColMinWidth = None
180 self._resizeColStyle = "LAST"
181 self._resizeCol = 0
b881fc78
RD
182 self.Bind(wx.EVT_SIZE, self._onResize)
183 self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
d14a1e28
RD
184
185
27ed367c
RD
186 def setResizeColumn(self, col):
187 """
188 Specify which column that should be autosized. Pass either
189 'LAST' or the column number. Default is 'LAST'.
190 """
191 if col == "LAST":
192 self._resizeColStyle = "LAST"
193 else:
194 self._resizeColStyle = "COL"
195 self._resizeCol = col
196
197
d14a1e28
RD
198 def resizeLastColumn(self, minWidth):
199 """ Resize the last column appropriately.
200
201 If the list's columns are too wide to fit within the window, we use
202 a horizontal scrollbar. Otherwise, we expand the right-most column
203 to take up the remaining free space in the list.
204
b881fc78 205 This method is called automatically when the wx.ListCtrl is resized;
d14a1e28
RD
206 you can also call it yourself whenever you want the last column to
207 be resized appropriately (eg, when adding, removing or resizing
208 columns).
209
210 'minWidth' is the preferred minimum width for the last column.
211 """
e4f0ea6b 212 self.resizeColumn(minWidth)
27ed367c
RD
213
214
215 def resizeColumn(self, minWidth):
216 self._resizeColMinWidth = minWidth
d14a1e28 217 self._doResize()
27ed367c 218
d14a1e28
RD
219
220 # =====================
221 # == Private Methods ==
222 # =====================
223
224 def _onResize(self, event):
b881fc78 225 """ Respond to the wx.ListCtrl being resized.
d14a1e28
RD
226
227 We automatically resize the last column in the list.
228 """
e31753ef
RD
229 if 'gtk2' in wx.PlatformInfo:
230 self._doResize()
231 else:
232 wx.CallAfter(self._doResize)
d14a1e28
RD
233 event.Skip()
234
235
236 def _doResize(self):
237 """ Resize the last column as appropriate.
238
239 If the list's columns are too wide to fit within the window, we use
240 a horizontal scrollbar. Otherwise, we expand the right-most column
241 to take up the remaining free space in the list.
242
243 We remember the current size of the last column, before resizing,
244 as the preferred minimum width if we haven't previously been given
245 or calculated a minimum width. This ensure that repeated calls to
246 _doResize() don't cause the last column to size itself too large.
247 """
a61c65b3
RD
248
249 if not self: # avoid a PyDeadObject error
250 return
e90db86e
RD
251
252 if self.GetSize().height < 32:
253 return # avoid an endless update bug when the height is small.
a61c65b3 254
d14a1e28
RD
255 numCols = self.GetColumnCount()
256 if numCols == 0: return # Nothing to resize.
257
27ed367c
RD
258 if(self._resizeColStyle == "LAST"):
259 resizeCol = self.GetColumnCount()
260 else:
261 resizeCol = self._resizeCol
262
263 if self._resizeColMinWidth == None:
264 self._resizeColMinWidth = self.GetColumnWidth(resizeCol - 1)
d14a1e28
RD
265
266 # We're showing the vertical scrollbar -> allow for scrollbar width
267 # NOTE: on GTK, the scrollbar is included in the client size, but on
268 # Windows it is not included
269 listWidth = self.GetClientSize().width
b881fc78 270 if wx.Platform != '__WXMSW__':
d14a1e28 271 if self.GetItemCount() > self.GetCountPerPage():
b881fc78 272 scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
d14a1e28
RD
273 listWidth = listWidth - scrollWidth
274
275 totColWidth = 0 # Width of all columns except last one.
27ed367c
RD
276 for col in range(numCols):
277 if col != (resizeCol-1):
278 totColWidth = totColWidth + self.GetColumnWidth(col)
d14a1e28 279
27ed367c 280 resizeColWidth = self.GetColumnWidth(resizeCol - 1)
d14a1e28 281
27ed367c 282 if totColWidth + self._resizeColMinWidth > listWidth:
d14a1e28
RD
283 # We haven't got the width to show the last column at its minimum
284 # width -> set it to its minimum width and allow the horizontal
285 # scrollbar to show.
27ed367c 286 self.SetColumnWidth(resizeCol-1, self._resizeColMinWidth)
d14a1e28
RD
287 return
288
289 # Resize the last column to take up the remaining available space.
290
27ed367c
RD
291 self.SetColumnWidth(resizeCol-1, listWidth - totColWidth)
292
d14a1e28
RD
293
294
295
23a02364 296#----------------------------------------------------------------------------
d14a1e28
RD
297#----------------------------------------------------------------------------
298
b881fc78 299SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
d14a1e28
RD
300def selectBeforePopup(event):
301 """Ensures the item the mouse is pointing at is selected before a popup.
302
303 Works with both single-select and multi-select lists."""
304 ctrl = event.GetEventObject()
8ab15340 305 if isinstance(ctrl, wx.ListCtrl):
d14a1e28
RD
306 n, flags = ctrl.HitTest(event.GetPosition())
307 if n >= 0:
b881fc78 308 if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
d14a1e28
RD
309 for i in range(ctrl.GetItemCount()):
310 ctrl.SetItemState(i, 0, SEL_FOC)
311 #for i in getListCtrlSelection(ctrl, SEL_FOC):
312 # ctrl.SetItemState(i, 0, SEL_FOC)
313 ctrl.SetItemState(n, SEL_FOC, SEL_FOC)
314
5841276a 315
b881fc78 316def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
d14a1e28
RD
317 """ Returns list of item indexes of given state (selected by defaults) """
318 res = []
319 idx = -1
320 while 1:
b881fc78 321 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
d14a1e28
RD
322 if idx == -1:
323 break
324 res.append(idx)
325 return res
326
b881fc78
RD
327wxEVT_DOPOPUPMENU = wx.NewEventType()
328EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
329
5841276a 330
d14a1e28
RD
331class ListCtrlSelectionManagerMix:
332 """Mixin that defines a platform independent selection policy
333
334 As selection single and multi-select list return the item index or a
335 list of item indexes respectively.
336 """
d14a1e28
RD
337 _menu = None
338
339 def __init__(self):
b881fc78
RD
340 self.Bind(wx.EVT_RIGHT_DOWN, self.OnLCSMRightDown)
341 self.Bind(EVT_DOPOPUPMENU, self.OnLCSMDoPopup)
342# self.Connect(-1, -1, self.wxEVT_DOPOPUPMENU, self.OnLCSMDoPopup)
d14a1e28 343
5841276a 344
d14a1e28
RD
345 def getPopupMenu(self):
346 """ Override to implement dynamic menus (create) """
347 return self._menu
348
5841276a 349
d14a1e28
RD
350 def setPopupMenu(self, menu):
351 """ Must be set for default behaviour """
352 self._menu = menu
353
5841276a 354
d14a1e28
RD
355 def afterPopupMenu(self, menu):
356 """ Override to implement dynamic menus (destroy) """
357 pass
358
5841276a 359
d14a1e28
RD
360 def getSelection(self):
361 res = getListCtrlSelection(self)
b881fc78 362 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
d14a1e28
RD
363 if res:
364 return res[0]
365 else:
366 return -1
367 else:
368 return res
369
5841276a 370
d14a1e28
RD
371 def OnLCSMRightDown(self, event):
372 selectBeforePopup(event)
373 event.Skip()
374 menu = self.getPopupMenu()
375 if menu:
b881fc78
RD
376 evt = wx.PyEvent()
377 evt.SetEventType(wxEVT_DOPOPUPMENU)
d14a1e28
RD
378 evt.menu = menu
379 evt.pos = event.GetPosition()
b881fc78 380 wx.PostEvent(self, evt)
d14a1e28 381
5841276a 382
d14a1e28
RD
383 def OnLCSMDoPopup(self, event):
384 self.PopupMenu(event.menu, event.pos)
385 self.afterPopupMenu(event.menu)
386
5841276a 387
23a02364 388#----------------------------------------------------------------------------
5841276a
RD
389#----------------------------------------------------------------------------
390from bisect import bisect
391
392
393class TextEditMixin:
6d3c4b2a
RD
394 """
395 A mixin class that enables any text in any column of a
5841276a
RD
396 multi-column listctrl to be edited by clicking on the given row
397 and column. You close the text editor by hitting the ENTER key or
398 clicking somewhere else on the listctrl. You switch to the next
399 column by hiting TAB.
400
401 To use the mixin you have to include it in the class definition
402 and call the __init__ function::
403
6d3c4b2a 404 class TestListCtrl(wx.ListCtrl, TextEditMixin):
5841276a
RD
405 def __init__(self, parent, ID, pos=wx.DefaultPosition,
406 size=wx.DefaultSize, style=0):
407 wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
6d3c4b2a 408 TextEditMixin.__init__(self)
5841276a
RD
409
410
411 Authors: Steve Zatz, Pim Van Heuven (pim@think-wize.com)
412 """
42a04f70
RD
413
414 editorBgColour = wx.Colour(255,255,175) # Yellow
415 editorFgColour = wx.Colour(0,0,0) # black
5841276a
RD
416
417 def __init__(self):
418 #editor = wx.TextCtrl(self, -1, pos=(-1,-1), size=(-1,-1),
419 # style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB \
420 # |wx.TE_RICH2)
15513a80
RD
421
422 self.make_editor()
423 self.Bind(wx.EVT_TEXT_ENTER, self.CloseEditor)
424 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
425 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
426 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
427
428
429 def make_editor(self, col_style=wx.LIST_FORMAT_LEFT):
15513a80
RD
430
431 style =wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_RICH2
27ed367c
RD
432 style |= {wx.LIST_FORMAT_LEFT: wx.TE_LEFT,
433 wx.LIST_FORMAT_RIGHT: wx.TE_RIGHT,
434 wx.LIST_FORMAT_CENTRE : wx.TE_CENTRE
435 }[col_style]
15513a80 436
991e49b6 437 editor = wx.TextCtrl(self, -1, style=style)
42a04f70
RD
438 editor.SetBackgroundColour(self.editorBgColour)
439 editor.SetForegroundColour(self.editorFgColour)
5841276a
RD
440 font = self.GetFont()
441 editor.SetFont(font)
442
15513a80
RD
443 self.curRow = 0
444 self.curCol = 0
445
446 editor.Hide()
991e49b6
RD
447 if hasattr(self, 'editor'):
448 self.editor.Destroy()
5841276a 449 self.editor = editor
15513a80
RD
450
451 self.col_style = col_style
5841276a
RD
452 self.editor.Bind(wx.EVT_CHAR, self.OnChar)
453 self.editor.Bind(wx.EVT_KILL_FOCUS, self.CloseEditor)
15513a80 454
5841276a
RD
455
456 def OnItemSelected(self, evt):
457 self.curRow = evt.GetIndex()
458 evt.Skip()
459
460
461 def OnChar(self, event):
15513a80
RD
462 ''' Catch the TAB, Shift-TAB, cursor DOWN/UP key code
463 so we can open the editor at the next column (if any).'''
464
465 keycode = event.GetKeyCode()
466 if keycode == wx.WXK_TAB and event.ShiftDown():
467 self.CloseEditor()
468 if self.curCol-1 >= 0:
469 self.OpenEditor(self.curCol-1, self.curRow)
470
471 elif keycode == wx.WXK_TAB:
5841276a
RD
472 self.CloseEditor()
473 if self.curCol+1 < self.GetColumnCount():
474 self.OpenEditor(self.curCol+1, self.curRow)
15513a80
RD
475
476 elif keycode == wx.WXK_ESCAPE:
477 self.CloseEditor()
478
479 elif keycode == wx.WXK_DOWN:
480 self.CloseEditor()
481 if self.curRow+1 < self.GetItemCount():
482 self._SelectIndex(self.curRow+1)
483 self.OpenEditor(self.curCol, self.curRow)
484
485 elif keycode == wx.WXK_UP:
486 self.CloseEditor()
487 if self.curRow > 0:
488 self._SelectIndex(self.curRow-1)
489 self.OpenEditor(self.curCol, self.curRow)
490
5841276a
RD
491 else:
492 event.Skip()
493
494
495 def OnLeftDown(self, evt=None):
496 ''' Examine the click and double
497 click events to see if a row has been click on twice. If so,
498 determine the current row and columnn and open the editor.'''
499
500 if self.editor.IsShown():
501 self.CloseEditor()
502
503 x,y = evt.GetPosition()
504 row,flags = self.HitTest((x,y))
505
506 if row != self.curRow: # self.curRow keeps track of the current row
507 evt.Skip()
508 return
509
510 # the following should really be done in the mixin's init but
511 # the wx.ListCtrl demo creates the columns after creating the
512 # ListCtrl (generally not a good idea) on the other hand,
513 # doing this here handles adjustable column widths
514
515 self.col_locs = [0]
516 loc = 0
517 for n in range(self.GetColumnCount()):
518 loc = loc + self.GetColumnWidth(n)
519 self.col_locs.append(loc)
15513a80 520
5841276a 521
15513a80 522 col = bisect(self.col_locs, x+self.GetScrollPos(wx.HORIZONTAL)) - 1
5841276a
RD
523 self.OpenEditor(col, row)
524
525
526 def OpenEditor(self, col, row):
527 ''' Opens an editor at the current position. '''
15513a80 528
f4102f17
RD
529 # give the derived class a chance to Allow/Veto this edit.
530 evt = wx.ListEvent(wx.wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT, self.GetId())
531 evt.m_itemIndex = row
532 evt.m_col = col
533 item = self.GetItem(row, col)
534 evt.m_item.SetId(item.GetId())
535 evt.m_item.SetColumn(item.GetColumn())
536 evt.m_item.SetData(item.GetData())
537 evt.m_item.SetText(item.GetText())
538 ret = self.GetEventHandler().ProcessEvent(evt)
539 if ret and not evt.IsAllowed():
540 return # user code doesn't allow the edit.
541
15513a80
RD
542 if self.GetColumn(col).m_format != self.col_style:
543 self.make_editor(self.GetColumn(col).m_format)
5841276a
RD
544
545 x0 = self.col_locs[col]
546 x1 = self.col_locs[col+1] - x0
547
15513a80
RD
548 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
549
27ed367c 550 # scroll forward
15513a80
RD
551 if x0+x1-scrolloffset > self.GetSize()[0]:
552 if wx.Platform == "__WXMSW__":
553 # don't start scrolling unless we really need to
554 offset = x0+x1-self.GetSize()[0]-scrolloffset
555 # scroll a bit more than what is minimum required
556 # so we don't have to scroll everytime the user presses TAB
557 # which is very tireing to the eye
558 addoffset = self.GetSize()[0]/4
559 # but be careful at the end of the list
560 if addoffset + scrolloffset < self.GetSize()[0]:
561 offset += addoffset
562
563 self.ScrollList(offset, 0)
564 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
565 else:
566 # Since we can not programmatically scroll the ListCtrl
567 # close the editor so the user can scroll and open the editor
568 # again
27ed367c
RD
569 self.editor.SetValue(self.GetItem(row, col).GetText())
570 self.curRow = row
571 self.curCol = col
15513a80
RD
572 self.CloseEditor()
573 return
574
5841276a
RD
575 y0 = self.GetItemRect(row)[1]
576
577 editor = self.editor
15513a80
RD
578 editor.SetDimensions(x0-scrolloffset,y0, x1,-1)
579
5841276a
RD
580 editor.SetValue(self.GetItem(row, col).GetText())
581 editor.Show()
582 editor.Raise()
583 editor.SetSelection(-1,-1)
584 editor.SetFocus()
585
586 self.curRow = row
587 self.curCol = col
588
589
42a04f70
RD
590 # FIXME: this function is usually called twice - second time because
591 # it is binded to wx.EVT_KILL_FOCUS. Can it be avoided? (MW)
5841276a
RD
592 def CloseEditor(self, evt=None):
593 ''' Close the editor and save the new value to the ListCtrl. '''
f4102f17
RD
594 if not self.editor.IsShown():
595 return
5841276a
RD
596 text = self.editor.GetValue()
597 self.editor.Hide()
42a04f70
RD
598 self.SetFocus()
599
600 # post wxEVT_COMMAND_LIST_END_LABEL_EDIT
601 # Event can be vetoed. It doesn't has SetEditCanceled(), what would
602 # require passing extra argument to CloseEditor()
603 evt = wx.ListEvent(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT, self.GetId())
604 evt.m_itemIndex = self.curRow
605 evt.m_col = self.curCol
606 item = self.GetItem(self.curRow, self.curCol)
607 evt.m_item.SetId(item.GetId())
608 evt.m_item.SetColumn(item.GetColumn())
609 evt.m_item.SetData(item.GetData())
610 evt.m_item.SetText(text) #should be empty string if editor was canceled
611 ret = self.GetEventHandler().ProcessEvent(evt)
612 if not ret or evt.IsAllowed():
613 if self.IsVirtual():
614 # replace by whather you use to populate the virtual ListCtrl
615 # data source
616 self.SetVirtualData(self.curRow, self.curCol, text)
617 else:
618 self.SetStringItem(self.curRow, self.curCol, text)
15513a80
RD
619 self.RefreshItem(self.curRow)
620
621 def _SelectIndex(self, row):
622 listlen = self.GetItemCount()
623 if row < 0 and not listlen:
624 return
625 if row > (listlen-1):
626 row = listlen -1
627
628 self.SetItemState(self.curRow, ~wx.LIST_STATE_SELECTED,
629 wx.LIST_STATE_SELECTED)
630 self.EnsureVisible(row)
631 self.SetItemState(row, wx.LIST_STATE_SELECTED,
632 wx.LIST_STATE_SELECTED)
5841276a
RD
633
634
635
23a02364
RD
636#----------------------------------------------------------------------------
637#----------------------------------------------------------------------------
638
639"""
640FILENAME: CheckListCtrlMixin.py
641AUTHOR: Bruce Who (bruce.who.hk at gmail.com)
642DATE: 2006-02-09
643$Revision$
644DESCRIPTION:
645 This script provide a mixin for ListCtrl which add a checkbox in the first
646 column of each row. It is inspired by limodou's CheckList.py(which can be
647 got from his NewEdit) and improved:
648 - You can just use InsertStringItem() to insert new items;
649 - Once a checkbox is checked/unchecked, the corresponding item is not
650 selected;
651 - You can use SetItemData() and GetItemData();
652 - Interfaces are changed to OnCheckItem(), IsChecked(), CheckItem().
653
654 You should not set a imagelist for the ListCtrl once this mixin is used.
655
656HISTORY:
6571.3 - You can check/uncheck a group of sequential items by <Shift-click>:
658 First click(or <Shift-Click>) item1 to check/uncheck it, then
659 Shift-click item2 to check/uncheck it, and you'll find that all
660 items between item1 and item2 are check/unchecked!
6611.2 - Add ToggleItem()
6621.1 - Initial version
663"""
664
665from wx import ImageFromStream, BitmapFromImage
666import cStringIO, zlib
667
668def getUncheckData():
669 return zlib.decompress(
670"x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
671\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xbb{\xba8\x86X\xf4\
672&\xa7\xa4$\xa5-`1\x08\\2\xbb\xb1\xb1\x91\xf5\xd8\x84o\xeb\xff\xfaw\x1d[.=[2\
673\x90'\x01\x08v\xec]\xd3\xa3qvU`l\x81\xd9\xd18\t\xd3\x84+\x0cll[\xa6t\xcc9\
674\xd4\xc1\xda\xc3<O\x9a1\xc3\x88\xc3j\xfa\x86_\xee@#\x19<]\xfd\\\xd69%4\x01\
675\x00\xdc\x80-\x05" )
676
677def getUncheckBitmap():
678 return BitmapFromImage(getUncheckImage())
679
680def getUncheckImage():
681 stream = cStringIO.StringIO(getUncheckData())
682 return ImageFromStream(stream)
683
684def getCheckData():
685 return zlib.decompress(
686'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
687\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe47{\xba8\x86X\xf4&\
688\xa7\xa4$\xa5-`1\x08\\2\xbb\xb1\xb1\x91\xf5\xd8\x84o\xeb\xff\xfaw\x1d[.=[2\
689\x90\'\x01\x08v\xec\\2C\xe3\xec+\xc3\xbd\x05fG\xe3\x14n1\xcc5\xad\x8a8\x1a\
690\xb9\xa1\xeb\xd1\x853-\xaa\xc76\xecb\xb8i\x16c&\\\xc2\xb8\xe9Xvbx\xa1T\xc3U\
691\xd6p\'\xbd\x85\x19\xff\xbe\xbf\xd7\xe7R\xcb`\xd8\xa5\xf8\x83\xe1^\xc4\x0e\
692\xa1"\xce\xc3n\x93x\x14\xd8\x16\xb0(\x15q)\x8b\x19\xf0U\xe4\xb10\x08V\xa8\
693\x99\xf3\xdd\xde\xad\x06t\x0e\x83\xa7\xab\x9f\xcb:\xa7\x84&\x00\xe0HE\xab' )
694
695def getCheckBitmap():
696 return BitmapFromImage(getCheckImage())
697
698def getCheckImage():
699 stream = cStringIO.StringIO(getCheckData())
700 return ImageFromStream(stream)
701
702
703
704class CheckListCtrlMixin:
705 """
706 This is a mixin for ListCtrl which add a checkbox in the first
707 column of each row. It is inspired by limodou's CheckList.py(which
9f4cc34f
RD
708 can be got from his NewEdit) and improved:
709
23a02364 710 - You can just use InsertStringItem() to insert new items;
9f4cc34f
RD
711
712 - Once a checkbox is checked/unchecked, the corresponding item
713 is not selected;
714
23a02364 715 - You can use SetItemData() and GetItemData();
9f4cc34f
RD
716
717 - Interfaces are changed to OnCheckItem(), IsChecked(),
718 CheckItem().
23a02364
RD
719
720 You should not set a imagelist for the ListCtrl once this mixin is used.
721 """
722 def __init__(self, check_image=None, uncheck_image=None):
723 self.__imagelist_ = wx.ImageList(16, 16)
724 if not check_image:
725 check_image = getCheckBitmap()
726 if not uncheck_image:
727 uncheck_image = getUncheckBitmap()
728 self.uncheck_image = self.__imagelist_.Add(uncheck_image)
729 self.check_image = self.__imagelist_.Add(check_image)
730 self.SetImageList(self.__imagelist_, wx.IMAGE_LIST_SMALL)
731 self.__last_check_ = None
732
733 self.Bind(wx.EVT_LEFT_DOWN, self.__OnLeftDown_)
734
735 # override the default methods of ListCtrl/ListView
736 self.InsertStringItem = self.__InsertStringItem_
737
738 # NOTE: if you use InsertItem, InsertImageItem or InsertImageStringItem,
739 # you must set the image yourself.
740 def __InsertStringItem_(self, index, label):
741 index = self.InsertImageStringItem(index, label, 0)
742 return index
743
744 def __OnLeftDown_(self, evt):
745 (index, flags) = self.HitTest(evt.GetPosition())
746 if flags == wx.LIST_HITTEST_ONITEMICON:
747 img_idx = self.GetItem(index).GetImage()
748 flag_check = img_idx == 0
749 begin_index = index
750 end_index = index
751 if self.__last_check_ is not None \
752 and wx.GetKeyState(wx.WXK_SHIFT):
753 last_index, last_flag_check = self.__last_check_
754 if last_flag_check == flag_check:
755 # XXX what if the previous item is deleted or new items
756 # are inserted?
757 item_count = self.GetItemCount()
758 if last_index < item_count:
759 if last_index < index:
760 begin_index = last_index
761 end_index = index
762 elif last_index > index:
763 begin_index = index
764 end_index = last_index
765 else:
766 assert False
767 while begin_index <= end_index:
768 self.CheckItem(begin_index, flag_check)
769 begin_index += 1
770 self.__last_check_ = (index, flag_check)
771 else:
772 evt.Skip()
773
774 def OnCheckItem(self, index, flag):
775 pass
776
777 def IsChecked(self, index):
778 return self.GetItem(index).GetImage() == 1
779
780 def CheckItem(self, index, check = True):
781 img_idx = self.GetItem(index).GetImage()
782 if img_idx == 0 and check is True:
783 self.SetItemImage(index, 1)
784 self.OnCheckItem(index, True)
785 elif img_idx == 1 and check is False:
786 self.SetItemImage(index, 0)
787 self.OnCheckItem(index, False)
788
789 def ToggleItem(self, index):
790 self.CheckItem(index, not self.IsChecked(index))
791
792
5841276a 793#----------------------------------------------------------------------------