diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index a5ef402f22..5dc0710d6e 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from functools import partial from zipfile import ZipFile -from PyQt4.Qt import QToolButton, QAction, QIcon, QObject +from PyQt4.Qt import QToolButton, QAction, QIcon, QObject, QMenu from calibre.gui2 import Dispatcher @@ -66,6 +66,14 @@ class InterfaceAction(QObject): #: shortcut must be a translated string if not None action_spec = ('text', 'icon', None, None) + #: If True, a menu is automatically created and added to self.qaction + action_add_menu = False + + #: If True, a clone of self.qaction is added to the menu of self.qaction + #: If you want the text of this action to be different from that of + #: self.qaction, set this variable to the new text + action_menu_clone_qaction = False + #: Set of locations to which this action must not be added. #: See :attr:`all_locations` for a list of possible locations dont_add_to = frozenset([]) @@ -94,6 +102,7 @@ class InterfaceAction(QObject): self.Dispatcher = partial(Dispatcher, parent=self) self.create_action() self.gui.addAction(self.qaction) + self.gui.addAction(self.menuless_qaction) self.genesis() def create_action(self, spec=None, attr='qaction'): @@ -104,18 +113,29 @@ class InterfaceAction(QObject): action = QAction(QIcon(I(icon)), text, self.gui) else: action = QAction(text, self.gui) - action.setAutoRepeat(self.auto_repeat) - text = tooltip if tooltip else text - action.setToolTip(text) - action.setStatusTip(text) - action.setWhatsThis(text) - action.setAutoRepeat(False) + if attr == 'qaction': + mt = (action.text() if self.action_menu_clone_qaction is True else + unicode(self.action_menu_clone_qaction)) + self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui) + ma.triggered.connect(action.trigger) + for a in ((action, ma) if attr == 'qaction' else (action,)): + a.setAutoRepeat(self.auto_repeat) + text = tooltip if tooltip else text + a.setToolTip(text) + a.setStatusTip(text) + a.setWhatsThis(text) if shortcut: + a = ma if attr == 'qaction' else action if isinstance(shortcut, list): - action.setShortcuts(shortcut) + a.setShortcuts(shortcut) else: - action.setShortcut(shortcut) + a.setShortcut(shortcut) setattr(self, attr, action) + if attr == 'qaction' and self.action_add_menu: + menu = QMenu() + action.setMenu(menu) + if self.action_menu_clone_qaction: + menu.addAction(self.menuless_qaction) return action def load_resources(self, names): diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index 3556f1db80..7f7bf72be9 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import os from functools import partial -from PyQt4.Qt import QPixmap, QMenu, QTimer +from PyQt4.Qt import QPixmap, QTimer from calibre.gui2 import error_dialog, choose_files, \ @@ -48,12 +48,12 @@ class AddAction(InterfaceAction): _('Add books to the calibre library/device from files on your computer') , _('A')) action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = _('Add books from a single directory') def genesis(self): self._add_filesystem_book = self.Dispatcher(self.__add_filesystem_book) - self.add_menu = QMenu() - self.add_menu.addAction(_('Add books from a single directory'), - self.add_books) + self.add_menu = self.qaction.menu() self.add_menu.addAction(_('Add books from directories, including ' 'sub-directories (One book per directory, assumes every ebook ' 'file is the same book in a different format)'), @@ -69,7 +69,6 @@ class AddAction(InterfaceAction): self.add_menu.addAction(_('Add files to selected book records'), self.add_formats, _('Shift+A')) - self.qaction.setMenu(self.add_menu) self.qaction.triggered.connect(self.add_books) def location_selected(self, loc): diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 726fee7910..579aa681b2 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -82,23 +82,20 @@ class ChooseLibraryAction(InterfaceAction): action_spec = (_('%d books'), 'lt.png', _('Choose calibre library to work with'), None) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) + action_add_menu = True + action_menu_clone_qaction = _('Switch/create library...') def genesis(self): self.count_changed(0) self.qaction.triggered.connect(self.choose_library, type=Qt.QueuedConnection) + self.action_choose = self.menuless_qaction self.stats = LibraryUsageStats() self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else QToolButton.MenuButtonPopup) - self.create_action(spec=(_('Switch/create library...'), 'lt.png', None, - None), attr='action_choose') - self.action_choose.triggered.connect(self.choose_library, - type=Qt.QueuedConnection) - self.choose_menu = QMenu(self.gui) - self.qaction.setMenu(self.choose_menu) - + self.choose_menu = self.qaction.menu() if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None): self.choose_menu.addAction(self.action_choose) @@ -110,7 +107,7 @@ class ChooseLibraryAction(InterfaceAction): self.delete_menu = QMenu(_('Remove library')) self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu) - ac = self.create_action(spec=(_('Pick a random book'), 'catalog.png', + ac = self.create_action(spec=(_('Pick a random book'), 'random.png', None, None), attr='action_pick_random') ac.triggered.connect(self.pick_random) self.choose_menu.addAction(ac) diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index e3dc697a45..b51d59aa19 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, QMenu +from PyQt4.Qt import QModelIndex, QIcon from calibre.gui2 import error_dialog, Dispatcher from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook @@ -22,18 +22,18 @@ class ConvertAction(InterfaceAction): action_spec = (_('Convert books'), 'convert.png', None, _('C')) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' + action_add_menu = True def genesis(self): - cm = QMenu() - cm.addAction(_('Convert individually'), partial(self.convert_ebook, + cm = self.qaction.menu() + cm.addAction(self.qaction.icon(), _('Convert individually'), partial(self.convert_ebook, False, bulk=False)) cm.addAction(_('Bulk convert'), partial(self.convert_ebook, False, bulk=True)) cm.addSeparator() - ac = cm.addAction( + 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) - self.qaction.setMenu(cm) self.qaction.triggered.connect(self.convert_ebook) self.convert_menu = cm self.conversion_jobs = {} diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index c3f5eecd0e..a70ebdad7b 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -9,7 +9,7 @@ import os from functools import partial from threading import Thread -from PyQt4.Qt import QMenu, QToolButton +from PyQt4.Qt import QToolButton from calibre.gui2.actions import InterfaceAction from calibre.gui2 import error_dialog, Dispatcher, warning_dialog @@ -95,10 +95,10 @@ class CopyToLibraryAction(InterfaceAction): popup_type = QToolButton.InstantPopup dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) action_type = 'current' + action_add_menu = True def genesis(self): - self.menu = QMenu(self.gui) - self.qaction.setMenu(self.menu) + self.menu = self.qaction.menu() @property def stats(self): diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index dcaf5208c6..31195fd94c 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QMenu, QObject, QTimer +from PyQt4.Qt import QObject, QTimer from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog @@ -18,7 +18,7 @@ from calibre.utils.recycle_bin import can_recycle single_shot = partial(QTimer.singleShot, 10) -class MultiDeleter(QObject): +class MultiDeleter(QObject): # {{{ def __init__(self, gui, ids, callback): from calibre.gui2.dialogs.progress import ProgressDialog @@ -77,17 +77,19 @@ class MultiDeleter(QObject): error_dialog(self.gui, _('Failed to delete'), _('Failed to delete some books, click the Show Details button' ' for details.'), det_msg='\n\n'.join(msg), show=True) +# }}} class DeleteAction(InterfaceAction): name = 'Remove Books' action_spec = (_('Remove books'), 'trash.png', None, 'Del') action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = _('Remove selected books') def genesis(self): self.qaction.triggered.connect(self.delete_books) - self.delete_menu = QMenu() - self.delete_menu.addAction(_('Remove selected books'), self.delete_books) + self.delete_menu = self.qaction.menu() self.delete_menu.addAction( _('Remove files of a specific format from selected books..'), self.delete_selected_formats) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 5dd71ad696..5e220fdb1d 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -24,12 +24,13 @@ class EditMetadataAction(InterfaceAction): name = 'Edit Metadata' action_spec = (_('Edit metadata'), 'edit_input.png', None, _('E')) action_type = 'current' + action_add_menu = True def genesis(self): self.create_action(spec=(_('Merge book records'), 'merge_books.png', None, _('M')), attr='action_merge') - md = QMenu() - md.addAction(_('Edit metadata individually'), + md = self.qaction.menu() + md.addAction(self.qaction.icon(), _('Edit metadata individually'), partial(self.edit_metadata, False, bulk=False)) md.addSeparator() md.addAction(_('Edit metadata in bulk'), @@ -56,7 +57,6 @@ class EditMetadataAction(InterfaceAction): md.addAction(self.action_merge) self.qaction.triggered.connect(self.edit_metadata) - self.qaction.setMenu(md) self.action_merge.triggered.connect(self.merge_books) def location_selected(self, loc): diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index c3bf9bbe8b..21a70ecf03 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QIcon, QMenu, Qt +from PyQt4.Qt import QIcon, Qt from calibre.gui2.actions import InterfaceAction from calibre.gui2.preferences.main import Preferences @@ -16,12 +16,13 @@ class PreferencesAction(InterfaceAction): name = 'Preferences' action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P')) + action_add_menu = True + action_menu_clone_qaction = _('Change calibre behavior') def genesis(self): - pm = QMenu() - pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config) + pm = self.qaction.menu() if isosx: - pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config) + 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')), @@ -33,7 +34,6 @@ class PreferencesAction(InterfaceAction): ac.setShortcut('Ctrl+Shift+R') self.gui.addAction(ac) - self.qaction.setMenu(pm) self.preferences_menu = pm for x in (self.gui.preferences_action, self.qaction): x.triggered.connect(self.do_config) diff --git a/src/calibre/gui2/actions/save_to_disk.py b/src/calibre/gui2/actions/save_to_disk.py index 93c35900a9..af3e1ebf34 100644 --- a/src/calibre/gui2/actions/save_to_disk.py +++ b/src/calibre/gui2/actions/save_to_disk.py @@ -38,12 +38,12 @@ class SaveToDiskAction(InterfaceAction): name = "Save To Disk" action_spec = (_('Save to disk'), 'save.png', None, _('S')) action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = True def genesis(self): self.qaction.triggered.connect(self.save_to_disk) - self.save_menu = QMenu() - self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk, - False)) + 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')% @@ -56,7 +56,6 @@ class SaveToDiskAction(InterfaceAction): 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) - self.qaction.setMenu(self.save_menu) def location_selected(self, loc): enabled = loc == 'library' diff --git a/src/calibre/gui2/actions/similar_books.py b/src/calibre/gui2/actions/similar_books.py index b1ee04a4d4..31148d6a7c 100644 --- a/src/calibre/gui2/actions/similar_books.py +++ b/src/calibre/gui2/actions/similar_books.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QMenu, QToolButton +from PyQt4.Qt import QToolButton from calibre.gui2.actions import InterfaceAction @@ -17,9 +17,10 @@ class SimilarBooksAction(InterfaceAction): action_spec = (_('Similar books...'), None, None, None) popup_type = QToolButton.InstantPopup action_type = 'current' + action_add_menu = True def genesis(self): - m = QMenu(self.gui) + m = self.qaction.menu() for text, icon, target, shortcut in [ (_('Books by same author'), 'user_profile.png', 'authors', _('Alt+A')), (_('Books in this series'), 'books_in_series.png', 'series', @@ -31,7 +32,6 @@ class SimilarBooksAction(InterfaceAction): m.addAction(ac) ac.triggered.connect(partial(self.show_similar_books, target)) self.qaction.setMenu(m) - self.similar_menu = m def show_similar_books(self, type, *args): search, join = [], ' ' diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 7f9b538bcf..3e68229332 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QMenu, QIcon, QSize +from PyQt4.Qt import QIcon, QSize from calibre.gui2 import error_dialog from calibre.gui2.actions import InterfaceAction @@ -17,16 +17,18 @@ from calibre.gui2.dialogs.confirm_delete import confirm class StoreAction(InterfaceAction): name = 'Store' - action_spec = (_('Get books'), 'store.png', None, None) + action_spec = (_('Get books'), 'store.png', None, _('G')) + action_add_menu = True + action_menu_clone_qaction = _('Search for ebooks') def genesis(self): self.qaction.triggered.connect(self.do_search) - self.store_menu = QMenu() + self.store_menu = self.qaction.menu() self.load_menu() def load_menu(self): self.store_menu.clear() - self.store_menu.addAction(_('Search for ebooks'), self.search) + 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) @@ -41,7 +43,6 @@ class StoreAction(InterfaceAction): self.store_list_menu.addAction(n, partial(self.open_store, p)) self.store_menu.addSeparator() self.store_menu.addAction(_('Choose stores'), self.choose) - self.qaction.setMenu(self.store_menu) def do_search(self): return self.search() diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index f67b0767d6..f6797b5e8f 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import os, time -from PyQt4.Qt import Qt, QMenu, QAction, pyqtSignal +from PyQt4.Qt import Qt, QAction, pyqtSignal from calibre.constants import isosx from calibre.gui2 import error_dialog, Dispatcher, question_dialog, config, \ @@ -35,20 +35,19 @@ class ViewAction(InterfaceAction): name = 'View' action_spec = (_('View'), 'view.png', None, _('V')) action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = True def genesis(self): self.persistent_files = [] self.qaction.triggered.connect(self.view_book) - self.view_menu = QMenu() + self.view_action = self.menuless_qaction + self.view_menu = self.qaction.menu() ac = self.view_specific_action = QAction(_('View specific format'), self.gui) - self.qaction.setMenu(self.view_menu) ac.setShortcut(Qt.AltModifier+Qt.Key_V) ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection) - ac = self.view_action = QAction(self.qaction.icon(), - self.qaction.text(), self.gui) - ac.triggered.connect(self.view_book) - ac = self.create_action(spec=(_('Read a random book'), 'catalog.png', + 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( diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 23813a27a8..db71b1ccc7 100755 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -527,6 +527,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes - Remove selected Books * - :kbd:`E` - Edit metadata of selected books + * - :kbd:`G` + - Get Books * - :kbd:`I` - Show book details * - :kbd:`M`