From 21df277dc4f90f507cd94adc4f4826f0180896d0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 9 Oct 2019 14:11:23 +0530 Subject: [PATCH] Viewer: Make it easier to use the bookmarks panel with only keyboard. Fixes #1847423 [[Feature] Make ebook viewer keyboard-friendly](https://bugs.launchpad.net/calibre/+bug/1847423) --- src/calibre/gui2/viewer/bookmarks.py | 10 +++++++- src/calibre/gui2/viewer/shortcuts.py | 36 ++++++++++++++++++++++++++++ src/calibre/gui2/viewer/ui.py | 12 +++++++--- src/calibre/gui2/viewer/web_view.py | 6 +++++ src/pyj/read_book/view.pyj | 4 ++++ src/pyj/viewer-main.pyj | 2 ++ 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/calibre/gui2/viewer/shortcuts.py diff --git a/src/calibre/gui2/viewer/bookmarks.py b/src/calibre/gui2/viewer/bookmarks.py index cfb4bcf4bd..4075656fda 100644 --- a/src/calibre/gui2/viewer/bookmarks.py +++ b/src/calibre/gui2/viewer/bookmarks.py @@ -13,6 +13,7 @@ from PyQt5.Qt import ( from calibre.gui2 import choose_files, choose_save_file from calibre.gui2.viewer.annotations import serialize_annotation +from calibre.gui2.viewer.shortcuts import get_shortcut_for from calibre.srv.render_book import parse_annotation from calibre.utils.date import EPOCH, utcnow from calibre.utils.icu import sort_key @@ -69,6 +70,7 @@ class BookmarkManager(QWidget): edited = pyqtSignal(object) activated = pyqtSignal(object) create_requested = pyqtSignal() + toggle_requested = pyqtSignal() def __init__(self, parent): QWidget.__init__(self, parent) @@ -102,7 +104,7 @@ class BookmarkManager(QWidget): b.clicked.connect(self.delete_bookmark) l.addWidget(b, l.rowCount() - 1, 1) - self.button_delete = b = QPushButton(_('Sort by &name'), self) + self.button_delete = b = QPushButton(_('Sort by na&me'), self) b.setToolTip(_('Sort bookmarks by name')) b.clicked.connect(self.sort_by_name) l.addWidget(b) @@ -281,3 +283,9 @@ class BookmarkManager(QWidget): self.set_bookmarks(bookmarks) self.set_current_bookmark(bm) self.edited.emit(bookmarks) + + def keyPressEvent(self, ev): + if ev.key() == Qt.Key_Escape or get_shortcut_for(self, ev) == 'toggle_bookmarks': + self.toggle_requested.emit() + return + return QWidget.keyPressEvent(self, ev) diff --git a/src/calibre/gui2/viewer/shortcuts.py b/src/calibre/gui2/viewer/shortcuts.py new file mode 100644 index 0000000000..4b2b5924f3 --- /dev/null +++ b/src/calibre/gui2/viewer/shortcuts.py @@ -0,0 +1,36 @@ +#!/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 + +from PyQt5.Qt import QKeySequence, QMainWindow, Qt + + +def get_main_window_for(widget): + p = widget + while p is not None: + if isinstance(p, QMainWindow): + return p + p = p.parent() + + +def key_to_text(key): + return QKeySequence(key).toString(QKeySequence.PortableText).lower() + + +def ev_to_index(ev): + m = ev.modifiers() + mods = [] + for x in ('ALT', 'CTRL', 'META', 'SHIFT'): + mods.append('y' if m & getattr(Qt, x) else 'n') + return ''.join(mods) + key_to_text(ev.key()) + + +def get_shortcut_for(widget, ev): + mw = get_main_window_for(widget) + if mw is None: + return + smap = mw.web_view.shortcut_map + idx = ev_to_index(ev) + return smap.get(idx) diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index b8ffe40d34..8949c8a5ed 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -120,8 +120,9 @@ class EbookViewer(MainWindow): connect_lambda( w.create_requested, self, lambda self: self.web_view.get_current_cfi(self.bookmarks_widget.create_new_bookmark)) - self.bookmarks_widget.edited.connect(self.bookmarks_edited) - self.bookmarks_widget.activated.connect(self.bookmark_activated) + w.edited.connect(self.bookmarks_edited) + w.activated.connect(self.bookmark_activated) + w.toggle_requested.connect(self.toggle_bookmarks) self.bookmarks_dock.setWidget(w) self.web_view = WebView(self) @@ -200,7 +201,12 @@ class EbookViewer(MainWindow): self.toc_dock.setVisible(not self.toc_dock.isVisible()) def toggle_bookmarks(self): - self.bookmarks_dock.setVisible(not self.bookmarks_dock.isVisible()) + is_visible = self.bookmarks_dock.isVisible() + self.bookmarks_dock.setVisible(not is_visible) + if is_visible: + self.web_view.setFocus(Qt.OtherFocusReason) + else: + self.bookmarks_widget.bookmarks_list.setFocus(Qt.OtherFocusReason) def toggle_lookup(self): self.lookup_dock.setVisible(not self.lookup_dock.isVisible()) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 7b6ece2509..50c1a25a84 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -235,6 +235,7 @@ class ViewerBridge(Bridge): overlay_visibility_changed = from_js(object) show_loading_message = from_js(object) show_error = from_js(object, object, object) + export_shortcut_map = from_js(object) create_view = to_js() start_book_load = to_js() @@ -405,6 +406,8 @@ class WebView(RestartingWebEngineView): self.bridge.overlay_visibility_changed.connect(self.overlay_visibility_changed) self.bridge.show_loading_message.connect(self.show_loading_message) self.bridge.show_error.connect(self.show_error) + self.bridge.export_shortcut_map.connect(self.set_shortcut_map) + self.shortcut_map = {} self.bridge.report_cfi.connect(self.call_callback) self.bridge.change_background_image.connect(self.change_background_image) self.pending_bridge_ready_actions = {} @@ -416,6 +419,9 @@ class WebView(RestartingWebEngineView): self.inspector = Inspector(parent.inspector_dock.toggleViewAction(), self) parent.inspector_dock.setWidget(self.inspector) + def set_shortcut_map(self, smap): + self.shortcut_map = smap + def url_changed(self, url): if url.hasFragment(): frag = url.fragment(url.FullyDecoded) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 06f001163e..9af7b9eb4c 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -155,6 +155,8 @@ class View: self.book_scrollbar = BookScrollbar(self) sd = get_session_data() self.keyboard_shortcut_map = create_shortcut_map(sd.get('keyboard_shortcuts')) + if ui_operations.export_shortcut_map: + ui_operations.export_shortcut_map(self.keyboard_shortcut_map) left_margin = E.div(svgicon('caret-left'), style='width:{}px;'.format(sd.get('margin_left', 20)), class_='book-side-margin', id='book-left-margin', onclick=self.left_margin_clicked) set_left_margin_handler(left_margin) right_margin = E.div(svgicon('caret-right'), style='width:{}px;'.format(sd.get('margin_right', 20)), class_='book-side-margin', id='book-right-margin', onclick=self.right_margin_clicked) @@ -580,6 +582,8 @@ class View: # redisplay_book() is called when settings are changed sd = get_session_data() self.keyboard_shortcut_map = create_shortcut_map(sd.get('keyboard_shortcuts')) + if ui_operations.export_shortcut_map: + ui_operations.export_shortcut_map(self.keyboard_shortcut_map) self.book_scrollbar.apply_visibility() self.display_book(self.book) diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 53b82b0a23..b741fada5d 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -316,6 +316,8 @@ if window is window.top: to_python.overlay_visibility_changed(visible) ui_operations.show_loading_message = def(msg): to_python.show_loading_message(msg) + ui_operations.export_shortcut_map = def(smap): + to_python.export_shortcut_map(smap) document.body.appendChild(E.div(id='view')) window.onerror = onerror