]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/mixins/listctrl.py
generated rename directives are no longer needed
[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 # 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
18 #
19 # o wxColumnSorterMixin -> ColumnSorterMixin
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
29 #
30
31 import locale
32 import wx
33
34 #----------------------------------------------------------------------------
35
36 class ColumnSorterMixin:
37 """
38 A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
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
44 returns the wx.ListCtrl to be sorted, and the list control
45 must exist at the time the wx.ColumnSorterMixin.__init__
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:
64 raise ValueError, "No wx.ListCtrl available"
65 list.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColClick, list)
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()
118 self._colSortFlag[col] = not self._colSortFlag[col]
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
160 class ListCtrlAutoWidthMixin:
161 """ A mix-in class that automatically resizes the last column to take up
162 the remaining width of the wx.ListCtrl.
163
164 This causes the wx.ListCtrl to automatically take up the full width of
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
170 WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
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>
175 """
176 def __init__(self):
177 """ Standard initialiser.
178 """
179 self._resizeColMinWidth = None
180 self._resizeColStyle = "LAST"
181 self._resizeCol = 0
182 self.Bind(wx.EVT_SIZE, self._onResize)
183 self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
184
185
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
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
205 This method is called automatically when the wx.ListCtrl is resized;
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 """
212 self.resizeColumn(minWidth)
213
214
215 def resizeColumn(self, minWidth):
216 self._resizeColMinWidth = minWidth
217 self._doResize()
218
219
220 # =====================
221 # == Private Methods ==
222 # =====================
223
224 def _onResize(self, event):
225 """ Respond to the wx.ListCtrl being resized.
226
227 We automatically resize the last column in the list.
228 """
229 if 'gtk2' in wx.PlatformInfo:
230 self._doResize()
231 else:
232 wx.CallAfter(self._doResize)
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 """
248
249 if not self: # avoid a PyDeadObject error
250 return
251
252 numCols = self.GetColumnCount()
253 if numCols == 0: return # Nothing to resize.
254
255 if(self._resizeColStyle == "LAST"):
256 resizeCol = self.GetColumnCount()
257 else:
258 resizeCol = self._resizeCol
259
260 if self._resizeColMinWidth == None:
261 self._resizeColMinWidth = self.GetColumnWidth(resizeCol - 1)
262
263 # We're showing the vertical scrollbar -> allow for scrollbar width
264 # NOTE: on GTK, the scrollbar is included in the client size, but on
265 # Windows it is not included
266 listWidth = self.GetClientSize().width
267 if wx.Platform != '__WXMSW__':
268 if self.GetItemCount() > self.GetCountPerPage():
269 scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
270 listWidth = listWidth - scrollWidth
271
272 totColWidth = 0 # Width of all columns except last one.
273 for col in range(numCols):
274 if col != (resizeCol-1):
275 totColWidth = totColWidth + self.GetColumnWidth(col)
276
277 resizeColWidth = self.GetColumnWidth(resizeCol - 1)
278
279 if totColWidth + self._resizeColMinWidth > listWidth:
280 # We haven't got the width to show the last column at its minimum
281 # width -> set it to its minimum width and allow the horizontal
282 # scrollbar to show.
283 self.SetColumnWidth(resizeCol-1, self._resizeColMinWidth)
284 return
285
286 # Resize the last column to take up the remaining available space.
287
288 self.SetColumnWidth(resizeCol-1, listWidth - totColWidth)
289
290
291
292
293 #----------------------------------------------------------------------------
294 #----------------------------------------------------------------------------
295
296 SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
297 def selectBeforePopup(event):
298 """Ensures the item the mouse is pointing at is selected before a popup.
299
300 Works with both single-select and multi-select lists."""
301 ctrl = event.GetEventObject()
302 if isinstance(ctrl, wx.ListCtrl):
303 n, flags = ctrl.HitTest(event.GetPosition())
304 if n >= 0:
305 if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
306 for i in range(ctrl.GetItemCount()):
307 ctrl.SetItemState(i, 0, SEL_FOC)
308 #for i in getListCtrlSelection(ctrl, SEL_FOC):
309 # ctrl.SetItemState(i, 0, SEL_FOC)
310 ctrl.SetItemState(n, SEL_FOC, SEL_FOC)
311
312
313 def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
314 """ Returns list of item indexes of given state (selected by defaults) """
315 res = []
316 idx = -1
317 while 1:
318 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
319 if idx == -1:
320 break
321 res.append(idx)
322 return res
323
324 wxEVT_DOPOPUPMENU = wx.NewEventType()
325 EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
326
327
328 class ListCtrlSelectionManagerMix:
329 """Mixin that defines a platform independent selection policy
330
331 As selection single and multi-select list return the item index or a
332 list of item indexes respectively.
333 """
334 _menu = None
335
336 def __init__(self):
337 self.Bind(wx.EVT_RIGHT_DOWN, self.OnLCSMRightDown)
338 self.Bind(EVT_DOPOPUPMENU, self.OnLCSMDoPopup)
339 # self.Connect(-1, -1, self.wxEVT_DOPOPUPMENU, self.OnLCSMDoPopup)
340
341
342 def getPopupMenu(self):
343 """ Override to implement dynamic menus (create) """
344 return self._menu
345
346
347 def setPopupMenu(self, menu):
348 """ Must be set for default behaviour """
349 self._menu = menu
350
351
352 def afterPopupMenu(self, menu):
353 """ Override to implement dynamic menus (destroy) """
354 pass
355
356
357 def getSelection(self):
358 res = getListCtrlSelection(self)
359 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
360 if res:
361 return res[0]
362 else:
363 return -1
364 else:
365 return res
366
367
368 def OnLCSMRightDown(self, event):
369 selectBeforePopup(event)
370 event.Skip()
371 menu = self.getPopupMenu()
372 if menu:
373 evt = wx.PyEvent()
374 evt.SetEventType(wxEVT_DOPOPUPMENU)
375 evt.menu = menu
376 evt.pos = event.GetPosition()
377 wx.PostEvent(self, evt)
378
379
380 def OnLCSMDoPopup(self, event):
381 self.PopupMenu(event.menu, event.pos)
382 self.afterPopupMenu(event.menu)
383
384
385 #----------------------------------------------------------------------------
386 #----------------------------------------------------------------------------
387 from bisect import bisect
388
389
390 class TextEditMixin:
391 """
392 A mixin class that enables any text in any column of a
393 multi-column listctrl to be edited by clicking on the given row
394 and column. You close the text editor by hitting the ENTER key or
395 clicking somewhere else on the listctrl. You switch to the next
396 column by hiting TAB.
397
398 To use the mixin you have to include it in the class definition
399 and call the __init__ function::
400
401 class TestListCtrl(wx.ListCtrl, TextEditMixin):
402 def __init__(self, parent, ID, pos=wx.DefaultPosition,
403 size=wx.DefaultSize, style=0):
404 wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
405 TextEditMixin.__init__(self)
406
407
408 Authors: Steve Zatz, Pim Van Heuven (pim@think-wize.com)
409 """
410
411 editorBgColour = wx.Colour(255,255,175) # Yellow
412 editorFgColour = wx.Colour(0,0,0) # black
413
414 def __init__(self):
415 #editor = wx.TextCtrl(self, -1, pos=(-1,-1), size=(-1,-1),
416 # style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB \
417 # |wx.TE_RICH2)
418
419 self.make_editor()
420 self.Bind(wx.EVT_TEXT_ENTER, self.CloseEditor)
421 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
422 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
423 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
424
425
426 def make_editor(self, col_style=wx.LIST_FORMAT_LEFT):
427 editor = wx.PreTextCtrl()
428
429 style =wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_RICH2
430 style |= {wx.LIST_FORMAT_LEFT: wx.TE_LEFT,
431 wx.LIST_FORMAT_RIGHT: wx.TE_RIGHT,
432 wx.LIST_FORMAT_CENTRE : wx.TE_CENTRE
433 }[col_style]
434
435 editor.Create(self, -1, style=style)
436 editor.SetBackgroundColour(self.editorBgColour)
437 editor.SetForegroundColour(self.editorFgColour)
438 font = self.GetFont()
439 editor.SetFont(font)
440
441 self.curRow = 0
442 self.curCol = 0
443
444 editor.Hide()
445 self.editor = editor
446
447 self.col_style = col_style
448 self.editor.Bind(wx.EVT_CHAR, self.OnChar)
449 self.editor.Bind(wx.EVT_KILL_FOCUS, self.CloseEditor)
450
451
452 def OnItemSelected(self, evt):
453 self.curRow = evt.GetIndex()
454 evt.Skip()
455
456
457 def OnChar(self, event):
458 ''' Catch the TAB, Shift-TAB, cursor DOWN/UP key code
459 so we can open the editor at the next column (if any).'''
460
461 keycode = event.GetKeyCode()
462 if keycode == wx.WXK_TAB and event.ShiftDown():
463 self.CloseEditor()
464 if self.curCol-1 >= 0:
465 self.OpenEditor(self.curCol-1, self.curRow)
466
467 elif keycode == wx.WXK_TAB:
468 self.CloseEditor()
469 if self.curCol+1 < self.GetColumnCount():
470 self.OpenEditor(self.curCol+1, self.curRow)
471
472 elif keycode == wx.WXK_ESCAPE:
473 self.CloseEditor()
474
475 elif keycode == wx.WXK_DOWN:
476 self.CloseEditor()
477 if self.curRow+1 < self.GetItemCount():
478 self._SelectIndex(self.curRow+1)
479 self.OpenEditor(self.curCol, self.curRow)
480
481 elif keycode == wx.WXK_UP:
482 self.CloseEditor()
483 if self.curRow > 0:
484 self._SelectIndex(self.curRow-1)
485 self.OpenEditor(self.curCol, self.curRow)
486
487 else:
488 event.Skip()
489
490
491 def OnLeftDown(self, evt=None):
492 ''' Examine the click and double
493 click events to see if a row has been click on twice. If so,
494 determine the current row and columnn and open the editor.'''
495
496 if self.editor.IsShown():
497 self.CloseEditor()
498
499 x,y = evt.GetPosition()
500 row,flags = self.HitTest((x,y))
501
502 if row != self.curRow: # self.curRow keeps track of the current row
503 evt.Skip()
504 return
505
506 # the following should really be done in the mixin's init but
507 # the wx.ListCtrl demo creates the columns after creating the
508 # ListCtrl (generally not a good idea) on the other hand,
509 # doing this here handles adjustable column widths
510
511 self.col_locs = [0]
512 loc = 0
513 for n in range(self.GetColumnCount()):
514 loc = loc + self.GetColumnWidth(n)
515 self.col_locs.append(loc)
516
517
518 col = bisect(self.col_locs, x+self.GetScrollPos(wx.HORIZONTAL)) - 1
519 self.OpenEditor(col, row)
520
521
522 def OpenEditor(self, col, row):
523 ''' Opens an editor at the current position. '''
524
525 if self.GetColumn(col).m_format != self.col_style:
526 self.make_editor(self.GetColumn(col).m_format)
527
528 x0 = self.col_locs[col]
529 x1 = self.col_locs[col+1] - x0
530
531 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
532
533 # scroll forward
534 if x0+x1-scrolloffset > self.GetSize()[0]:
535 if wx.Platform == "__WXMSW__":
536 # don't start scrolling unless we really need to
537 offset = x0+x1-self.GetSize()[0]-scrolloffset
538 # scroll a bit more than what is minimum required
539 # so we don't have to scroll everytime the user presses TAB
540 # which is very tireing to the eye
541 addoffset = self.GetSize()[0]/4
542 # but be careful at the end of the list
543 if addoffset + scrolloffset < self.GetSize()[0]:
544 offset += addoffset
545
546 self.ScrollList(offset, 0)
547 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
548 else:
549 # Since we can not programmatically scroll the ListCtrl
550 # close the editor so the user can scroll and open the editor
551 # again
552 self.editor.SetValue(self.GetItem(row, col).GetText())
553 self.curRow = row
554 self.curCol = col
555 self.CloseEditor()
556 return
557
558 y0 = self.GetItemRect(row)[1]
559
560 editor = self.editor
561 editor.SetDimensions(x0-scrolloffset,y0, x1,-1)
562
563 editor.SetValue(self.GetItem(row, col).GetText())
564 editor.Show()
565 editor.Raise()
566 editor.SetSelection(-1,-1)
567 editor.SetFocus()
568
569 self.curRow = row
570 self.curCol = col
571
572
573 # FIXME: this function is usually called twice - second time because
574 # it is binded to wx.EVT_KILL_FOCUS. Can it be avoided? (MW)
575 def CloseEditor(self, evt=None):
576 ''' Close the editor and save the new value to the ListCtrl. '''
577 text = self.editor.GetValue()
578 self.editor.Hide()
579 self.SetFocus()
580
581 # post wxEVT_COMMAND_LIST_END_LABEL_EDIT
582 # Event can be vetoed. It doesn't has SetEditCanceled(), what would
583 # require passing extra argument to CloseEditor()
584 evt = wx.ListEvent(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT, self.GetId())
585 evt.m_itemIndex = self.curRow
586 evt.m_col = self.curCol
587 item = self.GetItem(self.curRow, self.curCol)
588 evt.m_item.SetId(item.GetId())
589 evt.m_item.SetColumn(item.GetColumn())
590 evt.m_item.SetData(item.GetData())
591 evt.m_item.SetText(text) #should be empty string if editor was canceled
592 ret = self.GetEventHandler().ProcessEvent(evt)
593 if not ret or evt.IsAllowed():
594 if self.IsVirtual():
595 # replace by whather you use to populate the virtual ListCtrl
596 # data source
597 self.SetVirtualData(self.curRow, self.curCol, text)
598 else:
599 self.SetStringItem(self.curRow, self.curCol, text)
600 self.RefreshItem(self.curRow)
601
602 def _SelectIndex(self, row):
603 listlen = self.GetItemCount()
604 if row < 0 and not listlen:
605 return
606 if row > (listlen-1):
607 row = listlen -1
608
609 self.SetItemState(self.curRow, ~wx.LIST_STATE_SELECTED,
610 wx.LIST_STATE_SELECTED)
611 self.EnsureVisible(row)
612 self.SetItemState(row, wx.LIST_STATE_SELECTED,
613 wx.LIST_STATE_SELECTED)
614
615
616
617 #----------------------------------------------------------------------------
618 #----------------------------------------------------------------------------
619
620 """
621 FILENAME: CheckListCtrlMixin.py
622 AUTHOR: Bruce Who (bruce.who.hk at gmail.com)
623 DATE: 2006-02-09
624 $Revision$
625 DESCRIPTION:
626 This script provide a mixin for ListCtrl which add a checkbox in the first
627 column of each row. It is inspired by limodou's CheckList.py(which can be
628 got from his NewEdit) and improved:
629 - You can just use InsertStringItem() to insert new items;
630 - Once a checkbox is checked/unchecked, the corresponding item is not
631 selected;
632 - You can use SetItemData() and GetItemData();
633 - Interfaces are changed to OnCheckItem(), IsChecked(), CheckItem().
634
635 You should not set a imagelist for the ListCtrl once this mixin is used.
636
637 HISTORY:
638 1.3 - You can check/uncheck a group of sequential items by <Shift-click>:
639 First click(or <Shift-Click>) item1 to check/uncheck it, then
640 Shift-click item2 to check/uncheck it, and you'll find that all
641 items between item1 and item2 are check/unchecked!
642 1.2 - Add ToggleItem()
643 1.1 - Initial version
644 """
645
646 from wx import ImageFromStream, BitmapFromImage
647 import cStringIO, zlib
648
649 def getUncheckData():
650 return zlib.decompress(
651 "x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
652 \x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xbb{\xba8\x86X\xf4\
653 &\xa7\xa4$\xa5-`1\x08\\2\xbb\xb1\xb1\x91\xf5\xd8\x84o\xeb\xff\xfaw\x1d[.=[2\
654 \x90'\x01\x08v\xec]\xd3\xa3qvU`l\x81\xd9\xd18\t\xd3\x84+\x0cll[\xa6t\xcc9\
655 \xd4\xc1\xda\xc3<O\x9a1\xc3\x88\xc3j\xfa\x86_\xee@#\x19<]\xfd\\\xd69%4\x01\
656 \x00\xdc\x80-\x05" )
657
658 def getUncheckBitmap():
659 return BitmapFromImage(getUncheckImage())
660
661 def getUncheckImage():
662 stream = cStringIO.StringIO(getUncheckData())
663 return ImageFromStream(stream)
664
665 def getCheckData():
666 return zlib.decompress(
667 'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
668 \x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe47{\xba8\x86X\xf4&\
669 \xa7\xa4$\xa5-`1\x08\\2\xbb\xb1\xb1\x91\xf5\xd8\x84o\xeb\xff\xfaw\x1d[.=[2\
670 \x90\'\x01\x08v\xec\\2C\xe3\xec+\xc3\xbd\x05fG\xe3\x14n1\xcc5\xad\x8a8\x1a\
671 \xb9\xa1\xeb\xd1\x853-\xaa\xc76\xecb\xb8i\x16c&\\\xc2\xb8\xe9Xvbx\xa1T\xc3U\
672 \xd6p\'\xbd\x85\x19\xff\xbe\xbf\xd7\xe7R\xcb`\xd8\xa5\xf8\x83\xe1^\xc4\x0e\
673 \xa1"\xce\xc3n\x93x\x14\xd8\x16\xb0(\x15q)\x8b\x19\xf0U\xe4\xb10\x08V\xa8\
674 \x99\xf3\xdd\xde\xad\x06t\x0e\x83\xa7\xab\x9f\xcb:\xa7\x84&\x00\xe0HE\xab' )
675
676 def getCheckBitmap():
677 return BitmapFromImage(getCheckImage())
678
679 def getCheckImage():
680 stream = cStringIO.StringIO(getCheckData())
681 return ImageFromStream(stream)
682
683
684
685 class CheckListCtrlMixin:
686 """
687 This is a mixin for ListCtrl which add a checkbox in the first
688 column of each row. It is inspired by limodou's CheckList.py(which
689 can be got from his NewEdit) and improved:
690 - You can just use InsertStringItem() to insert new items;
691 - Once a checkbox is checked/unchecked, the corresponding item is not
692 selected;
693 - You can use SetItemData() and GetItemData();
694 - Interfaces are changed to OnCheckItem(), IsChecked(), CheckItem().
695
696 You should not set a imagelist for the ListCtrl once this mixin is used.
697 """
698 def __init__(self, check_image=None, uncheck_image=None):
699 self.__imagelist_ = wx.ImageList(16, 16)
700 if not check_image:
701 check_image = getCheckBitmap()
702 if not uncheck_image:
703 uncheck_image = getUncheckBitmap()
704 self.uncheck_image = self.__imagelist_.Add(uncheck_image)
705 self.check_image = self.__imagelist_.Add(check_image)
706 self.SetImageList(self.__imagelist_, wx.IMAGE_LIST_SMALL)
707 self.__last_check_ = None
708
709 self.Bind(wx.EVT_LEFT_DOWN, self.__OnLeftDown_)
710
711 # override the default methods of ListCtrl/ListView
712 self.InsertStringItem = self.__InsertStringItem_
713
714 # NOTE: if you use InsertItem, InsertImageItem or InsertImageStringItem,
715 # you must set the image yourself.
716 def __InsertStringItem_(self, index, label):
717 index = self.InsertImageStringItem(index, label, 0)
718 return index
719
720 def __OnLeftDown_(self, evt):
721 (index, flags) = self.HitTest(evt.GetPosition())
722 if flags == wx.LIST_HITTEST_ONITEMICON:
723 img_idx = self.GetItem(index).GetImage()
724 flag_check = img_idx == 0
725 begin_index = index
726 end_index = index
727 if self.__last_check_ is not None \
728 and wx.GetKeyState(wx.WXK_SHIFT):
729 last_index, last_flag_check = self.__last_check_
730 if last_flag_check == flag_check:
731 # XXX what if the previous item is deleted or new items
732 # are inserted?
733 item_count = self.GetItemCount()
734 if last_index < item_count:
735 if last_index < index:
736 begin_index = last_index
737 end_index = index
738 elif last_index > index:
739 begin_index = index
740 end_index = last_index
741 else:
742 assert False
743 while begin_index <= end_index:
744 self.CheckItem(begin_index, flag_check)
745 begin_index += 1
746 self.__last_check_ = (index, flag_check)
747 else:
748 evt.Skip()
749
750 def OnCheckItem(self, index, flag):
751 pass
752
753 def IsChecked(self, index):
754 return self.GetItem(index).GetImage() == 1
755
756 def CheckItem(self, index, check = True):
757 img_idx = self.GetItem(index).GetImage()
758 if img_idx == 0 and check is True:
759 self.SetItemImage(index, 1)
760 self.OnCheckItem(index, True)
761 elif img_idx == 1 and check is False:
762 self.SetItemImage(index, 0)
763 self.OnCheckItem(index, False)
764
765 def ToggleItem(self, index):
766 self.CheckItem(index, not self.IsChecked(index))
767
768
769 #----------------------------------------------------------------------------