2 ComboTreeBox provides a ComboBox that pops up a tree instead of a list.
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
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
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)
22 And later fetch the client data like this:
23 >>> somePythonObject = combo.GetClientData(item1)
25 To get the client data of the currently selected item (if any):
26 >>> currentItem = combo.GetSelection()
28 >>> somePythonObject = combo.GetClientData(currentItem)
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)
34 Supported platforms: wxMSW and wxMAC natively, wxGTK by means of a
37 Author: Frank Niessink <frank@niessink.com>
38 Copyright 2006, Frank Niessink
39 License: wxWidgets license
41 Date: September 6, 2006
46 __all__
= ['ComboTreeBox'] # Export only the ComboTreeBox widget
49 # ---------------------------------------------------------------------------
52 class IterableTreeCtrl(wx
.TreeCtrl
):
54 TreeCtrl is the same as wx.TreeCtrl, with a few convenience methods
55 added for easier navigation of items. """
57 def GetPreviousItem(self
, item
):
59 GetPreviousItem(self, TreeItemId item) -> TreeItemId
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.
65 previousSibling
= self
.GetPrevSibling(item
)
67 return self
.GetLastChildRecursively(previousSibling
)
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
77 def GetNextItem(self
, item
):
79 GetNextItem(self, TreeItemId item) -> TreeItemId
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.
85 if self
.ItemHasChildren(item
):
86 firstChild
, cookie
= self
.GetFirstChild(item
)
89 return self
.GetNextSiblingRecursively(item
)
91 def GetFirstItem(self
):
93 GetFirstItem(self) -> TreeItemId
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.
100 rootItem
= self
.GetRootItem()
101 if rootItem
and (self
.GetWindowStyle() & wx
.TR_HIDE_ROOT
):
102 firstChild
, cookie
= self
.GetFirstChild(rootItem
)
107 def GetLastChildRecursively(self
, item
):
109 GetLastChildRecursively(self, TreeItemId item) -> TreeItemId
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.
116 while self
.ItemHasChildren(lastChild
):
117 lastChild
= self
.GetLastChild(lastChild
)
120 def GetNextSiblingRecursively(self
, item
):
122 GetNextSiblingRecursively(self, TreeItemId item) -> TreeItemId
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.
130 if item
== self
.GetRootItem():
131 return wx
.TreeItemId() # Return an invalid TreeItemId
132 nextSibling
= self
.GetNextSibling(item
)
136 parent
= self
.GetItemParent(item
)
137 return self
.GetNextSiblingRecursively(parent
)
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
150 # ---------------------------------------------------------------------------
153 class BasePopupFrame(wx
.Frame
):
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. """
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
()
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')
174 def _layoutInterior(self
):
175 frameSizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
176 frameSizer
.Add(self
._tree
, flag
=wx
.EXPAND
, proportion
=1)
177 self
.SetSizerAndFit(frameSizer
)
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
)
184 def _bindKillFocus(self
):
185 self
._tree
.Bind(wx
.EVT_KILL_FOCUS
, self
.OnKillFocus
)
187 def _unbindKillFocus(self
):
188 self
._tree
.Unbind(wx
.EVT_KILL_FOCUS
)
190 def OnKillFocus(self
, event
):
191 # We hide the frame rather than destroy it, so it can be
192 # popped up again later:
194 self
.GetParent().NotifyNoItemSelected()
197 def OnChar(self
, keyEvent
):
198 if self
._keyShouldHidePopup
(keyEvent
):
200 self
.GetParent().NotifyNoItemSelected()
203 def _keyShouldHidePopup(self
, keyEvent
):
204 return keyEvent
.GetKeyCode() == wx
.WXK_ESCAPE
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
)
211 self
.GetParent().NotifyItemSelected(self
._tree
.GetItemText(item
))
215 def OnItemActivated(self
, event
):
216 item
= event
.GetItem()
218 self
.GetParent().NotifyItemSelected(self
._tree
.GetItemText(item
))
221 self
._bindKillFocus
()
222 wx
.CallAfter(self
._tree
.SetFocus
)
223 super(BasePopupFrame
, self
).Show()
226 self
._unbindKillFocus
()
227 super(BasePopupFrame
, self
).Hide()
233 class MSWPopupFrame(BasePopupFrame
):
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
238 if not self
._tree
.GetSelection():
239 self
._tree
.SelectItem(self
._tree
.GetFirstItem())
240 super(MSWPopupFrame
, self
).Show()
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
)
249 def _unbindKillFocus(self
):
250 self
.Unbind(wx
.EVT_ACTIVATE
)
252 def OnKillFocus(self
, event
):
253 if not event
.GetActive(): # We received a deactivate event
255 wx
.CallAfter(self
.GetParent().NotifyNoItemSelected
)
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
)
266 # ---------------------------------------------------------------------------
269 class BaseComboTreeBox(object):
270 """ BaseComboTreeBox is the base class for platform specific
271 versions of the ComboTreeBox. """
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
279 self
._readOnly
= False
280 if style
& wx
.CB_SORT
:
281 style
&= ~wx
.CB_SORT
# We manage sorting ourselves
285 super(BaseComboTreeBox
, self
).__init
__(style
=style
, *args
, **kwargs
)
286 self
._createInterior
()
287 self
._layoutInterior
()
288 self
._bindEventHandlers
()
290 # Methods to construct the widget.
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()
298 def _createTextCtrl(self
):
299 return self
# By default, the text control is the control itself.
301 def _createButton(self
):
302 return self
# By default, the dropdown button is the control itself.
304 def _createPopupFrame(self
):
305 # It is a subclass responsibility to provide the right PopupFrame,
306 # depending on platform:
307 raise NotImplementedError
309 def _layoutInterior(self
):
310 pass # By default, there is no layout to be done.
312 def _bindEventHandlers(self
):
313 for eventSource
, eventType
, eventHandler
in self
._eventsToBind
():
314 eventSource
.Bind(eventType
, eventHandler
)
316 def _eventsToBind(self
):
318 _eventsToBind(self) -> [(eventSource, eventType, eventHandler), ...]
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
)]
330 def OnMouseClick(self
, event
):
332 # Note that we don't call event.Skip() to prevent popping up the
333 # ComboBox's own box.
335 def OnKeyDown(self
, keyEvent
):
336 if self
._keyShouldNavigate
(keyEvent
):
337 self
._navigateUpOrDown
(keyEvent
)
338 elif self
._keyShouldPopUpTree
(keyEvent
):
343 def _keyShouldPopUpTree(self
, keyEvent
):
344 return (keyEvent
.AltDown() or keyEvent
.MetaDown()) and \
345 keyEvent
.GetKeyCode() == wx
.WXK_DOWN
347 def _keyShouldNavigate(self
, keyEvent
):
348 return keyEvent
.GetKeyCode() in (wx
.WXK_DOWN
, wx
.WXK_UP
) and not \
349 self
._keyShouldPopUpTree
(keyEvent
)
351 def _navigateUpOrDown(self
, keyEvent
):
352 item
= self
.GetSelection()
354 navigationMethods
= {wx
.WXK_DOWN
: self
._tree
.GetNextItem
,
355 wx
.WXK_UP
: self
._tree
.GetPreviousItem
}
356 getNextItem
= navigationMethods
[keyEvent
.GetKeyCode()]
357 nextItem
= getNextItem(item
)
359 nextItem
= self
._tree
.GetFirstItem()
361 self
.SetSelection(nextItem
)
363 def OnText(self
, event
):
365 item
= self
.FindString(self
._text
.GetValue())
367 if self
._tree
.GetSelection() != item
:
368 self
._tree
.SelectItem(item
)
370 self
._tree
.Unselect()
372 # Methods called by the PopupFrame, to let the ComboTreeBox know
373 # about what the user did.
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
)
382 def _postComboBoxSelectedEvent(self
, text
):
383 """ Simulate a selection event. """
384 event
= wx
.CommandEvent(wx
.wxEVT_COMMAND_COMBOBOX_SELECTED
,
386 event
.SetString(text
)
387 self
.GetEventHandler().ProcessEvent(event
)
389 def NotifyNoItemSelected(self
):
390 """ This is called by the PopupFrame when the user closes the
391 PopupFrame, without selecting an item. """
394 # Misc methods, not part of the ComboBox API.
400 Pops up the frame with the tree.
402 comboBoxSize
= self
.GetSize()
403 x
, y
= self
.GetParent().ClientToScreen(self
.GetPosition())
405 width
= comboBoxSize
[0]
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()
415 GetTree(self) -> wx.TreeCtrl
417 Returns the tree control that is popped up.
419 return self
._popupFrame
.GetTree()
421 def FindClientData(self
, clientData
, parent
=None):
423 FindClientData(self, PyObject clientData, TreeItemId parent=None)
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
430 parent
= parent
or self
._tree
.GetRootItem()
431 child
, cookie
= self
._tree
.GetFirstChild(parent
)
433 if self
.GetClientData(child
) == clientData
:
436 result
= self
.FindClientData(clientData
, child
)
439 child
, cookie
= self
._tree
.GetNextChild(parent
, cookie
)
442 def SetClientDataSelection(self
, clientData
):
444 SetClientDataSelection(self, PyObject clientData) -> bool
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.
450 item
= self
.FindClientData(clientData
)
452 self
._tree
.SelectItem(item
)
453 string
= self
._tree
.GetItemText(item
)
454 if self
._text
.GetValue() != string
:
455 self
._text
.SetValue(string
)
460 # The following methods are all part of the ComboBox API (actually
461 # the ControlWithItems API) and have been adapted to take TreeItemIds
462 # as parameter and return TreeItemIds, rather than indices.
464 def Append(self
, itemText
, parent
=None, clientData
=None):
466 Append(self, String itemText, TreeItemId parent=None, PyObject
467 clientData=None) -> TreeItemId
469 Adds the itemText to the control, associating the given clientData
470 with the item if not None. If parent is None, itemText is added
471 as a root item, else itemText is added as a child item of
472 parent. The return value is the TreeItemId of the newly added
475 parent
= self
._tree
.GetRootItem()
476 item
= self
._tree
.AppendItem(parent
, itemText
,
477 data
=wx
.TreeItemData(clientData
))
479 self
._tree
.SortChildren(parent
)
486 Removes all items from the control.
488 return self
._tree
.DeleteAllItems()
490 def Delete(self
, item
):
492 Delete(self, TreeItemId item)
494 Deletes the item from the control.
496 return self
._tree
.Delete(item
)
498 def FindString(self
, string
, parent
=None):
500 FindString(self, String string, TreeItemId parent=None) -> TreeItemId
502 Finds the *first* item in the tree with a label equal to the
503 given string. If no such item exists, an invalid item is
506 parent
= parent
or self
._tree
.GetRootItem()
507 child
, cookie
= self
._tree
.GetFirstChild(parent
)
509 if self
._tree
.GetItemText(child
) == string
:
512 result
= self
.FindString(string
, child
)
515 child
, cookie
= self
._tree
.GetNextChild(parent
, cookie
)
518 def GetSelection(self
):
520 GetSelection(self) -> TreeItemId
522 Returns the TreeItemId of the selected item or an invalid item
523 if no item is selected.
525 selectedItem
= self
._tree
.GetSelection()
526 if selectedItem
and selectedItem
!= self
._tree
.GetRootItem():
529 return self
.FindString(self
.GetValue())
531 def GetString(self
, item
):
533 GetString(self, TreeItemId item) -> String
535 Returns the label of the given item.
538 return self
._tree
.GetItemText(item
)
542 def GetStringSelection(self
):
544 GetStringSelection(self) -> String
546 Returns the label of the selected item or an empty string if no item
549 return self
.GetValue()
551 def Insert(self
, itemText
, previous
=None, parent
=None, clientData
=None):
553 Insert(self, String itemText, TreeItemId previous=None, TreeItemId
554 parent=None, PyObject clientData=None) -> TreeItemId
556 Insert an item into the control before the ``previous`` item
557 and/or as child of the ``parent`` item. The itemText is associated
558 with clientData when not None.
560 data
= wx
.TreeItemData(clientData
)
562 parent
= self
._tree
.GetRootItem()
564 item
= self
._tree
.InsertItemBefore(parent
, 0, itemText
, data
=data
)
566 item
= self
._tree
.InsertItem(parent
, previous
, itemText
, data
=data
)
568 self
._tree
.SortChildren(parent
)
573 IsEmpty(self) -> bool
575 Returns True if the control is empty or False if it has some items.
577 return self
.GetCount() == 0
581 GetCount(self) -> int
583 Returns the number of items in the control.
585 # Note: We don't need to substract 1 for the hidden root item,
586 # because the TreeCtrl does that for us
587 return self
._tree
.GetCount()
589 def SetSelection(self
, item
):
591 SetSelection(self, TreeItemId item)
593 Sets the provided item to be the selected item.
595 self
._tree
.SelectItem(item
)
596 self
._text
.SetValue(self
._tree
.GetItemText(item
))
598 Select
= SetSelection
600 def SetString(self
, item
, string
):
602 SetString(self, TreeItemId item, String string)
604 Sets the label for the provided item.
606 self
._tree
.SetItemText(item
, string
)
608 self
._tree
.SortChildren(self
._tree
.GetItemParent(item
))
610 def SetStringSelection(self
, string
):
612 SetStringSelection(self, String string) -> bool
614 Selects the item with the provided string in the control.
615 Returns True if the provided string has been selected, False if
616 it wasn't found in the control.
618 item
= self
.FindString(string
)
620 if self
._text
.GetValue() != string
:
621 self
._text
.SetValue(string
)
622 self
._tree
.SelectItem(item
)
627 def GetClientData(self
, item
):
629 GetClientData(self, TreeItemId item) -> PyObject
631 Returns the client data associated with the given item, if any.
633 return self
._tree
.GetItemPyData(item
)
635 def SetClientData(self
, item
, clientData
):
637 SetClientData(self, TreeItemId item, PyObject clientData)
639 Associate the given client data with the provided item.
641 self
._tree
.SetItemPyData(item
, clientData
)
645 GetValue(self) -> String
647 Returns the current value in the combobox text field.
649 if self
._text
== self
:
650 return super(BaseComboTreeBox
, self
).GetValue()
652 return self
._text
.GetValue()
654 def SetValue(self
, value
):
656 SetValue(self, String value)
658 Sets the text for the combobox text field.
660 NB: For a combobox with wxCB_READONLY style the string must be
661 in the combobox choices list, otherwise the call to SetValue()
664 item
= self
._tree
.GetSelection()
665 if not item
or self
._tree
.GetItemText(item
) != value
:
666 item
= self
.FindString(value
)
667 if self
._readOnly
and not item
:
669 if self
._text
== self
:
670 super(BaseComboTreeBox
, self
).SetValue(value
)
672 self
._text
.SetValue(value
)
674 if self
._tree
.GetSelection() != item
:
675 self
._tree
.SelectItem(item
)
677 self
._tree
.Unselect()
680 class NativeComboTreeBox(BaseComboTreeBox
, wx
.ComboBox
):
681 """ NativeComboTreeBox, and any subclass, uses the native ComboBox as
682 basis, but prevent it from popping up its drop down list and
683 instead pops up a PopupFrame containing a tree of items. """
685 def _eventsToBind(self
):
686 events
= super(NativeComboTreeBox
, self
)._eventsToBind
()
687 # Bind all mouse click events to self.OnMouseClick so we can
688 # intercept those events and prevent the native Combobox from
689 # popping up its list of choices.
690 for eventType
in (wx
.EVT_LEFT_DOWN
, wx
.EVT_LEFT_DCLICK
,
691 wx
.EVT_MIDDLE_DOWN
, wx
.EVT_MIDDLE_DCLICK
,
692 wx
.EVT_RIGHT_DOWN
, wx
.EVT_RIGHT_DCLICK
):
693 events
.append((self
._button
, eventType
, self
.OnMouseClick
))
695 events
.append((self
, wx
.EVT_CHAR
, self
.OnChar
))
698 def OnChar(self
, event
):
699 # OnChar is only called when in read only mode. We don't call
700 # event.Skip() on purpose, to prevent the characters from being
701 # displayed in the text field.
705 class MSWComboTreeBox(NativeComboTreeBox
):
706 """ MSWComboTreeBox adds one piece of functionality as compared to
707 NativeComboTreeBox: when the user browses through the tree, the
708 ComboTreeBox's text field is continuously updated to show the
709 currently selected item in the tree. If the user cancels
710 selecting a new item from the tree, e.g. by hitting escape, the
711 previous value (the one that was selected before the PopupFrame
712 was popped up) is restored. """
714 def _createPopupFrame(self
):
715 return MSWPopupFrame(self
)
717 def _eventsToBind(self
):
718 events
= super(MSWComboTreeBox
, self
)._eventsToBind
()
719 events
.append((self
._tree
, wx
.EVT_TREE_SEL_CHANGED
,
720 self
.OnSelectionChangedInTree
))
723 def OnSelectionChangedInTree(self
, event
):
724 if self
.IsBeingDeleted():
726 item
= event
.GetItem()
728 selectedValue
= self
._tree
.GetItemText(item
)
729 if self
.GetValue() != selectedValue
:
730 self
.SetValue(selectedValue
)
733 def _keyShouldPopUpTree(self
, keyEvent
):
734 return super(MSWComboTreeBox
, self
)._keyShouldPopUpTree
(keyEvent
) or \
735 (keyEvent
.GetKeyCode() == wx
.WXK_F4
) or \
736 ((keyEvent
.AltDown() or keyEvent
.MetaDown()) and \
737 keyEvent
.GetKeyCode() == wx
.WXK_UP
)
739 def SetValue(self
, value
):
740 """ Extend SetValue to also select the text in the
741 ComboTreeBox's text field. """
742 super(MSWComboTreeBox
, self
).SetValue(value
)
743 # We select the text in the ComboTreeBox's text field.
744 # There is a slight complication, however. When the control is
745 # deleted, SetValue is called. But if we call SetMark at that
746 # time, wxPython will crash. We can prevent this by comparing the
747 # result of GetLastPosition and the length of the value. If they
748 # match, all is fine. If they don't match, we don't call SetMark.
749 if self
._text
.GetLastPosition() == len(value
):
750 self
._text
.SetMark(0, self
._text
.GetLastPosition())
752 def Popup(self
, *args
, **kwargs
):
753 """ Extend Popup to store a copy of the current value, so we can
754 restore it later (in NotifyNoItemSelected). This is necessary
755 because MSWComboTreeBox will change the value as the user
756 browses through the items in the popped up tree. """
757 self
._previousValue
= self
.GetValue()
758 super(MSWComboTreeBox
, self
).Popup(*args
, **kwargs
)
760 def NotifyNoItemSelected(self
, *args
, **kwargs
):
761 """ Restore the value copied previously, because the user has
762 not selected a new value. """
763 self
.SetValue(self
._previousValue
)
764 super(MSWComboTreeBox
, self
).NotifyNoItemSelected(*args
, **kwargs
)
767 class MACComboTreeBox(NativeComboTreeBox
):
768 def _createPopupFrame(self
):
769 return MACPopupFrame(self
)
771 def _createButton(self
):
772 return self
.GetChildren()[0] # The choice button
774 def _keyShouldNavigate(self
, keyEvent
):
775 return False # No navigation with up and down on wxMac
777 def _keyShouldPopUpTree(self
, keyEvent
):
778 return super(MACComboTreeBox
, self
)._keyShouldPopUpTree
(keyEvent
) or \
779 keyEvent
.GetKeyCode() == wx
.WXK_DOWN
782 class GTKComboTreeBox(BaseComboTreeBox
, wx
.Panel
):
783 """ The ComboTreeBox widget for wxGTK. This is actually a work
784 around because on wxGTK, there doesn't seem to be a way to intercept
785 mouse events sent to the Combobox. Intercepting those events is
786 necessary to prevent the Combobox from popping up the list and pop up
787 the tree instead. So, until wxPython makes intercepting those events
788 possible we build a poor man's Combobox ourselves using a TextCtrl and
791 def _createPopupFrame(self
):
792 return GTKPopupFrame(self
)
794 def _createTextCtrl(self
):
796 style
= wx
.TE_READONLY
799 return wx
.TextCtrl(self
, style
=style
)
801 def _createButton(self
):
802 bitmap
= wx
.ArtProvider
.GetBitmap(wx
.ART_GO_DOWN
, client
=wx
.ART_BUTTON
)
803 return wx
.BitmapButton(self
, bitmap
=bitmap
)
805 def _layoutInterior(self
):
806 panelSizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
807 panelSizer
.Add(self
._text
, flag
=wx
.EXPAND
, proportion
=1)
808 panelSizer
.Add(self
._button
)
809 self
.SetSizerAndFit(panelSizer
)
812 # ---------------------------------------------------------------------------
815 def ComboTreeBox(*args
, **kwargs
):
816 """ Factory function to create the right ComboTreeBox depending on
817 platform. You may force a specific class, e.g. for testing
818 purposes, by setting the keyword argument 'platform', e.g.
819 'platform=GTK' or 'platform=MSW' or platform='MAC'. """
821 platform
= kwargs
.pop('platform', None) or wx
.PlatformInfo
[0][4:7]
822 ComboTreeBoxClassName
= '%sComboTreeBox' % platform
823 ComboTreeBoxClass
= globals()[ComboTreeBoxClassName
]
824 return ComboTreeBoxClass(*args
, **kwargs
)