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