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