From 86e17cc100b2aacd7912f4cd094871c41468ae93 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 8 Apr 2011 15:51:45 -0600 Subject: [PATCH] Add a menu bar. By default the menu bar is hidden (except on OS X). You can add actions to it via Preferences->Toolbars. --- src/calibre/gui2/__init__.py | 4 + src/calibre/gui2/actions/__init__.py | 2 +- src/calibre/gui2/actions/add_to_library.py | 2 +- src/calibre/gui2/actions/annotate.py | 1 + src/calibre/gui2/actions/catalog.py | 2 +- src/calibre/gui2/actions/choose_library.py | 3 +- src/calibre/gui2/actions/convert.py | 2 +- src/calibre/gui2/actions/device.py | 7 +- src/calibre/gui2/actions/edit_collections.py | 2 +- src/calibre/gui2/actions/next_match.py | 2 +- src/calibre/gui2/actions/open.py | 2 +- src/calibre/gui2/actions/preferences.py | 1 - src/calibre/gui2/actions/show_book_details.py | 2 +- src/calibre/gui2/actions/tweak_epub.py | 2 +- src/calibre/gui2/layout.py | 113 ++++++++++++++++-- src/calibre/gui2/preferences/main.py | 1 + src/calibre/gui2/preferences/toolbar.py | 30 ++++- src/calibre/gui2/ui.py | 4 +- 18 files changed, 146 insertions(+), 36 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 4e550de6ad..5b6430a5e6 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -23,6 +23,10 @@ from calibre.utils.date import UNDEFINED_DATE # Setup gprefs {{{ gprefs = JSONConfig('gui') +gprefs.defaults['action-layout-menubar'] = () + +gprefs.defaults['action-layout-menubar-device'] = () + gprefs.defaults['action-layout-toolbar'] = ( 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, 'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk', diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index a68659864a..a5ef402f22 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -75,7 +75,7 @@ class InterfaceAction(QObject): dont_remove_from = frozenset([]) all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu', - 'context-menu-device', 'toolbar-child']) + 'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device']) #: Type of action #: 'current' means acts on the current view diff --git a/src/calibre/gui2/actions/add_to_library.py b/src/calibre/gui2/actions/add_to_library.py index fd686e3833..2665ffe0a4 100644 --- a/src/calibre/gui2/actions/add_to_library.py +++ b/src/calibre/gui2/actions/add_to_library.py @@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction): name = 'Add To Library' action_spec = (_('Add books to library'), 'add_book.png', _('Add books to your calibre library from the connected device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/annotate.py b/src/calibre/gui2/actions/annotate.py index a702ba045e..48397936fb 100644 --- a/src/calibre/gui2/actions/annotate.py +++ b/src/calibre/gui2/actions/annotate.py @@ -18,6 +18,7 @@ class FetchAnnotationsAction(InterfaceAction): name = 'Fetch Annotations' action_spec = (_('Fetch annotations (experimental)'), None, None, None) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index 2af02efd13..fad6e59294 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -18,7 +18,7 @@ class GenerateCatalogAction(InterfaceAction): name = 'Generate Catalog' action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def generate_catalog(self): rows = self.gui.library_view.selectionModel().selectedRows() diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index fd7959a30e..4b262ad9dd 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -80,7 +80,7 @@ class ChooseLibraryAction(InterfaceAction): name = 'Choose Library' action_spec = (_('%d books'), 'lt.png', _('Choose calibre library to work with'), None) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def genesis(self): self.count_changed(0) @@ -210,7 +210,6 @@ class ChooseLibraryAction(InterfaceAction): rename_actions, delete_actions, qs_actions, self.action_choose) - def location_selected(self, loc): enabled = loc == 'library' self.qaction.setEnabled(enabled) diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index 36f420c7bb..ed0a064e88 100644 --- a/src/calibre/gui2/actions/convert.py +++ b/src/calibre/gui2/actions/convert.py @@ -20,7 +20,7 @@ class ConvertAction(InterfaceAction): name = 'Convert Books' action_spec = (_('Convert books'), 'convert.png', None, _('C')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index bfefbc5f64..64bc4e69d7 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -24,7 +24,7 @@ class ShareConnMenu(QMenu): # {{{ config_email = pyqtSignal() toggle_server = pyqtSignal() - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def __init__(self, parent=None): QMenu.__init__(self, parent) @@ -121,8 +121,7 @@ class SendToDeviceAction(InterfaceAction): name = 'Send To Device' action_spec = (_('Send to device'), 'sync.png', None, _('D')) - dont_remove_from = frozenset(['toolbar-device']) - dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) def genesis(self): self.qaction.triggered.connect(self.do_sync) @@ -169,7 +168,7 @@ class ConnectShareAction(InterfaceAction): def toggle_content_server(self): if self.gui.content_server is None: - self.gui.start_content_server() + self.gui.start_content_server() else: self.gui.content_server.threaded_exit() self.stopping_msg = info_dialog(self.gui, _('Stopping'), diff --git a/src/calibre/gui2/actions/edit_collections.py b/src/calibre/gui2/actions/edit_collections.py index c64a3249d4..1a0b1038ce 100644 --- a/src/calibre/gui2/actions/edit_collections.py +++ b/src/calibre/gui2/actions/edit_collections.py @@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction): name = 'Edit Collections' action_spec = (_('Manage collections'), None, _('Manage the collections on this device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/next_match.py b/src/calibre/gui2/actions/next_match.py index 1c74719674..8e076655a9 100644 --- a/src/calibre/gui2/actions/next_match.py +++ b/src/calibre/gui2/actions/next_match.py @@ -11,7 +11,7 @@ class NextMatchAction(InterfaceAction): name = 'Move to next highlighted book' action_spec = (_('Move to next match'), 'arrow-down.png', _('Move to next highlighted match'), [_('N'), _('F3')]) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/open.py b/src/calibre/gui2/actions/open.py index 141ff01a66..a66f68eee5 100644 --- a/src/calibre/gui2/actions/open.py +++ b/src/calibre/gui2/actions/open.py @@ -13,7 +13,7 @@ class OpenFolderAction(InterfaceAction): name = 'Open Folder' action_spec = (_('Open containing folder'), 'document_open.png', None, _('O')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index 6615f5c017..24d20b23f9 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -16,7 +16,6 @@ class PreferencesAction(InterfaceAction): name = 'Preferences' action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P')) - dont_remove_from = frozenset(['toolbar']) def genesis(self): pm = QMenu() diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 67903a7d58..11064f2f39 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -15,7 +15,7 @@ class ShowBookDetailsAction(InterfaceAction): name = 'Show Book Details' action_spec = (_('Show book details'), 'dialog_information.png', None, _('I')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py index 212aff8019..c9f2d7a8c6 100755 --- a/src/calibre/gui2/actions/tweak_epub.py +++ b/src/calibre/gui2/actions/tweak_epub.py @@ -15,7 +15,7 @@ class TweakEpubAction(InterfaceAction): action_spec = (_('Tweak ePub'), 'trim.png', _('Make small changes to ePub format books'), _('T')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 29c2cd4a7a..9a6148eb7b 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -8,11 +8,11 @@ __docformat__ = 'restructuredtext en' from functools import partial from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize, - pyqtSignal, QToolButton, QMenu, + pyqtSignal, QToolButton, QMenu, QMenuBar, QAction, QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) -from calibre.constants import __appname__ +from calibre.constants import __appname__, isosx from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.throbber import ThrobbingButton from calibre.gui2 import gprefs @@ -238,6 +238,80 @@ class Spacer(QWidget): # {{{ self.l.addStretch(10) # }}} +class MenuAction(QAction): + + def __init__(self, clone, parent): + QAction.__init__(self, clone.text(), parent) + self.clone = clone + clone.changed.connect(self.clone_changed) + + def clone_changed(self): + self.setText(self.clone.text()) + + +class MenuBar(QMenuBar): # {{{ + + def __init__(self, location_manager, parent): + QMenuBar.__init__(self, parent) + self.gui = parent + self.setNativeMenuBar(True) + + self.location_manager = location_manager + self.location_manager.locations_changed.connect(self.build_bar) + self.added_actions = [] + + self.donate_action = QAction(_('Donate'), self) + self.donate_menu = QMenu() + self.donate_menu.addAction(self.gui.donate_action) + self.donate_action.setMenu(self.donate_menu) + self.build_bar() + + def build_bar(self, changed_action=None): + showing_device = self.location_manager.has_device + actions = '-device' if showing_device else '' + actions = gprefs['action-layout-menubar'+actions] + + show_main = len(actions) > 0 + self.setVisible(show_main) + + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + + self.clear() + self.added_actions = [] + self.action_map = {} + + for what in actions: + if what is None: + continue + elif what == 'Location Manager': + for ac in self.location_manager.available_actions: + ac = self.build_menu(ac) + self.addAction(ac) + self.added_actions.append(ac) + elif what == 'Donate': + self.addAction(self.donate_action) + elif what in self.gui.iactions: + action = self.gui.iactions[what] + ac = self.build_menu(action.qaction) + self.addAction(ac) + self.added_actions.append(ac) + + def build_menu(self, action): + m = action.menu() + ac = MenuAction(action, self) + if m is None: + m = QMenu() + m.addAction(action) + ac.setMenu(m) + return ac + + + +# }}} + class ToolBar(QToolBar): # {{{ def __init__(self, donate, location_manager, child_bar, parent): @@ -284,6 +358,8 @@ class ToolBar(QToolBar): # {{{ mactions = gprefs['action-layout-toolbar'+mactions] cactions = gprefs['action-layout-toolbar-child'] + show_main = len(mactions) > 0 + self.setVisible(show_main) show_child = len(cactions) > 0 self.child_bar.setVisible(show_child) @@ -306,11 +382,16 @@ class ToolBar(QToolBar): # {{{ bar.added_actions.append(ac) bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) elif what == 'Donate': - self.d_widget = QWidget() - self.d_widget.setLayout(QVBoxLayout()) - self.d_widget.layout().addWidget(self.donate_button) - bar.addWidget(self.d_widget) - self.showing_donate = True + if isosx: + bar.addAction(self.gui.donate_action) + ch = self.setup_tool_button(bar, self.gui.donate_action) + ch.setText(_('Donate')) + else: + self.d_widget = QWidget() + self.d_widget.setLayout(QVBoxLayout()) + self.d_widget.layout().addWidget(self.donate_button) + bar.addWidget(self.d_widget) + self.showing_donate = True elif what in self.gui.iactions: action = self.gui.iactions[what] bar.addAction(action.qaction) @@ -325,17 +406,20 @@ class ToolBar(QToolBar): # {{{ ch.setAutoRaise(True) if ac.menu() is not None and menu_mode is not None: ch.setPopupMode(menu_mode) + return ch def resizeEvent(self, ev): QToolBar.resizeEvent(self, ev) style = Qt.ToolButtonTextUnderIcon - p = gprefs['toolbar_text'] - if p == 'never': - style = Qt.ToolButtonIconOnly + s = gprefs['toolbar_icon_size'] + if s != 'off': + p = gprefs['toolbar_text'] + if p == 'never': + style = Qt.ToolButtonIconOnly - if p == 'auto' and self.preferred_width > self.width()+35 and \ - not gprefs['action-layout-toolbar-child']: - style = Qt.ToolButtonIconOnly + if p == 'auto' and self.preferred_width > self.width()+35 and \ + not gprefs['action-layout-toolbar-child']: + style = Qt.ToolButtonIconOnly self.setToolButtonStyle(style) @@ -421,6 +505,9 @@ class MainWindowMixin(object): # {{{ self.location_manager, self.child_bar, self) self.addToolBar(Qt.TopToolBarArea, self.tool_bar) self.addToolBar(Qt.BottomToolBarArea, self.child_bar) + self.menu_bar = MenuBar(self.location_manager, self) + self.setMenuBar(self.menu_bar) + self.setUnifiedTitleAndToolBarOnMac(True) l = self.centralwidget.layout() l.addWidget(self.search_bar) diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index d930fb3ebd..9f26bea7ce 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -360,6 +360,7 @@ class Preferences(QMainWindow): self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.tool_bar.build_bar() + self.gui.menu_bar.build_bar() self.gui.build_context_menus() self.gui.tool_bar.apply_settings() diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index 93079110a5..9512fc7d3d 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -34,9 +34,12 @@ class BaseModel(QAbstractListModel): if name == 'Location Manager': return FakeAction(name, None, _('Switch between library and device views'), - dont_remove_from=set(['toolbar-device'])) + dont_add_to=frozenset(['menubar', 'toolbar', + 'toolbar-child', 'context-menu', + 'context-menu-device'])) if name is None: - return FakeAction('--- '+_('Separator')+' ---', None) + return FakeAction('--- '+_('Separator')+' ---', None, + dont_add_to=frozenset(['menubar', 'menubar-device'])) try: return gui.iactions[name] except: @@ -89,7 +92,7 @@ class AllModel(BaseModel): self._data = self.get_all_actions(current) def get_all_actions(self, current): - all = list(self.gui.iactions.keys()) + ['Donate'] + all = list(self.gui.iactions.keys()) + ['Donate', 'Location Manager'] all = [x for x in all if x not in current] + [None] all = [self.name_to_action(x, self.gui) for x in all] all = [x for x in all if self.key not in x.dont_add_to] @@ -208,12 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): LOCATIONS = [ ('toolbar', _('The main toolbar')), - ('toolbar-child', _('The optional second toolbar')), ('toolbar-device', _('The main toolbar when a device is connected')), + ('toolbar-child', _('The optional second toolbar')), + ('menubar', _('The menubar')), + ('menubar-device', _('The menubar when a device is connected')), ('context-menu', _('The context menu for the books in the ' 'calibre library')), ('context-menu-device', _('The context menu for the books on ' - 'the device')) + 'the device')), ] def genesis(self, gui): @@ -284,6 +289,21 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() def commit(self): + # Ensure preferences are showing in either the toolbar or + # the menubar. + pref_in_toolbar = lm_in_toolbar = False + cm = self.models['toolbar'] + for x in cm[1]._data: + if x.name == 'Preferences': + pref_in_toolbar = True + if x.name == 'Location Manager': + lm_in_toolbar = True + if not pref_in_toolbar: + self.models['menubar'][1].add(['Preferences']) + if not lm_in_toolbar: + self.models['menubar-device'][1].add(['Location Manager']) + + # Save data. for am, cm in self.models.values(): cm.commit() return False diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index a0aa0138bc..8b2beedfd4 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -153,6 +153,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ for ac in self.iactions.values(): ac.do_genesis() + self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self) MainWindowMixin.__init__(self, db) # Jobs Button {{{ @@ -186,8 +187,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.system_tray_menu = QMenu(self) self.restore_action = self.system_tray_menu.addAction( QIcon(I('page.png')), _('&Restore')) - self.donate_action = self.system_tray_menu.addAction( - QIcon(I('donate.png')), _('&Donate to support calibre')) + self.system_tray_menu.addAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setStatusTip(self.donate_button.toolTip()) self.eject_action = self.system_tray_menu.addAction(