2006-08-21 14:32:48 -04:00
|
|
|
"""
|
|
|
|
ComboTreeBox provides a ComboBox that pops up a tree instead of a list.
|
|
|
|
|
|
|
|
ComboTreeBox tries to provide the same interface as ComboBox as much as
|
|
|
|
possible. However, whereas the ComboBox widget uses indices to access
|
|
|
|
items in the list of choices, ComboTreeBox uses TreeItemId's instead. If
|
|
|
|
you add an item to the ComboTreeBox (using Append or Insert), the
|
|
|
|
TreeItemId associated with the added item is returned. You can then use
|
|
|
|
that TreeItemId to add items as children of that first item. For
|
|
|
|
example:
|
|
|
|
|
|
|
|
>>> from wx.lib.combotreebox import ComboTreeBox
|
|
|
|
>>> combo = ComboTreeBox(parent)
|
|
|
|
>>> item1 = combo.Append('Item 1') # Add a root item
|
|
|
|
>>> item1a = combo.Append('Item 1a', parent=item1) # Add a child to item1
|
|
|
|
|
|
|
|
You can also add client data to each of the items like this:
|
|
|
|
>>> item1 = combo.Append('Item 1', clientData=somePythonObject)
|
|
|
|
>>> item1a = combo.Append('Item 1a', parent=item1,
|
|
|
|
... clientData=someOtherPythonObject)
|
|
|
|
|
|
|
|
And later fetch the client data like this:
|
|
|
|
>>> somePythonObject = combo.GetClientData(item1)
|
|
|
|
|
|
|
|
To get the client data of the currently selected item (if any):
|
|
|
|
>>> currentItem = combo.GetSelection()
|
|
|
|
>>> if currentItem:
|
|
|
|
>>> somePythonObject = combo.GetClientData(currentItem)
|
|
|
|
|
|
|
|
Supported styles are the same as for ComboBox, i.e. wx.CB_READONLY and
|
|
|
|
wx.CB_SORT. Provide them as usual:
|
|
|
|
>>> combo = ComboTreeBox(parent, style=wx.CB_READONLY|wx.CB_SORT)
|
|
|
|
|
|
|
|
Supported platforms: wxMSW and wxMAC natively, wxGTK by means of a
|
|
|
|
workaround.
|
|
|
|
|
|
|
|
Author: Frank Niessink <frank@niessink.com>
|
|
|
|
Copyright 2006, Frank Niessink
|
|
|
|
License: wxWidgets license
|
2006-09-07 13:14:57 -04:00
|
|
|
Version: 0.9
|
|
|
|
Date: September 6, 2006
|
2006-08-21 14:32:48 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
import wx
|
|
|
|
|
|
|
|
__all__ = ['ComboTreeBox'] # Export only the ComboTreeBox widget
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class IterableTreeCtrl(wx.TreeCtrl):
|
|
|
|
"""
|
|
|
|
TreeCtrl is the same as wx.TreeCtrl, with a few convenience methods
|
|
|
|
added for easier navigation of items. """
|
|
|
|
|
|
|
|
def GetPreviousItem(self, item):
|
|
|
|
"""
|
|
|
|
GetPreviousItem(self, TreeItemId item) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the item that is on the line immediately above item
|
|
|
|
(as is displayed when the tree is fully expanded). The returned
|
|
|
|
item is invalid if item is the first item in the tree.
|
|
|
|
"""
|
|
|
|
previousSibling = self.GetPrevSibling(item)
|
|
|
|
if previousSibling:
|
|
|
|
return self.GetLastChildRecursively(previousSibling)
|
|
|
|
else:
|
|
|
|
parent = self.GetItemParent(item)
|
|
|
|
if parent == self.GetRootItem() and \
|
|
|
|
(self.GetWindowStyle() & wx.TR_HIDE_ROOT):
|
|
|
|
# Return an invalid item, because the root item is hidden
|
|
|
|
return previousSibling
|
|
|
|
else:
|
|
|
|
return parent
|
|
|
|
|
|
|
|
def GetNextItem(self, item):
|
|
|
|
"""
|
|
|
|
GetNextItem(self, TreeItemId item) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the item that is on the line immediately below item
|
|
|
|
(as is displayed when the tree is fully expanded). The returned
|
|
|
|
item is invalid if item is the last item in the tree.
|
|
|
|
"""
|
|
|
|
if self.ItemHasChildren(item):
|
|
|
|
firstChild, cookie = self.GetFirstChild(item)
|
|
|
|
return firstChild
|
|
|
|
else:
|
|
|
|
return self.GetNextSiblingRecursively(item)
|
|
|
|
|
|
|
|
def GetFirstItem(self):
|
|
|
|
"""
|
|
|
|
GetFirstItem(self) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the very first item in the tree. This is the root item
|
|
|
|
unless the root item is hidden. In that case the first child of
|
|
|
|
the root item is returned, if any. If the tree is empty, an
|
|
|
|
invalid tree item is returned.
|
|
|
|
"""
|
|
|
|
rootItem = self.GetRootItem()
|
|
|
|
if rootItem and (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
|
|
|
|
firstChild, cookie = self.GetFirstChild(rootItem)
|
|
|
|
return firstChild
|
|
|
|
else:
|
|
|
|
return rootItem
|
|
|
|
|
|
|
|
def GetLastChildRecursively(self, item):
|
|
|
|
"""
|
|
|
|
GetLastChildRecursively(self, TreeItemId item) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the last child of the last child ... of item. If item
|
|
|
|
has no children, item itself is returned. So the returned item
|
|
|
|
is always valid, assuming a valid item has been passed.
|
|
|
|
"""
|
|
|
|
lastChild = item
|
|
|
|
while self.ItemHasChildren(lastChild):
|
|
|
|
lastChild = self.GetLastChild(lastChild)
|
|
|
|
return lastChild
|
|
|
|
|
|
|
|
def GetNextSiblingRecursively(self, item):
|
|
|
|
"""
|
|
|
|
GetNextSiblingRecursively(self, TreeItemId item) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the next sibling of item if it has one. If item has no
|
|
|
|
next sibling the next sibling of the parent of item is returned.
|
|
|
|
If the parent has no next sibling the next sibling of the parent
|
|
|
|
of the parent is returned, etc. If none of the ancestors of item
|
|
|
|
has a next sibling, an invalid item is returned.
|
|
|
|
"""
|
|
|
|
if item == self.GetRootItem():
|
|
|
|
return wx.TreeItemId() # Return an invalid TreeItemId
|
|
|
|
nextSibling = self.GetNextSibling(item)
|
|
|
|
if nextSibling:
|
|
|
|
return nextSibling
|
|
|
|
else:
|
|
|
|
parent = self.GetItemParent(item)
|
|
|
|
return self.GetNextSiblingRecursively(parent)
|
|
|
|
|
|
|
|
def GetSelection(self):
|
|
|
|
""" Extend GetSelection to never return the root item if the
|
|
|
|
root item is hidden. """
|
|
|
|
selection = super(IterableTreeCtrl, self).GetSelection()
|
|
|
|
if selection == self.GetRootItem() and \
|
|
|
|
(self.GetWindowStyle() & wx.TR_HIDE_ROOT):
|
|
|
|
return wx.TreeItemId() # Return an invalid TreeItemId
|
|
|
|
else:
|
|
|
|
return selection
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
2006-09-07 13:14:57 -04:00
|
|
|
class BasePopupFrame(wx.Frame):
|
2006-08-21 14:32:48 -04:00
|
|
|
"""
|
|
|
|
BasePopupFrame is the base class for platform specific
|
|
|
|
versions of the PopupFrame. The PopupFrame is the frame that
|
|
|
|
is popped up by ComboTreeBox. It contains the tree of items
|
|
|
|
that the user can select one item from. Upon selection, or
|
|
|
|
when focus is lost, the frame is hidden. """
|
|
|
|
|
|
|
|
def __init__(self, parent):
|
|
|
|
super(BasePopupFrame, self).__init__(parent,
|
|
|
|
style=wx.DEFAULT_FRAME_STYLE & wx.FRAME_FLOAT_ON_PARENT &
|
|
|
|
~(wx.RESIZE_BORDER | wx.CAPTION))
|
|
|
|
self._createInterior()
|
|
|
|
self._layoutInterior()
|
|
|
|
self._bindEventHandlers()
|
|
|
|
|
|
|
|
def _createInterior(self):
|
|
|
|
self._tree = IterableTreeCtrl(self,
|
|
|
|
style=wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT|wx.TR_HAS_BUTTONS)
|
|
|
|
self._tree.AddRoot('Hidden root node')
|
|
|
|
|
|
|
|
def _layoutInterior(self):
|
|
|
|
frameSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
frameSizer.Add(self._tree, flag=wx.EXPAND, proportion=1)
|
|
|
|
self.SetSizerAndFit(frameSizer)
|
|
|
|
|
|
|
|
def _bindEventHandlers(self):
|
|
|
|
self._tree.Bind(wx.EVT_CHAR, self.OnChar)
|
|
|
|
self._tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
|
|
|
|
self._tree.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
|
|
|
|
|
|
|
|
def _bindKillFocus(self):
|
|
|
|
self._tree.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
|
|
|
|
|
|
|
|
def _unbindKillFocus(self):
|
|
|
|
self._tree.Unbind(wx.EVT_KILL_FOCUS)
|
|
|
|
|
|
|
|
def OnKillFocus(self, event):
|
|
|
|
# We hide the frame rather than destroy it, so it can be
|
|
|
|
# popped up again later:
|
|
|
|
self.Hide()
|
|
|
|
self.GetParent().NotifyNoItemSelected()
|
|
|
|
event.Skip()
|
|
|
|
|
|
|
|
def OnChar(self, keyEvent):
|
|
|
|
if self._keyShouldHidePopup(keyEvent):
|
|
|
|
self.Hide()
|
|
|
|
self.GetParent().NotifyNoItemSelected()
|
|
|
|
keyEvent.Skip()
|
|
|
|
|
|
|
|
def _keyShouldHidePopup(self, keyEvent):
|
|
|
|
return keyEvent.GetKeyCode() == wx.WXK_ESCAPE
|
|
|
|
|
|
|
|
def OnMouseClick(self, event):
|
|
|
|
item, flags = self._tree.HitTest(event.GetPosition())
|
|
|
|
if item and (flags & wx.TREE_HITTEST_ONITEMLABEL):
|
|
|
|
self._tree.SelectItem(item)
|
|
|
|
self.Hide()
|
|
|
|
self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
|
|
|
|
else:
|
|
|
|
event.Skip()
|
|
|
|
|
|
|
|
def OnItemActivated(self, event):
|
|
|
|
item = event.GetItem()
|
|
|
|
self.Hide()
|
|
|
|
self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
|
|
|
|
|
|
|
|
def Show(self):
|
|
|
|
self._bindKillFocus()
|
|
|
|
wx.CallAfter(self._tree.SetFocus)
|
|
|
|
super(BasePopupFrame, self).Show()
|
|
|
|
|
|
|
|
def Hide(self):
|
|
|
|
self._unbindKillFocus()
|
|
|
|
super(BasePopupFrame, self).Hide()
|
|
|
|
|
|
|
|
def GetTree(self):
|
|
|
|
return self._tree
|
|
|
|
|
|
|
|
|
|
|
|
class MSWPopupFrame(BasePopupFrame):
|
|
|
|
def Show(self):
|
|
|
|
# Comply with the MS Windows Combobox behaviour: if the text in
|
|
|
|
# the text field is not in the tree, the first item in the tree
|
|
|
|
# is selected.
|
|
|
|
if not self._tree.GetSelection():
|
|
|
|
self._tree.SelectItem(self._tree.GetFirstItem())
|
|
|
|
super(MSWPopupFrame, self).Show()
|
|
|
|
|
|
|
|
|
|
|
|
class MACPopupFrame(BasePopupFrame):
|
|
|
|
def _bindKillFocus(self):
|
|
|
|
# On wxMac, the kill focus event doesn't work, but the
|
|
|
|
# deactivate event does:
|
|
|
|
self.Bind(wx.EVT_ACTIVATE, self.OnKillFocus)
|
|
|
|
|
|
|
|
def _unbindKillFocus(self):
|
|
|
|
self.Unbind(wx.EVT_ACTIVATE)
|
|
|
|
|
|
|
|
def OnKillFocus(self, event):
|
|
|
|
if not event.GetActive(): # We received a deactivate event
|
|
|
|
self.Hide()
|
|
|
|
wx.CallAfter(self.GetParent().NotifyNoItemSelected)
|
|
|
|
event.Skip()
|
|
|
|
|
|
|
|
|
|
|
|
class GTKPopupFrame(BasePopupFrame):
|
|
|
|
def _keyShouldHidePopup(self, keyEvent):
|
|
|
|
# On wxGTK, Alt-Up also closes the popup:
|
|
|
|
return super(GTKPopupFrame, self)._keyShouldHidePopup(keyEvent) or \
|
|
|
|
(keyEvent.AltDown() and keyEvent.GetKeyCode() == wx.WXK_UP)
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class BaseComboTreeBox(object):
|
|
|
|
""" BaseComboTreeBox is the base class for platform specific
|
|
|
|
versions of the ComboTreeBox. """
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
style = kwargs.pop('style', 0)
|
|
|
|
if style & wx.CB_READONLY:
|
|
|
|
style &= ~wx.CB_READONLY # We manage readonlyness ourselves
|
|
|
|
self._readOnly = True
|
|
|
|
else:
|
|
|
|
self._readOnly = False
|
|
|
|
if style & wx.CB_SORT:
|
|
|
|
style &= ~wx.CB_SORT # We manage sorting ourselves
|
|
|
|
self._sort = True
|
|
|
|
else:
|
|
|
|
self._sort = False
|
|
|
|
super(BaseComboTreeBox, self).__init__(style=style, *args, **kwargs)
|
|
|
|
self._createInterior()
|
|
|
|
self._layoutInterior()
|
|
|
|
self._bindEventHandlers()
|
|
|
|
|
|
|
|
# Methods to construct the widget.
|
|
|
|
|
|
|
|
def _createInterior(self):
|
|
|
|
self._popupFrame = self._createPopupFrame()
|
|
|
|
self._text = self._createTextCtrl()
|
|
|
|
self._button = self._createButton()
|
|
|
|
self._tree = self._popupFrame.GetTree()
|
|
|
|
|
|
|
|
def _createTextCtrl(self):
|
|
|
|
return self # By default, the text control is the control itself.
|
|
|
|
|
|
|
|
def _createButton(self):
|
|
|
|
return self # By default, the dropdown button is the control itself.
|
|
|
|
|
|
|
|
def _createPopupFrame(self):
|
|
|
|
# It is a subclass responsibility to provide the right PopupFrame,
|
|
|
|
# depending on platform:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def _layoutInterior(self):
|
|
|
|
pass # By default, there is no layout to be done.
|
|
|
|
|
|
|
|
def _bindEventHandlers(self):
|
|
|
|
for eventSource, eventType, eventHandler in self._eventsToBind():
|
|
|
|
eventSource.Bind(eventType, eventHandler)
|
|
|
|
|
|
|
|
def _eventsToBind(self):
|
|
|
|
"""
|
|
|
|
_eventsToBind(self) -> [(eventSource, eventType, eventHandler), ...]
|
|
|
|
|
|
|
|
_eventsToBind returns a list of eventSource, eventType,
|
|
|
|
eventHandlers tuples that will be bound. This method can be
|
|
|
|
extended to bind additional events. In that case, don't
|
|
|
|
forget to call _eventsToBind on the super class. """
|
|
|
|
return [(self._text, wx.EVT_KEY_DOWN, self.OnKeyDown),
|
|
|
|
(self._text, wx.EVT_TEXT, self.OnText),
|
|
|
|
(self._button, wx.EVT_BUTTON, self.OnMouseClick)]
|
|
|
|
|
|
|
|
# Event handlers
|
|
|
|
|
|
|
|
def OnMouseClick(self, event):
|
|
|
|
self.Popup()
|
|
|
|
# Note that we don't call event.Skip() to prevent popping up the
|
|
|
|
# ComboBox's own box.
|
|
|
|
|
|
|
|
def OnKeyDown(self, keyEvent):
|
|
|
|
if self._keyShouldNavigate(keyEvent):
|
|
|
|
self._navigateUpOrDown(keyEvent)
|
|
|
|
elif self._keyShouldPopUpTree(keyEvent):
|
|
|
|
self.Popup()
|
|
|
|
else:
|
|
|
|
keyEvent.Skip()
|
|
|
|
|
|
|
|
def _keyShouldPopUpTree(self, keyEvent):
|
|
|
|
return (keyEvent.AltDown() or keyEvent.MetaDown()) and \
|
|
|
|
keyEvent.GetKeyCode() == wx.WXK_DOWN
|
|
|
|
|
|
|
|
def _keyShouldNavigate(self, keyEvent):
|
|
|
|
return keyEvent.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP) and not \
|
|
|
|
self._keyShouldPopUpTree(keyEvent)
|
|
|
|
|
|
|
|
def _navigateUpOrDown(self, keyEvent):
|
|
|
|
item = self.GetSelection()
|
|
|
|
if item:
|
|
|
|
navigationMethods = {wx.WXK_DOWN: self._tree.GetNextItem,
|
|
|
|
wx.WXK_UP: self._tree.GetPreviousItem}
|
|
|
|
getNextItem = navigationMethods[keyEvent.GetKeyCode()]
|
|
|
|
nextItem = getNextItem(item)
|
|
|
|
else:
|
|
|
|
nextItem = self._tree.GetFirstItem()
|
|
|
|
if nextItem:
|
|
|
|
self.SetSelection(nextItem)
|
|
|
|
|
|
|
|
def OnText(self, event):
|
|
|
|
event.Skip()
|
|
|
|
item = self.FindString(self._text.GetValue())
|
|
|
|
if item:
|
|
|
|
if self._tree.GetSelection() != item:
|
|
|
|
self._tree.SelectItem(item)
|
|
|
|
else:
|
|
|
|
self._tree.Unselect()
|
|
|
|
|
|
|
|
# Methods called by the PopupFrame, to let the ComboTreeBox know
|
|
|
|
# about what the user did.
|
|
|
|
|
|
|
|
def NotifyItemSelected(self, text):
|
|
|
|
""" Simulate selection of an item by the user. This is meant to
|
|
|
|
be called by the PopupFrame when the user selects an item. """
|
|
|
|
self._text.SetValue(text)
|
|
|
|
self._postComboBoxSelectedEvent(text)
|
|
|
|
self.SetFocus()
|
|
|
|
|
|
|
|
def _postComboBoxSelectedEvent(self, text):
|
|
|
|
""" Simulate a selection event. """
|
|
|
|
event = wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED,
|
|
|
|
self.GetId())
|
|
|
|
event.SetString(text)
|
|
|
|
self.GetEventHandler().ProcessEvent(event)
|
|
|
|
|
|
|
|
def NotifyNoItemSelected(self):
|
|
|
|
""" This is called by the PopupFrame when the user closes the
|
|
|
|
PopupFrame, without selecting an item. """
|
|
|
|
self.SetFocus()
|
|
|
|
|
|
|
|
# Misc methods, not part of the ComboBox API.
|
|
|
|
|
|
|
|
def Popup(self):
|
|
|
|
"""
|
|
|
|
Popup(self)
|
|
|
|
|
|
|
|
Pops up the frame with the tree.
|
|
|
|
"""
|
|
|
|
comboBoxSize = self.GetSize()
|
|
|
|
x, y = self.GetParent().ClientToScreen(self.GetPosition())
|
|
|
|
y += comboBoxSize[1]
|
|
|
|
width = comboBoxSize[0]
|
|
|
|
height = 300
|
|
|
|
self._popupFrame.SetDimensions(x, y, width, height)
|
|
|
|
# On wxGTK, when the Combobox width has been increased a call
|
|
|
|
# to SetMinSize is needed to force a resize of the popupFrame:
|
|
|
|
self._popupFrame.SetMinSize((width, height))
|
|
|
|
self._popupFrame.Show()
|
|
|
|
|
|
|
|
def GetTree(self):
|
|
|
|
"""
|
|
|
|
GetTree(self) -> wx.TreeCtrl
|
|
|
|
|
|
|
|
Returns the tree control that is popped up.
|
|
|
|
"""
|
|
|
|
return self._popupFrame.GetTree()
|
|
|
|
|
|
|
|
def FindClientData(self, clientData, parent=None):
|
|
|
|
"""
|
|
|
|
FindClientData(self, PyObject clientData, TreeItemId parent=None)
|
|
|
|
-> TreeItemId
|
|
|
|
|
|
|
|
Finds the *first* item in the tree with client data equal to the
|
|
|
|
given clientData. If no such item exists, an invalid item is
|
|
|
|
returned.
|
|
|
|
"""
|
|
|
|
parent = parent or self._tree.GetRootItem()
|
|
|
|
child, cookie = self._tree.GetFirstChild(parent)
|
|
|
|
while child:
|
|
|
|
if self.GetClientData(child) == clientData:
|
|
|
|
return child
|
|
|
|
else:
|
|
|
|
result = self.FindClientData(clientData, child)
|
|
|
|
if result:
|
|
|
|
return result
|
|
|
|
child, cookie = self._tree.GetNextChild(parent, cookie)
|
|
|
|
return child
|
|
|
|
|
|
|
|
def SetClientDataSelection(self, clientData):
|
|
|
|
"""
|
|
|
|
SetClientDataSelection(self, PyObject clientData) -> bool
|
|
|
|
|
|
|
|
Selects the item with the provided clientData in the control.
|
|
|
|
Returns True if the item belonging to the clientData has been
|
|
|
|
selected, False if it wasn't found in the control.
|
|
|
|
"""
|
|
|
|
item = self.FindClientData(clientData)
|
|
|
|
if item:
|
|
|
|
self._tree.SelectItem(item)
|
2006-09-07 13:14:57 -04:00
|
|
|
string = self._tree.GetItemText(item)
|
|
|
|
if self._text.GetValue() != string:
|
|
|
|
self._text.SetValue(string)
|
2006-08-21 14:32:48 -04:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# The following methods are all part of the ComboBox API (actually
|
|
|
|
# the ControlWithItems API) and have been adapted to take TreeItemIds
|
|
|
|
# as parameter and return TreeItemIds, rather than indices.
|
|
|
|
|
|
|
|
def Append(self, itemText, parent=None, clientData=None):
|
|
|
|
"""
|
|
|
|
Append(self, String itemText, TreeItemId parent=None, PyObject
|
|
|
|
clientData=None) -> TreeItemId
|
|
|
|
|
|
|
|
Adds the itemText to the control, associating the given clientData
|
|
|
|
with the item if not None. If parent is None, itemText is added
|
|
|
|
as a root item, else itemText is added as a child item of
|
|
|
|
parent. The return value is the TreeItemId of the newly added
|
|
|
|
item. """
|
|
|
|
if parent is None:
|
|
|
|
parent = self._tree.GetRootItem()
|
|
|
|
item = self._tree.AppendItem(parent, itemText,
|
|
|
|
data=wx.TreeItemData(clientData))
|
|
|
|
if self._sort:
|
|
|
|
self._tree.SortChildren(parent)
|
|
|
|
return item
|
|
|
|
|
|
|
|
def Clear(self):
|
|
|
|
"""
|
|
|
|
Clear(self)
|
|
|
|
|
|
|
|
Removes all items from the control.
|
|
|
|
"""
|
|
|
|
return self._tree.DeleteAllItems()
|
|
|
|
|
|
|
|
def Delete(self, item):
|
|
|
|
"""
|
|
|
|
Delete(self, TreeItemId item)
|
|
|
|
|
|
|
|
Deletes the item from the control.
|
|
|
|
"""
|
|
|
|
return self._tree.Delete(item)
|
|
|
|
|
|
|
|
def FindString(self, string, parent=None):
|
|
|
|
"""
|
|
|
|
FindString(self, String string, TreeItemId parent=None) -> TreeItemId
|
|
|
|
|
|
|
|
Finds the *first* item in the tree with a label equal to the
|
|
|
|
given string. If no such item exists, an invalid item is
|
|
|
|
returned.
|
|
|
|
"""
|
|
|
|
parent = parent or self._tree.GetRootItem()
|
|
|
|
child, cookie = self._tree.GetFirstChild(parent)
|
|
|
|
while child:
|
|
|
|
if self._tree.GetItemText(child) == string:
|
|
|
|
return child
|
|
|
|
else:
|
|
|
|
result = self.FindString(string, child)
|
|
|
|
if result:
|
|
|
|
return result
|
|
|
|
child, cookie = self._tree.GetNextChild(parent, cookie)
|
|
|
|
return child
|
|
|
|
|
|
|
|
def GetSelection(self):
|
|
|
|
"""
|
|
|
|
GetSelection(self) -> TreeItemId
|
|
|
|
|
|
|
|
Returns the TreeItemId of the selected item or an invalid item
|
|
|
|
if no item is selected.
|
|
|
|
"""
|
|
|
|
selectedItem = self._tree.GetSelection()
|
|
|
|
if selectedItem and selectedItem != self._tree.GetRootItem():
|
|
|
|
return selectedItem
|
|
|
|
else:
|
|
|
|
return self.FindString(self.GetValue())
|
|
|
|
|
|
|
|
def GetString(self, item):
|
|
|
|
"""
|
|
|
|
GetString(self, TreeItemId item) -> String
|
|
|
|
|
|
|
|
Returns the label of the given item.
|
|
|
|
"""
|
|
|
|
if item:
|
|
|
|
return self._tree.GetItemText(item)
|
|
|
|
else:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def GetStringSelection(self):
|
|
|
|
"""
|
|
|
|
GetStringSelection(self) -> String
|
|
|
|
|
|
|
|
Returns the label of the selected item or an empty string if no item
|
|
|
|
is selected.
|
|
|
|
"""
|
|
|
|
return self.GetValue()
|
|
|
|
|
|
|
|
def Insert(self, itemText, previous=None, parent=None, clientData=None):
|
|
|
|
"""
|
|
|
|
Insert(self, String itemText, TreeItemId previous=None, TreeItemId
|
|
|
|
parent=None, PyObject clientData=None) -> TreeItemId
|
|
|
|
|
|
|
|
Insert an item into the control before the ``previous`` item
|
|
|
|
and/or as child of the ``parent`` item. The itemText is associated
|
|
|
|
with clientData when not None.
|
|
|
|
"""
|
|
|
|
data = wx.TreeItemData(clientData)
|
|
|
|
if parent is None:
|
|
|
|
parent = self._tree.GetRootItem()
|
|
|
|
if previous is None:
|
|
|
|
item = self._tree.InsertItemBefore(parent, 0, itemText, data=data)
|
|
|
|
else:
|
|
|
|
item = self._tree.InsertItem(parent, previous, itemText, data=data)
|
|
|
|
if self._sort:
|
|
|
|
self._tree.SortChildren(parent)
|
|
|
|
return item
|
|
|
|
|
|
|
|
def IsEmpty(self):
|
|
|
|
"""
|
|
|
|
IsEmpty(self) -> bool
|
|
|
|
|
|
|
|
Returns True if the control is empty or False if it has some items.
|
|
|
|
"""
|
|
|
|
return self.GetCount() == 0
|
|
|
|
|
|
|
|
def GetCount(self):
|
|
|
|
"""
|
|
|
|
GetCount(self) -> int
|
|
|
|
|
|
|
|
Returns the number of items in the control.
|
|
|
|
"""
|
|
|
|
# Note: We don't need to substract 1 for the hidden root item,
|
|
|
|
# because the TreeCtrl does that for us
|
|
|
|
return self._tree.GetCount()
|
|
|
|
|
|
|
|
def SetSelection(self, item):
|
|
|
|
"""
|
|
|
|
SetSelection(self, TreeItemId item)
|
|
|
|
|
|
|
|
Sets the provided item to be the selected item.
|
|
|
|
"""
|
|
|
|
self._tree.SelectItem(item)
|
|
|
|
self._text.SetValue(self._tree.GetItemText(item))
|
|
|
|
|
|
|
|
Select = SetSelection
|
|
|
|
|
|
|
|
def SetString(self, item, string):
|
|
|
|
"""
|
|
|
|
SetString(self, TreeItemId item, String string)
|
|
|
|
|
|
|
|
Sets the label for the provided item.
|
|
|
|
"""
|
|
|
|
self._tree.SetItemText(item, string)
|
|
|
|
if self._sort:
|
|
|
|
self._tree.SortChildren(self._tree.GetItemParent(item))
|
|
|
|
|
|
|
|
def SetStringSelection(self, string):
|
|
|
|
"""
|
|
|
|
SetStringSelection(self, String string) -> bool
|
|
|
|
|
|
|
|
Selects the item with the provided string in the control.
|
|
|
|
Returns True if the provided string has been selected, False if
|
|
|
|
it wasn't found in the control.
|
|
|
|
"""
|
|
|
|
item = self.FindString(string)
|
|
|
|
if item:
|
|
|
|
if self._text.GetValue() != string:
|
|
|
|
self._text.SetValue(string)
|
|
|
|
self._tree.SelectItem(item)
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def GetClientData(self, item):
|
|
|
|
"""
|
|
|
|
GetClientData(self, TreeItemId item) -> PyObject
|
|
|
|
|
|
|
|
Returns the client data associated with the given item, if any.
|
|
|
|
"""
|
|
|
|
return self._tree.GetItemPyData(item)
|
|
|
|
|
|
|
|
def SetClientData(self, item, clientData):
|
|
|
|
"""
|
|
|
|
SetClientData(self, TreeItemId item, PyObject clientData)
|
|
|
|
|
|
|
|
Associate the given client data with the provided item.
|
|
|
|
"""
|
|
|
|
self._tree.SetItemPyData(item, clientData)
|
|
|
|
|
|
|
|
def GetValue(self):
|
|
|
|
"""
|
|
|
|
GetValue(self) -> String
|
|
|
|
|
|
|
|
Returns the current value in the combobox text field.
|
|
|
|
"""
|
|
|
|
if self._text == self:
|
|
|
|
return super(BaseComboTreeBox, self).GetValue()
|
|
|
|
else:
|
|
|
|
return self._text.GetValue()
|
|
|
|
|
|
|
|
def SetValue(self, value):
|
|
|
|
"""
|
|
|
|
SetValue(self, String value)
|
|
|
|
|
|
|
|
Sets the text for the combobox text field.
|
|
|
|
|
|
|
|
NB: For a combobox with wxCB_READONLY style the string must be
|
|
|
|
in the combobox choices list, otherwise the call to SetValue()
|
|
|
|
is ignored.
|
|
|
|
"""
|
|
|
|
item = self._tree.GetSelection()
|
|
|
|
if not item or self._tree.GetItemText(item) != value:
|
|
|
|
item = self.FindString(value)
|
|
|
|
if self._readOnly and not item:
|
|
|
|
return
|
|
|
|
if self._text == self:
|
|
|
|
super(BaseComboTreeBox, self).SetValue(value)
|
|
|
|
else:
|
|
|
|
self._text.SetValue(value)
|
|
|
|
if item:
|
|
|
|
if self._tree.GetSelection() != item:
|
|
|
|
self._tree.SelectItem(item)
|
|
|
|
else:
|
|
|
|
self._tree.Unselect()
|
|
|
|
|
|
|
|
|
|
|
|
class NativeComboTreeBox(BaseComboTreeBox, wx.ComboBox):
|
|
|
|
""" NativeComboTreeBox, and any subclass, uses the native ComboBox as
|
|
|
|
basis, but prevent it from popping up its drop down list and
|
|
|
|
instead pops up a PopupFrame containing a tree of items. """
|
|
|
|
|
|
|
|
def _eventsToBind(self):
|
|
|
|
events = super(NativeComboTreeBox, self)._eventsToBind()
|
|
|
|
# Bind all mouse click events to self.OnMouseClick so we can
|
|
|
|
# intercept those events and prevent the native Combobox from
|
|
|
|
# popping up its list of choices.
|
|
|
|
for eventType in (wx.EVT_LEFT_DOWN, wx.EVT_LEFT_DCLICK,
|
|
|
|
wx.EVT_MIDDLE_DOWN, wx.EVT_MIDDLE_DCLICK,
|
|
|
|
wx.EVT_RIGHT_DOWN, wx.EVT_RIGHT_DCLICK):
|
|
|
|
events.append((self._button, eventType, self.OnMouseClick))
|
|
|
|
if self._readOnly:
|
|
|
|
events.append((self, wx.EVT_CHAR, self.OnChar))
|
|
|
|
return events
|
|
|
|
|
|
|
|
def OnChar(self, event):
|
|
|
|
# OnChar is only called when in read only mode. We don't call
|
|
|
|
# event.Skip() on purpose, to prevent the characters from being
|
|
|
|
# displayed in the text field.
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class MSWComboTreeBox(NativeComboTreeBox):
|
|
|
|
""" MSWComboTreeBox adds one piece of functionality as compared to
|
|
|
|
NativeComboTreeBox: when the user browses through the tree, the
|
|
|
|
ComboTreeBox's text field is continuously updated to show the
|
|
|
|
currently selected item in the tree. If the user cancels
|
|
|
|
selecting a new item from the tree, e.g. by hitting escape, the
|
|
|
|
previous value (the one that was selected before the PopupFrame
|
|
|
|
was popped up) is restored. """
|
|
|
|
|
|
|
|
def _createPopupFrame(self):
|
|
|
|
return MSWPopupFrame(self)
|
|
|
|
|
|
|
|
def _eventsToBind(self):
|
|
|
|
events = super(MSWComboTreeBox, self)._eventsToBind()
|
|
|
|
events.append((self._tree, wx.EVT_TREE_SEL_CHANGED,
|
|
|
|
self.OnSelectionChangedInTree))
|
|
|
|
return events
|
|
|
|
|
|
|
|
def OnSelectionChangedInTree(self, event):
|
2006-09-07 13:14:57 -04:00
|
|
|
if self.IsBeingDeleted():
|
|
|
|
return
|
2006-08-21 14:32:48 -04:00
|
|
|
item = event.GetItem()
|
|
|
|
if item:
|
|
|
|
selectedValue = self._tree.GetItemText(item)
|
|
|
|
if self.GetValue() != selectedValue:
|
|
|
|
self.SetValue(selectedValue)
|
|
|
|
event.Skip()
|
|
|
|
|
|
|
|
def _keyShouldPopUpTree(self, keyEvent):
|
|
|
|
return super(MSWComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
|
|
|
|
(keyEvent.GetKeyCode() == wx.WXK_F4) or \
|
|
|
|
((keyEvent.AltDown() or keyEvent.MetaDown()) and \
|
|
|
|
keyEvent.GetKeyCode() == wx.WXK_UP)
|
|
|
|
|
|
|
|
def SetValue(self, value):
|
|
|
|
""" Extend SetValue to also select the text in the
|
|
|
|
ComboTreeBox's text field. """
|
|
|
|
super(MSWComboTreeBox, self).SetValue(value)
|
|
|
|
# We select the text in the ComboTreeBox's text field.
|
|
|
|
# There is a slight complication, however. When the control is
|
|
|
|
# deleted, SetValue is called. But if we call SetMark at that
|
|
|
|
# time, wxPython will crash. We can prevent this by comparing the
|
|
|
|
# result of GetLastPosition and the length of the value. If they
|
|
|
|
# match, all is fine. If they don't match, we don't call SetMark.
|
|
|
|
if self._text.GetLastPosition() == len(value):
|
|
|
|
self._text.SetMark(0, self._text.GetLastPosition())
|
|
|
|
|
|
|
|
def Popup(self, *args, **kwargs):
|
|
|
|
""" Extend Popup to store a copy of the current value, so we can
|
|
|
|
restore it later (in NotifyNoItemSelected). This is necessary
|
|
|
|
because MSWComboTreeBox will change the value as the user
|
|
|
|
browses through the items in the popped up tree. """
|
|
|
|
self._previousValue = self.GetValue()
|
|
|
|
super(MSWComboTreeBox, self).Popup(*args, **kwargs)
|
|
|
|
|
|
|
|
def NotifyNoItemSelected(self, *args, **kwargs):
|
|
|
|
""" Restore the value copied previously, because the user has
|
|
|
|
not selected a new value. """
|
|
|
|
self.SetValue(self._previousValue)
|
|
|
|
super(MSWComboTreeBox, self).NotifyNoItemSelected(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class MACComboTreeBox(NativeComboTreeBox):
|
|
|
|
def _createPopupFrame(self):
|
|
|
|
return MACPopupFrame(self)
|
|
|
|
|
|
|
|
def _createButton(self):
|
|
|
|
return self.GetChildren()[0] # The choice button
|
|
|
|
|
|
|
|
def _keyShouldNavigate(self, keyEvent):
|
|
|
|
return False # No navigation with up and down on wxMac
|
|
|
|
|
|
|
|
def _keyShouldPopUpTree(self, keyEvent):
|
|
|
|
return super(MACComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
|
|
|
|
keyEvent.GetKeyCode() == wx.WXK_DOWN
|
|
|
|
|
|
|
|
|
|
|
|
class GTKComboTreeBox(BaseComboTreeBox, wx.Panel):
|
|
|
|
""" The ComboTreeBox widget for wxGTK. This is actually a work
|
|
|
|
around because on wxGTK, there doesn't seem to be a way to intercept
|
|
|
|
mouse events sent to the Combobox. Intercepting those events is
|
|
|
|
necessary to prevent the Combobox from popping up the list and pop up
|
|
|
|
the tree instead. So, until wxPython makes intercepting those events
|
|
|
|
possible we build a poor man's Combobox ourselves using a TextCtrl and
|
|
|
|
a BitmapButton. """
|
|
|
|
|
|
|
|
def _createPopupFrame(self):
|
|
|
|
return GTKPopupFrame(self)
|
|
|
|
|
|
|
|
def _createTextCtrl(self):
|
|
|
|
if self._readOnly:
|
|
|
|
style = wx.TE_READONLY
|
|
|
|
else:
|
|
|
|
style = 0
|
|
|
|
return wx.TextCtrl(self, style=style)
|
|
|
|
|
|
|
|
def _createButton(self):
|
|
|
|
bitmap = wx.ArtProvider.GetBitmap(wx.ART_GO_DOWN, client=wx.ART_BUTTON)
|
|
|
|
return wx.BitmapButton(self, bitmap=bitmap)
|
|
|
|
|
|
|
|
def _layoutInterior(self):
|
|
|
|
panelSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
panelSizer.Add(self._text, flag=wx.EXPAND, proportion=1)
|
|
|
|
panelSizer.Add(self._button)
|
|
|
|
self.SetSizerAndFit(panelSizer)
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def ComboTreeBox(*args, **kwargs):
|
|
|
|
""" Factory function to create the right ComboTreeBox depending on
|
|
|
|
platform. You may force a specific class, e.g. for testing
|
|
|
|
purposes, by setting the keyword argument 'platform', e.g.
|
|
|
|
'platform=GTK' or 'platform=MSW' or platform='MAC'. """
|
|
|
|
|
|
|
|
platform = kwargs.pop('platform', None) or wx.PlatformInfo[0][4:7]
|
|
|
|
ComboTreeBoxClassName = '%sComboTreeBox' % platform
|
|
|
|
ComboTreeBoxClass = globals()[ComboTreeBoxClassName]
|
|
|
|
return ComboTreeBoxClass(*args, **kwargs)
|
|
|
|
|