]> git.saurik.com Git - wxWidgets.git/blob - wxPython/demo/ListCtrl.py
Reorder the controls so the RadioButtons work right on Windows
[wxWidgets.git] / wxPython / demo / ListCtrl.py
1 #----------------------------------------------------------------------------
2 # Name: ListCtrl.py
3 # Purpose: Testing lots of stuff, controls, window types, etc.
4 #
5 # Author: Robin Dunn & Gary Dumer
6 #
7 # Created:
8 # RCS-ID: $Id$
9 # Copyright: (c) 1998 by Total Control Software
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------------
12
13 import wx
14 import wx.lib.mixins.listctrl as listmix
15
16 import images
17
18 #---------------------------------------------------------------------------
19
20 musicdata = {
21 1 : ("Bad English", "The Price Of Love", "Rock"),
22 2 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
23 3 : ("George Michael", "Praying For Time", "Rock"),
24 4 : ("Gloria Estefan", "Here We Are", "Rock"),
25 5 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
26 6 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
27 7 : ("Paul Young", "Oh Girl", "Rock"),
28 8 : ("Paula Abdul", "Opposites Attract", "Rock"),
29 9 : ("Richard Marx", "Should've Known Better", "Rock"),
30 10: ("Rod Stewart", "Forever Young", "Rock"),
31 11: ("Roxette", "Dangerous", "Rock"),
32 12: ("Sheena Easton", "The Lover In Me", "Rock"),
33 13: ("Sinead O'Connor", "Nothing Compares 2 U", "Rock"),
34 14: ("Stevie B.", "Because I Love You", "Rock"),
35 15: ("Taylor Dayne", "Love Will Lead You Back", "Rock"),
36 16: ("The Bangles", "Eternal Flame", "Rock"),
37 17: ("Wilson Phillips", "Release Me", "Rock"),
38 18: ("Billy Joel", "Blonde Over Blue", "Rock"),
39 19: ("Billy Joel", "Famous Last Words", "Rock"),
40 20: ("Billy Joel", "Lullabye (Goodnight, My Angel)", "Rock"),
41 21: ("Billy Joel", "The River Of Dreams", "Rock"),
42 22: ("Billy Joel", "Two Thousand Years", "Rock"),
43 23: ("Janet Jackson", "Alright", "Rock"),
44 24: ("Janet Jackson", "Black Cat", "Rock"),
45 25: ("Janet Jackson", "Come Back To Me", "Rock"),
46 26: ("Janet Jackson", "Escapade", "Rock"),
47 27: ("Janet Jackson", "Love Will Never Do (Without You)", "Rock"),
48 28: ("Janet Jackson", "Miss You Much", "Rock"),
49 29: ("Janet Jackson", "Rhythm Nation", "Rock"),
50 30: ("Janet Jackson", "State Of The World", "Rock"),
51 31: ("Janet Jackson", "The Knowledge", "Rock"),
52 32: ("Spyro Gyra", "End of Romanticism", "Jazz"),
53 33: ("Spyro Gyra", "Heliopolis", "Jazz"),
54 34: ("Spyro Gyra", "Jubilee", "Jazz"),
55 35: ("Spyro Gyra", "Little Linda", "Jazz"),
56 36: ("Spyro Gyra", "Morning Dance", "Jazz"),
57 37: ("Spyro Gyra", "Song for Lorraine", "Jazz"),
58 38: ("Yes", "Owner Of A Lonely Heart", "Rock"),
59 39: ("Yes", "Rhythm Of Love", "Rock"),
60 40: ("Cusco", "Dream Catcher", "New Age"),
61 41: ("Cusco", "Geronimos Laughter", "New Age"),
62 42: ("Cusco", "Ghost Dance", "New Age"),
63 43: ("Blue Man Group", "Drumbone", "New Age"),
64 44: ("Blue Man Group", "Endless Column", "New Age"),
65 45: ("Blue Man Group", "Klein Mandelbrot", "New Age"),
66 46: ("Kenny G", "Silhouette", "Jazz"),
67 47: ("Sade", "Smooth Operator", "Jazz"),
68 48: ("David Arkenstone", "Papillon (On The Wings Of The Butterfly)", "New Age"),
69 49: ("David Arkenstone", "Stepping Stars", "New Age"),
70 50: ("David Arkenstone", "Carnation Lily Lily Rose", "New Age"),
71 51: ("David Lanz", "Behind The Waterfall", "New Age"),
72 52: ("David Lanz", "Cristofori's Dream", "New Age"),
73 53: ("David Lanz", "Heartsounds", "New Age"),
74 54: ("David Lanz", "Leaves on the Seine", "New Age"),
75 }
76
77 #---------------------------------------------------------------------------
78
79 class TestListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
80 def __init__(self, parent, ID, pos=wx.DefaultPosition,
81 size=wx.DefaultSize, style=0):
82 wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
83 listmix.ListCtrlAutoWidthMixin.__init__(self)
84
85
86 class TestListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
87 def __init__(self, parent, log):
88 wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
89
90 self.log = log
91 tID = wx.NewId()
92
93 self.il = wx.ImageList(16, 16)
94
95 self.idx1 = self.il.Add(images.getSmilesBitmap())
96 self.sm_up = self.il.Add(images.getSmallUpArrowBitmap())
97 self.sm_dn = self.il.Add(images.getSmallDnArrowBitmap())
98
99 self.list = TestListCtrl(self, tID,
100 style=wx.LC_REPORT
101 #| wx.BORDER_SUNKEN
102 | wx.BORDER_NONE
103 | wx.LC_EDIT_LABELS
104 #| wxLC_NO_HEADER
105 #| wxLC_VRULES | wxLC_HRULES
106 )
107
108 self.list.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
109
110 self.PopulateList()
111
112 # Now that the list exists we can init the other base class,
113 # see wxPython/lib/mixins/listctrl.py
114 self.itemDataMap = musicdata
115 listmix.ColumnSorterMixin.__init__(self, 3)
116 #self.SortListItems(0, True)
117
118 self.Bind(wx.EVT_SIZE, self.OnSize)
119
120 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list)
121 self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.list)
122 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self.list)
123 self.Bind(wx.EVT_LIST_DELETE_ITEM, self.OnItemDelete, self.list)
124 self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)
125 self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColRightClick, self.list)
126 self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self.OnColBeginDrag, self.list)
127 self.Bind(wx.EVT_LIST_COL_DRAGGING, self.OnColDragging, self.list)
128 self.Bind(wx.EVT_LIST_COL_END_DRAG, self.OnColEndDrag, self.list)
129 self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit, self.list)
130
131 self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
132 self.list.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
133
134 # for wxMSW
135 self.list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick)
136
137 # for wxGTK
138 self.list.Bind(wx.EVT_RIGHT_UP, self.OnRightClick)
139
140
141 def PopulateList(self):
142 if 0:
143 # for normal, simple columns, you can add them like this:
144 self.list.InsertColumn(0, "Artist")
145 self.list.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
146 self.list.InsertColumn(2, "Genre")
147 else:
148 # but since we want images on the column header we have to do it the hard way:
149 info = wx.ListItem()
150 info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
151 info.m_image = -1
152 info.m_format = 0
153 info.m_text = "Artist"
154 self.list.InsertColumnInfo(0, info)
155
156 info.m_format = wx.LIST_FORMAT_RIGHT
157 info.m_text = "Title"
158 self.list.InsertColumnInfo(1, info)
159
160 info.m_format = 0
161 info.m_text = "Genre"
162 self.list.InsertColumnInfo(2, info)
163
164 items = musicdata.items()
165 for x in range(len(items)):
166 key, data = items[x]
167 self.list.InsertImageStringItem(x, data[0], self.idx1)
168 self.list.SetStringItem(x, 1, data[1])
169 self.list.SetStringItem(x, 2, data[2])
170 self.list.SetItemData(x, key)
171
172 self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
173 self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE)
174 self.list.SetColumnWidth(2, 100)
175
176 # show how to select an item
177 self.list.SetItemState(5, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
178
179 # show how to change the colour of a couple items
180 item = self.list.GetItem(1)
181 item.SetTextColour(wx.BLUE)
182 self.list.SetItem(item)
183 item = self.list.GetItem(4)
184 item.SetTextColour(wx.RED)
185 self.list.SetItem(item)
186
187 self.currentItem = 0
188
189
190 # Used by the ColumnSorterMixin, see wxPython/lib/mixins/listctrl.py
191 def GetListCtrl(self):
192 return self.list
193
194 # Used by the ColumnSorterMixin, see wxPython/lib/mixins/listctrl.py
195 def GetSortImages(self):
196 return (self.sm_dn, self.sm_up)
197
198
199 def OnRightDown(self, event):
200 self.x = event.GetX()
201 self.y = event.GetY()
202 self.log.WriteText("x, y = %s\n" % str((self.x, self.y)))
203 item, flags = self.list.HitTest((self.x, self.y))
204
205 if flags & wx.LIST_HITTEST_ONITEM:
206 self.list.Select(item)
207
208 event.Skip()
209
210
211 def getColumnText(self, index, col):
212 item = self.list.GetItem(index, col)
213 return item.GetText()
214
215
216 def OnItemSelected(self, event):
217 ##print event.GetItem().GetTextColour()
218 self.currentItem = event.m_itemIndex
219 self.log.WriteText("OnItemSelected: %s, %s, %s, %s\n" %
220 (self.currentItem,
221 self.list.GetItemText(self.currentItem),
222 self.getColumnText(self.currentItem, 1),
223 self.getColumnText(self.currentItem, 2)))
224
225 if self.currentItem == 10:
226 self.log.WriteText("OnItemSelected: Veto'd selection\n")
227 #event.Veto() # doesn't work
228 # this does
229 self.list.SetItemState(10, 0, wx.LIST_STATE_SELECTED)
230
231 event.Skip()
232
233
234 def OnItemDeselected(self, evt):
235 item = evt.GetItem()
236 self.log.WriteText("OnItemDeselected: %d" % evt.m_itemIndex)
237
238 # Show how to reselect something we don't want deselected
239 if evt.m_itemIndex == 11:
240 wx.CallAfter(self.list.SetItemState, 11, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
241
242
243 def OnItemActivated(self, event):
244 self.currentItem = event.m_itemIndex
245 self.log.WriteText("OnItemActivated: %s\nTopItem: %s" %
246 (self.list.GetItemText(self.currentItem), self.list.GetTopItem()))
247
248 def OnBeginEdit(self, event):
249 self.log.WriteText("OnBeginEdit")
250 event.Allow()
251
252 def OnItemDelete(self, event):
253 self.log.WriteText("OnItemDelete\n")
254
255 def OnColClick(self, event):
256 self.log.WriteText("OnColClick: %d\n" % event.GetColumn())
257 event.Skip()
258
259 def OnColRightClick(self, event):
260 item = self.list.GetColumn(event.GetColumn())
261 self.log.WriteText("OnColRightClick: %d %s\n" %
262 (event.GetColumn(), (item.GetText(), item.GetAlign(),
263 item.GetWidth(), item.GetImage())))
264
265 def OnColBeginDrag(self, event):
266 self.log.WriteText("OnColBeginDrag\n")
267 ## Show how to not allow a column to be resized
268 #if event.GetColumn() == 0:
269 # event.Veto()
270
271
272 def OnColDragging(self, event):
273 self.log.WriteText("OnColDragging\n")
274
275 def OnColEndDrag(self, event):
276 self.log.WriteText("OnColEndDrag\n")
277
278 def OnDoubleClick(self, event):
279 self.log.WriteText("OnDoubleClick item %s\n" % self.list.GetItemText(self.currentItem))
280 event.Skip()
281
282 def OnRightClick(self, event):
283 self.log.WriteText("OnRightClick %s\n" % self.list.GetItemText(self.currentItem))
284
285 # only do this part the first time so the events are only bound once
286 if not hasattr(self, "popupID1"):
287 self.popupID1 = wx.NewId()
288 self.popupID2 = wx.NewId()
289 self.popupID3 = wx.NewId()
290 self.popupID4 = wx.NewId()
291 self.popupID5 = wx.NewId()
292 self.popupID6 = wx.NewId()
293
294 self.Bind(wx.EVT_MENU, self.OnPopupOne, id=self.popupID1)
295 self.Bind(wx.EVT_MENU, self.OnPopupTwo, id=self.popupID2)
296 self.Bind(wx.EVT_MENU, self.OnPopupThree, id=self.popupID3)
297 self.Bind(wx.EVT_MENU, self.OnPopupFour, id=self.popupID4)
298 self.Bind(wx.EVT_MENU, self.OnPopupFive, id=self.popupID5)
299 self.Bind(wx.EVT_MENU, self.OnPopupSix, id=self.popupID6)
300
301 # make a menu
302 menu = wx.Menu()
303 # add some items
304 menu.Append(self.popupID1, "FindItem tests")
305 menu.Append(self.popupID2, "Iterate Selected")
306 menu.Append(self.popupID3, "ClearAll and repopulate")
307 menu.Append(self.popupID4, "DeleteAllItems")
308 menu.Append(self.popupID5, "GetItem")
309 menu.Append(self.popupID6, "Edit")
310
311 # Popup the menu. If an item is selected then its handler
312 # will be called before PopupMenu returns.
313 self.PopupMenu(menu, (self.x, self.y))
314 menu.Destroy()
315
316
317 def OnPopupOne(self, event):
318 self.log.WriteText("Popup one\n")
319 print "FindItem:", self.list.FindItem(-1, "Roxette")
320 print "FindItemData:", self.list.FindItemData(-1, 11)
321
322 def OnPopupTwo(self, event):
323 self.log.WriteText("Selected items:\n")
324 index = self.list.GetFirstSelected()
325
326 while index != -1:
327 self.log.WriteText(" %s: %s\n" % (self.list.GetItemText(index), self.getColumnText(index, 1)))
328 index = self.list.GetNextSelected(index)
329
330 def OnPopupThree(self, event):
331 self.log.WriteText("Popup three\n")
332 self.list.ClearAll()
333 wx.CallAfter(self.PopulateList)
334
335 def OnPopupFour(self, event):
336 self.list.DeleteAllItems()
337
338 def OnPopupFive(self, event):
339 item = self.list.GetItem(self.currentItem)
340 print item.m_text, item.m_itemId, self.list.GetItemData(self.currentItem)
341
342 def OnPopupSix(self, event):
343 self.list.EditLabel(self.currentItem)
344
345
346 def OnSize(self, event):
347 w,h = self.GetClientSizeTuple()
348 self.list.SetDimensions(0, 0, w, h)
349
350
351
352 #---------------------------------------------------------------------------
353
354 def runTest(frame, nb, log):
355 win = TestListCtrlPanel(nb, log)
356 return win
357
358 #---------------------------------------------------------------------------
359
360
361 overview = """\
362 <html>
363 <body>
364 A list control presents lists in a number of formats: list view, report view,
365 icon view and small icon view. In any case, elements are numbered from zero.
366 For all these modes (but not for virtual list controls), the items are stored
367 in the control and must be added to it using InsertItem method.
368
369 <p>To intercept events from a list control, use the event table macros described in
370 <code>wxListEvent.</code>
371
372 <h3>Mix-ins</h3>
373 This example demonstrates how to use mixins. The following mixins are available.
374
375 <h4>ColumnSorterMixin</h4>
376
377 <code><b>ColumnSorterMixin(numColumns)</b></code>
378
379 <p>A mixin class that handles sorting of a wxListCtrl in REPORT mode when the column
380 header is clicked on.
381
382 <p>There are a few requirments needed in order for this to work genericly:
383 <p><ol>
384 <li>The combined class must have a <code>GetListCtrl</code> method that returns
385 the ListCtrl to be sorted, and the list control must exist at the time the
386 <code>ColumnSorterMixin.__init__()</code>method is called because it uses
387 <code>GetListCtrl</code>.
388
389 <li>Items in the list control must have a unique data value set with
390 <code>list.SetItemData</code>.
391
392 <li>The combined class must have an attribute named <code>itemDataMap</code>
393 that is a dictionary mapping the data values to a sequence of objects
394 representing the values in each column. These valuesare compared in
395 the column sorter to determine sort order.
396 </ol>
397
398 <p>Interesting methods to override are <code>GetColumnSorter</code>,
399 <code>GetSecondarySortValues</code>, and <code>GetSortImages</code>.
400
401 <h5>Methods</h5>
402 <dl>
403 <dt><code>SetColumnCount(newNumColumns)</code>
404 <dd>Informs the mixin as to the number of columns in the control. When it is
405 set, it also sets up an event handler for <code>EVT_LIST_COL_CLICK</code> events.
406
407 <dt><code>SortListItems(col=-1, ascending=1)</code>
408 <dd>Sort the list on demand. Can also be used to set the sort column and order.
409
410 <dt><code>GetColumnWidths()</code>
411 <dd>Returns a list of column widths. Can be used to help restore the current
412 view later.
413
414 <dt><code>GetSortImages()</code>
415 <dd>Returns a tuple of image list indexes the indexes in the image list for an
416 image to be put on the column header when sorting in descending order
417
418 <dt><code>GetColumnSorter()</code>
419 <dd>Returns a callable object to be used for comparing column values when sorting.
420
421 <dt><code>GetSecondarySortValues(col, key1, key2)</code>
422 <dd>Returns a tuple of 2 values to use for secondary sort values when the
423 items in the selected column match equal. The default just returns the
424 item data values.
425
426 </dl>
427
428 <h4>ListCtrlAutoWidthMixin</h4>
429
430 <code><b>ListCtrlAutoWidthMixin()</b></code>
431
432 <p>A mix-in class that automatically resizes the last column to take up the
433 remaining width of the ListCtrl.
434
435 <p>This causes the ListCtrl to automatically take up the full width of the list,
436 without either a horizontal scroll bar (unless absolutely necessary) or empty
437 space to the right of the last column.
438
439 <p><b>NOTE:</b> This only works for report-style lists.
440
441 <p><b>WARNING:</b> If you override the <code>EVT_SIZE</code> event in your ListCtrl,
442 make sure you call event.Skip() to ensure that the mixin's _OnResize method is
443 called.
444
445 <p>This mix-in class was written by <a href='mailto:ewestra@wave.co.nz'>Erik Westra </a>
446
447 <h5>Methods</h5>
448 <dl>
449
450 <dt><code>resizeLastColumn(minWidth)</code>
451 <dd>Resize the last column appropriately. If the list's columns are too wide to
452 fit within the window, we use a horizontal scrollbar. Otherwise, we expand the
453 right-most column to take up the remaining free space in the list. This method is
454 called automatically when the ListCtrl is resized; you can also call it yourself
455 whenever you want the last column to be resized appropriately (eg, when adding,
456 removing or resizing columns). 'minWidth' is the preferred minimum width for
457 the last column.
458
459 </dl>
460
461
462 <h4>ListCtrlSelectionManagerMix</h4>
463
464 <code><b>ListCtrlSelectionManagerMix()</b></code>
465
466 <p>Mixin that defines a platform independent selection policy
467
468 <p>As selection single and multi-select list return the item index or a
469 list of item indexes respectively.
470
471 <h5>Methods</h5>
472 <dl>
473
474 <dt><code>getPopupMenu()</code>
475 <dd>Override to implement dynamic menus (create)
476
477 <dt><code>setPopupMenu(menu)</code>
478 <dd>Must be set for default behaviour.
479
480 <dt><code>afterPopupMenu()</code>
481 <dd>Override to implement dynamic menus (destroy).
482
483 <dt><code>getSelection()</code>
484 <dd>Returns the current selection (or selections as a Python list if extended
485 selection is enabled)
486
487
488 </body>
489 </html>
490 """
491
492
493 if __name__ == '__main__':
494 import sys,os
495 import run
496 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
497