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 functools import partial
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu,
|
from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu, QPoint,
|
||||||
QKeySequence)
|
QKeySequence)
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
@ -18,6 +18,62 @@ from calibre.gui2.keyboard import NameConflict
|
|||||||
from polyglot.builtins import string_or_bytes
|
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):
|
def menu_action_unique_name(plugin, unique_name):
|
||||||
return '%s : menu action : %s'%(plugin.unique_name, unique_name)
|
return '%s : menu action : %s'%(plugin.unique_name, unique_name)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
from qt.core import QMenu, QToolButton
|
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):
|
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
|
# 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.
|
# is a bit weird but it works as well as a popping up a dialog.
|
||||||
def show_menu(self):
|
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)
|
show_menu_under_widget(self.gui, self.menu, self.qaction, self.name)
|
||||||
|
|
||||||
def about_to_show_menu(self):
|
def about_to_show_menu(self):
|
||||||
|
@ -2,55 +2,9 @@
|
|||||||
# License: GPLv3 Copyright: 2022, Charles Haley
|
# 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
|
from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
|
||||||
|
|
||||||
|
|
||||||
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)))
|
|
||||||
|
|
||||||
|
|
||||||
class SavedSearchesAction(InterfaceAction):
|
class SavedSearchesAction(InterfaceAction):
|
||||||
|
@ -8,10 +8,10 @@ from contextlib import suppress
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget,
|
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.library.field_metadata import category_icon_map
|
||||||
from calibre.utils.icu import primary_sort_key
|
from calibre.utils.icu import primary_sort_key
|
||||||
from polyglot.builtins import iteritems
|
from polyglot.builtins import iteritems
|
||||||
@ -40,13 +40,25 @@ class SortByAction(InterfaceAction):
|
|||||||
name = 'Sort By'
|
name = 'Sort By'
|
||||||
action_spec = (_('Sort by'), 'sort.png', _('Sort the list of books'), None)
|
action_spec = (_('Sort by'), 'sort.png', _('Sort the list of books'), None)
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
|
popup_type = QToolButton.ToolButtonPopupMode.MenuButtonPopup
|
||||||
action_add_menu = True
|
action_add_menu = True
|
||||||
dont_add_to = frozenset(('context-menu-cover-browser', ))
|
dont_add_to = frozenset(('context-menu-cover-browser', ))
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
self.sorted_icon = QIcon.ic('ok.png')
|
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=()):
|
def c(attr, title, tooltip, callback, keys=()):
|
||||||
ac = self.create_action(spec=(title, None, tooltip, keys), attr=attr)
|
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('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')
|
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):
|
def reverse_sort(self):
|
||||||
self.gui.current_view().reverse_sort()
|
self.gui.current_view().reverse_sort()
|
||||||
|
|
||||||
@ -68,17 +86,20 @@ class SortByAction(InterfaceAction):
|
|||||||
self.qaction.setEnabled(enabled)
|
self.qaction.setEnabled(enabled)
|
||||||
self.menuless_qaction.setEnabled(enabled)
|
self.menuless_qaction.setEnabled(enabled)
|
||||||
|
|
||||||
def about_to_show(self):
|
|
||||||
self.update_menu()
|
|
||||||
|
|
||||||
def library_changed(self, db):
|
def library_changed(self, db):
|
||||||
self.update_menu()
|
self.update_menu()
|
||||||
|
|
||||||
def initialization_complete(self):
|
def initialization_complete(self):
|
||||||
self.update_menu()
|
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):
|
def update_menu(self):
|
||||||
menu = self.qaction.menu() if menu is None else menu
|
menu = self.qaction.menu()
|
||||||
for action in menu.actions():
|
for action in menu.actions():
|
||||||
if hasattr(action, 'sort_requested'):
|
if hasattr(action, 'sort_requested'):
|
||||||
action.sort_requested.disconnect()
|
action.sort_requested.disconnect()
|
||||||
@ -96,17 +117,22 @@ class SortByAction(InterfaceAction):
|
|||||||
menu.addAction(name, partial(self.named_sort_selected, saved_sorts[name]))
|
menu.addAction(name, partial(self.named_sort_selected, saved_sorts[name]))
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
|
|
||||||
|
# Note the current sort column so it can be specially handled below
|
||||||
try:
|
try:
|
||||||
sort_col, order = m.sorted_on
|
sort_col, order = m.sorted_on
|
||||||
except TypeError:
|
except TypeError:
|
||||||
sort_col, order = 'date', True
|
sort_col, order = 'date', True
|
||||||
fm = db.field_metadata
|
|
||||||
name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
|
# The operations to choose which columns to display and to create saved sorts
|
||||||
hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
|
|
||||||
menu.addAction(_('Select sortable columns')).triggered.connect(self.select_sortable_columns)
|
menu.addAction(_('Select sortable columns')).triggered.connect(self.select_sortable_columns)
|
||||||
menu.addAction(_('Sort on multiple columns'), self.choose_multisort)
|
menu.addAction(_('Sort on multiple columns'), self.choose_multisort)
|
||||||
menu.addSeparator()
|
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)
|
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:
|
for name in all_names:
|
||||||
key = name_map[name]
|
key = name_map[name]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user