]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/mixins/listctrl.py
MaskedEdit patch from Will Sadkin:
[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
251
d14a1e28
RD
252 numCols = self.GetColumnCount()
253 if numCols == 0: return # Nothing to resize.
254
27ed367c
RD
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)
d14a1e28
RD
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
b881fc78 267 if wx.Platform != '__WXMSW__':
d14a1e28 268 if self.GetItemCount() > self.GetCountPerPage():
b881fc78 269 scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
d14a1e28
RD
270 listWidth = listWidth - scrollWidth
271
272 totColWidth = 0 # Width of all columns except last one.
27ed367c
RD
273 for col in range(numCols):
274 if col != (resizeCol-1):
275 totColWidth = totColWidth + self.GetColumnWidth(col)
d14a1e28 276
27ed367c 277 resizeColWidth = self.GetColumnWidth(resizeCol - 1)
d14a1e28 278
27ed367c 279 if totColWidth + self._resizeColMinWidth > listWidth:
d14a1e28
RD
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.
27ed367c 283 self.SetColumnWidth(resizeCol-1, self._resizeColMinWidth)
d14a1e28
RD
284 return
285
286 # Resize the last column to take up the remaining available space.
287
27ed367c
RD
288 self.SetColumnWidth(resizeCol-1, listWidth - totColWidth)
289
d14a1e28
RD
290
291
292
23a02364 293#----------------------------------------------------------------------------
d14a1e28
RD
294#----------------------------------------------------------------------------
295
b881fc78 296SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
d14a1e28
RD
297def 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()
8ab15340 302 if isinstance(ctrl, wx.ListCtrl):
d14a1e28
RD
303 n, flags = ctrl.HitTest(event.GetPosition())
304 if n >= 0:
b881fc78 305 if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
d14a1e28
RD
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
5841276a 312
b881fc78 313def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
d14a1e28
RD
314 """ Returns list of item indexes of given state (selected by defaults) """
315 res = []
316 idx = -1
317 while 1:
b881fc78 318 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
d14a1e28
RD
319 if idx == -1:
320 break
321 res.append(idx)
322 return res
323
b881fc78
RD
324wxEVT_DOPOPUPMENU = wx.NewEventType()
325EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
326
5841276a 327
d14a1e28
RD
328class 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 """
d14a1e28
RD
334 _menu = None
335
336 def __init__(self):
b881fc78
RD
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)
d14a1e28 340
5841276a 341
d14a1e28
RD
342 def getPopupMenu(self):
343 """ Override to implement dynamic menus (create) """
344 return self._menu
345
5841276a 346
d14a1e28
RD
347 def setPopupMenu(self, menu):
348 """ Must be set for default behaviour """
349 self._menu = menu
350
5841276a 351
d14a1e28
RD
352 def afterPopupMenu(self, menu):
353 """ Override to implement dynamic menus (destroy) """
354 pass
355
5841276a 356
d14a1e28
RD
357 def getSelection(self):
358 res = getListCtrlSelection(self)
b881fc78 359 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
d14a1e28
RD
360 if res:
361 return res[0]
362 else:
363 return -1
364 else:
365 return res
366
5841276a 367
d14a1e28
RD
368 def OnLCSMRightDown(self, event):
369 selectBeforePopup(event)
370 event.Skip()
371 menu = self.getPopupMenu()
372 if menu:
b881fc78
RD
373 evt = wx.PyEvent()
374 evt.SetEventType(wxEVT_DOPOPUPMENU)
d14a1e28
RD
375 evt.menu = menu
376 evt.pos = event.GetPosition()
b881fc78 377 wx.PostEvent(self, evt)
d14a1e28 378
5841276a 379
d14a1e28
RD
380 def OnLCSMDoPopup(self, event):
381 self.PopupMenu(event.menu, event.pos)
382 self.afterPopupMenu(event.menu)
383
5841276a 384
23a02364 385#----------------------------------------------------------------------------
5841276a
RD
386#----------------------------------------------------------------------------
387from bisect import bisect
388
389
390class TextEditMixin:
6d3c4b2a
RD
391 """
392 A mixin class that enables any text in any column of a
5841276a
RD
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
6d3c4b2a 401 class TestListCtrl(wx.ListCtrl, TextEditMixin):
5841276a
RD
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)
6d3c4b2a 405 TextEditMixin.__init__(self)
5841276a
RD
406
407
408 Authors: Steve Zatz, Pim Van Heuven (pim@think-wize.com)
409 """
42a04f70
RD
410
411 editorBgColour = wx.Colour(255,255,175) # Yellow
412 editorFgColour = wx.Colour(0,0,0) # black
5841276a
RD
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)
15513a80
RD
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):
15513a80
RD
427
428 style =wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_RICH2
27ed367c
RD
429 style |= {wx.LIST_FORMAT_LEFT: wx.TE_LEFT,
430 wx.LIST_FORMAT_RIGHT: wx.TE_RIGHT,
431 wx.LIST_FORMAT_CENTRE : wx.TE_CENTRE
432 }[col_style]
15513a80 433
991e49b6 434 editor = wx.TextCtrl(self, -1, style=style)
42a04f70
RD
435 editor.SetBackgroundColour(self.editorBgColour)
436 editor.SetForegroundColour(self.editorFgColour)
5841276a
RD
437 font = self.GetFont()
438 editor.SetFont(font)
439
15513a80
RD
440 self.curRow = 0
441 self.curCol = 0
442
443 editor.Hide()
991e49b6
RD
444 if hasattr(self, 'editor'):
445 self.editor.Destroy()
5841276a 446 self.editor = editor
15513a80
RD
447
448 self.col_style = col_style
5841276a
RD
449 self.editor.Bind(wx.EVT_CHAR, self.OnChar)
450 self.editor.Bind(wx.EVT_KILL_FOCUS, self.CloseEditor)
15513a80 451
5841276a
RD
452
453 def OnItemSelected(self, evt):
454 self.curRow = evt.GetIndex()
455 evt.Skip()
456
457
458 def OnChar(self, event):
15513a80
RD
459 ''' Catch the TAB, Shift-TAB, cursor DOWN/UP key code
460 so we can open the editor at the next column (if any).'''
461
462 keycode = event.GetKeyCode()
463 if keycode == wx.WXK_TAB and event.ShiftDown():
464 self.CloseEditor()
465 if self.curCol-1 >= 0:
466 self.OpenEditor(self.curCol-1, self.curRow)
467
468 elif keycode == wx.WXK_TAB:
5841276a
RD
469 self.CloseEditor()
470 if self.curCol+1 < self.GetColumnCount():
471 self.OpenEditor(self.curCol+1, self.curRow)
15513a80
RD
472
473 elif keycode == wx.WXK_ESCAPE:
474 self.CloseEditor()
475
476 elif keycode == wx.WXK_DOWN:
477 self.CloseEditor()
478 if self.curRow+1 < self.GetItemCount():
479 self._SelectIndex(self.curRow+1)
480 self.OpenEditor(self.curCol, self.curRow)
481
482 elif keycode == wx.WXK_UP:
483 self.CloseEditor()
484 if self.curRow > 0:
485 self._SelectIndex(self.curRow-1)
486 self.OpenEditor(self.curCol, self.curRow)
487
5841276a
RD
488 else:
489 event.Skip()
490
491
492 def OnLeftDown(self, evt=None):
493 ''' Examine the click and double
494 click events to see if a row has been click on twice. If so,
495 determine the current row and columnn and open the editor.'''
496
497 if self.editor.IsShown():
498 self.CloseEditor()
499
500 x,y = evt.GetPosition()
501 row,flags = self.HitTest((x,y))
502
503 if row != self.curRow: # self.curRow keeps track of the current row
504 evt.Skip()
505 return
506
507 # the following should really be done in the mixin's init but
508 # the wx.ListCtrl demo creates the columns after creating the
509 # ListCtrl (generally not a good idea) on the other hand,
510 # doing this here handles adjustable column widths
511
512 self.col_locs = [0]
513 loc = 0
514 for n in range(self.GetColumnCount()):
515 loc = loc + self.GetColumnWidth(n)
516 self.col_locs.append(loc)
15513a80 517
5841276a 518
15513a80 519 col = bisect(self.col_locs, x+self.GetScrollPos(wx.HORIZONTAL)) - 1
5841276a
RD
520 self.OpenEditor(col, row)
521
522
523 def OpenEditor(self, col, row):
524 ''' Opens an editor at the current position. '''
15513a80 525
f4102f17
RD
526 # give the derived class a chance to Allow/Veto this edit.
527 evt = wx.ListEvent(wx.wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT, self.GetId())
528 evt.m_itemIndex = row
529 evt.m_col = col
530 item = self.GetItem(row, col)
531 evt.m_item.SetId(item.GetId())
532 evt.m_item.SetColumn(item.GetColumn())
533 evt.m_item.SetData(item.GetData())
534 evt.m_item.SetText(item.GetText())
535 ret = self.GetEventHandler().ProcessEvent(evt)
536 if ret and not evt.IsAllowed():
537 return # user code doesn't allow the edit.
538
15513a80
RD
539 if self.GetColumn(col).m_format != self.col_style:
540 self.make_editor(self.GetColumn(col).m_format)
5841276a
RD
541
542 x0 = self.col_locs[col]
543 x1 = self.col_locs[col+1] - x0
544
15513a80
RD
545 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
546
27ed367c 547 # scroll forward
15513a80
RD
548 if x0+x1-scrolloffset > self.GetSize()[0]:
549 if wx.Platform == "__WXMSW__":
550 # don't start scrolling unless we really need to
551 offset = x0+x1-self.GetSize()[0]-scrolloffset
552 # scroll a bit more than what is minimum required
553 # so we don't have to scroll everytime the user presses TAB
554 # which is very tireing to the eye
555 addoffset = self.GetSize()[0]/4
556 # but be careful at the end of the list
557 if addoffset + scrolloffset < self.GetSize()[0]:
558 offset += addoffset
559
560 self.ScrollList(offset, 0)
561 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
562 else:
563 # Since we can not programmatically scroll the ListCtrl
564 # close the editor so the user can scroll and open the editor
565 # again
27ed367c
RD
566 self.editor.SetValue(self.GetItem(row, col).GetText())
567 self.curRow = row
568 self.curCol = col
15513a80
RD
569 self.CloseEditor()
570 return
571
5841276a
RD
572 y0 = self.GetItemRect(row)[1]
573
574 editor = self.editor
15513a80
RD
575 editor.SetDimensions(x0-scrolloffset,y0, x1,-1)
576
5841276a
RD
577 editor.SetValue(self.GetItem(row, col).GetText())
578 editor.Show()
579 editor.Raise()
580 editor.SetSelection(-1,-1)
581 editor.SetFocus()
582
583 self.curRow = row
584 self.curCol = col
585
586
42a04f70
RD
587 # FIXME: this function is usually called twice - second time because
588 # it is binded to wx.EVT_KILL_FOCUS. Can it be avoided? (MW)
5841276a
RD
589 def CloseEditor(self, evt=None):
590 ''' Close the editor and save the new value to the ListCtrl. '''
f4102f17
RD
591 if not self.editor.IsShown():
592 return
5841276a
RD
593 text = self.editor.GetValue()
594 self.editor.Hide()
42a04f70
RD
595 self.SetFocus()
596
597 # post wxEVT_COMMAND_LIST_END_LABEL_EDIT
598 # Event can be vetoed. It doesn't has SetEditCanceled(), what would
599 # require passing extra argument to CloseEditor()
600 evt = wx.ListEvent(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT, self.GetId())
601 evt.m_itemIndex = self.curRow
602 evt.m_col = self.curCol
603 item = self.GetItem(self.curRow, self.curCol)
604 evt.m_item.SetId(item.GetId())
605 evt.m_item.SetColumn(item.GetColumn())
606 evt.m_item.SetData(item.GetData())
607 evt.m_item.SetText(text) #should be empty string if editor was canceled
608 ret = self.GetEventHandler().ProcessEvent(evt)
609 if not ret or evt.IsAllowed():
610 if self.IsVirtual():
611 # replace by whather you use to populate the virtual ListCtrl
612 # data source
613 self.SetVirtualData(self.curRow, self.curCol, text)
614 else:
615 self.SetStringItem(self.curRow, self.curCol, text)
15513a80
RD
616 self.RefreshItem(self.curRow)
617
618 def _SelectIndex(self, row):
619 listlen = self.GetItemCount()
620 if row < 0 and not listlen:
621 return
622 if row > (listlen-1):
623 row = listlen -1
624
625 self.SetItemState(self.curRow, ~wx.LIST_STATE_SELECTED,
626 wx.LIST_STATE_SELECTED)
627 self.EnsureVisible(row)
628 self.SetItemState(row, wx.LIST_STATE_SELECTED,
629 wx.LIST_STATE_SELECTED)
5841276a
RD
630
631
632
23a02364
RD
633#----------------------------------------------------------------------------
634#----------------------------------------------------------------------------
635
636"""
637FILENAME: CheckListCtrlMixin.py
638AUTHOR: Bruce Who (bruce.who.hk at gmail.com)
639DATE: 2006-02-09
640$Revision$
641DESCRIPTION:
642 This script provide a mixin for ListCtrl which add a checkbox in the first
643 column of each row. It is inspired by limodou's CheckList.py(which can be
644 got from his NewEdit) and improved:
645 - You can just use InsertStringItem() to insert new items;
646 - Once a checkbox is checked/unchecked, the corresponding item is not
647 selected;
648 - You can use SetItemData() and GetItemData();
649 - Interfaces are changed to OnCheckItem(), IsChecked(), CheckItem().
650
651 You should not set a imagelist for the ListCtrl once this mixin is used.
652
653HISTORY:
6541.3 - You can check/uncheck a group of sequential items by <Shift-click>:
655 First click(or <Shift-Click>) item1 to check/uncheck it, then
656 Shift-click item2 to check/uncheck it, and you'll find that all
657 items between item1 and item2 are check/unchecked!
6581.2 - Add ToggleItem()
6591.1 - Initial version
660"""
661
662from wx import ImageFromStream, BitmapFromImage
663import cStringIO, zlib
664
665def getUncheckData():
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\xe4\xbb{\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]\xd3\xa3qvU`l\x81\xd9\xd18\t\xd3\x84+\x0cll[\xa6t\xcc9\
671\xd4\xc1\xda\xc3<O\x9a1\xc3\x88\xc3j\xfa\x86_\xee@#\x19<]\xfd\\\xd69%4\x01\
672\x00\xdc\x80-\x05" )
673
674def getUncheckBitmap():
675 return BitmapFromImage(getUncheckImage())
676
677def getUncheckImage():
678 stream = cStringIO.StringIO(getUncheckData())
679 return ImageFromStream(stream)
680
681def getCheckData():
682 return zlib.decompress(
683'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
684\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe47{\xba8\x86X\xf4&\
685\xa7\xa4$\xa5-`1\x08\\2\xbb\xb1\xb1\x91\xf5\xd8\x84o\xeb\xff\xfaw\x1d[.=[2\
686\x90\'\x01\x08v\xec\\2C\xe3\xec+\xc3\xbd\x05fG\xe3\x14n1\xcc5\xad\x8a8\x1a\
687\xb9\xa1\xeb\xd1\x853-\xaa\xc76\xecb\xb8i\x16c&\\\xc2\xb8\xe9Xvbx\xa1T\xc3U\
688\xd6p\'\xbd\x85\x19\xff\xbe\xbf\xd7\xe7R\xcb`\xd8\xa5\xf8\x83\xe1^\xc4\x0e\
689\xa1"\xce\xc3n\x93x\x14\xd8\x16\xb0(\x15q)\x8b\x19\xf0U\xe4\xb10\x08V\xa8\
690\x99\xf3\xdd\xde\xad\x06t\x0e\x83\xa7\xab\x9f\xcb:\xa7\x84&\x00\xe0HE\xab' )
691
692def getCheckBitmap():
693 return BitmapFromImage(getCheckImage())
694
695def getCheckImage():
696 stream = cStringIO.StringIO(getCheckData())
697 return ImageFromStream(stream)
698
699
700
701class CheckListCtrlMixin:
702 """
703 This is a mixin for ListCtrl which add a checkbox in the first
704 column of each row. It is inspired by limodou's CheckList.py(which
705 can be got from his NewEdit) and improved:
706 - You can just use InsertStringItem() to insert new items;
707 - Once a checkbox is checked/unchecked, the corresponding item is not
708 selected;
709 - You can use SetItemData() and GetItemData();
710 - Interfaces are changed to OnCheckItem(), IsChecked(), CheckItem().
711
712 You should not set a imagelist for the ListCtrl once this mixin is used.
713 """
714 def __init__(self, check_image=None, uncheck_image=None):
715 self.__imagelist_ = wx.ImageList(16, 16)
716 if not check_image:
717 check_image = getCheckBitmap()
718 if not uncheck_image:
719 uncheck_image = getUncheckBitmap()
720 self.uncheck_image = self.__imagelist_.Add(uncheck_image)
721 self.check_image = self.__imagelist_.Add(check_image)
722 self.SetImageList(self.__imagelist_, wx.IMAGE_LIST_SMALL)
723 self.__last_check_ = None
724
725 self.Bind(wx.EVT_LEFT_DOWN, self.__OnLeftDown_)
726
727 # override the default methods of ListCtrl/ListView
728 self.InsertStringItem = self.__InsertStringItem_
729
730 # NOTE: if you use InsertItem, InsertImageItem or InsertImageStringItem,
731 # you must set the image yourself.
732 def __InsertStringItem_(self, index, label):
733 index = self.InsertImageStringItem(index, label, 0)
734 return index
735
736 def __OnLeftDown_(self, evt):
737 (index, flags) = self.HitTest(evt.GetPosition())
738 if flags == wx.LIST_HITTEST_ONITEMICON:
739 img_idx = self.GetItem(index).GetImage()
740 flag_check = img_idx == 0
741 begin_index = index
742 end_index = index
743 if self.__last_check_ is not None \
744 and wx.GetKeyState(wx.WXK_SHIFT):
745 last_index, last_flag_check = self.__last_check_
746 if last_flag_check == flag_check:
747 # XXX what if the previous item is deleted or new items
748 # are inserted?
749 item_count = self.GetItemCount()
750 if last_index < item_count:
751 if last_index < index:
752 begin_index = last_index
753 end_index = index
754 elif last_index > index:
755 begin_index = index
756 end_index = last_index
757 else:
758 assert False
759 while begin_index <= end_index:
760 self.CheckItem(begin_index, flag_check)
761 begin_index += 1
762 self.__last_check_ = (index, flag_check)
763 else:
764 evt.Skip()
765
766 def OnCheckItem(self, index, flag):
767 pass
768
769 def IsChecked(self, index):
770 return self.GetItem(index).GetImage() == 1
771
772 def CheckItem(self, index, check = True):
773 img_idx = self.GetItem(index).GetImage()
774 if img_idx == 0 and check is True:
775 self.SetItemImage(index, 1)
776 self.OnCheckItem(index, True)
777 elif img_idx == 1 and check is False:
778 self.SetItemImage(index, 0)
779 self.OnCheckItem(index, False)
780
781 def ToggleItem(self, index):
782 self.CheckItem(index, not self.IsChecked(index))
783
784
5841276a 785#----------------------------------------------------------------------------