]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/mixins/listctrl.py
build fix
[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 122 evt.Skip()
cbfc9df6
RD
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
d14a1e28
RD
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
d4b73b1b 170class ListCtrlAutoWidthMixin:
d14a1e28 171 """ A mix-in class that automatically resizes the last column to take up
b881fc78 172 the remaining width of the wx.ListCtrl.
d14a1e28 173
b881fc78 174 This causes the wx.ListCtrl to automatically take up the full width of
d14a1e28
RD
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
b881fc78 180 WARNING: If you override the EVT_SIZE event in your wx.ListCtrl, make
d14a1e28
RD
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>
5841276a 185 """
d14a1e28
RD
186 def __init__(self):
187 """ Standard initialiser.
188 """
27ed367c
RD
189 self._resizeColMinWidth = None
190 self._resizeColStyle = "LAST"
191 self._resizeCol = 0
b881fc78
RD
192 self.Bind(wx.EVT_SIZE, self._onResize)
193 self.Bind(wx.EVT_LIST_COL_END_DRAG, self._onResize, self)
d14a1e28
RD
194
195
27ed367c
RD
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
d14a1e28
RD
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
b881fc78 215 This method is called automatically when the wx.ListCtrl is resized;
d14a1e28
RD
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 """
e4f0ea6b 222 self.resizeColumn(minWidth)
27ed367c
RD
223
224
225 def resizeColumn(self, minWidth):
226 self._resizeColMinWidth = minWidth
d14a1e28 227 self._doResize()
27ed367c 228
d14a1e28
RD
229
230 # =====================
231 # == Private Methods ==
232 # =====================
233
234 def _onResize(self, event):
b881fc78 235 """ Respond to the wx.ListCtrl being resized.
d14a1e28
RD
236
237 We automatically resize the last column in the list.
238 """
e31753ef
RD
239 if 'gtk2' in wx.PlatformInfo:
240 self._doResize()
241 else:
242 wx.CallAfter(self._doResize)
d14a1e28
RD
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 """
a61c65b3
RD
258
259 if not self: # avoid a PyDeadObject error
260 return
e90db86e
RD
261
262 if self.GetSize().height < 32:
263 return # avoid an endless update bug when the height is small.
a61c65b3 264
d14a1e28
RD
265 numCols = self.GetColumnCount()
266 if numCols == 0: return # Nothing to resize.
267
27ed367c
RD
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)
d14a1e28
RD
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
b881fc78 280 if wx.Platform != '__WXMSW__':
d14a1e28 281 if self.GetItemCount() > self.GetCountPerPage():
b881fc78 282 scrollWidth = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
d14a1e28
RD
283 listWidth = listWidth - scrollWidth
284
285 totColWidth = 0 # Width of all columns except last one.
27ed367c
RD
286 for col in range(numCols):
287 if col != (resizeCol-1):
288 totColWidth = totColWidth + self.GetColumnWidth(col)
d14a1e28 289
27ed367c 290 resizeColWidth = self.GetColumnWidth(resizeCol - 1)
d14a1e28 291
27ed367c 292 if totColWidth + self._resizeColMinWidth > listWidth:
d14a1e28
RD
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.
27ed367c 296 self.SetColumnWidth(resizeCol-1, self._resizeColMinWidth)
d14a1e28
RD
297 return
298
299 # Resize the last column to take up the remaining available space.
300
27ed367c
RD
301 self.SetColumnWidth(resizeCol-1, listWidth - totColWidth)
302
d14a1e28
RD
303
304
305
23a02364 306#----------------------------------------------------------------------------
d14a1e28
RD
307#----------------------------------------------------------------------------
308
b881fc78 309SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
d14a1e28
RD
310def 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()
8ab15340 315 if isinstance(ctrl, wx.ListCtrl):
d14a1e28
RD
316 n, flags = ctrl.HitTest(event.GetPosition())
317 if n >= 0:
b881fc78 318 if not ctrl.GetItemState(n, wx.LIST_STATE_SELECTED):
d14a1e28
RD
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
5841276a 325
b881fc78 326def getListCtrlSelection(listctrl, state=wx.LIST_STATE_SELECTED):
d14a1e28
RD
327 """ Returns list of item indexes of given state (selected by defaults) """
328 res = []
329 idx = -1
330 while 1:
b881fc78 331 idx = listctrl.GetNextItem(idx, wx.LIST_NEXT_ALL, state)
d14a1e28
RD
332 if idx == -1:
333 break
334 res.append(idx)
335 return res
336
b881fc78
RD
337wxEVT_DOPOPUPMENU = wx.NewEventType()
338EVT_DOPOPUPMENU = wx.PyEventBinder(wxEVT_DOPOPUPMENU, 0)
339
5841276a 340
d14a1e28
RD
341class 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 """
d14a1e28
RD
347 _menu = None
348
349 def __init__(self):
b881fc78
RD
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)
d14a1e28 353
5841276a 354
d14a1e28
RD
355 def getPopupMenu(self):
356 """ Override to implement dynamic menus (create) """
357 return self._menu
358
5841276a 359
d14a1e28
RD
360 def setPopupMenu(self, menu):
361 """ Must be set for default behaviour """
362 self._menu = menu
363
5841276a 364
d14a1e28
RD
365 def afterPopupMenu(self, menu):
366 """ Override to implement dynamic menus (destroy) """
367 pass
368
5841276a 369
d14a1e28
RD
370 def getSelection(self):
371 res = getListCtrlSelection(self)
b881fc78 372 if self.GetWindowStyleFlag() & wx.LC_SINGLE_SEL:
d14a1e28
RD
373 if res:
374 return res[0]
375 else:
376 return -1
377 else:
378 return res
379
5841276a 380
d14a1e28
RD
381 def OnLCSMRightDown(self, event):
382 selectBeforePopup(event)
383 event.Skip()
384 menu = self.getPopupMenu()
385 if menu:
b881fc78
RD
386 evt = wx.PyEvent()
387 evt.SetEventType(wxEVT_DOPOPUPMENU)
d14a1e28
RD
388 evt.menu = menu
389 evt.pos = event.GetPosition()
b881fc78 390 wx.PostEvent(self, evt)
d14a1e28 391
5841276a 392
d14a1e28
RD
393 def OnLCSMDoPopup(self, event):
394 self.PopupMenu(event.menu, event.pos)
395 self.afterPopupMenu(event.menu)
396
5841276a 397
23a02364 398#----------------------------------------------------------------------------
5841276a
RD
399#----------------------------------------------------------------------------
400from bisect import bisect
401
402
403class TextEditMixin:
6d3c4b2a
RD
404 """
405 A mixin class that enables any text in any column of a
5841276a
RD
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
6d3c4b2a 414 class TestListCtrl(wx.ListCtrl, TextEditMixin):
5841276a
RD
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)
6d3c4b2a 418 TextEditMixin.__init__(self)
5841276a
RD
419
420
421 Authors: Steve Zatz, Pim Van Heuven (pim@think-wize.com)
422 """
42a04f70
RD
423
424 editorBgColour = wx.Colour(255,255,175) # Yellow
425 editorFgColour = wx.Colour(0,0,0) # black
5841276a
RD
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)
15513a80
RD
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):
15513a80
RD
440
441 style =wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_RICH2
27ed367c
RD
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]
15513a80 446
991e49b6 447 editor = wx.TextCtrl(self, -1, style=style)
42a04f70
RD
448 editor.SetBackgroundColour(self.editorBgColour)
449 editor.SetForegroundColour(self.editorFgColour)
5841276a
RD
450 font = self.GetFont()
451 editor.SetFont(font)
452
15513a80
RD
453 self.curRow = 0
454 self.curCol = 0
455
456 editor.Hide()
991e49b6
RD
457 if hasattr(self, 'editor'):
458 self.editor.Destroy()
5841276a 459 self.editor = editor
15513a80
RD
460
461 self.col_style = col_style
5841276a
RD
462 self.editor.Bind(wx.EVT_CHAR, self.OnChar)
463 self.editor.Bind(wx.EVT_KILL_FOCUS, self.CloseEditor)
15513a80 464
5841276a
RD
465
466 def OnItemSelected(self, evt):
467 self.curRow = evt.GetIndex()
468 evt.Skip()
469
470
471 def OnChar(self, event):
15513a80
RD
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:
5841276a
RD
482 self.CloseEditor()
483 if self.curCol+1 < self.GetColumnCount():
484 self.OpenEditor(self.curCol+1, self.curRow)
15513a80
RD
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
5841276a
RD
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)
15513a80 530
5841276a 531
15513a80 532 col = bisect(self.col_locs, x+self.GetScrollPos(wx.HORIZONTAL)) - 1
5841276a
RD
533 self.OpenEditor(col, row)
534
535
536 def OpenEditor(self, col, row):
537 ''' Opens an editor at the current position. '''
15513a80 538
f4102f17
RD
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
15513a80
RD
552 if self.GetColumn(col).m_format != self.col_style:
553 self.make_editor(self.GetColumn(col).m_format)
5841276a
RD
554
555 x0 = self.col_locs[col]
556 x1 = self.col_locs[col+1] - x0
557
15513a80
RD
558 scrolloffset = self.GetScrollPos(wx.HORIZONTAL)
559
27ed367c 560 # scroll forward
15513a80
RD
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
27ed367c
RD
579 self.editor.SetValue(self.GetItem(row, col).GetText())
580 self.curRow = row
581 self.curCol = col
15513a80
RD
582 self.CloseEditor()
583 return
584
5841276a
RD
585 y0 = self.GetItemRect(row)[1]
586
587 editor = self.editor
15513a80
RD
588 editor.SetDimensions(x0-scrolloffset,y0, x1,-1)
589
5841276a
RD
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
42a04f70
RD
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)
5841276a
RD
602 def CloseEditor(self, evt=None):
603 ''' Close the editor and save the new value to the ListCtrl. '''
f4102f17
RD
604 if not self.editor.IsShown():
605 return
5841276a
RD
606 text = self.editor.GetValue()
607 self.editor.Hide()
42a04f70
RD
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)
15513a80
RD
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)
5841276a
RD
643
644
645
23a02364
RD
646#----------------------------------------------------------------------------
647#----------------------------------------------------------------------------
648
649"""
650FILENAME: CheckListCtrlMixin.py
651AUTHOR: Bruce Who (bruce.who.hk at gmail.com)
652DATE: 2006-02-09
653$Revision$
654DESCRIPTION:
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
666HISTORY:
6671.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!
6711.2 - Add ToggleItem()
6721.1 - Initial version
673"""
674
675from wx import ImageFromStream, BitmapFromImage
676import cStringIO, zlib
677
678def 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
687def getUncheckBitmap():
688 return BitmapFromImage(getUncheckImage())
689
690def getUncheckImage():
691 stream = cStringIO.StringIO(getUncheckData())
692 return ImageFromStream(stream)
693
694def 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
705def getCheckBitmap():
706 return BitmapFromImage(getCheckImage())
707
708def getCheckImage():
709 stream = cStringIO.StringIO(getCheckData())
710 return ImageFromStream(stream)
711
712
713
714class 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
9f4cc34f
RD
718 can be got from his NewEdit) and improved:
719
23a02364 720 - You can just use InsertStringItem() to insert new items;
9f4cc34f
RD
721
722 - Once a checkbox is checked/unchecked, the corresponding item
723 is not selected;
724
23a02364 725 - You can use SetItemData() and GetItemData();
9f4cc34f
RD
726
727 - Interfaces are changed to OnCheckItem(), IsChecked(),
728 CheckItem().
23a02364
RD
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
5841276a 803#----------------------------------------------------------------------------