From 78d048c809896fd5ac4db359137a9c43b048ffb2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Oct 2019 15:29:59 +0530 Subject: [PATCH] Implement a vertical toolbar --- src/calibre/gui2/viewer/shortcuts.py | 9 ++ src/calibre/gui2/viewer/toolbars.py | 130 +++++++++++++++++++++++++++ src/calibre/gui2/viewer/ui.py | 6 ++ src/calibre/gui2/viewer/web_view.py | 10 +++ src/pyj/read_book/shortcuts.pyj | 8 ++ src/pyj/read_book/view.pyj | 15 ++++ src/pyj/viewer-main.pyj | 6 ++ 7 files changed, 184 insertions(+) create mode 100644 src/calibre/gui2/viewer/toolbars.py diff --git a/src/calibre/gui2/viewer/shortcuts.py b/src/calibre/gui2/viewer/shortcuts.py index 4b2b5924f3..cc964cb7d0 100644 --- a/src/calibre/gui2/viewer/shortcuts.py +++ b/src/calibre/gui2/viewer/shortcuts.py @@ -15,6 +15,15 @@ def get_main_window_for(widget): p = p.parent() +def index_to_key_sequence(idx): + mods = [] + for i, x in enumerate(('ALT', 'CTRL', 'META', 'SHIFT')): + if idx[i] == 'y': + mods.append(x.capitalize()) + mods.append(idx[4:]) + return QKeySequence('+'.join(mods)) + + def key_to_text(key): return QKeySequence(key).toString(QKeySequence.PortableText).lower() diff --git a/src/calibre/gui2/viewer/toolbars.py b/src/calibre/gui2/viewer/toolbars.py new file mode 100644 index 0000000000..292c68eaf3 --- /dev/null +++ b/src/calibre/gui2/viewer/toolbars.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2019, Kovid Goyal + +from __future__ import absolute_import, division, print_function, unicode_literals + +import os +from collections import defaultdict +from functools import partial + +from PyQt5.Qt import QAction, QIcon, QKeySequence, QMenu, Qt, QToolBar, pyqtSignal +from PyQt5.QtWebEngineWidgets import QWebEnginePage + +from calibre.gui2 import elided_text +from calibre.gui2.viewer.shortcuts import index_to_key_sequence +from calibre.gui2.viewer.web_view import get_session_pref, set_book_path +from polyglot.builtins import iteritems + + +class VerticalToolBar(QToolBar): + + action_triggered = pyqtSignal(object) + open_book_at_path = pyqtSignal(object) + + def __init__(self, parent=None): + QToolBar.__init__(self, parent) + self.setObjectName('vertical_toolbar') + self.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) + self.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.setOrientation(Qt.Vertical) + + def initialize(self, web_view): + self.action_triggered.connect(web_view.trigger_shortcut) + page = web_view.page() + web_view.shortcuts_changed.connect(self.set_tooltips) + web_view.paged_mode_changed.connect(self.update_mode_action) + self.shortcut_actions = {} + + self.back_action = page.action(QWebEnginePage.Back) + self.back_action.setIcon(QIcon(I('back.png'))) + self.back_action.setText(_('Back')) + self.addAction(self.back_action) + self.forward_action = page.action(QWebEnginePage.Forward) + self.forward_action.setIcon(QIcon(I('forward.png'))) + self.forward_action.setText(_('Forward')) + self.addAction(self.forward_action) + self.addSeparator() + + def shortcut_action(icon, text, sc): + a = QAction(QIcon(I(icon)), text, self) + self.addAction(a) + connect_lambda(a.triggered, self, lambda self: self.action_triggered.emit(sc)) + self.shortcut_actions[sc] = a + return a + + self.open_action = a = QAction(QIcon(I('document_open.png')), _('Open e-book'), self) + self.open_menu = m = QMenu(self) + a.setMenu(m) + m.aboutToShow.connect(self.populate_open_menu) + connect_lambda(a.triggered, self, lambda self: self.open_book_at_path.emit(None)) + self.addAction(a) + self.copy_action = a = page.action(QWebEnginePage.Copy) + a.setIcon(QIcon(I('edit-copy.png'))), a.setText(_('Copy to clipboard')) + self.addAction(a) + self.increase_font_size_action = shortcut_action('font_size_larger.png', _('Increase font size'), 'increase_font_size') + self.decrease_font_size_action = shortcut_action('font_size_smaller.png', _('Decrease font size'), 'decrease_font_size') + self.fullscreen_action = shortcut_action('page.png', _('Toggle full screen'), 'toggle_full_screen') + self.addSeparator() + + self.next_action = shortcut_action('next.png', _('Next page'), 'next') + self.previous_action = shortcut_action('previous.png', _('Previous page'), 'previous') + self.addSeparator() + + self.toc_action = shortcut_action('toc.png', _('Table of Contents'), 'toggle_toc') + self.bookmarks_action = shortcut_action('bookmarks.png', _('Bookmarks'), 'toggle_bookmarks') + self.lookup_action = shortcut_action('search.png', _('Lookup words'), 'toggle_lookup') + self.chrome_action = shortcut_action('tweaks.png', _('Show viewer controls'), 'show_chrome') + self.addSeparator() + + self.mode_action = a = shortcut_action('scroll.png', _('Toggle paged mode'), 'toggle_paged_mode') + a.setCheckable(True) + self.print_action = shortcut_action('print.png', _('Print book'), 'print') + self.preferences_action = shortcut_action('config.png', _('Preferences'), 'preferences') + self.metadata_action = shortcut_action('metadata.png', _('Show book metadata'), 'metadata') + self.update_mode_action() + self.addSeparator() + + def update_mode_action(self): + mode = get_session_pref('read_mode', default='paged', group=None) + a = self.mode_action + if mode == 'paged': + a.setChecked(False) + a.setToolTip(_('Switch to flow mode — where the text is not broken into pages')) + else: + a.setChecked(True) + a.setToolTip(_('Switch to paged mode — where the text is broken into pages')) + + def set_tooltips(self, smap): + rmap = defaultdict(list) + for k, v in iteritems(smap): + rmap[v].append(k) + for sc, a in iteritems(self.shortcut_actions): + if a.isCheckable(): + continue + x = rmap.get(sc) + if x is not None: + + def as_text(idx): + return index_to_key_sequence(idx).toString(QKeySequence.NativeText) + + keys = sorted(filter(None, map(as_text, x))) + if keys: + a.setToolTip('{} [{}]'.format(a.text(), ', '.join(keys))) + + def populate_open_menu(self): + m = self.open_menu + m.clear() + recent = get_session_pref('standalone_recently_opened', group=None, default=()) + if recent: + for entry in recent: + try: + path = os.path.abspath(entry['pathtoebook']) + except Exception: + continue + if path == os.path.abspath(set_book_path.pathtoebook): + continue + m.addAction('{}\t {}'.format( + elided_text(entry['title'], pos='right', width=250), + elided_text(os.path.basename(path), width=250))).triggered.connect(partial( + self.open_book_at_path.emit, path)) diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 60bf4397a1..f8c17bde89 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -32,6 +32,7 @@ from calibre.gui2.viewer.convert_book import prepare_book, update_book from calibre.gui2.viewer.lookup import Lookup from calibre.gui2.viewer.overlay import LoadingOverlay from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView +from calibre.gui2.viewer.toolbars import VerticalToolBar from calibre.gui2.viewer.web_view import ( WebView, get_path_for_name, get_session_pref, set_book_path, viewer_config_dir, vprefs @@ -98,6 +99,10 @@ class EbookViewer(MainWindow): self.setWindowTitle(self.base_window_title) self.in_full_screen_mode = None self.image_popup = ImagePopup(self) + self.vertical_toolbar = vt = VerticalToolBar(self) + vt.open_book_at_path.connect(self.ask_for_open) + self.addToolBar(Qt.LeftToolBarArea, vt) + # vt.setVisible(False) try: os.makedirs(annotations_dir) except EnvironmentError: @@ -156,6 +161,7 @@ class EbookViewer(MainWindow): self.web_view.show_loading_message.connect(self.show_loading_message) self.web_view.show_error.connect(self.show_error) self.web_view.print_book.connect(self.print_book, type=Qt.QueuedConnection) + self.vertical_toolbar.initialize(self.web_view) self.setCentralWidget(self.web_view) self.loading_overlay = LoadingOverlay(self) self.restore_state() diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 73b6b2521d..62263ac5b7 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -265,6 +265,7 @@ class ViewerBridge(Bridge): show_home_page = to_js() background_image_changed = to_js() goto_frac = to_js() + trigger_shortcut = to_js() def apply_font_settings(page_or_view): @@ -399,6 +400,8 @@ class WebView(RestartingWebEngineView): show_loading_message = pyqtSignal(object) show_error = pyqtSignal(object, object, object) print_book = pyqtSignal() + shortcuts_changed = pyqtSignal(object) + paged_mode_changed = pyqtSignal() def __init__(self, parent=None): self._host_widget = None @@ -447,6 +450,7 @@ class WebView(RestartingWebEngineView): def set_shortcut_map(self, smap): self.shortcut_map = smap + self.shortcuts_changed.emit(smap) def url_changed(self, url): if url.hasFragment(): @@ -527,12 +531,15 @@ class WebView(RestartingWebEngineView): if key == '*' and val is None: vprefs['session_data'] = {} apply_font_settings(self._page) + self.paged_mode_changed.emit() elif key != '*': sd = vprefs['session_data'] sd[key] = val vprefs['session_data'] = sd if key in ('standalone_font_settings', 'base_font_size'): apply_font_settings(self._page) + elif key == 'read_mode': + self.paged_mode_changed.emit() def set_local_storage(self, key, val): if key == '*' and val is None: @@ -573,3 +580,6 @@ class WebView(RestartingWebEngineView): def clear_history(self): self._page.history().clear() + + def trigger_shortcut(self, which): + self.execute_when_ready('trigger_shortcut', which) diff --git a/src/pyj/read_book/shortcuts.pyj b/src/pyj/read_book/shortcuts.pyj index 91e1d1234f..b37b31d682 100644 --- a/src/pyj/read_book/shortcuts.pyj +++ b/src/pyj/read_book/shortcuts.pyj @@ -245,6 +245,7 @@ def shortcuts_definition(): 'ui', _('Show the viewer controls'), ), + } return ans @@ -288,6 +289,13 @@ def add_standalone_viewer_shortcuts(): _('Quit the viewer'), ) + sc['print'] = desc( + "Ctrl+P", + 'ui', + _('Print book to PDF'), + ) + + def create_shortcut_map(custom_shortcuts): ans = {} scd = shortcuts_definition() diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 5dd27cc0d0..9d310dbf14 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -339,6 +339,8 @@ class View: ui_operations.toggle_inspector() elif data.name is 'toggle_lookup': ui_operations.toggle_lookup() + elif data.name is 'toggle_full_screen': + ui_operations.toggle_full_screen() elif data.name is 'toggle_paged_mode': self.toggle_paged_mode() elif data.name is 'quit': @@ -367,6 +369,19 @@ class View: self.on_next_section({'forward': False}) elif data.name is 'open_book': self.overlay.open_book() + elif data.name is 'next': + self.iframe_wrapper.send_message( + 'next_screen', backwards=False, all_pages_on_screen=get_session_data().get('paged_margin_clicks_scroll_by_screen')) + elif data.name is 'previous': + self.iframe_wrapper.send_message( + 'next_screen', backwards=True, all_pages_on_screen=get_session_data().get('paged_margin_clicks_scroll_by_screen')) + elif data.name is 'print': + ui_operations.print_book() + elif data.name is 'preferences': + self.overlay.show_prefs() + elif data.name is 'metadata': + self.overlay.show_metadata() + def on_selection_change(self, data): self.currently_showing.selected_text = data.text diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 04a3595ffd..06cc8469ea 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -269,6 +269,12 @@ def background_image_changed(img_id): img.src = READER_BACKGROUND_URL + '?' + Date().getTime() +@from_python +def trigger_shortcut(which): + if view: + view.on_handle_shortcut({'name': which}) + + def onerror(msg, script_url, line_number, column_number, error_object): if not error_object: # cross domain error