Source code for enaml.wx.wx_tool_bar

#------------------------------------------------------------------------------
#  Copyright (c) 2012, Enthought, Inc.
#  All rights reserved.
#------------------------------------------------------------------------------
import wx

from .wx_action import WxAction, wxAction, EVT_ACTION_CHANGED
from .wx_action_group import WxActionGroup
from .wx_constraints_widget import WxConstraintsWidget


#: A mapping from Enaml orientation to wx Orientation
_ORIENTATION_MAP = {
    'horizontal': wx.HORIZONTAL,
    'vertical': wx.VERTICAL,
}


class wxToolBar(wx.ToolBar):
    """ A wx.ToolBar subclass which handles wxAction instances.

    """
    def __init__(self, *args, **kwargs):
        """ Initialize a wxToolBar.

        Parameters
        ----------
        *args, **kwargs
            The position and keyword arguments needed to initialize
            an AuiToolBar.

        """
        super(wxToolBar, self).__init__(*args, **kwargs)
        self._all_items = []
        self._actions_map = {}
        self.Bind(wx.EVT_MENU, self.OnMenu)

    #--------------------------------------------------------------------------
    # Private API
    #--------------------------------------------------------------------------
    def _InsertAction(self, index, action):
        """ Insert a new tool into the tool bar for the given action.

        Parameters
        ----------
        action : wxAction
            The wxAction instance to add to the tool bar.

        Returns
        -------
        result : wxToolBarToolBase
            The tool base item created when adding the control to the
            tool bar.

        """
        if action.IsSeparator():
            item = self.InsertSeparator(index)
        else:
            text = action.GetText()
            short_help = action.GetToolTip()
            long_help = action.GetStatusTip()
            action_id = action.GetId()
            bmp = wx.EmptyBitmap(0, 0)
            if action.IsCheckable():
                item = self.InsertLabelTool(
                    index, action_id, text, bmp, kind=wx.ITEM_CHECK,
                    shortHelp=short_help, longHelp=long_help,
                )
                if action.IsChecked() != item.IsToggled():
                    item.Toggle()
            else:
                item = self.InsertLabelTool(
                    index, action_id, text, bmp, kind=wx.ITEM_NORMAL,
                    shortHelp=short_help, longHelp=long_help,
                )
            item.Enable(action.IsEnabled())
        return item

    def OnMenu(self, event):
        """ The event handler for the EVT_MENU event.

        This handler maps the event to the appropriate wxAction.

        """
        action = wxAction.FindById(event.GetId())
        if action is not None:
            if action.IsCheckable():
                action.SetChecked(event.Checked())
            action.Trigger()

    def OnActionChanged(self, event):
        """ The event handler for the EVT_ACTION_CHANGED event.

        This handler will be called when a child action changes. It
        ensures that the new state of the child action is in sync with
        the associated tool bar item.

        """
        event.Skip()
        action = event.GetEventObject()
        item = self._actions_map.get(action)

        # Handle a visibility change. The tool must be added/removed.
        visible = action.IsVisible()
        if visible != bool(item):
            if visible:
                index = self._all_items.index(action)
                index = min(index, len(self._actions_map))
                new_item = self._InsertAction(index, action)
                self._actions_map[action] = new_item
                self.Realize()
            else:
                self.DeleteTool(item.GetId())
                del self._actions_map[action]
            return

        # If the item is invisible, there is nothing to update.
        if not item:
            return

        # Handle a separator change. The existing tool must be replaced.
        if action.IsSeparator() != item.IsSeparator():
            self.DeleteTool(item.GetId())
            del self._actions_map[action]
            index = self._all_items.index(action)
            index = min(index, len(self._actions_map))
            new_item = self._InsertAction(index, action)
            self._actions_map[action] = new_item
            self.Realize()
            return

        # Handle a checkable change. The existing too must be replaced.
        if action.IsCheckable() != item.CanBeToggled():
            self.DeleteTool(item.GetId())
            del self._actions_map[action]
            index = self._all_items.index(action)
            index = min(index, len(self._actions_map))
            new_item = self._InsertAction(index, action)
            self._actions_map[action] = new_item
            self.Realize()
            return

        # All other state can be updated in-place.
        item.SetLabel(action.GetText())
        item.SetShortHelp(action.GetToolTip())
        item.SetLongHelp(action.GetStatusTip())
        if action.IsCheckable():
            if action.IsChecked() != item.IsToggled():
                item.Toggle()
        item.Enable(action.IsEnabled())
        self.Realize()

    #--------------------------------------------------------------------------
    # Public API
    #--------------------------------------------------------------------------
    def AddAction(self, action, realize=True):
        """ Add an action to the tool bar.

        If the action already exists in the toolbar, it will be moved
        to the end.

        Parameters
        ----------
        action : wxAction
            The wxAction instance to add to the tool bar.

        realize : bool, optional
            Whether the toolbar should realize the change immediately.
            If False, Realize() will need to be called manually once
            all desired changes have been made. The default is True.

        """
        self.InsertAction(None, action, realize)

    def AddActions(self, actions, realize=True):
        """ Add multiple wx actions to the tool bar.

        If an action already exists in the tool bar, it will be moved
        to the end.

        Parameters
        ----------
        actions : iterable
            An iterable of wxAction instances to add to the tool bar.

        realize : bool, optional
            Whether the toolbar should realize the change immediately.
            If False, Realize() will need to be called manually once
            all desired changes have been made. The default is True.

        """
        insert = self.InsertAction
        for action in actions:
            insert(None, action, False)
        if realize:
            self.Realize()

    def InsertAction(self, before, action, realize=True):
        """ Insert a wx action into the tool bar.

        If the action already exists in the tool bar, it will be moved
        to the proper location.

        Parameters
        ----------
        before : wxAction or None
            The action in the tool bar which should come directly after
            the new action.

        action : wxAction
            The wxAction instance to insert into this tool bar.

        realize : bool, optional
            Whether the toolbar should realize the change immediately.
            If False, Realize() will need to be called manually once
            all desired changes have been made. The default is True.

        """
        all_items = self._all_items
        if action not in all_items:
            if before in all_items:
                index = all_items.index(before)
            else:
                index = len(all_items)
            all_items.insert(index, action)
            if action.IsVisible():
                max_index = len(self._actions_map)
                index = min(index, max_index)
                item = self._InsertAction(index, action)
                self._actions_map[action] = item
            action.Bind(EVT_ACTION_CHANGED, self.OnActionChanged)
            if realize:
                self.Realize()
        else:
            # XXX this is a potentially slow way to do things if the
            # number of actions being moved around is large. But, the
            # Wx apis don't appear to offer a better way, so this is
            # what we get (as usual...).
            self.RemoveAction(action)
            self.InsertAction(before, action, realize)

    def InsertActions(self, before, actions, realize=True):
        """ Insert multiple wx actions into the Menu.

        If an action already exists in this menu, it will be moved to
        the proper location.

        Parameters
        ----------
        before : wxAction, wxMenu, or None
            The item in the menu which should come directly after the
            new actions.

        actions : iterable
            An iterable of wxAction instances to add to the tool bar.

        realize : bool, optional
            Whether the toolbar should realize the change immediately.
            If False, Realize() will need to be called manually once
            all desired changes have been made. The default is True.

        """
        insert = self.InsertAction
        for action in actions:
            insert(before, action, False)
        if realize:
            self.Realize()

    def RemoveAction(self, action):
        """ Remove a wx action from the tool bar.

        If the action does not exist in the tool bar, this is a no-op.

        Parameters
        ----------
        action : wxAction
            The wxAction instance to remove from this tool bar.

        """
        all_items = self._all_items
        if action in all_items:
            all_items.remove(action)
            action.Unbind(EVT_ACTION_CHANGED, handler=self.OnActionChanged)
            item = self._actions_map.pop(action, None)
            if item is not None:
                self.DeleteTool(item.GetId())

    def RemoveActions(self, actions):
        """ Remove multiple actions from the tool bar.

        If an action does not exist in the tool bar, it will be ignored.

        Parameters
        ----------
        actions : iterable
            An iterable of wxActions to remove from the tool bar.

        """
        remove = self.RemoveAction
        for action in actions:
            remove(action)


