Centralize management of keyboard shortcuts

This commit is contained in:
Kovid Goyal 2011-08-05 21:33:02 -06:00
parent 902a50c711
commit 292cca0507
17 changed files with 260 additions and 110 deletions

View File

@ -106,6 +106,10 @@ class InterfaceAction(QObject):
self.gui.addAction(self.menuless_qaction)
self.genesis()
@property
def unique_name(self):
return u'%s(%s)'%(self.__class__.__name__, self.name)
def create_action(self, spec=None, attr='qaction'):
if spec is None:
spec = self.action_spec
@ -125,12 +129,19 @@ class InterfaceAction(QObject):
a.setToolTip(text)
a.setStatusTip(text)
a.setWhatsThis(text)
if shortcut:
a = ma if attr == 'qaction' else action
if isinstance(shortcut, list):
a.setShortcuts(shortcut)
else:
a.setShortcut(shortcut)
keys = ()
shortcut_action = action
desc = tooltip if tooltip else None
if attr == 'qaction':
shortcut_action = ma
if shortcut is not None:
keys = ((shortcut,) if isinstance(shortcut, basestring) else
tuple(shortcut))
self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr,
unicode(shortcut_action.text()), default_keys=keys,
action=shortcut_action, description=desc)
if attr is not None:
setattr(self, attr, action)
if attr == 'qaction' and self.action_add_menu:
menu = QMenu()
@ -139,6 +150,30 @@ class InterfaceAction(QObject):
menu.addAction(self.menuless_qaction)
return action
def create_menu_action(self, menu, unique_name, text, icon=None, shortcut=None,
description=None, triggered=None):
ac = menu.addAction(text)
if icon is not None:
if not isinstance(icon, QIcon):
icon = QIcon(I(icon))
ac.setIcon(icon)
keys = ()
if shortcut is not None and shortcut is not False:
keys = ((shortcut,) if isinstance(shortcut, basestring) else
tuple(shortcut))
unique_name = '%s : menu action : %s'%(self.unique_name, unique_name)
if description is not None:
ac.setToolTip(description)
ac.setStatusTip(description)
ac.setWhatsThis(description)
if shortcut is not False:
self.gui.keyboard.register_shortcut(unique_name,
unicode(text), default_keys=keys,
action=ac, description=description)
if triggered is not None:
ac.triggered.connect(triggered)
return ac
def load_resources(self, names):
'''
If this plugin comes in a ZIP file (user added plugin), this method

View File

@ -54,20 +54,22 @@ class AddAction(InterfaceAction):
def genesis(self):
self._add_filesystem_book = self.Dispatcher(self.__add_filesystem_book)
self.add_menu = self.qaction.menu()
self.add_menu.addAction(_('Add books from directories, including '
ma = partial(self.create_menu_action, self.add_menu)
ma('recursive-single', _('Add books from directories, including '
'sub-directories (One book per directory, assumes every ebook '
'file is the same book in a different format)'),
'file is the same book in a different format)')).triggered.connect(
self.add_recursive_single)
self.add_menu.addAction(_('Add books from directories, including '
ma('recursive-multiple', _('Add books from directories, including '
'sub directories (Multiple books per directory, assumes every '
'ebook file is a different book)'), self.add_recursive_multiple)
'ebook file is a different book)')).triggered.connect(
self.add_recursive_multiple)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty, _('Shift+Ctrl+E'))
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
ma('add-empty', _('Add Empty book. (Book entry with no formats)'),
shortcut=_('Shift+Ctrl+E')).triggered.connect(self.add_empty)
ma('add-isbn', _('Add from ISBN')).triggered.connect(self.add_from_isbn)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add files to selected book records'),
self.add_formats, _('Shift+A'))
ma('add-formats', _('Add files to selected book records'),
triggered=self.add_formats, shortcut=_('Shift+A'))
self.qaction.triggered.connect(self.add_books)
@ -82,7 +84,8 @@ class AddAction(InterfaceAction):
view = self.gui.library_view
rows = view.selectionModel().selectedRows()
if not rows:
return
return error_dialog(self.gui, _('No books selected'),
_('Cannot add files as no books are selected'), show=True)
ids = [view.model().id(r) for r in rows]
if len(ids) > 1 and not question_dialog(self.gui,

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import QModelIndex, QIcon
from PyQt4.Qt import QModelIndex
from calibre.gui2 import error_dialog, Dispatcher
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook
@ -25,17 +25,19 @@ class ConvertAction(InterfaceAction):
action_add_menu = True
def genesis(self):
cm = self.qaction.menu()
cm.addAction(self.qaction.icon(), _('Convert individually'), partial(self.convert_ebook,
m = self.convert_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.convert_menu)
cm('convert-individual', _('Convert individually'),
icon=self.qaction.icon(), triggered=partial(self.convert_ebook,
False, bulk=False))
cm.addAction(_('Bulk convert'),
partial(self.convert_ebook, False, bulk=True))
cm.addSeparator()
ac = cm.addAction(QIcon(I('catalog.png')),
_('Create a catalog of the books in your calibre library'))
ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog)
cm('convert-bulk', _('Bulk convert'),
triggered=partial(self.convert_ebook, False, bulk=True))
m.addSeparator()
cm('create-catalog',
_('Create a catalog of the books in your calibre library'),
icon='catalog.png', shortcut=False,
triggered=self.gui.iactions['Generate Catalog'].generate_catalog)
self.qaction.triggered.connect(self.convert_ebook)
self.convert_menu = cm
self.conversion_jobs = {}
def location_selected(self, loc):

View File

@ -90,21 +90,23 @@ class DeleteAction(InterfaceAction):
def genesis(self):
self.qaction.triggered.connect(self.delete_books)
self.delete_menu = self.qaction.menu()
self.delete_menu.addAction(
m = partial(self.create_menu_action, self.delete_menu)
m('delete-specific',
_('Remove files of a specific format from selected books..'),
self.delete_selected_formats)
self.delete_menu.addAction(
triggered=self.delete_selected_formats)
m('delete-except',
_('Remove all formats from selected books, except...'),
self.delete_all_but_selected_formats)
self.delete_menu.addAction(
triggered=self.delete_all_but_selected_formats)
m('delete-all',
_('Remove all formats from selected books'),
self.delete_all_formats)
self.delete_menu.addAction(
_('Remove covers from selected books'), self.delete_covers)
triggered=self.delete_all_formats)
m('delete-covers',
_('Remove covers from selected books'),
triggered=self.delete_covers)
self.delete_menu.addSeparator()
self.delete_menu.addAction(
m('delete-matching',
_('Remove matching books from device'),
self.remove_matching_books_from_device)
triggered=self.remove_matching_books_from_device)
self.qaction.setMenu(self.delete_menu)
self.delete_memory = {}

View File

@ -60,6 +60,15 @@ class ShareConnMenu(QMenu): # {{{
self.email_actions = []
if hasattr(parent, 'keyboard'):
r = parent.keyboard.register_shortcut
prefix = 'Share/Connect Menu '
for attr in ('folder', 'bambook', 'itunes'):
if not (iswindows or isosx) and attr == 'itunes':
continue
ac = getattr(self, 'connect_to_%s_action'%attr)
r(prefix + attr, unicode(ac.text()), action=ac)
def server_state_changed(self, running):
text = _('Start Content Server')
if running:

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import Qt, QMenu, QModelIndex, QTimer
from PyQt4.Qt import QMenu, QModelIndex, QTimer
from calibre.gui2 import error_dialog, Dispatcher, question_dialog
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
@ -27,37 +27,38 @@ class EditMetadataAction(InterfaceAction):
action_add_menu = True
def genesis(self):
self.create_action(spec=(_('Merge book records'), 'merge_books.png',
None, _('M')), attr='action_merge')
md = self.qaction.menu()
md.addAction(self.qaction.icon(), _('Edit metadata individually'),
partial(self.edit_metadata, False, bulk=False))
cm = partial(self.create_menu_action, md)
cm('individual', _('Edit metadata individually'), icon=self.qaction.icon(),
triggered=partial(self.edit_metadata, False, bulk=False))
md.addSeparator()
md.addAction(_('Edit metadata in bulk'),
partial(self.edit_metadata, False, bulk=True))
cm('bulk', _('Edit metadata in bulk'),
triggered=partial(self.edit_metadata, False, bulk=True))
md.addSeparator()
md.addAction(_('Download metadata and covers'), self.download_metadata,
Qt.ControlModifier+Qt.Key_D)
cm('download', _('Download metadata and covers'),
triggered=partial(self.download_metadata, ids=None),
shortcut='Ctrl+D')
self.metadata_menu = md
mb = QMenu()
mb.addAction(_('Merge into first selected book - delete others'),
self.merge_books)
cm2 = partial(self.create_menu_action, mb)
cm2('merge delete', _('Merge into first selected book - delete others'),
triggered=self.merge_books)
mb.addSeparator()
mb.addAction(_('Merge into first selected book - keep others'),
partial(self.merge_books, safe_merge=True),
Qt.AltModifier+Qt.Key_M)
cm2('merge keep', _('Merge into first selected book - keep others'),
triggered=partial(self.merge_books, safe_merge=True),
shortcut='Alt+M')
mb.addSeparator()
mb.addAction(_('Merge only formats into first selected book - delete others'),
partial(self.merge_books, merge_only_formats=True),
Qt.AltModifier+Qt.ShiftModifier+Qt.Key_M)
cm2('merge formats', _('Merge only formats into first selected book - delete others'),
triggered=partial(self.merge_books, merge_only_formats=True),
shortcut='Alt+Shift+M')
self.merge_menu = mb
self.action_merge.setMenu(mb)
md.addSeparator()
md.addAction(self.action_merge)
self.action_merge = cm('merge', _('Merge book records'), icon='merge_books.png',
shortcut=_('M'), triggered=self.merge_books)
self.action_merge.setMenu(mb)
self.qaction.triggered.connect(self.edit_metadata)
self.action_merge.triggered.connect(self.merge_books)
def location_selected(self, loc):
enabled = loc == 'library'

View File

@ -5,6 +5,8 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QIcon, Qt
from calibre.gui2.actions import InterfaceAction
@ -21,18 +23,17 @@ class PreferencesAction(InterfaceAction):
def genesis(self):
pm = self.qaction.menu()
cm = partial(self.create_menu_action, pm)
if isosx:
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
self.gui.run_wizard)
pm.addAction(QIcon(I('plugins/plugin_updater.png')),
_('Get plugins to enhance calibre'), self.get_plugins)
cm('welcome wizard', _('Run welcome wizard'),
icon='wizard.png', triggered=self.gui.run_wizard)
cm('plugin updater', _('Get plugins to enhance calibre'),
icon='plugins/plugin_updater.png', triggered=self.get_plugins)
if not DEBUG:
pm.addSeparator()
ac = pm.addAction(QIcon(I('debug.png')), _('Restart in debug mode'),
self.debug_restart)
ac.setShortcut('Ctrl+Shift+R')
self.gui.addAction(ac)
cm('restart', _('Restart in debug mode'), icon='debug.png',
triggered=self.debug_restart, shortcut='Ctrl+Shift+R')
self.preferences_menu = pm
for x in (self.gui.preferences_action, self.qaction):

View File

@ -44,15 +44,16 @@ class SaveToDiskAction(InterfaceAction):
def genesis(self):
self.qaction.triggered.connect(self.save_to_disk)
self.save_menu = self.qaction.menu()
self.save_menu.addAction(_('Save to disk in a single directory'),
partial(self.save_to_single_dir, False))
self.save_menu.addAction(_('Save only %s format to disk')%
cm = partial(self.create_menu_action, self.save_menu)
cm('single dir', _('Save to disk in a single directory'),
triggered=partial(self.save_to_single_dir, False))
cm('single format', _('Save only %s format to disk')%
prefs['output_format'].upper(),
partial(self.save_single_format_to_disk, False))
self.save_menu.addAction(
triggered=partial(self.save_single_format_to_disk, False))
cm('fingle dir and format',
_('Save only %s format to disk in a single directory')%
prefs['output_format'].upper(),
partial(self.save_single_fmt_to_single_dir, False))
triggered=partial(self.save_single_fmt_to_single_dir, False))
self.save_sub_menu = SaveMenu(self.gui)
self.save_sub_menu_action = self.save_menu.addMenu(self.save_sub_menu)
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)

View File

@ -24,16 +24,21 @@ class StoreAction(InterfaceAction):
def genesis(self):
self.qaction.triggered.connect(self.do_search)
self.store_menu = self.qaction.menu()
self.load_menu()
def load_menu(self):
self.store_menu.clear()
self.store_menu.addAction(self.menuless_qaction)
self.store_menu.addAction(_('Search for this author'), self.search_author)
self.store_menu.addAction(_('Search for this title'), self.search_title)
self.store_menu.addAction(_('Search for this book'), self.search_author_title)
cm = partial(self.create_menu_action, self.store_menu)
for x, t in [('author', _('author')), ('title', _('title')),
('book', _('book'))]:
func = getattr(self, 'search_%s'%('author_title' if x == 'book'
else x))
ac = cm(x, _('Search for this %s'%t), triggered=func)
setattr(self, 'action_search_by_'+x, ac)
self.store_menu.addSeparator()
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
self.load_menu()
self.store_menu.addSeparator()
cm('choose stores', _('Choose stores'), triggered=self.choose)
def load_menu(self):
self.store_list_menu.clear()
icon = QIcon()
icon.addFile(I('donate.png'), QSize(16, 16))
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
@ -41,8 +46,6 @@ class StoreAction(InterfaceAction):
self.store_list_menu.addAction(icon, n, partial(self.open_store, p))
else:
self.store_list_menu.addAction(n, partial(self.open_store, p))
self.store_menu.addSeparator()
self.store_menu.addAction(_('Choose stores'), self.choose)
def do_search(self):
return self.search()

View File

@ -6,6 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, time
from functools import partial
from PyQt4.Qt import Qt, QAction, pyqtSignal
@ -43,36 +44,33 @@ class ViewAction(InterfaceAction):
self.qaction.triggered.connect(self.view_book)
self.view_action = self.menuless_qaction
self.view_menu = self.qaction.menu()
ac = self.view_specific_action = QAction(_('View specific format'),
self.gui)
ac.setShortcut(Qt.AltModifier+Qt.Key_V)
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
ac = self.create_action(spec=(_('Read a random book'), 'random.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.view_random)
ac = self.clear_history_action = QAction(
_('Clear recently viewed list'), self.gui)
ac.triggered.connect(self.clear_history)
cm = partial(self.create_menu_action, self.view_menu)
self.view_specific_action = cm('specific', _('View specific format'),
shortcut='Alt+V', triggered=self.view_specific_format)
self.action_pick_random = cm('pick random', _('Read a random book'),
icon='random.png', triggered=self.view_random)
self.clear_sep1 = self.view_menu.addSeparator()
self.clear_sep2 = self.view_menu.addSeparator()
self.clear_history_action = cm('clear history',
_('Clear recently viewed list'), triggered=self.clear_history)
self.history_actions = [self.clear_sep1]
def initialization_complete(self):
self.build_menus(self.gui.current_db)
def build_menus(self, db):
self.view_menu.clear()
self.view_menu.addAction(self.view_action)
self.view_menu.addAction(self.view_specific_action)
self.view_menu.addSeparator()
self.view_menu.addAction(self.action_pick_random)
for ac in self.history_actions:
self.view_menu.removeAction(ac)
self.history_actions = []
history = db.prefs.get('gui_view_history', [])
if history:
self.view_menu.addSeparator()
self.view_menu.insertAction(self.clear_sep2, self.clear_sep1)
self.history_actions.append(self.clear_sep1)
for id_, title in history:
ac = HistoryAction(id_, title, self.view_menu)
self.view_menu.addAction(ac)
self.view_menu.insertAction(self.clear_sep2, ac)
ac.view_historical.connect(self.view_historical)
self.view_menu.addSeparator()
self.view_menu.addAction(self.clear_history_action)
self.history_actions.append(ac)
def clear_history(self):
db = self.gui.current_db

View File

@ -218,7 +218,7 @@ class LayoutMixin(object): # {{{
self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.png'),
orientation=Qt.Vertical, parent=self, side_index=1,
shortcut=_('Alt+D'))
shortcut=_('Shift+Alt+D'))
self.bd_splitter.addWidget(self.stack)
self.bd_splitter.addWidget(self.book_details)
self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False)

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from collections import OrderedDict
from PyQt4.Qt import (QObject, QKeySequence)
from calibre.utils.config import JSONConfig
from calibre.constants import DEBUG
from calibre import prints
class NameConflict(ValueError):
pass
class Manager(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.config = JSONConfig('shortcuts/main')
self.custom_keys_map = {}
self.shortcuts = OrderedDict()
self.keys_map = {}
for unique_name, keys in self.config.get(
'map', {}).iteritems():
self.custom_keys_map[unique_name] = tuple(keys)
def register_shortcut(self, unique_name, name, default_keys=(),
description=None, action=None):
if unique_name in self.shortcuts:
name = self.shortcuts[unique_name]['name']
raise NameConflict('Shortcut for %r already registered by %s'%(
unique_name, name))
shortcut = {'name':name, 'desc':description, 'action': action,
'default_keys':tuple(default_keys)}
self.shortcuts[unique_name] = shortcut
def finalize(self):
seen = {}
for unique_name, shortcut in self.shortcuts.iteritems():
custom_keys = self.custom_keys_map.get(unique_name, None)
if custom_keys is None:
candidates = shortcut['default_keys']
else:
candidates = custom_keys
keys = []
for x in candidates:
ks = QKeySequence(x, QKeySequence.PortableText)
x = unicode(ks.toString(QKeySequence.PortableText))
if x in seen:
if DEBUG:
prints('Key %r for shortcut %s is already used by'
' %s, ignoring'%(x, shortcut['name'], seen[x]['name']))
continue
seen[x] = shortcut
keys.append(ks)
keys = tuple(keys)
#print (111111, unique_name, candidates, keys)
self.keys_map[unique_name] = keys
ac = shortcut['action']
if ac is not None:
ac.setShortcuts(list(keys))

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import (QIcon, Qt, QWidget, QSize,
pyqtSignal, QToolButton, QMenu,
pyqtSignal, QToolButton, QMenu, QAction,
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
@ -178,7 +178,12 @@ class SearchBar(QWidget): # {{{
x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
parent.advanced_search_button = x = QToolButton(self)
parent.advanced_search_button.setShortcut(_("Shift+Ctrl+F"))
parent.advanced_search_toggle_action = ac = QAction(parent)
parent.addAction(ac)
parent.keyboard.register_shortcut('advanced search toggle',
_('Advanced search'), default_keys=(_("Shift+Ctrl+F"),),
action=ac)
ac.triggered.connect(x.click)
x.setIcon(QIcon(I('search.png')))
l.addWidget(x)
x.setToolTip(_("Advanced search"))

View File

@ -376,9 +376,12 @@ class SearchBoxMixin(object): # {{{
self.search.clear()
self.search.setMaximumWidth(self.width()-150)
self.action_focus_search = QAction(self)
shortcuts = QKeySequence.keyBindings(QKeySequence.Find)
shortcuts = list(shortcuts) + [QKeySequence('/'), QKeySequence('Alt+S')]
self.action_focus_search.setShortcuts(shortcuts)
shortcuts = list(
map(lambda x:unicode(x.toString()),
QKeySequence.keyBindings(QKeySequence.Find)))
shortcuts += ['/', 'Alt+S']
self.keyboard.register_shortcut('start search', _('Start search'),
default_keys=shortcuts, action=self.action_focus_search)
self.action_focus_search.triggered.connect(self.focus_search_box)
self.addAction(self.action_focus_search)
self.search.setStatusTip(re.sub(r'<\w+>', ' ',

View File

@ -16,7 +16,7 @@ from collections import OrderedDict
from PyQt4.Qt import (Qt, SIGNAL, QTimer, QHelpEvent, QAction,
QMenu, QIcon, pyqtSignal, QUrl,
QDialog, QSystemTrayIcon, QApplication, QKeySequence)
QDialog, QSystemTrayIcon, QApplication)
from calibre import prints
from calibre.constants import __appname__, isosx
@ -40,6 +40,7 @@ from calibre.gui2.init import LibraryViewMixin, LayoutMixin
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
from calibre.gui2.tag_browser.ui import TagBrowserMixin
from calibre.gui2.keyboard import Manager
class Listener(Thread): # {{{
@ -104,6 +105,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def __init__(self, opts, parent=None, gui_debug=None):
global _gui
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
self.keyboard = Manager(self)
_gui = self
self.opts = opts
self.device_connected = None
@ -238,7 +240,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.eject_action.setEnabled(False)
self.addAction(self.quit_action)
self.system_tray_menu.addAction(self.quit_action)
self.quit_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q))
self.keyboard.register_shortcut('quit calibre', _('Quit calibre'),
default_keys=('Ctrl+Q',), action=self.quit_action)
self.system_tray_icon.setContextMenu(self.system_tray_menu)
self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
@ -249,7 +252,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.esc_action = QAction(self)
self.addAction(self.esc_action)
self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape))
self.keyboard.register_shortcut('clear current search',
_('Clear the current search'), default_keys=('Esc',),
action=self.esc_action)
self.esc_action.triggered.connect(self.esc)
####################### Start spare job server ########################
@ -340,6 +345,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
raise
self.device_manager.set_current_library_uuid(db.library_id)
self.keyboard.finalize()
# Collect cycles now
gc.collect()

View File

@ -1063,9 +1063,16 @@ class Splitter(QSplitter):
self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label,
self)
self.action_toggle.triggered.connect(self.toggle_triggered)
self.action_toggle.setShortcut(shortcut)
if parent is not None:
parent.addAction(self.action_toggle)
if hasattr(parent, 'keyboard'):
parent.keyboard.register_shortcut('splitter %s %s'%(name,
label), unicode(self.action_toggle.text()),
default_keys=(shortcut,), action=self.action_toggle)
else:
self.action_toggle.setShortcut(shortcut)
else:
self.action_toggle.setShortcut(shortcut)
def toggle_triggered(self, *args):
self.toggle_side_pane()

View File

@ -547,6 +547,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- Toggle jobs list
* - :kbd:`Alt+Shift+B`
- Toggle Cover Browser
* - :kbd:`Alt+Shift+B`
- Toggle Book Details panel
* - :kbd:`Alt+Shift+T`
- Toggle Tag Browser
* - :kbd:`Alt+A`