mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Viewer: Allow right clicking on the scrollbar to easily access commonly used scrolling shortcuts
This commit is contained in:
parent
9f222e809c
commit
8655121e16
@ -13,8 +13,8 @@ from hashlib import sha256
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QApplication, QDockWidget, QEvent, QMimeData, QModelIndex, QPixmap, QScrollBar,
|
QApplication, QCursor, QDockWidget, QEvent, QMenu, QMimeData, QModelIndex,
|
||||||
Qt, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal
|
QPixmap, Qt, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
@ -78,13 +78,6 @@ def path_key(path):
|
|||||||
return sha256(as_bytes(path)).hexdigest()
|
return sha256(as_bytes(path)).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class ScrollBar(QScrollBar):
|
|
||||||
|
|
||||||
def paintEvent(self, ev):
|
|
||||||
if self.isEnabled():
|
|
||||||
return QScrollBar.paintEvent(self, ev)
|
|
||||||
|
|
||||||
|
|
||||||
class EbookViewer(MainWindow):
|
class EbookViewer(MainWindow):
|
||||||
|
|
||||||
msg_from_anotherinstance = pyqtSignal(object)
|
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.reset_interface.connect(self.reset_interface, type=Qt.QueuedConnection)
|
||||||
self.web_view.quit.connect(self.quit, 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.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.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction())
|
||||||
self.setCentralWidget(self.web_view)
|
self.setCentralWidget(self.web_view)
|
||||||
self.loading_overlay = LoadingOverlay(self)
|
self.loading_overlay = LoadingOverlay(self)
|
||||||
@ -193,14 +187,38 @@ class EbookViewer(MainWindow):
|
|||||||
rmap[v].append(k)
|
rmap[v].append(k)
|
||||||
self.actions_toolbar.set_tooltips(rmap)
|
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):
|
def resizeEvent(self, ev):
|
||||||
self.loading_overlay.resize(self.size())
|
self.loading_overlay.resize(self.size())
|
||||||
return MainWindow.resizeEvent(self, ev)
|
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 {{{
|
# IPC {{{
|
||||||
def handle_commandline_arg(self, arg):
|
def handle_commandline_arg(self, arg):
|
||||||
if arg:
|
if arg:
|
||||||
@ -246,6 +264,10 @@ class EbookViewer(MainWindow):
|
|||||||
|
|
||||||
# Docks (ToC, Bookmarks, Lookup, etc.) {{{
|
# 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):
|
def toggle_toc(self):
|
||||||
self.toc_dock.setVisible(not self.toc_dock.isVisible())
|
self.toc_dock.setVisible(not self.toc_dock.isVisible())
|
||||||
|
|
||||||
|
@ -272,6 +272,7 @@ class ViewerBridge(Bridge):
|
|||||||
reset_interface = from_js()
|
reset_interface = from_js()
|
||||||
quit = from_js()
|
quit = from_js()
|
||||||
customize_toolbar = from_js()
|
customize_toolbar = from_js()
|
||||||
|
scrollbar_context_menu = from_js(object, object, object)
|
||||||
|
|
||||||
create_view = to_js()
|
create_view = to_js()
|
||||||
start_book_load = to_js()
|
start_book_load = to_js()
|
||||||
@ -443,6 +444,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
reset_interface = pyqtSignal()
|
reset_interface = pyqtSignal()
|
||||||
quit = pyqtSignal()
|
quit = pyqtSignal()
|
||||||
customize_toolbar = pyqtSignal()
|
customize_toolbar = pyqtSignal()
|
||||||
|
scrollbar_context_menu = pyqtSignal(object, object, object)
|
||||||
shortcuts_changed = pyqtSignal(object)
|
shortcuts_changed = pyqtSignal(object)
|
||||||
paged_mode_changed = pyqtSignal()
|
paged_mode_changed = pyqtSignal()
|
||||||
standalone_misc_settings_changed = pyqtSignal(object)
|
standalone_misc_settings_changed = pyqtSignal(object)
|
||||||
@ -491,6 +493,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
self.bridge.reset_interface.connect(self.reset_interface)
|
self.bridge.reset_interface.connect(self.reset_interface)
|
||||||
self.bridge.quit.connect(self.quit)
|
self.bridge.quit.connect(self.quit)
|
||||||
self.bridge.customize_toolbar.connect(self.customize_toolbar)
|
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.bridge.export_shortcut_map.connect(self.set_shortcut_map)
|
||||||
self.shortcut_map = {}
|
self.shortcut_map = {}
|
||||||
self.bridge.report_cfi.connect(self.call_callback)
|
self.bridge.report_cfi.connect(self.call_callback)
|
||||||
|
@ -116,6 +116,7 @@ class IframeBoss:
|
|||||||
'window_size': self.received_window_size,
|
'window_size': self.received_window_size,
|
||||||
'overlay_visibility_changed': self.on_overlay_visibility_changed,
|
'overlay_visibility_changed': self.on_overlay_visibility_changed,
|
||||||
'show_search_result': self.show_search_result,
|
'show_search_result': self.show_search_result,
|
||||||
|
'handle_navigation_shortcut': self.on_handle_navigation_shortcut,
|
||||||
}
|
}
|
||||||
self.comm = IframeClient(handlers)
|
self.comm = IframeClient(handlers)
|
||||||
self.last_window_ypos = 0
|
self.last_window_ypos = 0
|
||||||
@ -458,6 +459,10 @@ class IframeBoss:
|
|||||||
else:
|
else:
|
||||||
self.send_message('handle_shortcut', name=sc_name)
|
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):
|
def oncontextmenu(self, evt):
|
||||||
if self.content_ready:
|
if self.content_ready:
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
|
@ -7,7 +7,7 @@ from elementmaker import E
|
|||||||
from book_list.globals import get_session_data
|
from book_list.globals import get_session_data
|
||||||
from book_list.theme import cached_color_to_rgba
|
from book_list.theme import cached_color_to_rgba
|
||||||
from dom import unique_id
|
from dom import unique_id
|
||||||
|
from read_book.globals import ui_operations
|
||||||
|
|
||||||
SIZE = 10
|
SIZE = 10
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ class BookScrollbar:
|
|||||||
return E.div(
|
return E.div(
|
||||||
id=self.container_id,
|
id=self.container_id,
|
||||||
style=f'height: 100vh; background-color: #aaa; width: {SIZE}px; border-radius: 5px',
|
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(
|
E.div(
|
||||||
style=f'position: relative; width: 100%; height: {int(2.2*SIZE)}px; background-color: #444; border-radius: 5px',
|
style=f'position: relative; width: 100%; height: {int(2.2*SIZE)}px; background-color: #444; border-radius: 5px',
|
||||||
onmousedown=self.on_bob_mousedown,
|
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):
|
def bar_clicked(self, evt):
|
||||||
if evt.button is 0:
|
if evt.button is 0:
|
||||||
c = self.container
|
c = self.container
|
||||||
|
@ -442,6 +442,8 @@ class View:
|
|||||||
self.toggle_autoscroll()
|
self.toggle_autoscroll()
|
||||||
elif data.name.startsWith('switch_color_scheme:'):
|
elif data.name.startsWith('switch_color_scheme:'):
|
||||||
self.switch_color_scheme(data.name.partition(':')[-1])
|
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):
|
def on_selection_change(self, data):
|
||||||
self.currently_showing.selected_text = data.text
|
self.currently_showing.selected_text = data.text
|
||||||
|
@ -403,6 +403,8 @@ if window is window.top:
|
|||||||
to_python.autoscroll_state_changed(active)
|
to_python.autoscroll_state_changed(active)
|
||||||
ui_operations.search_result_not_found = def(sr):
|
ui_operations.search_result_not_found = def(sr):
|
||||||
to_python.search_result_not_found(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'))
|
document.body.appendChild(E.div(id='view'))
|
||||||
window.onerror = onerror
|
window.onerror = onerror
|
||||||
|
Loading…
x
Reference in New Issue
Block a user