[docs]class WxToolBar(WxConstraintsWidget): """ A Wx implementation of an Enaml ToolBar. """ #-------------------------------------------------------------------------- # Setup Methods #--------------------------------------------------------------------------
[docs] def create_widget(self, parent, tree): """ Create the underlying tool bar widget. """ # The orientation of a tool bar can only be set at creation time. # Wx does not support changing it dynamically. It is only set if # the tool bar is a child of something other than a wx.Frame. # The style must include TB_FLAT or separators won't be drawn. style = wx.TB_FLAT | wx.TB_TEXT | wx.NO_BORDER if not isinstance(parent, wx.Frame): style |= _ORIENTATION_MAP[tree['orientation']] else: style |= wx.HORIZONTAL tbar = wxToolBar(parent, style=style) # Setting the tool bar to double buffered avoids a ton of # flickering on Windows during resize events. tbar.SetDoubleBuffered(True) # For now, we set the bitmap size to 0 since we don't yet # support icons or images. tbar.SetToolBitmapSize(wx.Size(0, 0)) return tbar
[docs] def create(self, tree): """ Create and initialize the underlying tool bar control. """ super(WxToolBar, self).create(tree) self.set_orientation(tree['orientation']) self.set_movable(tree['movable']) self.set_floatable(tree['floatable']) self.set_floating(tree['floating']) self.set_dock_area(tree['dock_area']) self.set_allowed_dock_areas(tree['allowed_dock_areas'])
[docs] def init_layout(self): """ Initialize the layout for the toolbar. """ super(WxToolBar, self).init_layout() widget = self.widget() for child in self.children(): if isinstance(child, WxAction): widget.AddAction(child.widget(), False) elif isinstance(child, WxActionGroup): widget.AddActions(child.actions(), False) widget.Realize() #-------------------------------------------------------------------------- # Child Events #--------------------------------------------------------------------------
[docs] def child_removed(self, child): """ Handle the child removed event for a WxToolBar. """ if isinstance(child, WxAction): self.widget().RemoveAction(child.widget()) elif isinstance(child, WxActionGroup): self.widget().RemoveActions(child.actions())
[docs] def child_added(self, child): """ Handle the child added event for a WxToolBar. """ before = self.find_next_action(child) if isinstance(child, WxAction): self.widget().InsertAction(before, child.widget()) elif isinstance(child, WxActionGroup): self.widget().InsertActions(before, child.actions()) #-------------------------------------------------------------------------- # Utility Methods #--------------------------------------------------------------------------
[docs] def find_next_action(self, child): """ Get the wxAction instance which comes immediately after the actions of the given child. Parameters ---------- child : WxActionGroup, or WxAction The child of interest. Returns ------- result : wxAction or None The wxAction which comes immediately after the actions of the given child, or None if no actions follow the child. """ index = self.index_of(child) if index != -1: for child in self.children()[index + 1:]: target = None if isinstance(child, WxAction): target = child.widget() elif isinstance(child, WxActionGroup): acts = child.actions() target = acts[0] if acts else None if target is not None: return target #-------------------------------------------------------------------------- # Message Handling #--------------------------------------------------------------------------
[docs] def on_action_set_movable(self, content): """ Handle the 'set_movable' action from the Enaml widget. """ self.set_movable(content['movable'])
[docs] def on_action_set_floatable(self, content): """ Handle the 'set_floatable' action from the Enaml widget. """ self.set_floatable(content['floatable'])
[docs] def on_action_set_floating(self, content): """ Handle the 'set_floating' action from the Enaml widget. """ self.set_floating(content['floating'])
[docs] def on_action_set_dock_area(self, content): """ Handle the 'set_dock_area' action from the Enaml widget. """ self.set_dock_area(content['dock_area'])
[docs] def on_action_set_allowed_dock_areas(self, content): """ Handle the 'set_allowed_dock_areas' action from the Enaml widget. """ self.set_allowed_dock_areas(content['allowed_dock_areas']) #-------------------------------------------------------------------------- # Widget Update Methods #--------------------------------------------------------------------------\
[docs] def set_visible(self, visible): """ Overridden parent class visibility setter which properly handles the visibility of the tool bar. """ # XXX implement me! pass
[docs] def set_orientation(self, orientation): """ Set the orientation of the underlying widget. """ # Wx does not support dynamically changing the orientation. pass
[docs] def set_movable(self, movable): """ Set the movable state on the underlying widget. """ # The standard wx toolbar doesn't support docking. The Aui # toolbar sucks, don't use it. pass
[docs] def set_floatable(self, floatable): """ Set the floatable state on the underlying widget. """ # The standard wx toolbar doesn't support docking. The Aui # toolbar sucks, don't use it. pass
[docs] def set_floating(self, floating): """ Set the floating staet on the underlying widget. """ # The standard wx toolbar doesn't support docking. The Aui # toolbar sucks, don't use it. pass
[docs] def set_dock_area(self, dock_area): """ Set the dock area on the underyling widget. """ # The standard wx toolbar doesn't support docking. The Aui # toolbar sucks, don't use it. pass
[docs] def set_allowed_dock_areas(self, dock_areas): """ Set the allowed dock areas on the underlying widget. """ # The standard wx toolbar doesn't support docking. The Aui # toolbar sucks, don't use it. pass