Implement the scrollbar inside the web view

This allows it to be hidden naturally when displaying the overlay.
Also gives nice control when clicking in the gutter to scroll by page
This commit is contained in:
Kovid Goyal 2019-10-07 13:18:24 +05:30
parent bb5b7b0253
commit 63fda1fed3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 109 additions and 90 deletions

View File

@ -12,8 +12,8 @@ from hashlib import sha256
from threading import Thread
from PyQt5.Qt import (
QApplication, QDockWidget, QEvent, QHBoxLayout, QMimeData, QModelIndex, QPixmap,
QScrollBar, Qt, QUrl, QVBoxLayout, QWidget, pyqtSignal
QApplication, QDockWidget, QEvent, QMimeData, QModelIndex, QPixmap, QScrollBar,
Qt, QUrl, QVBoxLayout, QWidget, pyqtSignal
)
from calibre import prints
@ -66,64 +66,6 @@ class ScrollBar(QScrollBar):
return QScrollBar.paintEvent(self, ev)
class CentralWidget(QWidget):
def __init__(self, web_view, parent):
QWidget.__init__(self, parent)
self._ignore_value_changes = False
self.web_view = web_view
self.l = l = QHBoxLayout(self)
l.setContentsMargins(0, 0, 0, 0), l.setSpacing(0)
l.addWidget(web_view)
self.vertical_scrollbar = vs = ScrollBar(Qt.Vertical, self)
vs.valueChanged[int].connect(self.value_changed)
l.addWidget(vs)
self.current_book_length = None
web_view.notify_progress_frac.connect(self.update_scrollbar_positions_on_scroll)
web_view.scrollbar_visibility_changed.connect(self.apply_scrollbar_visibility)
web_view.overlay_visibility_changed.connect(self.overlay_visibility_changed)
self.apply_scrollbar_visibility()
def __enter__(self):
self._ignore_value_changes = True
def __exit__(self, *a):
self._ignore_value_changes = False
def apply_scrollbar_visibility(self):
visible = get_session_pref('standalone_scrollbar', default=False, group=None)
self.vertical_scrollbar.setVisible(bool(visible))
def overlay_visibility_changed(self, visible):
self.vertical_scrollbar.setEnabled(not visible)
def set_scrollbar_value(self, frac):
with self:
val = int(self.vertical_scrollbar.maximum() * frac)
self.vertical_scrollbar.setValue(val)
def value_changed(self, val):
if not self._ignore_value_changes:
frac = val / self.vertical_scrollbar.maximum()
self.web_view.goto_frac(frac)
def initialize_scrollbars(self, book_length):
with self:
self.current_book_length = book_length
maximum = book_length / 10
bar = self.vertical_scrollbar
bar.setMinimum(0)
bar.setMaximum(maximum)
bar.setSingleStep(10)
bar.setPageStep(100)
def update_scrollbar_positions_on_scroll(self, progress_frac, file_progress_frac, book_length):
if book_length != self.current_book_length:
self.initialize_scrollbars(book_length)
if not self.vertical_scrollbar.isSliderDown():
self.set_scrollbar_value(progress_frac)
class EbookViewer(MainWindow):
msg_from_anotherinstance = pyqtSignal(object)
@ -192,8 +134,7 @@ class EbookViewer(MainWindow):
self.web_view.selection_changed.connect(self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
self.web_view.view_image.connect(self.view_image, type=Qt.QueuedConnection)
self.web_view.copy_image.connect(self.copy_image, type=Qt.QueuedConnection)
self.central_widget = CentralWidget(self.web_view, self)
self.setCentralWidget(self.central_widget)
self.setCentralWidget(self.web_view)
self.restore_state()
if continue_reading:
self.continue_reading()
@ -354,7 +295,6 @@ class EbookViewer(MainWindow):
self.web_view.show_home_page()
return
set_book_path(data['base'], data['pathtoebook'])
self.central_widget.initialize_scrollbars(set_book_path.parsed_manifest['spine_length'])
self.current_book_data = data
self.current_book_data['annotations_map'] = defaultdict(list)
self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'

View File

@ -232,7 +232,6 @@ class ViewerBridge(Bridge):
view_image = from_js(object)
copy_image = from_js(object)
change_background_image = from_js(object)
notify_progress_frac = from_js(object, object, object)
overlay_visibility_changed = from_js(object)
create_view = to_js()
@ -376,8 +375,6 @@ class WebView(RestartingWebEngineView):
selection_changed = pyqtSignal(object)
view_image = pyqtSignal(object)
copy_image = pyqtSignal(object)
scrollbar_visibility_changed = pyqtSignal()
notify_progress_frac = pyqtSignal(object, object, object)
overlay_visibility_changed = pyqtSignal(object)
def __init__(self, parent=None):
@ -405,7 +402,6 @@ class WebView(RestartingWebEngineView):
self.bridge.selection_changed.connect(self.selection_changed)
self.bridge.view_image.connect(self.view_image)
self.bridge.copy_image.connect(self.copy_image)
self.bridge.notify_progress_frac.connect(self.notify_progress_frac)
self.bridge.overlay_visibility_changed.connect(self.overlay_visibility_changed)
self.bridge.report_cfi.connect(self.call_callback)
self.bridge.change_background_image.connect(self.change_background_image)
@ -507,8 +503,6 @@ class WebView(RestartingWebEngineView):
vprefs['session_data'] = sd
if key in ('standalone_font_settings', 'base_font_size'):
apply_font_settings(self._page)
elif key == 'standalone_scrollbar':
self.scrollbar_visibility_changed.emit()
def do_callback(self, func_name, callback):
cid = next(self.callback_id_counter)

View File

@ -10,8 +10,6 @@ from dom import unique_id
from widgets import create_button
from session import defaults
from read_book.globals import runtime
CONTAINER = unique_id('standalone-scrolling-settings')
@ -43,10 +41,9 @@ def create_scrolling_panel(container):
container.appendChild(cb(
'paged_margin_clicks_scroll_by_screen', _('Clicking on the margins scrolls by screen fulls instead of pages')))
if runtime.is_standalone_viewer:
container.appendChild(E.div(style='margin-top:1ex; border-top: solid 1px', '\xa0'))
container.appendChild(cb(
'standalone_scrollbar', _('Show a scrollbar')))
'book_scrollbar', _('Show a scrollbar')))
container.appendChild(E.div(
style='margin-top: 1rem', create_button(_('Restore defaults'), action=restore_defaults)

View File

@ -0,0 +1,91 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from dom import unique_id
from elementmaker import E
from book_list.globals import get_session_data
class BookScrollbar:
def __init__(self, view):
self.view = view
self.container_id = unique_id('book-scrollbar')
self.sync_to_contents_timer = 0
self.sync_contents_timer = 0
@property
def container(self):
return document.getElementById(self.container_id)
def create(self):
self.on_bob_mousedown = self.on_bob_mouse_event.bind(None, 'down')
self.on_bob_mousemove = self.on_bob_mouse_event.bind(None, 'move')
self.on_bob_mouseup = self.on_bob_mouse_event.bind(None, 'up')
return E.div(
id=self.container_id,
style='height: 100vh; background-color: #aaa; width: 10px; border-radius: 5px',
onclick=self.bar_clicked,
E.div(
style='position: relative; width: 100%; height: 22px; background-color: #444; border-radius: 5px',
onmousedown=self.on_bob_mousedown,
),
E.div(
style='position: absolute; z-index: 30000; width: 100vw; height: 100vh; left: 0; top: 0; display: none;'
)
)
def bar_clicked(self, evt):
if evt.button is 0:
c = self.container
b = c.firstChild
bob_top = b.offsetTop
bob_bottom = bob_top + b.offsetHeight
if evt.clientY < bob_top:
self.view.left_margin_clicked(evt)
elif evt.clientY > bob_bottom:
self.view.right_margin_clicked(evt)
def on_bob_mouse_event(self, which, evt):
c = self.container
bob = c.firstChild
mouse_grab = bob.nextSibling
if which is 'move':
top = evt.pageY - self.down_y
height = c.clientHeight - bob.clientHeight
top = max(0, min(top, height))
bob.style.top = f'{top}px'
evt.preventDefault(), evt.stopPropagation()
frac = bob.offsetTop / height
if self.sync_contents_timer:
window.clearTimeout(self.sync_contents_timer)
self.sync_contents_timer = window.setTimeout(self.view.goto_frac.bind(None, frac), 2)
elif which is 'down':
if evt.button is not 0:
return
evt.preventDefault(), evt.stopPropagation()
self.down_y = evt.clientY - bob.getBoundingClientRect().top
mouse_grab.style.display = 'block'
mouse_grab.addEventListener('mousemove', self.on_bob_mousemove, {'capture': True, 'passive': False})
mouse_grab.addEventListener('mouseup', self.on_bob_mouseup, {'capture': True, 'passive': False})
elif which is 'up':
self.down_y = 0
mouse_grab.removeEventListener('mousemove', self.on_bob_mousemove, {'capture': True, 'passive': False})
mouse_grab.removeEventListener('mouseup', self.on_bob_mouseup, {'capture': True, 'passive': False})
window.setTimeout(def(): self.container.firstChild.nextSibling.style.display = 'none';, 10)
evt.preventDefault(), evt.stopPropagation()
def apply_visibility(self):
sd = get_session_data()
self.container.style.display = 'block' if sd.get('book_scrollbar') else 'none'
def set_position(self, frac):
c = self.container
frac = max(0, min(frac, 1))
c.firstChild.style.top = f'{frac * (c.clientHeight - c.firstChild.clientHeight)}px'
def sync_to_contents(self, frac):
if self.sync_to_contents_timer:
window.clearTimeout(self.sync_to_contents_timer)
self.sync_to_contents_timer = window.setTimeout(self.set_position.bind(None, frac), 100)

View File

@ -13,6 +13,7 @@ from dom import add_extra_css, build_rule, set_css, svgicon, unique_id
from iframe_comm import IframeWrapper
from modals import error_dialog, warning_dialog
from read_book.content_popup import ContentPopupOverlay
from read_book.scrollbar import BookScrollbar
from read_book.globals import (
current_book, runtime, set_current_spine_item, ui_operations
)
@ -150,7 +151,8 @@ class View:
self.report_cfi_callbacks = {}
self.show_chrome_counter = 0
self.show_loading_callback_timer = None
self.clock_timer_id = 0
self.timer_ids = {'clock': 0}
self.book_scrollbar = BookScrollbar(self)
sd = get_session_data()
self.keyboard_shortcut_map = create_shortcut_map(sd.get('keyboard_shortcuts'))
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)
@ -169,6 +171,7 @@ class View:
margin_elem(sd, 'margin_bottom', 'book-bottom-margin', self.bottom_margin_clicked),
),
right_margin,
self.book_scrollbar.create(),
E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-content-popup-overlay'), # content popup overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-overlay'), # main overlay
@ -574,6 +577,7 @@ 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'))
self.book_scrollbar.apply_visibility()
self.display_book(self.book)
def iframe_settings(self, name):
@ -813,12 +817,12 @@ class View:
if div:
render_template(div, 'margin_top', 'header')
if has_clock:
if not self.clock_timer_id:
self.clock_timer_id = window.setInterval(self.update_header_footer, 60000)
if not self.timer_ids.clock:
self.timer_ids.clock = window.setInterval(self.update_header_footer, 60000)
else:
if self.clock_timer_id:
window.clearInterval(self.clock_timer_id)
self.clock_timer_id = 0
if self.timer_ids.clock:
window.clearInterval(self.timer_ids.clock)
self.timer_ids.clock = 0
def on_update_toc_position(self, data):
update_visible_toc_nodes(data.visible_anchors)
@ -856,11 +860,7 @@ class View:
def set_progress_frac(self, progress_frac, file_progress_frac):
self.current_progress_frac = progress_frac or 0
self.current_file_progress_frac = file_progress_frac or 0
if ui_operations.notify_progress_frac:
book_length = 0
if self.book?.manifest:
book_length = self.book.manifest.spine_length or 0
ui_operations.notify_progress_frac(self.current_progress_frac, self.current_file_progress_frac, book_length)
self.book_scrollbar.sync_to_contents(self.current_progress_frac)
def update_font_size(self):
self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size'))

View File

@ -46,9 +46,9 @@ defaults = {
'word_actions': v'[]',
'hide_tooltips': False,
'keyboard_shortcuts': {},
'book_scrollbar': False,
'standalone_font_settings': {},
'standalone_misc_settings': {},
'standalone_scrollbar': False,
'standalone_recently_opened': v'[]',
'paged_wheel_scrolls_by_screen': False,
'paged_margin_clicks_scroll_by_screen': True,
@ -73,7 +73,6 @@ is_local_setting = {
'standalone_font_settings': True,
'standalone_misc_settings': True,
'standalone_recently_opened': True,
'standalone_scrollbar': False,
}

View File

@ -312,8 +312,6 @@ if window is window.top:
to_python.copy_image(name)
ui_operations.change_background_image = def(img_id):
to_python.change_background_image(img_id)
ui_operations.notify_progress_frac = def (pf, fpf, book_length):
to_python.notify_progress_frac(pf, fpf, book_length)
ui_operations.quit = def():
to_python.quit()
ui_operations.overlay_visibility_changed = def(visible):