1# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX
2# All rights reserved.
3#
4# This software is provided without warranty under the terms of the BSD
5# license included in LICENSE.txt and may be redistributed only under
6# the conditions described in the aforementioned license. The license
7# is also available online at http://www.enthought.com/licenses/BSD.txt
8#
9# Thanks for using Enthought open source!
10# (C) Copyright 2007 Riverbank Computing Limited
11# This software is provided without warranty under the terms of the BSD license.
12# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply
13
14
15""" The PyQt specific implementation of a menu manager. """
16
17
18from pyface.qt import QtCore, QtGui
19
20
21from traits.api import Instance, List, Str
22
23
24from pyface.action.action_manager import ActionManager
25from pyface.action.action_manager_item import ActionManagerItem
26from pyface.action.action_item import _Tool, Action
27from pyface.action.group import Group
28
29
30class MenuManager(ActionManager, ActionManagerItem):
31    """ A menu manager realizes itself in a menu control.
32
33    This could be a sub-menu or a context (popup) menu.
34    """
35
36    # 'MenuManager' interface ---------------------------------------------#
37
38    # The menu manager's name (if the manager is a sub-menu, this is what its
39    # label will be).
40    name = Str()
41
42    # The default action for tool button when shown in a toolbar (Qt only)
43    action = Instance(Action)
44
45    # Private interface ---------------------------------------------------#
46
47    #: Keep track of all created menus in order to properly dispose of them
48    _menus = List()
49
50    # ------------------------------------------------------------------------
51    # 'MenuManager' interface.
52    # ------------------------------------------------------------------------
53
54    def create_menu(self, parent, controller=None):
55        """ Creates a menu representation of the manager. """
56
57        # If a controller is required it can either be set as a trait on the
58        # menu manager (the trait is part of the 'ActionManager' API), or
59        # passed in here (if one is passed in here it takes precedence over the
60        # trait).
61        if controller is None:
62            controller = self.controller
63
64        menu = _Menu(self, parent, controller)
65        self._menus.append(menu)
66
67        return menu
68
69    # ------------------------------------------------------------------------
70    # 'ActionManager' interface.
71    # ------------------------------------------------------------------------
72
73    def destroy(self):
74        while self._menus:
75            menu = self._menus.pop()
76            menu.dispose()
77
78        super(MenuManager, self).destroy()
79
80    # ------------------------------------------------------------------------
81    # 'ActionManagerItem' interface.
82    # ------------------------------------------------------------------------
83
84    def add_to_menu(self, parent, menu, controller):
85        """ Adds the item to a menu. """
86
87        submenu = self.create_menu(parent, controller)
88        submenu.menuAction().setText(self.name)
89        menu.addMenu(submenu)
90
91    def add_to_toolbar(
92        self, parent, tool_bar, image_cache, controller, show_labels=True
93    ):
94        """ Adds the item to a tool bar. """
95        menu = self.create_menu(parent, controller)
96        if self.action:
97            tool_action = _Tool(
98                parent, tool_bar, image_cache, self, controller, show_labels
99            ).control
100            tool_action.setMenu(menu)
101        else:
102            tool_action = menu.menuAction()
103            tool_bar.addAction(tool_action)
104
105        tool_action.setText(self.name)
106        tool_button = tool_bar.widgetForAction(tool_action)
107        tool_button.setPopupMode(
108            tool_button.MenuButtonPopup
109            if self.action
110            else tool_button.InstantPopup
111        )
112
113
114class _Menu(QtGui.QMenu):
115    """ The toolkit-specific menu control. """
116
117    # ------------------------------------------------------------------------
118    # 'object' interface.
119    # ------------------------------------------------------------------------
120
121    def __init__(self, manager, parent, controller):
122        """ Creates a new tree. """
123
124        # Base class constructor.
125        QtGui.QMenu.__init__(self, parent)
126
127        # The parent of the menu.
128        self._parent = parent
129
130        # The manager that the menu is a view of.
131        self._manager = manager
132
133        # The controller.
134        self._controller = controller
135
136        # List of menu items
137        self.menu_items = []
138
139        # Create the menu structure.
140        self.refresh()
141
142        # Listen to the manager being updated.
143        self._manager.on_trait_change(self.refresh, "changed")
144        self._manager.on_trait_change(self._on_enabled_changed, "enabled")
145        self._manager.on_trait_change(self._on_visible_changed, "visible")
146        self._manager.on_trait_change(self._on_name_changed, "name")
147        self._manager.on_trait_change(self._on_image_changed, "image")
148        self.setEnabled(self._manager.enabled)
149        self.menuAction().setVisible(self._manager.visible)
150
151        return
152
153    def dispose(self):
154        self._manager.on_trait_change(self.refresh, "changed", remove=True)
155        self._manager.on_trait_change(
156            self._on_enabled_changed, "enabled", remove=True
157        )
158        self._manager.on_trait_change(
159            self._on_visible_changed, "visible", remove=True
160        )
161        self._manager.on_trait_change(
162            self._on_name_changed, "name", remove=True
163        )
164        self._manager.on_trait_change(
165            self._on_image_changed, "image", remove=True
166        )
167        # Removes event listeners from downstream menu items
168        self.clear()
169
170    # ------------------------------------------------------------------------
171    # '_Menu' interface.
172    # ------------------------------------------------------------------------
173
174    def clear(self):
175        """ Clears the items from the menu. """
176
177        for item in self.menu_items:
178            item.dispose()
179
180        self.menu_items = []
181
182        super(_Menu, self).clear()
183
184    def is_empty(self):
185        """ Is the menu empty? """
186
187        return self.isEmpty()
188
189    def refresh(self):
190        """ Ensures that the menu reflects the state of the manager. """
191
192        self.clear()
193
194        manager = self._manager
195        parent = self._parent
196
197        previous_non_empty_group = None
198        for group in manager.groups:
199            previous_non_empty_group = self._add_group(
200                parent, group, previous_non_empty_group
201            )
202
203        self.setEnabled(manager.enabled)
204
205    def show(self, x=None, y=None):
206        """ Show the menu at the specified location. """
207
208        if x is None or y is None:
209            point = QtGui.QCursor.pos()
210        else:
211            point = QtCore.QPoint(x, y)
212        self.popup(point)
213
214    # ------------------------------------------------------------------------
215    # Private interface.
216    # ------------------------------------------------------------------------
217
218    def _on_enabled_changed(self, obj, trait_name, old, new):
219        """ Dynamic trait change handler. """
220
221        self.setEnabled(new)
222
223    def _on_visible_changed(self, obj, trait_name, old, new):
224        """ Dynamic trait change handler. """
225
226        self.menuAction().setVisible(new)
227
228    def _on_name_changed(self, obj, trait_name, old, new):
229        """ Dynamic trait change handler. """
230
231        self.menuAction().setText(new)
232
233    def _on_image_changed(self, obj, trait_name, old, new):
234        """ Dynamic trait change handler. """
235
236        self.menuAction().setIcon(new.create_icon())
237
238    def _add_group(self, parent, group, previous_non_empty_group=None):
239        """ Adds a group to a menu. """
240
241        if len(group.items) > 0:
242            # Is a separator required?
243            if previous_non_empty_group is not None and group.separator:
244                self.addSeparator()
245
246            # Create actions and sub-menus for each contribution item in
247            # the group.
248            for item in group.items:
249                if isinstance(item, Group):
250                    if len(item.items) > 0:
251                        self._add_group(parent, item, previous_non_empty_group)
252
253                        if (
254                            previous_non_empty_group is not None
255                            and previous_non_empty_group.separator
256                            and item.separator
257                        ):
258                            self.addSeparator()
259
260                        previous_non_empty_group = item
261
262                else:
263                    item.add_to_menu(parent, self, self._controller)
264
265            previous_non_empty_group = group
266
267        return previous_non_empty_group
268