This commit is contained in:
Kovid Goyal 2024-02-18 21:20:45 +05:30
commit 52f80efdb9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 207 additions and 12 deletions

View File

@ -1097,6 +1097,12 @@ class ActionBooklistContextMenu(InterfaceActionBase):
description = _('Open the context menu for the column') description = _('Open the context menu for the column')
class ActionAllActions(InterfaceActionBase):
name = 'All GUI actions'
author = 'Charles Haley'
actual_plugin = 'calibre.gui2.actions.all_actions:AllGUIActions'
description = _('Open a menu showing all installed GUI actions')
class ActionVirtualLibrary(InterfaceActionBase): class ActionVirtualLibrary(InterfaceActionBase):
name = 'Virtual Library' name = 'Virtual Library'
actual_plugin = 'calibre.gui2.actions.virtual_library:VirtualLibraryAction' actual_plugin = 'calibre.gui2.actions.virtual_library:VirtualLibraryAction'
@ -1128,7 +1134,7 @@ class ActionPluginUpdater(InterfaceActionBase):
actual_plugin = 'calibre.gui2.actions.plugin_updates:PluginUpdaterAction' actual_plugin = 'calibre.gui2.actions.plugin_updates:PluginUpdaterAction'
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, plugins += [ActionAdd, ActionAllActions, ActionFetchAnnotations, ActionGenerateCatalog,
ActionConvert, ActionDelete, ActionEditMetadata, ActionView, ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
ActionFetchNews, ActionSaveToDisk, ActionQuickview, ActionPolish, ActionFetchNews, ActionSaveToDisk, ActionQuickview, ActionPolish,
ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare, ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare,

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Charles Haley
from functools import partial
from math import ceil
from qt.core import QIcon, QMenu, QToolButton, Qt
from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
from calibre.gui2.preferences.toolbar import AllModel, CurrentModel
from calibre.utils.icu import sort_key
class AllGUIActions(InterfaceAction):
name = 'All GUI actions'
action_spec = (_('All GUI actions'), 'wizard.png',
_("Show a menu of all available GUI and plugin actions.\nThis menu "
"is not available when looking at books on a device"), None)
action_type = 'current'
popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
action_add_menu = True
dont_add_to = frozenset()
def genesis(self):
self.layout_icon = QIcon.ic('wizard.png')
self.menu = m = self.qaction.menu()
m.aboutToShow.connect(self.about_to_show_menu)
# Create a "hidden" menu that can have a shortcut.
self.hidden_menu = QMenu()
self.shortcut_action = self.create_menu_action(
menu=self.hidden_menu,
unique_name='Main window layout',
shortcut=None,
text=_("Save and restore layout item sizes, and add/remove/toggle "
"layout items such as the search bar, tag browser, etc. "),
icon='layout.png',
triggered=self.show_menu)
# We want to show the menu when a shortcut is used. Apparently the only way
# to do that is to scan the toolbar(s) for the action button then exec the
# associated menu. The search is done here to take adding and removing the
# action from toolbars into account.
#
# If a shortcut is triggered and there isn't a toolbar button visible then
# 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):
show_menu_under_widget(self.gui, self.menu, self.qaction, self.name)
def gui_layout_complete(self):
self.qaction.menu().aboutToShow.connect(self.about_to_show_menu)
def initialization_complete(self):
self.populate_menu()
def about_to_show_menu(self):
self.populate_menu()
def location_selected(self, loc):
self.qaction.setEnabled(loc == 'library')
def populate_menu(self):
# Need to do this on every invocation because shortcuts can change
m = self.qaction.menu()
m.clear()
name_data = {} # A dict of display names to actions data
# Use model data from Preferences / Toolbars, with location 'toolbar' or
# 'toolbar-device' depending on whether a device is connected.
location = 'toolbar' + ('-device' if self.gui.location_manager.has_device else '')
for model in (AllModel(location, self.gui), CurrentModel(location, self.gui)):
for i in range(0, model.rowCount(None)):
dex = model.index(i)
name = model.names((dex,))[0] # this is the action name
if name is not None and not name.startswith('---'):
name_data[model.data(dex, Qt.ItemDataRole.DisplayRole)] = {
'action': model.name_to_action(name, self.gui),
'action_name':name,
'icon': model.data(dex, Qt.ItemDataRole.DecorationRole)}
# The loop leaves the variable 'model' set to a valid value
# Get display names of builtin and user plugins. We tell the difference
# using the class full module name. Plugins start with 'calibre_plugins'
builtin_actions = list()
user_plugins = list()
for display_name, act_data in name_data.items():
act = model.name_to_action(act_data['action_name'], self.gui)
if act is not None:
module = f'{act.__module__}.{act.__class__ .__name__}'
if module.startswith('calibre_plugins'):
user_plugins.append(display_name)
else:
builtin_actions.append(display_name)
builtin_actions = sorted(builtin_actions, key=sort_key)
user_plugins = sorted(user_plugins, key=sort_key)
# Build the map of action shortcuts so we can display the shortcuts for
# the actions. For shortcuts sometimes the action name and sometimes the
# display name is used. Test both. Unfortunately, there are case
# differences to deal with so we use lower case keys.
lower_names = ({n['action_name'].lower() for n in name_data.values()} |
{n.lower() for n in name_data.keys()})
kbd = self.gui.keyboard
shortcut_map = {}
for n,v in kbd.keys_map.items():
act_name = kbd.shortcuts[n]['name'].lower()
if act_name in lower_names:
shortcuts = list((sc.toString() for sc in v))
shortcut_map[act_name] = f'\t{", ".join(shortcuts)}'
# This function constructs a menu action, dealing with the action being
# either a popup menu or a dialog. It adds the shortcuts to the menu
# line. Happily, a tab causes the shortcuts to be right-aligned. The tab
# is added when the shortcut map is built.
def add_action(menu, display_name):
shortcuts = shortcut_map.get(display_name.lower(), '')
act = name_data[display_name]['action']
if not hasattr(act, 'popup_type'): # FakeAction
return
menu_text = f'{display_name}{shortcuts}'
icon = name_data[display_name]['icon']
if act.popup_type == QToolButton.ToolButtonPopupMode.MenuButtonPopup:
if act.action_add_menu:
# The action offers both a 'click' and a menu. Use the menu.
menu.addAction(icon, menu_text, partial(self._do_menu, display_name, act))
else:
# The action is a dialog.
menu.addAction(act.qaction.icon(), menu_text, partial(self._do_action, act))
else:
# The action is a menu.
menu.addAction(icon, menu_text, partial(self._do_menu, display_name, act))
# Finally the real work, building the action menu. Partition long lists
# of actions into sublists of some arbitrary length.
def partition(names):
count_in_partition = 10 # arbitrary
if len(names) >= count_in_partition:
partition_count = len(names) // (count_in_partition - 1)
step = int(ceil(len(names) / partition_count))
for first in range(0, len(names), step):
last = min(first + step - 1, len(names) - 1)
dnf = names[first]
dnl = names[last]
if dnf != dnl:
sm = m.addMenu(QIcon.ic('wizard.png'), f'{dnf} - {dnl}')
else:
sm = m.addMenu(QIcon.ic('wizard.png'), f'{dnf}')
for name in names[first:last+1]:
add_action(sm, name)
else:
for name in names:
act = self.gui.iactions[name]
add_action(m, name)
# Add a named section for builtin actions if user plugins are installed.
if user_plugins:
m.addSection(_('Built-in calibre actions') + ' ')
partition(builtin_actions)
m.addSection(_('Plugins') + ' ')
partition(user_plugins)
else:
partition(builtin_actions)
# Add access to the toolbars and keyboard shortcuts preferences dialogs
m.addSection(_('Preferences') + ' ')
m.addAction(_('Toolbars'), self._do_pref_toolbar)
m.addAction(_('Keyboard shortcuts'), self._do_pref_shortcuts)
def _do_pref_toolbar(self):
self.gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Toolbar'), close_after_initial=True)
def _do_pref_shortcuts(self):
self.gui.iactions['Preferences'].do_config(initial_plugin=('Advanced', 'Keyboard'), close_after_initial=True)
def _do_action(self, action):
action.qaction.trigger()
def _do_menu(self, name, action):
# This method clones and shows the action's menu. If the action is in a
# context menu, the action menu is displayed there without using this
# method. If not it is displayed underneath a toolbar or menu button. If
# neither is true then the menu is displayed in the upper left corner.
menu = QMenu()
menu.addSection(name)
action.qaction.menu().aboutToShow.emit()
for m in action.qaction.menu().actions():
menu.addAction(m)
show_menu_under_widget(self.gui, menu, self.qaction, self.name)

View File

@ -40,13 +40,15 @@ 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.MenuButtonPopup popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
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.triggered.connect(self.show_menu) self.menu = m = self.qaction.menu()
m.aboutToShow.connect(self.about_to_show_menu)
# self.qaction.triggered.connect(self.show_menu)
# Create a "hidden" menu that can have a shortcut. This also lets us # 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 # manually show the menu instead of letting Qt do it to work around a
@ -69,10 +71,10 @@ 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): def about_to_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() self.update_menu()
def show_menu(self):
show_menu_under_widget(self.gui, self.qaction.menu(), self.qaction, self.name) show_menu_under_widget(self.gui, self.qaction.menu(), self.qaction, self.name)
def reverse_sort(self): def reverse_sort(self):
@ -91,12 +93,6 @@ class SortByAction(InterfaceAction):
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=None):
menu = menu or self.qaction.menu() menu = menu or self.qaction.menu()