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 
  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
.MiniFrame
): 
 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
) 
 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. 
 461     def Append(self
, itemText
, parent
=None, clientData
=None): 
 463         Append(self, String itemText, TreeItemId parent=None, PyObject 
 464                clientData=None) -> TreeItemId 
 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 
 472             parent 
= self
._tree
.GetRootItem() 
 473         item 
= self
._tree
.AppendItem(parent
, itemText
,  
 474                                      data
=wx
.TreeItemData(clientData
)) 
 476             self
._tree
.SortChildren(parent
) 
 483         Removes all items from the control. 
 485         return self
._tree
.DeleteAllItems() 
 487     def Delete(self
, item
): 
 489         Delete(self, TreeItemId item) 
 491         Deletes the item from the control.  
 493         return self
._tree
.Delete(item
) 
 495     def FindString(self
, string
, parent
=None): 
 497         FindString(self, String string, TreeItemId parent=None) -> TreeItemId 
 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 
 503         parent 
= parent 
or self
._tree
.GetRootItem() 
 504         child
, cookie 
= self
._tree
.GetFirstChild(parent
) 
 506             if self
._tree
.GetItemText(child
) == string
: 
 509                 result 
= self
.FindString(string
, child
) 
 512             child
, cookie 
= self
._tree
.GetNextChild(parent
, cookie
) 
 515     def GetSelection(self
): 
 517         GetSelection(self) -> TreeItemId 
 519         Returns the TreeItemId of the selected item or an invalid item 
 520         if no item is selected. 
 522         selectedItem 
= self
._tree
.GetSelection() 
 523         if selectedItem 
and selectedItem 
!= self
._tree
.GetRootItem(): 
 526             return self
.FindString(self
.GetValue()) 
 528     def GetString(self
, item
): 
 530         GetString(self, TreeItemId item) -> String 
 532         Returns the label of the given item. 
 535             return self
._tree
.GetItemText(item
) 
 539     def GetStringSelection(self
): 
 541         GetStringSelection(self) -> String 
 543         Returns the label of the selected item or an empty string if no item  
 546         return self
.GetValue() 
 548     def Insert(self
, itemText
, previous
=None, parent
=None, clientData
=None): 
 550         Insert(self, String itemText, TreeItemId previous=None, TreeItemId 
 551                parent=None, PyObject clientData=None) -> TreeItemId 
 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. 
 557         data 
= wx
.TreeItemData(clientData
) 
 559             parent 
= self
._tree
.GetRootItem() 
 561             item 
= self
._tree
.InsertItemBefore(parent
, 0, itemText
, data
=data
) 
 563             item 
= self
._tree
.InsertItem(parent
, previous
, itemText
, data
=data
) 
 565             self
._tree
.SortChildren(parent
) 
 570         IsEmpty(self) -> bool 
 572         Returns True if the control is empty or False if it has some items. 
 574         return self
.GetCount() == 0 
 578         GetCount(self) -> int 
 580         Returns the number of items in the control. 
 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()  
 586     def SetSelection(self
, item
): 
 588         SetSelection(self, TreeItemId item)  
 590         Sets the provided item to be the selected item. 
 592         self
._tree
.SelectItem(item
) 
 593         self
._text
.SetValue(self
._tree
.GetItemText(item
)) 
 595     Select 
= SetSelection
 
 597     def SetString(self
, item
, string
): 
 599         SetString(self, TreeItemId item, String string) 
 601         Sets the label for the provided item. 
 603         self
._tree
.SetItemText(item
, string
) 
 605             self
._tree
.SortChildren(self
._tree
.GetItemParent(item
)) 
 607     def SetStringSelection(self
, string
): 
 609         SetStringSelection(self, String string) -> bool 
 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. 
 615         item 
= self
.FindString(string
) 
 617             if self
._text
.GetValue() != string
: 
 618                 self
._text
.SetValue(string
) 
 619             self
._tree
.SelectItem(item
) 
 624     def GetClientData(self
, item
): 
 626         GetClientData(self, TreeItemId item) -> PyObject 
 628         Returns the client data associated with the given item, if any. 
 630         return self
._tree
.GetItemPyData(item
) 
 632     def SetClientData(self
, item
, clientData
): 
 634         SetClientData(self, TreeItemId item, PyObject clientData) 
 636         Associate the given client data with the provided item. 
 638         self
._tree
.SetItemPyData(item
, clientData
) 
 642         GetValue(self) -> String 
 644         Returns the current value in the combobox text field. 
 646         if self
._text 
== self
: 
 647             return super(BaseComboTreeBox
, self
).GetValue() 
 649             return self
._text
.GetValue() 
 651     def SetValue(self
, value
): 
 653         SetValue(self, String value) 
 655         Sets the text for the combobox text field. 
 657         NB: For a combobox with wxCB_READONLY style the string must be 
 658         in the combobox choices list, otherwise the call to SetValue() 
 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
: 
 666         if self
._text 
== self
: 
 667             super(BaseComboTreeBox
, self
).SetValue(value
) 
 669             self
._text
.SetValue(value
) 
 671             if self
._tree
.GetSelection() != item
: 
 672                 self
._tree
.SelectItem(item
) 
 674             self
._tree
.Unselect() 
 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. """ 
 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
)) 
 692             events
.append((self
, wx
.EVT_CHAR
, self
.OnChar
)) 
 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. 
 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. """ 
 711     def _createPopupFrame(self
): 
 712         return MSWPopupFrame(self
) 
 714     def _eventsToBind(self
): 
 715         events 
= super(MSWComboTreeBox
, self
)._eventsToBind
() 
 716         events
.append((self
._tree
, wx
.EVT_TREE_SEL_CHANGED
, 
 717             self
.OnSelectionChangedInTree
)) 
 720     def OnSelectionChangedInTree(self
, event
): 
 721         item 
= event
.GetItem() 
 723             selectedValue 
= self
._tree
.GetItemText(item
) 
 724             if self
.GetValue() != selectedValue
: 
 725                 self
.SetValue(selectedValue
) 
 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
) 
 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()) 
 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
) 
 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
) 
 762 class MACComboTreeBox(NativeComboTreeBox
): 
 763     def _createPopupFrame(self
): 
 764         return MACPopupFrame(self
) 
 766     def _createButton(self
): 
 767         return self
.GetChildren()[0] # The choice button 
 769     def _keyShouldNavigate(self
, keyEvent
): 
 770         return False # No navigation with up and down on wxMac 
 772     def _keyShouldPopUpTree(self
, keyEvent
): 
 773         return super(MACComboTreeBox
, self
)._keyShouldPopUpTree
(keyEvent
) or \
 
 774             keyEvent
.GetKeyCode() == wx
.WXK_DOWN
 
 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 
 786     def _createPopupFrame(self
): 
 787         return GTKPopupFrame(self
) 
 789     def _createTextCtrl(self
): 
 791             style 
= wx
.TE_READONLY
 
 794         return wx
.TextCtrl(self
, style
=style
) 
 796     def _createButton(self
): 
 797         bitmap 
= wx
.ArtProvider
.GetBitmap(wx
.ART_GO_DOWN
, client
=wx
.ART_BUTTON
) 
 798         return wx
.BitmapButton(self
, bitmap
=bitmap
) 
 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
) 
 807 # --------------------------------------------------------------------------- 
 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'. """ 
 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
)