]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/mixins/listctrl.py
Compile fixes for Panther
[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 119 self.GetListCtrl().SortItems(self.GetColumnSorter())
d10d4362
KO
120 if wx.Platform != "__WXMAC__" or wx.SystemOptions.GetOptionInt("mac.listctrl.always_use_generic") == 1:
121 self.__updateImages(oldCol)
d14a1e28
RD
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
d4b73b1b 161class ListCtrlAutoWidthMixin:
d14a1e28 162 """ A mix-in class that automatically resizes the last column to take up
b881fc78 163 the remaining width of the wx.ListCtrl.
d14a1e28 164
b881fc78 165 This causes the wx.ListCtrl to automatically take up the full width of
d14a1e28
RD
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
b881fc78 171 WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
d14a1e28
RD
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>
5841276a 176 """
d14a1e28
RD
177 def __init__(self):
178 """ Standard initialiser.
179 """
27ed367c
RD
180 self._resizeColMinWidth = None
181 self._resizeColStyle = "LAST"
182 self._resizeCol = 0
b881fc78
RD
183 self.Bind(wx.EVT_SIZE, self._onResize)
184 self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
d14a1e28
RD
185
186
27ed367c
RD
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
d14a1e28
RD
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
b881fc78 206 This method is called automatically when the wx.ListCtrl is resized;
d14a1e28
RD
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 """
e4f0ea6b 213 self.resizeColumn(minWidth)
27ed367c
RD
214
215
216 def resizeColumn(self, minWidth):
217 self._resizeColMinWidth = minWidth
d14a1e28 218 self._doResize()
27ed367c 219
d14a1e28
RD
220
221 # =====================
222 # == Private Methods ==
223 # =====================
224
225 def _onResize(self, event):
b881fc78 226 """ Respond to the wx.ListCtrl being resized.
d14a1e28
RD
227
228 We automatically resize the last column in the list.
229 """
e31753ef
RD
230 if 'gtk2' in wx.PlatformInfo:
231 self._doResize()
232 else:
233 wx.CallAfter(self._doResize)
d14a1e28
RD
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 """
a61c65b3
RD
249
250 if not self: # avoid a PyDeadObject error
251 return
e90db86e
RD
252
253 if self.GetSize().height < 32:
254 return # avoid an endless update bug when the height is small.
a61c65b3 255
d14a1e28
RD
256 numCols = self.GetColumnCount()
257 if numCols == 0: return # Nothing to resize.
258
27ed367c
RD
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)
d14a1e28
RD
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
b881fc78 271 if wx.Platform != '__WXMSW__':
d14a1e28 272 if self.GetItemCount() > self.GetCountPerPage():
b881fc78 273 scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
d14a1e28
RD
274 listWidth = listWidth - scrollWidth
275
276 totColWidth = 0 # Width of all columns except last one.
27ed367c
RD
277 for col in range(numCols):
278 if col != (resizeCol-1):
279 totColWidth = totColWidth + self.GetColumnWidth(col)
d14a1e28 280
27ed367c 281 resizeColWidth = self.GetColumnWidth(resizeCol - 1)
d14a1e28 282
27ed367c 283 if totColWidth + self._resizeColMinWidth > listWidth:
d14a1e28
RD
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.
27ed367c 287 self.SetColumnWidth(resizeCol-1, self._resizeColMinWidth)
d14a1e28
RD
288 return
289
290 # Resize the last column to take up the remaining available space.
291
27ed367c
RD
292 self.SetColumnWidth(resizeCol-1, listWidth - totColWidth)
293
d14a1e28
RD
294
295
296
23a02364 297#----------------------------------------------------------------------------
d14a1e28
RD
298#----------------------------------------------------------------------------
299
b881fc78 300SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
d14a1e28
RD
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()
8ab15340 306 if isinstance(ctrl, wx.ListCtrl):
d14a1e28
RD
307 n, flags = ctrl.HitTest(event.GetPosition())
308 if n >= 0:
b881fc78 309 if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
d14a1e28
RD
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
5841276a 316
b881fc78 317def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
d14a1e28
RD
318 """ Returns list of item indexes of given state (selected by defaults) """
319 res = []
320 idx = -1
321 while 1:
b881fc78 322 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
d14a1e28
RD
323 if idx == -1:
324 break
325 res.append(idx)
326 return res
327
b881fc78
RD
328wxEVT_DOPOPUPMENU = wx.NewEventType()
329EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
330
5841276a 331
d14a1e28
RD
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 """
d14a1e28
RD
338 _menu = None
339
340 def __init__(self):
b881fc78
RD
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)
d14a1e28 344
5841276a 345
d14a1e28
RD
346 def getPopupMenu(self):
347 """ Override to implement dynamic menus (create) """
348 return self._menu
349
5841276a 350
d14a1e28
RD
351 def setPopupMenu(self, menu):
352 """ Must be set for default behaviour """
353 self._menu = menu
354
5841276a 355
d14a1e28
RD
356 def afterPopupMenu(self, menu):
357 """ Override to implement dynamic menus (destroy) """
358 pass
359
5841276a 360
d14a1e28
RD
361 def getSelection(self):
362 res = getListCtrlSelection(self)
b881fc78 363 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
d14a1e28
RD
364 if res:
365 return res[0]
366 else:
367 return -1
368 else:
369 return res
370
5841276a 371
d14a1e28
RD
372 def OnLCSMRightDown(self, event):
373 selectBeforePopup(event)
374 event.Skip()
375 menu = self.getPopupMenu()
376 if menu:
b881fc78
RD
377 evt = wx.PyEvent()
378 evt.SetEventType(wxEVT_DOPOPUPMENU)
d14a1e28
RD
379 evt.menu = menu
380 evt.pos = event.GetPosition()
b881fc78 381 wx.PostEvent(self, evt)
d14a1e28 382
5841276a 383
d14a1e28
RD
384 def OnLCSMDoPopup(self, event):
385 self.PopupMenu(event.menu, event.pos)
386 self.afterPopupMenu(event.menu)
387
5841276a 388
23a02364 389#----------------------------------------------------------------------------
5841276a
RD
390#----------------------------------------------------------------------------
391from bisect import bisect
392
393
394class TextEditMixin:
6d3c4b2a
RD
395 """
396 A mixin class that enables any text in any column of a
5841276a
RD
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
6d3c4b2a 405 class TestListCtrl(wx.ListCtrl, TextEditMixin):
5841276a
RD
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)
6d3c4b2a 409 TextEditMixin.__init__(self)
5841276a
RD
410
411
412 Authors: Steve Zatz, Pim Van Heuven (pim@think-wize.com)
413 """
42a04f70
RD
414
415 editorBgColour = wx.Colour(255,255,175) # Yellow
416 editorFgColour = wx.Colour(0,0,0) # black
5841276a
RD
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)
15513a80
RD
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):
15513a80
RD
431
432 style =wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_RICH2
27ed367c
RD
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]
15513a80 437
991e49b6 438 editor = wx.TextCtrl(self, -1, style=style)
42a04f70
RD
439 editor.SetBackgroundColour(self.editorBgColour)
440 editor.SetForegroundColour(self.editorFgColour)
5841276a
RD
441 font = self.GetFont()
442 editor.SetFont(font)
443
15513a80
RD
444 self.curRow = 0
445 self.curCol = 0
446
447 editor.Hide()
991e49b6
RD
448 if hasattr(self, 'editor'):
449 self.editor.Destroy()
5841276a 450 self.editor = editor
15513a80
RD
451
452 self.col_style = col_style
5841276a
RD
453 self.editor.Bind(wx.EVT_CHAR, self.OnChar)
454 self.editor.Bind(wx.EVT_KILL_FOCUS, self.CloseEditor)
15513a80 455
5841276a
RD
456
457 def OnItemSelected(self, evt):
458 self.curRow = evt.GetIndex()
459 evt.Skip()
460
461
462 def OnChar(self, event):
15513a80
RD
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:
5841276a
RD
473 self.CloseEditor()
474 if self.curCol+1 < self.GetColumnCount():
475 self.OpenEditor(self.curCol+1, self.curRow)
15513a80
RD
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
5841276a
RD
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)
15513a80 521
5841276a 522
15513a80 523 col = bisect(self.col_locs, x+self.GetScrollPos(wx.HORIZONTAL)) - 1
5841276a
RD
524 self.OpenEditor(col, row)
525
526
527 def OpenEditor(self, col, row):
528 ''' Opens an editor at the current position. '''
15513a80 529
f4102f17
RD
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
15513a80
RD
543 if self.GetColumn(col).m_format != self.col_style:
544 self.make_editor(self.GetColumn(col).m_format)
5841276a
RD
545
546 x0 = self.col_locs[col]
547 x1 = self.col_locs[col+1] - x0
548
15513a80
RD
549 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
550
27ed367c 551 # scroll forward
15513a80
RD
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
27ed367c
RD
570 self.editor.SetValue(self.GetItem(row, col).GetText())
571 self.curRow = row
572 self.curCol = col
15513a80
RD
573 self.CloseEditor()
574 return
575
5841276a
RD
576 y0 = self.GetItemRect(row)[1]
577
578 editor = self.editor
15513a80
RD
579 editor.SetDimensions(x0-scrolloffset,y0, x1,-1)
580
5841276a
RD
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
42a04f70
RD
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)
5841276a
RD
593 def CloseEditor(self, evt=None):
594 ''' Close the editor and save the new value to the ListCtrl. '''
f4102f17
RD
595 if not self.editor.IsShown():
596 return
5841276a
RD
597 text = self.editor.GetValue()
598 self.editor.Hide()
42a04f70
RD
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)
15513a80
RD
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)
5841276a
RD
634
635
636
23a02364
RD
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
9f4cc34f
RD
709 can be got from his NewEdit) and improved:
710
23a02364 711 - You can just use InsertStringItem() to insert new items;
9f4cc34f
RD
712
713 - Once a checkbox is checked/unchecked, the corresponding item
714 is not selected;
715
23a02364 716 - You can use SetItemData() and GetItemData();
9f4cc34f
RD
717
718 - Interfaces are changed to OnCheckItem(), IsChecked(),
719 CheckItem().
23a02364
RD
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
5841276a 794#----------------------------------------------------------------------------