diff --git a/src/calibre/gui2/actions/manage_categories.py b/src/calibre/gui2/actions/manage_categories.py index 13db2d844f..938b61b745 100644 --- a/src/calibre/gui2/actions/manage_categories.py +++ b/src/calibre/gui2/actions/manage_categories.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2022, Charles Haley # -from qt.core import QPoint from calibre.gui2.actions import InterfaceAction @@ -29,25 +28,8 @@ class ManageCategoriesAction(InterfaceAction): # show the menu in the upper left corner of the library view pane. Yes, this # is a bit weird but it works as well as a popping up a dialog. def show_menu(self): - for x in self.gui.bars_manager.main_bars + self.gui.bars_manager.child_bars: - try: - w = x.widgetForAction(self.qaction) - # It seems that multiple copies of the action can exist, such as - # when the device-connected menu is changed while the device is - # connected. Use the one that has an actual position. - if w.pos().x() == 0: - continue - # The button might be hidden - if not w.isVisible(): - continue - # The w.height() assures that the menu opens below the button. - self.menu.exec(w.mapToGlobal(QPoint(0, w.height()))) - return - except: - continue - # No visible button found. Fall back to displaying in upper left corner - # of the library view. - self.menu.exec(self.gui.library_view.mapToGlobal(QPoint(10, 10))) + from calibre.gui2.actions.saved_searches import show_menu_under_widget + show_menu_under_widget(self.gui, self.menu, self.qaction, self.name) def about_to_show_menu(self): db = self.gui.current_db diff --git a/src/calibre/gui2/actions/saved_searches.py b/src/calibre/gui2/actions/saved_searches.py index eb9161a432..fab03b1b3b 100644 --- a/src/calibre/gui2/actions/saved_searches.py +++ b/src/calibre/gui2/actions/saved_searches.py @@ -1,12 +1,56 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2022, Charles Haley # -from functools import partial -from qt.core import QPoint, QIcon +from qt.core import QPoint from calibre.gui2.actions import InterfaceAction -from calibre.utils.icu import primary_sort_key + + +def show_menu_under_widget(gui, menu, action, name): + # First try the tool bar + for x in gui.bars_manager.main_bars + gui.bars_manager.child_bars: + try: + w = x.widgetForAction(action) + # It seems that multiple copies of the action can exist, such as + # when the device-connected menu is changed while the device is + # connected. Use the one that has an actual position. + if w.pos().x() == 0: + continue + # The button might be hidden + if not w.isVisible(): + continue + # The w.height() assures that the menu opens below the button. + menu.exec(w.mapToGlobal(QPoint(0, w.height()))) + return + except: + continue + # Now try the menu bar + for x in gui.bars_manager.menu_bar.added_actions: + # This depends on no two menus with the same name. + # I don't know if this works on a Mac + if x.text() == name: + try: + # The menu item might be hidden + if not x.isVisible(): + continue + # We can't use x.trigger() because it doesn't put the menu + # in the right place. Instead get the position of the menu + # widget on the menu bar + p = x.parent().menu_bar + r = p.actionGeometry(x) + # Make sure that the menu item is actually displayed in the menu + # and not the overflow + if p.geometry().width() < (r.x() + r.width()): + continue + # Show the menu under the name in the menu bar + menu.exec(p.mapToGlobal(QPoint(r.x()+2, r.height()-2))) + return + except: + continue + # No visible button found. Fall back to displaying in upper left corner + # of the library view. + menu.exec(gui.library_view.mapToGlobal(QPoint(10, 10))) class SavedSearchesAction(InterfaceAction): @@ -32,47 +76,10 @@ class SavedSearchesAction(InterfaceAction): # show the menu in the upper left corner of the library view pane. Yes, this # is a bit weird but it works as well as a popping up a dialog. def show_menu(self): - for x in self.gui.bars_manager.main_bars + self.gui.bars_manager.child_bars: - try: - w = x.widgetForAction(self.qaction) - # It seems that multiple copies of the action can exist, such as - # when the device-connected menu is changed while the device is - # connected. Use the one that has an actual position. - if w.pos().x() == 0: - continue - # The button might be hidden - if not w.isVisible(): - continue - # The w.height() assures that the menu opens below the button. - self.menu.exec(w.mapToGlobal(QPoint(0, w.height()))) - return - except: - continue - # No visible button found. Fall back to displaying in upper left corner - # of the library view. - self.menu.exec(self.gui.library_view.mapToGlobal(QPoint(10, 10))) + show_menu_under_widget(self.gui, self.menu, self.qaction, self.name) def about_to_show_menu(self): - db = self.gui.current_db - m = self.menu - m.clear() - submenus = {} - for name in sorted(db.saved_search_names(), key=lambda x: primary_sort_key(x.strip())): - components = tuple(n.strip() for n in name.split('.')) - hierarchy = components[:-1] - last = components[-1] - current_menu = m - # Walk the hierarchy, creating submenus as needed - for i,c in enumerate(hierarchy, start=1): - hierarchical_prefix = '.'.join(hierarchy[:i]) - if hierarchical_prefix not in submenus: - current_menu = current_menu.addMenu(c) - current_menu.setIcon(QIcon.ic('folder_saved_search.png')) - submenus[hierarchical_prefix] = current_menu - else: - current_menu = submenus[hierarchical_prefix] - ac = current_menu.addAction(last, partial(self.gui.search.set_search_string, 'search:"='+name+'"')) - ac.setIcon(QIcon.ic('search.png')) + self.gui.populate_add_saved_search_menu(to_menu=self.menu) def location_selected(self, loc): enabled = loc == 'library' diff --git a/src/calibre/gui2/dialogs/saved_search_editor.py b/src/calibre/gui2/dialogs/saved_search_editor.py index de353b5ca9..20deb7072c 100644 --- a/src/calibre/gui2/dialogs/saved_search_editor.py +++ b/src/calibre/gui2/dialogs/saved_search_editor.py @@ -100,11 +100,11 @@ class SavedSearchEditor(Dialog): db = get_gui().current_db self.l = l = QVBoxLayout(self) b = self.bb.addButton(_('&Add search'), QDialogButtonBox.ButtonRole.ActionRole) - b.setIcon(QIcon.ic('plus.png')) + b.setIcon(QIcon.ic('search_add_saved.png')) b.clicked.connect(self.add_search) b = self.bb.addButton(_('&Remove search'), QDialogButtonBox.ButtonRole.ActionRole) - b.setIcon(QIcon.ic('minus.png')) + b.setIcon(QIcon.ic('search_delete_saved.png')) b.clicked.connect(self.del_search) b = self.bb.addButton(_('&Edit search'), QDialogButtonBox.ButtonRole.ActionRole) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index ed11ecd916..1725dc7817 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -296,7 +296,7 @@ class SearchBar(QFrame): # {{{ x.setCursor(Qt.CursorShape.PointingHandCursor) x.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) x.setAutoRaise(True) - x.setIcon(QIcon.ic("bookmarks.png")) + x.setIcon(QIcon.ic("folder_saved_search.png")) l.addWidget(x) x.setVisible(not tweaks['show_saved_search_box']) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index af1f044b6c..13ba3a215d 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -576,27 +576,45 @@ class SavedSearchBoxMixin: # {{{ "Press and hold for a pop-up options menu.") + '

