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