]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/combotreebox.py
Patch #1547057: Disable grid lines in wx.lib.printout.PrintTable
[wxWidgets.git] / wxPython / wx / lib / combotreebox.py
1 """
2 ComboTreeBox provides a ComboBox that pops up a tree instead of a list.
3
4 ComboTreeBox tries to provide the same interface as ComboBox as much as
5 possible. However, whereas the ComboBox widget uses indices to access
6 items in the list of choices, ComboTreeBox uses TreeItemId's instead. If
7 you add an item to the ComboTreeBox (using Append or Insert), the
8 TreeItemId associated with the added item is returned. You can then use
9 that TreeItemId to add items as children of that first item. For
10 example:
11
12 >>> from wx.lib.combotreebox import ComboTreeBox
13 >>> combo = ComboTreeBox(parent)
14 >>> item1 = combo.Append('Item 1') # Add a root item
15 >>> item1a = combo.Append('Item 1a', parent=item1) # Add a child to item1
16
17 You can also add client data to each of the items like this:
18 >>> item1 = combo.Append('Item 1', clientData=somePythonObject)
19 >>> item1a = combo.Append('Item 1a', parent=item1,
20 ... clientData=someOtherPythonObject)
21
22 And later fetch the client data like this:
23 >>> somePythonObject = combo.GetClientData(item1)
24
25 To get the client data of the currently selected item (if any):
26 >>> currentItem = combo.GetSelection()
27 >>> if currentItem:
28 >>> somePythonObject = combo.GetClientData(currentItem)
29
30 Supported styles are the same as for ComboBox, i.e. wx.CB_READONLY and
31 wx.CB_SORT. Provide them as usual:
32 >>> combo = ComboTreeBox(parent, style=wx.CB_READONLY|wx.CB_SORT)
33
34 Supported platforms: wxMSW and wxMAC natively, wxGTK by means of a
35 workaround.
36
37 Author: Frank Niessink <frank@niessink.com>
38 Copyright 2006, Frank Niessink
39 License: wxWidgets license
40 Version: 0.8
41 Date: August 21, 2006
42 """
43
44 import wx
45
46 __all__ = ['ComboTreeBox'] # Export only the ComboTreeBox widget
47
48
49 # ---------------------------------------------------------------------------
50
51
52 class IterableTreeCtrl(wx.TreeCtrl):
53 """
54 TreeCtrl is the same as wx.TreeCtrl, with a few convenience methods
55 added for easier navigation of items. """
56
57 def GetPreviousItem(self, item):
58 """
59 GetPreviousItem(self, TreeItemId item) -> TreeItemId
60
61 Returns the item that is on the line immediately above item
62 (as is displayed when the tree is fully expanded). The returned
63 item is invalid if item is the first item in the tree.
64 """
65 previousSibling = self.GetPrevSibling(item)
66 if previousSibling:
67 return self.GetLastChildRecursively(previousSibling)
68 else:
69 parent = self.GetItemParent(item)
70 if parent == self.GetRootItem() and \
71 (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
72 # Return an invalid item, because the root item is hidden
73 return previousSibling
74 else:
75 return parent
76
77 def GetNextItem(self, item):
78 """
79 GetNextItem(self, TreeItemId item) -> TreeItemId
80
81 Returns the item that is on the line immediately below item
82 (as is displayed when the tree is fully expanded). The returned
83 item is invalid if item is the last item in the tree.
84 """
85 if self.ItemHasChildren(item):
86 firstChild, cookie = self.GetFirstChild(item)
87 return firstChild
88 else:
89 return self.GetNextSiblingRecursively(item)
90
91 def GetFirstItem(self):
92 """
93 GetFirstItem(self) -> TreeItemId
94
95 Returns the very first item in the tree. This is the root item
96 unless the root item is hidden. In that case the first child of
97 the root item is returned, if any. If the tree is empty, an
98 invalid tree item is returned.
99 """
100 rootItem = self.GetRootItem()
101 if rootItem and (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
102 firstChild, cookie = self.GetFirstChild(rootItem)
103 return firstChild
104 else:
105 return rootItem
106
107 def GetLastChildRecursively(self, item):
108 """
109 GetLastChildRecursively(self, TreeItemId item) -> TreeItemId
110
111 Returns the last child of the last child ... of item. If item
112 has no children, item itself is returned. So the returned item
113 is always valid, assuming a valid item has been passed.
114 """
115 lastChild = item
116 while self.ItemHasChildren(lastChild):
117 lastChild = self.GetLastChild(lastChild)
118 return lastChild
119
120 def GetNextSiblingRecursively(self, item):
121 """
122 GetNextSiblingRecursively(self, TreeItemId item) -> TreeItemId
123
124 Returns the next sibling of item if it has one. If item has no
125 next sibling the next sibling of the parent of item is returned.
126 If the parent has no next sibling the next sibling of the parent
127 of the parent is returned, etc. If none of the ancestors of item
128 has a next sibling, an invalid item is returned.
129 """
130 if item == self.GetRootItem():
131 return wx.TreeItemId() # Return an invalid TreeItemId
132 nextSibling = self.GetNextSibling(item)
133 if nextSibling:
134 return nextSibling
135 else:
136 parent = self.GetItemParent(item)
137 return self.GetNextSiblingRecursively(parent)
138
139 def GetSelection(self):
140 """ Extend GetSelection to never return the root item if the
141 root item is hidden. """
142 selection = super(IterableTreeCtrl, self).GetSelection()
143 if selection == self.GetRootItem() and \
144 (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
145 return wx.TreeItemId() # Return an invalid TreeItemId
146 else:
147 return selection
148
149
150 # ---------------------------------------------------------------------------
151
152
153 class BasePopupFrame(wx.MiniFrame):
154 """
155 BasePopupFrame is the base class for platform specific
156 versions of the PopupFrame. The PopupFrame is the frame that
157 is popped up by ComboTreeBox. It contains the tree of items
158 that the user can select one item from. Upon selection, or
159 when focus is lost, the frame is hidden. """
160
161 def __init__(self, parent):
162 super(BasePopupFrame, self).__init__(parent,
163 style=wx.DEFAULT_FRAME_STYLE & wx.FRAME_FLOAT_ON_PARENT &
164 ~(wx.RESIZE_BORDER | wx.CAPTION))
165 self._createInterior()
166 self._layoutInterior()
167 self._bindEventHandlers()
168
169 def _createInterior(self):
170 self._tree = IterableTreeCtrl(self,
171 style=wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT|wx.TR_HAS_BUTTONS)
172 self._tree.AddRoot('Hidden root node')
173
174 def _layoutInterior(self):
175 frameSizer = wx.BoxSizer(wx.HORIZONTAL)
176 frameSizer.Add(self._tree, flag=wx.EXPAND, proportion=1)
177 self.SetSizerAndFit(frameSizer)
178
179 def _bindEventHandlers(self):
180 self._tree.Bind(wx.EVT_CHAR, self.OnChar)
181 self._tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
182 self._tree.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
183
184 def _bindKillFocus(self):
185 self._tree.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
186
187 def _unbindKillFocus(self):
188 self._tree.Unbind(wx.EVT_KILL_FOCUS)
189
190 def OnKillFocus(self, event):
191 # We hide the frame rather than destroy it, so it can be
192 # popped up again later:
193 self.Hide()
194 self.GetParent().NotifyNoItemSelected()
195 event.Skip()
196
197 def OnChar(self, keyEvent):
198 if self._keyShouldHidePopup(keyEvent):
199 self.Hide()
200 self.GetParent().NotifyNoItemSelected()
201 keyEvent.Skip()
202
203 def _keyShouldHidePopup(self, keyEvent):
204 return keyEvent.GetKeyCode() == wx.WXK_ESCAPE
205
206 def OnMouseClick(self, event):
207 item, flags = self._tree.HitTest(event.GetPosition())
208 if item and (flags & wx.TREE_HITTEST_ONITEMLABEL):
209 self._tree.SelectItem(item)
210 self.Hide()
211 self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
212 else:
213 event.Skip()
214
215 def OnItemActivated(self, event):
216 item = event.GetItem()
217 self.Hide()
218 self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
219
220 def Show(self):
221 self._bindKillFocus()
222 wx.CallAfter(self._tree.SetFocus)
223 super(BasePopupFrame, self).Show()
224
225 def Hide(self):
226 self._unbindKillFocus()
227 super(BasePopupFrame, self).Hide()
228
229 def GetTree(self):
230 return self._tree
231
232
233 class MSWPopupFrame(BasePopupFrame):
234 def Show(self):
235 # Comply with the MS Windows Combobox behaviour: if the text in
236 # the text field is not in the tree, the first item in the tree
237 # is selected.
238 if not self._tree.GetSelection():
239 self._tree.SelectItem(self._tree.GetFirstItem())
240 super(MSWPopupFrame, self).Show()
241
242
243 class MACPopupFrame(BasePopupFrame):
244 def _bindKillFocus(self):
245 # On wxMac, the kill focus event doesn't work, but the
246 # deactivate event does:
247 self.Bind(wx.EVT_ACTIVATE, self.OnKillFocus)
248
249 def _unbindKillFocus(self):
250 self.Unbind(wx.EVT_ACTIVATE)
251
252 def OnKillFocus(self, event):
253 if not event.GetActive(): # We received a deactivate event
254 self.Hide()
255 wx.CallAfter(self.GetParent().NotifyNoItemSelected)
256 event.Skip()
257
258
259 class GTKPopupFrame(BasePopupFrame):
260 def _keyShouldHidePopup(self, keyEvent):
261 # On wxGTK, Alt-Up also closes the popup:
262 return super(GTKPopupFrame, self)._keyShouldHidePopup(keyEvent) or \
263 (keyEvent.AltDown() and keyEvent.GetKeyCode() == wx.WXK_UP)
264
265
266 # ---------------------------------------------------------------------------
267
268
269 class BaseComboTreeBox(object):
270 """ BaseComboTreeBox is the base class for platform specific
271 versions of the ComboTreeBox. """
272
273 def __init__(self, *args, **kwargs):
274 style = kwargs.pop('style', 0)
275 if style & wx.CB_READONLY:
276 style &= ~wx.CB_READONLY # We manage readonlyness ourselves
277 self._readOnly = True
278 else:
279 self._readOnly = False
280 if style & wx.CB_SORT:
281 style &= ~wx.CB_SORT # We manage sorting ourselves
282 self._sort = True
283 else:
284 self._sort = False
285 super(BaseComboTreeBox, self).__init__(style=style, *args, **kwargs)
286 self._createInterior()
287 self._layoutInterior()
288 self._bindEventHandlers()
289
290 # Methods to construct the widget.
291
292 def _createInterior(self):
293 self._popupFrame = self._createPopupFrame()
294 self._text = self._createTextCtrl()
295 self._button = self._createButton()
296 self._tree = self._popupFrame.GetTree()
297
298 def _createTextCtrl(self):
299 return self # By default, the text control is the control itself.
300
301 def _createButton(self):
302 return self # By default, the dropdown button is the control itself.
303
304 def _createPopupFrame(self):
305 # It is a subclass responsibility to provide the right PopupFrame,
306 # depending on platform:
307 raise NotImplementedError
308
309 def _layoutInterior(self):
310 pass # By default, there is no layout to be done.
311
312 def _bindEventHandlers(self):
313 for eventSource, eventType, eventHandler in self._eventsToBind():
314 eventSource.Bind(eventType, eventHandler)
315
316 def _eventsToBind(self):
317 """
318 _eventsToBind(self) -> [(eventSource, eventType, eventHandler), ...]
319
320 _eventsToBind returns a list of eventSource, eventType,
321 eventHandlers tuples that will be bound. This method can be
322 extended to bind additional events. In that case, don't
323 forget to call _eventsToBind on the super class. """
324 return [(self._text, wx.EVT_KEY_DOWN, self.OnKeyDown),
325 (self._text, wx.EVT_TEXT, self.OnText),
326 (self._button, wx.EVT_BUTTON, self.OnMouseClick)]
327
328 # Event handlers
329
330 def OnMouseClick(self, event):
331 self.Popup()
332 # Note that we don't call event.Skip() to prevent popping up the
333 # ComboBox's own box.
334
335 def OnKeyDown(self, keyEvent):
336 if self._keyShouldNavigate(keyEvent):
337 self._navigateUpOrDown(keyEvent)
338 elif self._keyShouldPopUpTree(keyEvent):
339 self.Popup()
340 else:
341 keyEvent.Skip()
342
343 def _keyShouldPopUpTree(self, keyEvent):
344 return (keyEvent.AltDown() or keyEvent.MetaDown()) and \
345 keyEvent.GetKeyCode() == wx.WXK_DOWN
346
347 def _keyShouldNavigate(self, keyEvent):
348 return keyEvent.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP) and not \
349 self._keyShouldPopUpTree(keyEvent)
350
351 def _navigateUpOrDown(self, keyEvent):
352 item = self.GetSelection()
353 if item:
354 navigationMethods = {wx.WXK_DOWN: self._tree.GetNextItem,
355 wx.WXK_UP: self._tree.GetPreviousItem}
356 getNextItem = navigationMethods[keyEvent.GetKeyCode()]
357 nextItem = getNextItem(item)
358 else:
359 nextItem = self._tree.GetFirstItem()
360 if nextItem:
361 self.SetSelection(nextItem)
362
363 def OnText(self, event):
364 event.Skip()
365 item = self.FindString(self._text.GetValue())
366 if item:
367 if self._tree.GetSelection() != item:
368 self._tree.SelectItem(item)
369 else:
370 self._tree.Unselect()
371
372 # Methods called by the PopupFrame, to let the ComboTreeBox know
373 # about what the user did.
374
375 def NotifyItemSelected(self, text):
376 """ Simulate selection of an item by the user. This is meant to
377 be called by the PopupFrame when the user selects an item. """
378 self._text.SetValue(text)
379 self._postComboBoxSelectedEvent(text)
380 self.SetFocus()
381
382 def _postComboBoxSelectedEvent(self, text):
383 """ Simulate a selection event. """
384 event = wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED,
385 self.GetId())
386 event.SetString(text)
387 self.GetEventHandler().ProcessEvent(event)
388
389 def NotifyNoItemSelected(self):
390 """ This is called by the PopupFrame when the user closes the
391 PopupFrame, without selecting an item. """
392 self.SetFocus()
393
394 # Misc methods, not part of the ComboBox API.
395
396 def Popup(self):
397 """
398 Popup(self)
399
400 Pops up the frame with the tree.
401 """
402 comboBoxSize = self.GetSize()
403 x, y = self.GetParent().ClientToScreen(self.GetPosition())
404 y += comboBoxSize[1]
405 width = comboBoxSize[0]
406 height = 300
407 self._popupFrame.SetDimensions(x, y, width, height)
408 # On wxGTK, when the Combobox width has been increased a call
409 # to SetMinSize is needed to force a resize of the popupFrame:
410 self._popupFrame.SetMinSize((width, height))
411 self._popupFrame.Show()
412
413 def GetTree(self):
414 """
415 GetTree(self) -> wx.TreeCtrl
416
417 Returns the tree control that is popped up.
418 """
419 return self._popupFrame.GetTree()
420
421 def FindClientData(self, clientData, parent=None):
422 """
423 FindClientData(self, PyObject clientData, TreeItemId parent=None)
424 -> TreeItemId
425
426 Finds the *first* item in the tree with client data equal to the
427 given clientData. If no such item exists, an invalid item is
428 returned.
429 """
430 parent = parent or self._tree.GetRootItem()
431 child, cookie = self._tree.GetFirstChild(parent)
432 while child:
433 if self.GetClientData(child) == clientData:
434 return child
435 else:
436 result = self.FindClientData(clientData, child)
437 if result:
438 return result
439 child, cookie = self._tree.GetNextChild(parent, cookie)
440 return child
441
442 def SetClientDataSelection(self, clientData):
443 """
444 SetClientDataSelection(self, PyObject clientData) -> bool
445
446 Selects the item with the provided clientData in the control.
447 Returns True if the item belonging to the clientData has been
448 selected, False if it wasn't found in the control.
449 """
450 item = self.FindClientData(clientData)
451 if item:
452 self._tree.SelectItem(item)
453 return True
454 else:
455 return False
456
457 # The following methods are all part of the ComboBox API (actually
458 # the ControlWithItems API) and have been adapted to take TreeItemIds
459 # as parameter and return TreeItemIds, rather than indices.
460
461 def Append(self, itemText, parent=None, clientData=None):
462 """
463 Append(self, String itemText, TreeItemId parent=None, PyObject
464 clientData=None) -> TreeItemId
465
466 Adds the itemText to the control, associating the given clientData
467 with the item if not None. If parent is None, itemText is added
468 as a root item, else itemText is added as a child item of
469 parent. The return value is the TreeItemId of the newly added
470 item. """
471 if parent is None:
472 parent = self._tree.GetRootItem()
473 item = self._tree.AppendItem(parent, itemText,
474 data=wx.TreeItemData(clientData))
475 if self._sort:
476 self._tree.SortChildren(parent)
477 return item
478
479 def Clear(self):
480 """
481 Clear(self)
482
483 Removes all items from the control.
484 """
485 return self._tree.DeleteAllItems()
486
487 def Delete(self, item):
488 """
489 Delete(self, TreeItemId item)
490
491 Deletes the item from the control.
492 """
493 return self._tree.Delete(item)
494
495 def FindString(self, string, parent=None):
496 """
497 FindString(self, String string, TreeItemId parent=None) -> TreeItemId
498
499 Finds the *first* item in the tree with a label equal to the
500 given string. If no such item exists, an invalid item is
501 returned.
502 """
503 parent = parent or self._tree.GetRootItem()
504 child, cookie = self._tree.GetFirstChild(parent)
505 while child:
506 if self._tree.GetItemText(child) == string:
507 return child
508 else:
509 result = self.FindString(string, child)
510 if result:
511 return result
512 child, cookie = self._tree.GetNextChild(parent, cookie)
513 return child
514
515 def GetSelection(self):
516 """
517 GetSelection(self) -> TreeItemId
518
519 Returns the TreeItemId of the selected item or an invalid item
520 if no item is selected.
521 """
522 selectedItem = self._tree.GetSelection()
523 if selectedItem and selectedItem != self._tree.GetRootItem():
524 return selectedItem
525 else:
526 return self.FindString(self.GetValue())
527
528 def GetString(self, item):
529 """
530 GetString(self, TreeItemId item) -> String
531
532 Returns the label of the given item.
533 """
534 if item:
535 return self._tree.GetItemText(item)
536 else:
537 return ''
538
539 def GetStringSelection(self):
540 """
541 GetStringSelection(self) -> String
542
543 Returns the label of the selected item or an empty string if no item
544 is selected.
545 """
546 return self.GetValue()
547
548 def Insert(self, itemText, previous=None, parent=None, clientData=None):
549 """
550 Insert(self, String itemText, TreeItemId previous=None, TreeItemId
551 parent=None, PyObject clientData=None) -> TreeItemId
552
553 Insert an item into the control before the ``previous`` item
554 and/or as child of the ``parent`` item. The itemText is associated
555 with clientData when not None.
556 """
557 data = wx.TreeItemData(clientData)
558 if parent is None:
559 parent = self._tree.GetRootItem()
560 if previous is None:
561 item = self._tree.InsertItemBefore(parent, 0, itemText, data=data)
562 else:
563 item = self._tree.InsertItem(parent, previous, itemText, data=data)
564 if self._sort:
565 self._tree.SortChildren(parent)
566 return item
567
568 def IsEmpty(self):
569 """
570 IsEmpty(self) -> bool
571
572 Returns True if the control is empty or False if it has some items.
573 """
574 return self.GetCount() == 0
575
576 def GetCount(self):
577 """
578 GetCount(self) -> int
579
580 Returns the number of items in the control.
581 """
582 # Note: We don't need to substract 1 for the hidden root item,
583 # because the TreeCtrl does that for us
584 return self._tree.GetCount()
585
586 def SetSelection(self, item):
587 """
588 SetSelection(self, TreeItemId item)
589
590 Sets the provided item to be the selected item.
591 """
592 self._tree.SelectItem(item)
593 self._text.SetValue(self._tree.GetItemText(item))
594
595 Select = SetSelection
596
597 def SetString(self, item, string):
598 """
599 SetString(self, TreeItemId item, String string)
600
601 Sets the label for the provided item.
602 """
603 self._tree.SetItemText(item, string)
604 if self._sort:
605 self._tree.SortChildren(self._tree.GetItemParent(item))
606
607 def SetStringSelection(self, string):
608 """
609 SetStringSelection(self, String string) -> bool
610
611 Selects the item with the provided string in the control.
612 Returns True if the provided string has been selected, False if
613 it wasn't found in the control.
614 """
615 item = self.FindString(string)
616 if item:
617 if self._text.GetValue() != string:
618 self._text.SetValue(string)
619 self._tree.SelectItem(item)
620 return True
621 else:
622 return False
623
624 def GetClientData(self, item):
625 """
626 GetClientData(self, TreeItemId item) -> PyObject
627
628 Returns the client data associated with the given item, if any.
629 """
630 return self._tree.GetItemPyData(item)
631
632 def SetClientData(self, item, clientData):
633 """
634 SetClientData(self, TreeItemId item, PyObject clientData)
635
636 Associate the given client data with the provided item.
637 """
638 self._tree.SetItemPyData(item, clientData)
639
640 def GetValue(self):
641 """
642 GetValue(self) -> String
643
644 Returns the current value in the combobox text field.
645 """
646 if self._text == self:
647 return super(BaseComboTreeBox, self).GetValue()
648 else:
649 return self._text.GetValue()
650
651 def SetValue(self, value):
652 """
653 SetValue(self, String value)
654
655 Sets the text for the combobox text field.
656
657 NB: For a combobox with wxCB_READONLY style the string must be
658 in the combobox choices list, otherwise the call to SetValue()
659 is ignored.
660 """
661 item = self._tree.GetSelection()
662 if not item or self._tree.GetItemText(item) != value:
663 item = self.FindString(value)
664 if self._readOnly and not item:
665 return
666 if self._text == self:
667 super(BaseComboTreeBox, self).SetValue(value)
668 else:
669 self._text.SetValue(value)
670 if item:
671 if self._tree.GetSelection() != item:
672 self._tree.SelectItem(item)
673 else:
674 self._tree.Unselect()
675
676
677 class NativeComboTreeBox(BaseComboTreeBox, wx.ComboBox):
678 """ NativeComboTreeBox, and any subclass, uses the native ComboBox as
679 basis, but prevent it from popping up its drop down list and
680 instead pops up a PopupFrame containing a tree of items. """
681
682 def _eventsToBind(self):
683 events = super(NativeComboTreeBox, self)._eventsToBind()
684 # Bind all mouse click events to self.OnMouseClick so we can
685 # intercept those events and prevent the native Combobox from
686 # popping up its list of choices.
687 for eventType in (wx.EVT_LEFT_DOWN, wx.EVT_LEFT_DCLICK,
688 wx.EVT_MIDDLE_DOWN, wx.EVT_MIDDLE_DCLICK,
689 wx.EVT_RIGHT_DOWN, wx.EVT_RIGHT_DCLICK):
690 events.append((self._button, eventType, self.OnMouseClick))
691 if self._readOnly:
692 events.append((self, wx.EVT_CHAR, self.OnChar))
693 return events
694
695 def OnChar(self, event):
696 # OnChar is only called when in read only mode. We don't call
697 # event.Skip() on purpose, to prevent the characters from being
698 # displayed in the text field.
699 pass
700
701
702 class MSWComboTreeBox(NativeComboTreeBox):
703 """ MSWComboTreeBox adds one piece of functionality as compared to
704 NativeComboTreeBox: when the user browses through the tree, the
705 ComboTreeBox's text field is continuously updated to show the
706 currently selected item in the tree. If the user cancels
707 selecting a new item from the tree, e.g. by hitting escape, the
708 previous value (the one that was selected before the PopupFrame
709 was popped up) is restored. """
710
711 def _createPopupFrame(self):
712 return MSWPopupFrame(self)
713
714 def _eventsToBind(self):
715 events = super(MSWComboTreeBox, self)._eventsToBind()
716 events.append((self._tree, wx.EVT_TREE_SEL_CHANGED,
717 self.OnSelectionChangedInTree))
718 return events
719
720 def OnSelectionChangedInTree(self, event):
721 item = event.GetItem()
722 if item:
723 selectedValue = self._tree.GetItemText(item)
724 if self.GetValue() != selectedValue:
725 self.SetValue(selectedValue)
726 event.Skip()
727
728 def _keyShouldPopUpTree(self, keyEvent):
729 return super(MSWComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
730 (keyEvent.GetKeyCode() == wx.WXK_F4) or \
731 ((keyEvent.AltDown() or keyEvent.MetaDown()) and \
732 keyEvent.GetKeyCode() == wx.WXK_UP)
733
734 def SetValue(self, value):
735 """ Extend SetValue to also select the text in the
736 ComboTreeBox's text field. """
737 super(MSWComboTreeBox, self).SetValue(value)
738 # We select the text in the ComboTreeBox's text field.
739 # There is a slight complication, however. When the control is
740 # deleted, SetValue is called. But if we call SetMark at that
741 # time, wxPython will crash. We can prevent this by comparing the
742 # result of GetLastPosition and the length of the value. If they
743 # match, all is fine. If they don't match, we don't call SetMark.
744 if self._text.GetLastPosition() == len(value):
745 self._text.SetMark(0, self._text.GetLastPosition())
746
747 def Popup(self, *args, **kwargs):
748 """ Extend Popup to store a copy of the current value, so we can
749 restore it later (in NotifyNoItemSelected). This is necessary
750 because MSWComboTreeBox will change the value as the user
751 browses through the items in the popped up tree. """
752 self._previousValue = self.GetValue()
753 super(MSWComboTreeBox, self).Popup(*args, **kwargs)
754
755 def NotifyNoItemSelected(self, *args, **kwargs):
756 """ Restore the value copied previously, because the user has
757 not selected a new value. """
758 self.SetValue(self._previousValue)
759 super(MSWComboTreeBox, self).NotifyNoItemSelected(*args, **kwargs)
760
761
762 class MACComboTreeBox(NativeComboTreeBox):
763 def _createPopupFrame(self):
764 return MACPopupFrame(self)
765
766 def _createButton(self):
767 return self.GetChildren()[0] # The choice button
768
769 def _keyShouldNavigate(self, keyEvent):
770 return False # No navigation with up and down on wxMac
771
772 def _keyShouldPopUpTree(self, keyEvent):
773 return super(MACComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
774 keyEvent.GetKeyCode() == wx.WXK_DOWN
775
776
777 class GTKComboTreeBox(BaseComboTreeBox, wx.Panel):
778 """ The ComboTreeBox widget for wxGTK. This is actually a work
779 around because on wxGTK, there doesn't seem to be a way to intercept
780 mouse events sent to the Combobox. Intercepting those events is
781 necessary to prevent the Combobox from popping up the list and pop up
782 the tree instead. So, until wxPython makes intercepting those events
783 possible we build a poor man's Combobox ourselves using a TextCtrl and
784 a BitmapButton. """
785
786 def _createPopupFrame(self):
787 return GTKPopupFrame(self)
788
789 def _createTextCtrl(self):
790 if self._readOnly:
791 style = wx.TE_READONLY
792 else:
793 style = 0
794 return wx.TextCtrl(self, style=style)
795
796 def _createButton(self):
797 bitmap = wx.ArtProvider.GetBitmap(wx.ART_GO_DOWN, client=wx.ART_BUTTON)
798 return wx.BitmapButton(self, bitmap=bitmap)
799
800 def _layoutInterior(self):
801 panelSizer = wx.BoxSizer(wx.HORIZONTAL)
802 panelSizer.Add(self._text, flag=wx.EXPAND, proportion=1)
803 panelSizer.Add(self._button)
804 self.SetSizerAndFit(panelSizer)
805
806
807 # ---------------------------------------------------------------------------
808
809
810 def ComboTreeBox(*args, **kwargs):
811 """ Factory function to create the right ComboTreeBox depending on
812 platform. You may force a specific class, e.g. for testing
813 purposes, by setting the keyword argument 'platform', e.g.
814 'platform=GTK' or 'platform=MSW' or platform='MAC'. """
815
816 platform = kwargs.pop('platform', None) or wx.PlatformInfo[0][4:7]
817 ComboTreeBoxClassName = '%sComboTreeBox' % platform
818 ComboTreeBoxClass = globals()[ComboTreeBoxClassName]
819 return ComboTreeBoxClass(*args, **kwargs)
820