diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index 83748766b2..dbeaa61ea1 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -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,13 +129,20 @@ 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) - setattr(self, attr, action) + 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() action.setMenu(menu) @@ -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 diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index 7f7bf72be9..379e72822d 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -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, diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index b51d59aa19..0dbbdaf1f3 100644 --- a/src/calibre/gui2/actions/convert.py +++ b/src/calibre/gui2/actions/convert.py @@ -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): diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 31195fd94c..0455f75043 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -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 = {} diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index debcbb6c1a..d4ed26ba8a 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -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: diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 5e220fdb1d..64d6f2cbd6 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -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' diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index 21a70ecf03..cd9de36fce 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -5,6 +5,8 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __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): diff --git a/src/calibre/gui2/actions/save_to_disk.py b/src/calibre/gui2/actions/save_to_disk.py index af3e1ebf34..9e9cbe5f54 100644 --- a/src/calibre/gui2/actions/save_to_disk.py +++ b/src/calibre/gui2/actions/save_to_disk.py @@ -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) diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 3e68229332..e843534c37 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -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() diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index f6797b5e8f..84060de786 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -6,6 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' __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 diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 4aa94fc8e1..4458104a3a 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -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) diff --git a/src/calibre/gui2/keyboard.py b/src/calibre/gui2/keyboard.py new file mode 100644 index 0000000000..ee936f5f4f --- /dev/null +++ b/src/calibre/gui2/keyboard.py @@ -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 ' +__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)) + diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 76b9f5f9a2..423907d8fd 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -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")) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 19cfb7417e..fd085923e2 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -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+>', ' ', diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 61df7e0e06..c1cc07f56c 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -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() diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 96fbafe4d5..01f7029921 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -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() diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index db71b1ccc7..a1448dfe84 100755 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -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`