mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
5d1e886cf9
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
from functools import partial
|
||||
from zipfile import ZipFile
|
||||
|
||||
from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu,
|
||||
from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu, QPoint,
|
||||
QKeySequence)
|
||||
|
||||
from calibre import prints
|
||||
@ -18,6 +18,62 @@ from calibre.gui2.keyboard import NameConflict
|
||||
from polyglot.builtins import string_or_bytes
|
||||
|
||||
|
||||
def toolbar_widgets_for_action(gui, action):
|
||||
# Search the the toolbars for the widget associated with an action, passing
|
||||
# them to the caller for further processing
|
||||
for x in gui.bars_manager.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 is None or w.pos().x() == 0:
|
||||
continue
|
||||
# The button might be hidden
|
||||
if not w.isVisible():
|
||||
continue
|
||||
yield(w)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def show_menu_under_widget(gui, menu, action, name):
|
||||
# First try the tool bar
|
||||
for w in toolbar_widgets_for_action(gui, action):
|
||||
try:
|
||||
# The w.height() assures that the menu opens below the button.
|
||||
menu.exec(w.mapToGlobal(QPoint(0, w.height())))
|
||||
return
|
||||
except Exception:
|
||||
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 Exception:
|
||||
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)))
|
||||
|
||||
|
||||
def menu_action_unique_name(plugin, unique_name):
|
||||
return '%s : menu action : %s'%(plugin.unique_name, unique_name)
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
from qt.core import QMenu, QToolButton
|
||||
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
|
||||
|
||||
|
||||
class ManageCategoriesAction(InterfaceAction):
|
||||
@ -39,7 +39,6 @@ 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):
|
||||
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):
|
||||
|
@ -2,55 +2,9 @@
|
||||
# License: GPLv3 Copyright: 2022, Charles Haley
|
||||
#
|
||||
|
||||
from qt.core import QPoint, QMenu, QToolButton
|
||||
from qt.core import QMenu, QToolButton
|
||||
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
|
||||
|
||||
def show_menu_under_widget(gui, menu, action, name):
|
||||
# First try the tool bar
|
||||
for x in gui.bars_manager.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 is None or 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 Exception:
|
||||
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 Exception:
|
||||
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)))
|
||||
from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
|
||||
|
||||
|
||||
class SavedSearchesAction(InterfaceAction):
|
||||
|
@ -8,10 +8,10 @@ from contextlib import suppress
|
||||
from functools import partial
|
||||
from qt.core import (
|
||||
QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget,
|
||||
QListWidgetItem, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
|
||||
QListWidgetItem, QMenu, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
|
||||
)
|
||||
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
from calibre.gui2.actions import InterfaceAction, show_menu_under_widget, toolbar_widgets_for_action
|
||||
from calibre.library.field_metadata import category_icon_map
|
||||
from calibre.utils.icu import primary_sort_key
|
||||
from polyglot.builtins import iteritems
|
||||
@ -40,13 +40,25 @@ class SortByAction(InterfaceAction):
|
||||
name = 'Sort By'
|
||||
action_spec = (_('Sort by'), 'sort.png', _('Sort the list of books'), None)
|
||||
action_type = 'current'
|
||||
popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
|
||||
popup_type = QToolButton.ToolButtonPopupMode.MenuButtonPopup
|
||||
action_add_menu = True
|
||||
dont_add_to = frozenset(('context-menu-cover-browser', ))
|
||||
|
||||
def genesis(self):
|
||||
self.sorted_icon = QIcon.ic('ok.png')
|
||||
self.qaction.menu().aboutToShow.connect(self.about_to_show)
|
||||
self.qaction.triggered.connect(self.show_menu)
|
||||
|
||||
# Create a "hidden" menu that can have a shortcut. This also lets us
|
||||
# manually show the menu instead of letting Qt do it to work around a
|
||||
# problem where Qt can show the menu on the wrong screen.
|
||||
self.hidden_menu = QMenu()
|
||||
self.shortcut_action = self.create_menu_action(
|
||||
menu=self.hidden_menu,
|
||||
unique_name=_('Sort by'),
|
||||
text=_('Show the Sort by menu'),
|
||||
icon=None,
|
||||
shortcut='Ctrl+F5',
|
||||
triggered=self.show_menu)
|
||||
|
||||
def c(attr, title, tooltip, callback, keys=()):
|
||||
ac = self.create_action(spec=(title, None, tooltip, keys), attr=attr)
|
||||
@ -57,6 +69,12 @@ class SortByAction(InterfaceAction):
|
||||
c('reverse_sort_action', _('Reverse current sort'), _('Reverse the current sort order'), self.reverse_sort, 'shift+f5')
|
||||
c('reapply_sort_action', _('Re-apply current sort'), _('Re-apply the current sort'), self.reapply_sort, 'f5')
|
||||
|
||||
def show_menu(self):
|
||||
# We manually show the menu instead of letting Qt do it to work around a
|
||||
# problem where the menu can show on the wrong screen.
|
||||
self.update_menu()
|
||||
show_menu_under_widget(self.gui, self.qaction.menu(), self.qaction, self.name)
|
||||
|
||||
def reverse_sort(self):
|
||||
self.gui.current_view().reverse_sort()
|
||||
|
||||
@ -68,17 +86,20 @@ class SortByAction(InterfaceAction):
|
||||
self.qaction.setEnabled(enabled)
|
||||
self.menuless_qaction.setEnabled(enabled)
|
||||
|
||||
def about_to_show(self):
|
||||
self.update_menu()
|
||||
|
||||
def library_changed(self, db):
|
||||
self.update_menu()
|
||||
|
||||
def initialization_complete(self):
|
||||
self.update_menu()
|
||||
# Remove the down arrow from the buttons as they serve no purpose. They
|
||||
# show exactly what clicking the button shows
|
||||
for w in toolbar_widgets_for_action(self.gui, self.qaction):
|
||||
# Not sure why both styles are necessary, but they are
|
||||
w.setStyleSheet('QToolButton::menu-button {image: none; }'
|
||||
'QToolButton::menu-arrow {image: none; }')
|
||||
|
||||
def update_menu(self, menu=None):
|
||||
menu = self.qaction.menu() if menu is None else menu
|
||||
def update_menu(self):
|
||||
menu = self.qaction.menu()
|
||||
for action in menu.actions():
|
||||
if hasattr(action, 'sort_requested'):
|
||||
action.sort_requested.disconnect()
|
||||
@ -96,17 +117,22 @@ class SortByAction(InterfaceAction):
|
||||
menu.addAction(name, partial(self.named_sort_selected, saved_sorts[name]))
|
||||
menu.addSeparator()
|
||||
|
||||
# Note the current sort column so it can be specially handled below
|
||||
try:
|
||||
sort_col, order = m.sorted_on
|
||||
except TypeError:
|
||||
sort_col, order = 'date', True
|
||||
fm = db.field_metadata
|
||||
name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
|
||||
hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
|
||||
|
||||
# The operations to choose which columns to display and to create saved sorts
|
||||
menu.addAction(_('Select sortable columns')).triggered.connect(self.select_sortable_columns)
|
||||
menu.addAction(_('Sort on multiple columns'), self.choose_multisort)
|
||||
menu.addSeparator()
|
||||
|
||||
# Add the columns to the menu
|
||||
fm = db.field_metadata
|
||||
name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
|
||||
all_names = sorted(name_map, key=primary_sort_key)
|
||||
hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
|
||||
|
||||
for name in all_names:
|
||||
key = name_map[name]
|
||||
|
Loading…
x
Reference in New Issue
Block a user