mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Work on viewer setting for background image
This commit is contained in:
parent
62d38e6706
commit
92ffeebb4a
@ -17,7 +17,6 @@ from PyQt5.Qt import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import config_dir
|
|
||||||
from calibre.customize.ui import available_input_formats
|
from calibre.customize.ui import available_input_formats
|
||||||
from calibre.gui2 import choose_files, error_dialog
|
from calibre.gui2 import choose_files, error_dialog
|
||||||
from calibre.gui2.image_popup import ImagePopup
|
from calibre.gui2.image_popup import ImagePopup
|
||||||
@ -30,14 +29,15 @@ from calibre.gui2.viewer.convert_book import prepare_book, update_book
|
|||||||
from calibre.gui2.viewer.lookup import Lookup
|
from calibre.gui2.viewer.lookup import Lookup
|
||||||
from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView
|
from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView
|
||||||
from calibre.gui2.viewer.web_view import (
|
from calibre.gui2.viewer.web_view import (
|
||||||
WebView, get_path_for_name, get_session_pref, set_book_path, vprefs
|
WebView, get_path_for_name, get_session_pref, set_book_path, viewer_config_dir,
|
||||||
|
vprefs
|
||||||
)
|
)
|
||||||
from calibre.utils.date import utcnow
|
from calibre.utils.date import utcnow
|
||||||
from calibre.utils.ipc.simple_worker import WorkerError
|
from calibre.utils.ipc.simple_worker import WorkerError
|
||||||
from calibre.utils.serialize import json_loads
|
from calibre.utils.serialize import json_loads
|
||||||
from polyglot.builtins import as_bytes, itervalues
|
from polyglot.builtins import as_bytes, itervalues
|
||||||
|
|
||||||
annotations_dir = os.path.join(config_dir, 'viewer', 'annots')
|
annotations_dir = os.path.join(viewer_config_dir, 'annots')
|
||||||
|
|
||||||
|
|
||||||
def dock_defs():
|
def dock_defs():
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
@ -19,11 +20,12 @@ from PyQt5.QtWebEngineWidgets import (
|
|||||||
|
|
||||||
from calibre import as_unicode, prints
|
from calibre import as_unicode, prints
|
||||||
from calibre.constants import (
|
from calibre.constants import (
|
||||||
FAKE_HOST, FAKE_PROTOCOL, __version__, is_running_from_develop, isosx, iswindows
|
FAKE_HOST, FAKE_PROTOCOL, __version__, config_dir, is_running_from_develop,
|
||||||
|
isosx, iswindows
|
||||||
)
|
)
|
||||||
from calibre.ebooks.metadata.book.base import field_metadata
|
from calibre.ebooks.metadata.book.base import field_metadata
|
||||||
from calibre.ebooks.oeb.polish.utils import guess_type
|
from calibre.ebooks.oeb.polish.utils import guess_type
|
||||||
from calibre.gui2 import error_dialog, safe_open_url
|
from calibre.gui2 import choose_images, error_dialog, safe_open_url
|
||||||
from calibre.gui2.webengine import (
|
from calibre.gui2.webengine import (
|
||||||
Bridge, RestartingWebEngineView, create_script, from_js, insert_scripts,
|
Bridge, RestartingWebEngineView, create_script, from_js, insert_scripts,
|
||||||
secure_webengine, to_js
|
secure_webengine, to_js
|
||||||
@ -31,7 +33,7 @@ from calibre.gui2.webengine import (
|
|||||||
from calibre.srv.code import get_translations_data
|
from calibre.srv.code import get_translations_data
|
||||||
from calibre.utils.config import JSONConfig
|
from calibre.utils.config import JSONConfig
|
||||||
from calibre.utils.serialize import json_loads
|
from calibre.utils.serialize import json_loads
|
||||||
from polyglot.builtins import iteritems
|
from polyglot.builtins import as_bytes, iteritems
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5 import sip
|
from PyQt5 import sip
|
||||||
@ -39,6 +41,7 @@ except ImportError:
|
|||||||
import sip
|
import sip
|
||||||
|
|
||||||
vprefs = JSONConfig('viewer-webengine')
|
vprefs = JSONConfig('viewer-webengine')
|
||||||
|
viewer_config_dir = os.path.join(config_dir, 'viewer')
|
||||||
vprefs.defaults['session_data'] = {}
|
vprefs.defaults['session_data'] = {}
|
||||||
vprefs.defaults['main_window_state'] = None
|
vprefs.defaults['main_window_state'] = None
|
||||||
vprefs.defaults['main_window_geometry'] = None
|
vprefs.defaults['main_window_geometry'] = None
|
||||||
@ -77,6 +80,20 @@ def get_data(name):
|
|||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def background_image():
|
||||||
|
ans = getattr(background_image, 'ans', None)
|
||||||
|
if ans is None:
|
||||||
|
img_path = os.path.join(viewer_config_dir, 'bg-image.data')
|
||||||
|
if os.path.exists(img_path):
|
||||||
|
with open(img_path, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
mt, data = data.split(b'|', 1)
|
||||||
|
else:
|
||||||
|
ans = b'image/jpeg', b''
|
||||||
|
ans = background_image.ans = mt.decode('utf-8'), data
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def send_reply(rq, mime_type, data):
|
def send_reply(rq, mime_type, data):
|
||||||
if sip.isdeleted(rq):
|
if sip.isdeleted(rq):
|
||||||
return
|
return
|
||||||
@ -131,6 +148,12 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||||||
elif name == 'manifest':
|
elif name == 'manifest':
|
||||||
data = b'[' + set_book_path.manifest + b',' + set_book_path.metadata + b']'
|
data = b'[' + set_book_path.manifest + b',' + set_book_path.metadata + b']'
|
||||||
send_reply(rq, set_book_path.manifest_mime, data)
|
send_reply(rq, set_book_path.manifest_mime, data)
|
||||||
|
elif name == 'reader-background':
|
||||||
|
mt, data = background_image()
|
||||||
|
if data:
|
||||||
|
send_reply(rq, mt, data)
|
||||||
|
else:
|
||||||
|
rq.fail(rq.UrlNotFound)
|
||||||
elif name.startswith('mathjax/'):
|
elif name.startswith('mathjax/'):
|
||||||
from calibre.gui2.viewer.mathjax import monkeypatch_mathjax
|
from calibre.gui2.viewer.mathjax import monkeypatch_mathjax
|
||||||
if name == 'mathjax/manifest.json':
|
if name == 'mathjax/manifest.json':
|
||||||
@ -206,6 +229,7 @@ class ViewerBridge(Bridge):
|
|||||||
selection_changed = from_js(object)
|
selection_changed = from_js(object)
|
||||||
copy_selection = from_js(object)
|
copy_selection = from_js(object)
|
||||||
view_image = from_js(object)
|
view_image = from_js(object)
|
||||||
|
change_background_image = from_js(object)
|
||||||
|
|
||||||
create_view = to_js()
|
create_view = to_js()
|
||||||
show_preparing_message = to_js()
|
show_preparing_message = to_js()
|
||||||
@ -215,6 +239,7 @@ class ViewerBridge(Bridge):
|
|||||||
full_screen_state_changed = to_js()
|
full_screen_state_changed = to_js()
|
||||||
get_current_cfi = to_js()
|
get_current_cfi = to_js()
|
||||||
show_home_page = to_js()
|
show_home_page = to_js()
|
||||||
|
background_image_changed = to_js()
|
||||||
|
|
||||||
|
|
||||||
def apply_font_settings(page_or_view):
|
def apply_font_settings(page_or_view):
|
||||||
@ -369,6 +394,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
self.bridge.selection_changed.connect(self.selection_changed)
|
self.bridge.selection_changed.connect(self.selection_changed)
|
||||||
self.bridge.view_image.connect(self.view_image)
|
self.bridge.view_image.connect(self.view_image)
|
||||||
self.bridge.report_cfi.connect(self.call_callback)
|
self.bridge.report_cfi.connect(self.call_callback)
|
||||||
|
self.bridge.change_background_image.connect(self.change_background_image)
|
||||||
self.pending_bridge_ready_actions = {}
|
self.pending_bridge_ready_actions = {}
|
||||||
self.setPage(self._page)
|
self.setPage(self._page)
|
||||||
self.setAcceptDrops(False)
|
self.setAcceptDrops(False)
|
||||||
@ -483,3 +509,13 @@ class WebView(RestartingWebEngineView):
|
|||||||
|
|
||||||
def show_home_page(self):
|
def show_home_page(self):
|
||||||
self.execute_when_ready('show_home_page')
|
self.execute_when_ready('show_home_page')
|
||||||
|
|
||||||
|
def change_background_image(self, img_id):
|
||||||
|
files = choose_images(self, 'viewer-background-image', _('Choose background image'), formats=['png', 'gif', 'jpg', 'jpeg'])
|
||||||
|
if files:
|
||||||
|
img = files[0]
|
||||||
|
with open(img, 'rb') as src, open(os.path.join(viewer_config_dir, 'bg-image.data'), 'wb') as dest:
|
||||||
|
dest.write(as_bytes(guess_type(img)[0] or 'image/jpeg') + b'|')
|
||||||
|
shutil.copyfileobj(src, dest)
|
||||||
|
background_image.ans = None
|
||||||
|
self.execute_when_ready('background_image_changed', img_id)
|
||||||
|
@ -69,7 +69,7 @@ class Prefs:
|
|||||||
items = [
|
items = [
|
||||||
create_item(_('Colors'), def():self.show_panel('colors');, _('Colors of the page and text')),
|
create_item(_('Colors'), def():self.show_panel('colors');, _('Colors of the page and text')),
|
||||||
create_item(_('Page layout'), def():self.show_panel('layout');, _('Page margins and number of pages per screen')),
|
create_item(_('Page layout'), def():self.show_panel('layout');, _('Page margins and number of pages per screen')),
|
||||||
create_item(_('User style sheet'), def():self.show_panel('user_stylesheet');, _('Style rules for text')),
|
create_item(_('Styles'), def():self.show_panel('user_stylesheet');, _('Style rules for text and background image')),
|
||||||
create_item(_('Headers and footers'), def():self.show_panel('head_foot');, _('Customize the headers and footers')),
|
create_item(_('Headers and footers'), def():self.show_panel('head_foot');, _('Customize the headers and footers')),
|
||||||
create_item(_('Keyboard shortcuts'), def():self.show_panel('keyboard');, _('Customize the keyboard shortcuts')),
|
create_item(_('Keyboard shortcuts'), def():self.show_panel('keyboard');, _('Customize the keyboard shortcuts')),
|
||||||
]
|
]
|
||||||
@ -109,7 +109,7 @@ class Prefs:
|
|||||||
commit_layout(self.onchange, self.container)
|
commit_layout(self.onchange, self.container)
|
||||||
|
|
||||||
def display_user_stylesheet(self, container):
|
def display_user_stylesheet(self, container):
|
||||||
document.getElementById(self.title_id).textContent = _('User style sheet')
|
document.getElementById(self.title_id).textContent = _('Styles')
|
||||||
create_user_stylesheet_panel(container)
|
create_user_stylesheet_panel(container)
|
||||||
|
|
||||||
def close_user_stylesheet(self):
|
def close_user_stylesheet(self):
|
||||||
|
@ -6,18 +6,54 @@ from elementmaker import E
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
from book_list.globals import get_session_data
|
from book_list.globals import get_session_data
|
||||||
from read_book.globals import runtime
|
from read_book.globals import runtime, ui_operations
|
||||||
|
from viewer.constants import READER_BACKGROUND_URL
|
||||||
|
from widgets import create_button
|
||||||
|
from dom import unique_id
|
||||||
|
|
||||||
|
|
||||||
|
BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
|
||||||
|
|
||||||
|
|
||||||
|
def change_background_image(img_id):
|
||||||
|
ui_operations.change_background_image(img_id)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_image(img_id):
|
||||||
|
document.getElementById(img_id).src = BLANK
|
||||||
|
|
||||||
|
|
||||||
|
def background_widget(sd):
|
||||||
|
if sd.get('background_image'):
|
||||||
|
src = READER_BACKGROUND_URL
|
||||||
|
else:
|
||||||
|
src = BLANK
|
||||||
|
img_id = unique_id('bg-image')
|
||||||
|
|
||||||
|
return E.div(
|
||||||
|
style='display: flex; align-items: center',
|
||||||
|
E.div(E.img(src=src, id=img_id, class_='bg-image-preview', style='width: 75px; height: 75px; border: solid 1px')),
|
||||||
|
E.div('\xa0', style='margin: 0.5rem'),
|
||||||
|
create_button(_('Change image'), action=change_background_image.bind(None, img_id)),
|
||||||
|
E.div('\xa0', style='margin: 0.5rem'),
|
||||||
|
create_button(_('Clear image'), action=clear_image.bind(None, img_id)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_user_stylesheet_panel(container):
|
def create_user_stylesheet_panel(container):
|
||||||
sd = get_session_data()
|
sd = get_session_data()
|
||||||
container.appendChild(
|
container.appendChild(
|
||||||
E.div(
|
E.div(
|
||||||
style='min-height: 80vh; display: flex; flex-flow: column; margin: 1ex 1rem; padding: 1ex 0',
|
style='min-height: 75vh; display: flex; flex-flow: column; margin: 1ex 1rem; padding: 1ex 0',
|
||||||
|
E.div(
|
||||||
|
style='border-bottom: solid 1px; margin-bottom: 1.5ex; padding-bottom: 1.5ex',
|
||||||
|
E.div(_('Choose a background image to display behind the book text'), style='margin-bottom: 1.5ex'),
|
||||||
|
background_widget(sd),
|
||||||
|
),
|
||||||
E.div(
|
E.div(
|
||||||
style='flex-grow: 10; display: flex; flex-flow: column',
|
style='flex-grow: 10; display: flex; flex-flow: column',
|
||||||
E.div(
|
E.div(
|
||||||
_('A CSS style sheet that can be used to control the look and feel of books. For examples, click'), ' ',
|
_('A CSS style sheet that can be used to control the look and feel of the text. For examples, click'), ' ',
|
||||||
E.a(class_='blue-link', title=_("Examples of user style sheets"),
|
E.a(class_='blue-link', title=_("Examples of user style sheets"),
|
||||||
target=('_self' if runtime.is_standalone_viewer else '_blank'),
|
target=('_self' if runtime.is_standalone_viewer else '_blank'),
|
||||||
href='https://www.mobileread.com/forums/showthread.php?t=51500', _('here'))
|
href='https://www.mobileread.com/forums/showthread.php?t=51500', _('here'))
|
||||||
@ -38,6 +74,16 @@ def commit_user_stylesheet(onchange, container):
|
|||||||
ta = container.querySelector('[name=user-stylesheet]')
|
ta = container.querySelector('[name=user-stylesheet]')
|
||||||
val = ta.value or ''
|
val = ta.value or ''
|
||||||
old = sd.get('user_stylesheet')
|
old = sd.get('user_stylesheet')
|
||||||
|
changed = False
|
||||||
if old is not val:
|
if old is not val:
|
||||||
sd.set('user_stylesheet', val)
|
sd.set('user_stylesheet', val)
|
||||||
|
changed = True
|
||||||
|
bg_image = container.querySelector('img.bg-image-preview').src
|
||||||
|
if bg_image is BLANK:
|
||||||
|
bg_image = None
|
||||||
|
old = sd.get('background_image')
|
||||||
|
if old is not bg_image:
|
||||||
|
sd.set('background_image', bg_image)
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
onchange()
|
onchange()
|
||||||
|
@ -29,7 +29,10 @@ from read_book.timers import Timers
|
|||||||
from read_book.toc import get_current_toc_nodes, update_visible_toc_nodes
|
from read_book.toc import get_current_toc_nodes, update_visible_toc_nodes
|
||||||
from read_book.touch import set_left_margin_handler, set_right_margin_handler
|
from read_book.touch import set_left_margin_handler, set_right_margin_handler
|
||||||
from session import get_device_uuid, get_interface_data
|
from session import get_device_uuid, get_interface_data
|
||||||
from utils import html_escape, is_ios, parse_url_params, username_key, safe_set_inner_html
|
from utils import (
|
||||||
|
html_escape, is_ios, parse_url_params, safe_set_inner_html, username_key
|
||||||
|
)
|
||||||
|
from viewer.constants import READER_BACKGROUND_URL
|
||||||
|
|
||||||
add_extra_css(def():
|
add_extra_css(def():
|
||||||
sel = '.book-side-margin'
|
sel = '.book-side-margin'
|
||||||
@ -435,7 +438,17 @@ class View:
|
|||||||
s = m.style
|
s = m.style
|
||||||
s.color = ans.foreground
|
s.color = ans.foreground
|
||||||
s.backgroundColor = ans.background
|
s.backgroundColor = ans.background
|
||||||
|
sd = get_session_data()
|
||||||
self.iframe.style.backgroundColor = ans.background or 'white'
|
self.iframe.style.backgroundColor = ans.background or 'white'
|
||||||
|
bg_image = sd.get('background_image')
|
||||||
|
if bg_image:
|
||||||
|
if runtime.is_standalone_viewer:
|
||||||
|
self.iframe.style.backgroundImage = f'url({READER_BACKGROUND_URL})'
|
||||||
|
else:
|
||||||
|
self.iframe.style.backgroundImage = f'url({bg_image})'
|
||||||
|
else:
|
||||||
|
self.iframe.style.backgroundImage = 'none'
|
||||||
|
|
||||||
m.parentNode.style.backgroundColor = ans.background # this is needed on iOS where the bottom margin has its own margin, so we dont want the body background color to bleed through
|
m.parentNode.style.backgroundColor = ans.background # this is needed on iOS where the bottom margin has its own margin, so we dont want the body background color to bleed through
|
||||||
self.content_popup_overlay.apply_color_scheme(ans.background, ans.foreground)
|
self.content_popup_overlay.apply_color_scheme(ans.background, ans.foreground)
|
||||||
return ans
|
return ans
|
||||||
|
@ -34,6 +34,8 @@ defaults = {
|
|||||||
'max_text_width': 0,
|
'max_text_width': 0,
|
||||||
'columns_per_screen': {'portrait':0, 'landscape':0},
|
'columns_per_screen': {'portrait':0, 'landscape':0},
|
||||||
'user_stylesheet': '',
|
'user_stylesheet': '',
|
||||||
|
'background_image': None,
|
||||||
|
'background_image_style': 'stretch',
|
||||||
'current_color_scheme': 'white',
|
'current_color_scheme': 'white',
|
||||||
'user_color_schemes': {},
|
'user_color_schemes': {},
|
||||||
'base_font_size': 16,
|
'base_font_size': 16,
|
||||||
@ -58,6 +60,8 @@ is_local_setting = {
|
|||||||
'max_text_width': True,
|
'max_text_width': True,
|
||||||
'columns_per_screen': True,
|
'columns_per_screen': True,
|
||||||
'user_stylesheet': True,
|
'user_stylesheet': True,
|
||||||
|
'background_image': True,
|
||||||
|
'background_image_style': True,
|
||||||
'current_color_scheme': True,
|
'current_color_scheme': True,
|
||||||
'base_font_size': True,
|
'base_font_size': True,
|
||||||
'controls_help_shown_count': True,
|
'controls_help_shown_count': True,
|
||||||
|
@ -22,7 +22,7 @@ from read_book.shortcuts import add_standalone_viewer_shortcuts
|
|||||||
from read_book.view import View
|
from read_book.view import View
|
||||||
from session import session_defaults
|
from session import session_defaults
|
||||||
from utils import encode_query_with_path, parse_url_params
|
from utils import encode_query_with_path, parse_url_params
|
||||||
from viewer.constants import FAKE_HOST, FAKE_PROTOCOL
|
from viewer.constants import FAKE_HOST, FAKE_PROTOCOL, READER_BACKGROUND_URL
|
||||||
|
|
||||||
runtime.is_standalone_viewer = True
|
runtime.is_standalone_viewer = True
|
||||||
runtime.FAKE_HOST = FAKE_HOST
|
runtime.FAKE_HOST = FAKE_HOST
|
||||||
@ -237,6 +237,14 @@ def get_current_cfi(request_id):
|
|||||||
view.get_current_cfi(request_id, ui_operations.report_cfi)
|
view.get_current_cfi(request_id, ui_operations.report_cfi)
|
||||||
|
|
||||||
|
|
||||||
|
@from_python
|
||||||
|
def background_image_changed(img_id):
|
||||||
|
img = document.getElementById(img_id)
|
||||||
|
if img:
|
||||||
|
img.src = ''
|
||||||
|
img.src = READER_BACKGROUND_URL
|
||||||
|
|
||||||
|
|
||||||
def onerror(msg, script_url, line_number, column_number, error_object):
|
def onerror(msg, script_url, line_number, column_number, error_object):
|
||||||
if not error_object:
|
if not error_object:
|
||||||
# cross domain error
|
# cross domain error
|
||||||
@ -295,6 +303,8 @@ if window is window.top:
|
|||||||
to_python.copy_selection(text or None)
|
to_python.copy_selection(text or None)
|
||||||
ui_operations.view_image = def(name):
|
ui_operations.view_image = def(name):
|
||||||
to_python.view_image(name)
|
to_python.view_image(name)
|
||||||
|
ui_operations.change_background_image = def(img_id):
|
||||||
|
to_python.change_background_image(img_id)
|
||||||
|
|
||||||
document.body.appendChild(E.div(id='view'))
|
document.body.appendChild(E.div(id='view'))
|
||||||
window.onerror = onerror
|
window.onerror = onerror
|
||||||
|
@ -5,3 +5,4 @@ from __python__ import bound_methods, hash_literals
|
|||||||
|
|
||||||
FAKE_PROTOCOL = '__FAKE_PROTOCOL__'
|
FAKE_PROTOCOL = '__FAKE_PROTOCOL__'
|
||||||
FAKE_HOST = '__FAKE_HOST__'
|
FAKE_HOST = '__FAKE_HOST__'
|
||||||
|
READER_BACKGROUND_URL = f'{FAKE_PROTOCOL}://{FAKE_HOST}/reader-background'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user