From 8655121e1690a61a7a7a7124c207b9d9b589f05c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Feb 2020 12:34:53 +0530 Subject: [PATCH] Viewer: Allow right clicking on the scrollbar to easily access commonly used scrolling shortcuts --- src/calibre/gui2/viewer/ui.py | 48 +++++++++++++++++++++-------- src/calibre/gui2/viewer/web_view.py | 3 ++ src/pyj/read_book/iframe.pyj | 5 +++ src/pyj/read_book/scrollbar.pyj | 14 +++++++-- src/pyj/read_book/view.pyj | 2 ++ src/pyj/viewer-main.pyj | 2 ++ 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 5efbad2fa9..19549f30ee 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -13,8 +13,8 @@ from hashlib import sha256 from threading import Thread from PyQt5.Qt import ( - QApplication, QDockWidget, QEvent, QMimeData, QModelIndex, QPixmap, QScrollBar, - Qt, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal + QApplication, QCursor, QDockWidget, QEvent, QMenu, QMimeData, QModelIndex, + QPixmap, Qt, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal ) from calibre import prints @@ -78,13 +78,6 @@ def path_key(path): return sha256(as_bytes(path)).hexdigest() -class ScrollBar(QScrollBar): - - def paintEvent(self, ev): - if self.isEnabled(): - return QScrollBar.paintEvent(self, ev) - - class EbookViewer(MainWindow): msg_from_anotherinstance = pyqtSignal(object) @@ -178,6 +171,7 @@ class EbookViewer(MainWindow): self.web_view.reset_interface.connect(self.reset_interface, type=Qt.QueuedConnection) self.web_view.quit.connect(self.quit, type=Qt.QueuedConnection) self.web_view.shortcuts_changed.connect(self.shortcuts_changed) + self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction()) self.setCentralWidget(self.web_view) self.loading_overlay = LoadingOverlay(self) @@ -193,14 +187,38 @@ class EbookViewer(MainWindow): rmap[v].append(k) self.actions_toolbar.set_tooltips(rmap) - def toggle_inspector(self): - visible = self.inspector_dock.toggleViewAction().isChecked() - self.inspector_dock.setVisible(not visible) - def resizeEvent(self, ev): self.loading_overlay.resize(self.size()) return MainWindow.resizeEvent(self, ev) + def scrollbar_context_menu(self, x, y, frac): + m = QMenu(self) + amap = {} + + def a(text, name): + m.addAction(text) + amap[text] = name + + a(_('Scroll here'), 'here') + m.addSeparator() + a(_('Start of book'), 'start_of_book') + a(_('End of book'), 'end_of_book') + m.addSeparator() + a(_('Previous section'), 'previous_section') + a(_('Next section'), 'next_section') + m.addSeparator() + a(_('Start of current file'), 'start_of_file') + a(_('End of current file'), 'end_of_file') + + q = m.exec_(QCursor.pos()) + if not q: + return + q = amap[q.text()] + if q == 'here': + self.web_view.goto_frac(frac) + else: + self.web_view.trigger_shortcut(q) + # IPC {{{ def handle_commandline_arg(self, arg): if arg: @@ -246,6 +264,10 @@ class EbookViewer(MainWindow): # Docks (ToC, Bookmarks, Lookup, etc.) {{{ + def toggle_inspector(self): + visible = self.inspector_dock.toggleViewAction().isChecked() + self.inspector_dock.setVisible(not visible) + def toggle_toc(self): self.toc_dock.setVisible(not self.toc_dock.isVisible()) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 9002c84287..62ddc7b0c7 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -272,6 +272,7 @@ class ViewerBridge(Bridge): reset_interface = from_js() quit = from_js() customize_toolbar = from_js() + scrollbar_context_menu = from_js(object, object, object) create_view = to_js() start_book_load = to_js() @@ -443,6 +444,7 @@ class WebView(RestartingWebEngineView): reset_interface = pyqtSignal() quit = pyqtSignal() customize_toolbar = pyqtSignal() + scrollbar_context_menu = pyqtSignal(object, object, object) shortcuts_changed = pyqtSignal(object) paged_mode_changed = pyqtSignal() standalone_misc_settings_changed = pyqtSignal(object) @@ -491,6 +493,7 @@ class WebView(RestartingWebEngineView): self.bridge.reset_interface.connect(self.reset_interface) self.bridge.quit.connect(self.quit) self.bridge.customize_toolbar.connect(self.customize_toolbar) + self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.bridge.export_shortcut_map.connect(self.set_shortcut_map) self.shortcut_map = {} self.bridge.report_cfi.connect(self.call_callback) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index c2bc8b3a17..00facccc43 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -116,6 +116,7 @@ class IframeBoss: 'window_size': self.received_window_size, 'overlay_visibility_changed': self.on_overlay_visibility_changed, 'show_search_result': self.show_search_result, + 'handle_navigation_shortcut': self.on_handle_navigation_shortcut, } self.comm = IframeClient(handlers) self.last_window_ypos = 0 @@ -458,6 +459,10 @@ class IframeBoss: else: self.send_message('handle_shortcut', name=sc_name) + def on_handle_navigation_shortcut(self, data): + self.handle_navigation_shortcut(data.name, data.key or { + 'key': '', 'altKey': False, 'ctrlKey': False, 'shiftKey': False, 'metaKey': False}) + def oncontextmenu(self, evt): if self.content_ready: evt.preventDefault() diff --git a/src/pyj/read_book/scrollbar.pyj b/src/pyj/read_book/scrollbar.pyj index cd7d89e835..639cf6dae6 100644 --- a/src/pyj/read_book/scrollbar.pyj +++ b/src/pyj/read_book/scrollbar.pyj @@ -7,7 +7,7 @@ from elementmaker import E from book_list.globals import get_session_data from book_list.theme import cached_color_to_rgba from dom import unique_id - +from read_book.globals import ui_operations SIZE = 10 @@ -31,7 +31,7 @@ class BookScrollbar: return E.div( id=self.container_id, style=f'height: 100vh; background-color: #aaa; width: {SIZE}px; border-radius: 5px', - onclick=self.bar_clicked, + onclick=self.bar_clicked, oncontextmenu=self.context_menu, E.div( style=f'position: relative; width: 100%; height: {int(2.2*SIZE)}px; background-color: #444; border-radius: 5px', onmousedown=self.on_bob_mousedown, @@ -41,6 +41,16 @@ class BookScrollbar: ) ) + def context_menu(self, ev): + if ui_operations.scrollbar_context_menu: + ev.preventDefault(), ev.stopPropagation() + c = self.container + bob = c.firstChild + height = c.clientHeight - bob.clientHeight + top = max(0, min(ev.clientY - bob.clientHeight, height)) + frac = max(0, min(top / height, 1)) + ui_operations.scrollbar_context_menu(ev.screenX, ev.screenY, frac) + def bar_clicked(self, evt): if evt.button is 0: c = self.container diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index a1fd39f8e8..a5ece1ebde 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -442,6 +442,8 @@ class View: self.toggle_autoscroll() elif data.name.startsWith('switch_color_scheme:'): self.switch_color_scheme(data.name.partition(':')[-1]) + else: + self.iframe_wrapper.send_message('handle_navigation_shortcut', name=data.name) 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 8da2bedf32..6d4119f1b0 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -403,6 +403,8 @@ if window is window.top: to_python.autoscroll_state_changed(active) ui_operations.search_result_not_found = def(sr): to_python.search_result_not_found(sr) + ui_operations.scrollbar_context_menu = def(x, y, frac): + to_python.scrollbar_context_menu(x, y, frac) document.body.appendChild(E.div(id='view')) window.onerror = onerror