') self.save_search_button.setMenu(QMenu(self.save_search_button)) self.save_search_button.menu().addAction( - QIcon.ic('plus.png'), + QIcon.ic('search_add_saved.png'), _('Create Saved search'), self.saved_search.save_search_button_clicked) self.save_search_button.menu().addAction( - QIcon.ic('trash.png'), _('Delete Saved search'), self.saved_search.delete_current_search) + QIcon.ic('search_delete_saved.png'), _('Delete Saved search'), self.saved_search.delete_current_search) self.save_search_button.menu().addAction( QIcon.ic('search.png'), _('Manage Saved searches'), partial(self.do_saved_search_edit, None)) self.add_saved_search_button.setMenu(QMenu(self.add_saved_search_button)) self.add_saved_search_button.menu().aboutToShow.connect(self.populate_add_saved_search_menu) - def populate_add_saved_search_menu(self): - m = self.add_saved_search_button.menu() + + def populate_add_saved_search_menu(self, to_menu=None): + m = to_menu if to_menu is not None else self.add_saved_search_button.menu() m.clear() - m.addAction(QIcon.ic('plus.png'), _('Add Saved search'), self.add_saved_search) - m.addAction(QIcon.ic("search_copy_saved.png"), _('Get Saved search expression'), - self.get_saved_search_text) - m.addActions(list(self.save_search_button.menu().actions())[-1:]) + m.clear() + m.addAction(QIcon.ic('search_add_saved.png'), _('Add Saved search'), self.add_saved_search) + m.addAction(QIcon.ic('search_copy_saved.png'), _('Get Saved search expression'), + self.get_saved_search_text) + m.addAction(QIcon.ic('folder_saved_search.png'), _('Manage Saved searches'), + partial(self.do_saved_search_edit, None)) m.addSeparator() db = self.current_db + submenus = {} for name in sorted(db.saved_search_names(), key=lambda x: primary_sort_key(x.strip())): - m.addAction(name.strip(), partial(self.saved_search.saved_search_selected, name)) + components = tuple(n.strip() for n in name.split('.')) + hierarchy = components[:-1] + last = components[-1] + current_menu = m + # Walk the hierarchy, creating submenus as needed + for i,c in enumerate(hierarchy, start=1): + hierarchical_prefix = '.'.join(hierarchy[:i]) + if hierarchical_prefix not in submenus: + current_menu = current_menu.addMenu(c) + current_menu.setIcon(QIcon.ic('folder_saved_search.png')) + submenus[hierarchical_prefix] = current_menu + else: + current_menu = submenus[hierarchical_prefix] + ac = current_menu.addAction(last, partial(self.search.set_search_string, 'search:"='+name+'"')) + ac.setIcon(QIcon.ic('search.png')) def saved_searches_changed(self, set_restriction=None, recount=True): self.build_search_restriction_list()