Do not use WebKit to display book details

We are phasing out WebKit
This commit is contained in:
Kovid Goyal 2019-06-22 04:55:30 +05:30
parent 27c5324269
commit e3fd6f9bff
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 250 additions and 217 deletions

View File

@ -19,7 +19,7 @@ from calibre.utils.formatter import EvalFormatter
from calibre.utils.date import is_date_undefined
from calibre.utils.localization import calibre_langcode_to_name
from calibre.utils.serialize import json_dumps
from polyglot.builtins import unicode_type, filter
from polyglot.builtins import filter
from polyglot.binary import as_hex_unicode
default_sort = ('title', 'title_sort', 'authors', 'author_sort', 'series', 'rating', 'pubdate', 'tags', 'publisher', 'identifiers')
@ -52,9 +52,18 @@ def get_field_list(mi):
yield field, True
def search_href(search_term, value):
search = '%s:"=%s"' % (search_term, value.replace('"', '\\"'))
return prepare_string_for_xml('search:' + as_hex_unicode(search.encode('utf-8')), True)
def action(main, **keys):
keys['type'] = main
return 'action:' + as_hex_unicode(json_dumps(keys))
def search_action(search_term, value, **k):
return action('search', term=search_term, value=value, **k)
def search_action_with_data(search_term, value, book_id, field=None):
field = field or search_term
return search_action(search_term, value, field=field, book_id=book_id)
DEFAULT_AUTHOR_LINK = 'search-{}'.format(DEFAULT_AUTHOR_SOURCE)
@ -62,7 +71,7 @@ DEFAULT_AUTHOR_LINK = 'search-{}'.format(DEFAULT_AUTHOR_SOURCE)
def author_search_href(which, title=None, author=None):
if which == 'calibre':
return search_href('authors', author), _('Search the calibre library for books by %s') % author
return 'calibre', _('Search the calibre library for books by %s') % author
search_type, key = 'author', which
if which.endswith('-book'):
key, search_type = which.rpartition('-')[::2]
@ -78,10 +87,6 @@ def author_search_href(which, title=None, author=None):
return func(key, title=title, author=author), tt
def item_data(field_name, value, book_id):
return as_hex_unicode(json_dumps((field_name, value, book_id)))
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False):
if field_list is None:
field_list = get_field_list(mi)
@ -149,13 +154,13 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
else:
if not metadata['is_multiple']:
val = '<a href="%s" title="%s">%s</a>' % (
search_href(field, val),
search_action(field, val),
_('Click to see books with {0}: {1}').format(metadata['name'], a(val)), p(val))
else:
all_vals = [v.strip()
for v in val.split(metadata['is_multiple']['list_to_ui']) if v.strip()]
links = ['<a href="%s" title="%s">%s</a>' % (
search_href(field, x), _('Click to see books with {0}: {1}').format(
search_action(field, x), _('Click to see books with {0}: {1}').format(
metadata['name'], a(x)), p(x)) for x in all_vals]
val = metadata['is_multiple']['list_to_ui'].join(links)
ans.append((field, row % (name, val)))
@ -163,17 +168,16 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
if mi.path:
path = force_unicode(mi.path, filesystem_encoding)
scheme = u'devpath' if isdevice else u'path'
url = prepare_string_for_xml(path if isdevice else
unicode_type(book_id), True)
loc = path if isdevice else book_id
pathstr = _('Click to open')
extra = ''
if isdevice:
durl = url
durl = path
if durl.startswith('mtp:::'):
durl = ':::'.join((durl.split(':::'))[2:])
extra = '<br><span style="font-size:smaller">%s</span>'%(
prepare_string_for_xml(durl))
link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url,
link = '<a href="%s" title="%s">%s</a>%s' % (action(scheme, loc=loc),
prepare_string_for_xml(path, True), pathstr, extra)
ans.append((field, row % (name, link)))
elif field == 'formats':
@ -186,15 +190,19 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
bpath = os.sep.join((os.path.basename(h), t))
data = ({
'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')),
'ext':x.lower(), 'id':book_id, 'bpath':bpath, 'sep':os.sep
'ext':x.lower(), 'id':book_id, 'bpath':bpath, 'sep':os.sep,
'action':action('format', book_id=book_id, fmt=x, path=path or '', fname=mi.format_files.get(x, ''))
} for x in mi.formats)
fmts = [u'<a data-full-path="{path}{sep}{fname}.{ext}" title="{bpath}{sep}{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x)
fmts = ['<a title="{bpath}{sep}{fname}.{ext}" href="{action}">{fmt}</a>'.format(**x)
for x in data]
ans.append((field, row % (name, u', '.join(fmts))))
ans.append((field, row % (name, ', '.join(fmts))))
elif field == 'identifiers':
urls = urls_from_identifiers(mi.identifiers)
links = [u'<a href="%s" title="%s:%s" data-item="%s">%s</a>' % (a(url), a(id_typ), a(id_val), a(item_data(field, id_typ, book_id)), p(namel))
for namel, id_typ, id_val, url in urls]
links = [
'<a href="%s" title="%s:%s">%s</a>' % (
action('identifier', url=url, name=namel, type=id_typ, value=id_val, field='identifiers', book_id=book_id),
a(id_typ), a(id_val), p(namel))
for namel, id_typ, id_val, url in urls]
links = u', '.join(links)
if links:
ans.append((field, row % (_('Ids')+':', links)))
@ -220,23 +228,24 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
link = lt = formatter.safe_format(default_author_link, vals, '', vals)
aut = p(aut)
if link:
authors.append(u'<a calibre-data="authors" title="%s" href="%s">%s</a>'%(a(lt), a(link), aut))
authors.append('<a title="%s" href="%s">%s</a>'%(a(lt), action('author', url=link, name=aut, title=lt), aut))
else:
authors.append(aut)
ans.append((field, row % (name, u' & '.join(authors))))
ans.append((field, row % (name, ' & '.join(authors))))
elif field == 'languages':
if not mi.languages:
continue
names = filter(None, map(calibre_langcode_to_name, mi.languages))
names = ['<a href="%s" title="%s">%s</a>' % (search_href('languages', n), _(
names = ['<a href="%s" title="%s">%s</a>' % (search_action('languages', n), _(
'Search calibre for books with the language: {}').format(n), n) for n in names]
ans.append((field, row % (name, u', '.join(names))))
elif field == 'publisher':
if not mi.publisher:
continue
val = '<a href="%s" title="%s" data-item="%s">%s</a>' % (
search_href('publisher', mi.publisher), _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)),
a(item_data('publisher', mi.publisher, book_id)), p(mi.publisher))
val = '<a href="%s" title="%s">%s</a>' % (
search_action_with_data('publisher', mi.publisher, book_id),
_('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)),
p(mi.publisher))
ans.append((field, row % (name, val)))
elif field == 'title':
# otherwise title gets metadata['datatype'] == 'text'
@ -260,11 +269,10 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
st = field
series = getattr(mi, field)
val = _(
'%(sidx)s of <a href="%(href)s" title="%(tt)s" data-item="%(data)s">'
'%(sidx)s of <a href="%(href)s" title="%(tt)s">'
'<span class="%(cls)s">%(series)s</span></a>') % dict(
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name",
series=p(series), href=search_href(st, series),
data=a(item_data(field, series, book_id)),
series=p(series), href=search_action_with_data(st, series, book_id, field),
tt=p(_('Click to see books in this series')))
elif metadata['datatype'] == 'datetime':
aval = getattr(mi, field)
@ -278,9 +286,9 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
all_vals = mi.get(field)
if not metadata.get('display', {}).get('is_names', False):
all_vals = sorted(all_vals, key=sort_key)
links = ['<a href="%s" title="%s" data-item="%s">%s</a>' % (
search_href(st, x), _('Click to see books with {0}: {1}').format(
metadata['name'], a(x)), a(item_data(field, x, book_id)), p(x))
links = ['<a href="%s" title="%s">%s</a>' % (
search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format(
metadata['name'], a(x)), p(x))
for x in all_vals]
val = metadata['is_multiple']['list_to_ui'].join(links)
elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration':
@ -289,9 +297,9 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
st = metadata['search_terms'][0]
except Exception:
st = field
val = '<a href="%s" title="%s" data-item="%s">%s</a>' % (
search_href(st, val), a(_('Click to see books with {0}: {1}').format(metadata['name'], val)),
a(item_data(field, val, book_id)), p(val))
val = '<a href="%s" title="%s">%s</a>' % (
search_action_with_data(st, val, book_id, field), a(
_('Click to see books with {0}: {1}').format(metadata['name'], val)), p(val))
ans.append((field, row % (name, val)))

View File

@ -3,7 +3,6 @@
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import re
from collections import namedtuple
from functools import partial
@ -13,7 +12,6 @@ from PyQt5.Qt import (
QMimeData, QPainter, QPalette, QPen, QPixmap, QPropertyAnimation, QRect, QSize,
QSizePolicy, Qt, QUrl, QWidget, pyqtProperty, pyqtSignal
)
from PyQt5.QtWebKitWidgets import QWebView
from calibre import fit_image
from calibre.ebooks import BOOK_EXTENSIONS
@ -30,28 +28,29 @@ from calibre.gui2 import (
from calibre.gui2.dnd import (
dnd_get_files, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions
)
from calibre.gui2.widgets2 import HTMLDisplay
from calibre.utils.config import tweaks
from calibre.utils.img import blend_image, image_from_x
from calibre.utils.localization import is_rtl
from calibre.utils.serialize import json_loads
from polyglot.binary import from_hex_bytes
from polyglot.builtins import unicode_type
from polyglot.binary import from_hex_bytes, from_hex_unicode
_css = None
InternetSearch = namedtuple('InternetSearch', 'author where')
def set_html(mi, html, web_view):
def set_html(mi, html, text_browser):
from calibre.gui2.ui import get_gui
gui = get_gui()
book_id = getattr(mi, 'id', None)
search_paths = []
if gui and book_id is not None:
path = gui.current_db.abspath(book_id, index_is_id=True)
if path:
web_view.setHtml(html, QUrl.fromLocalFile(os.path.join(path, 'metadata.html')))
return
web_view.setHtml(html)
search_paths = [path]
text_browser.setSearchPaths(search_paths)
text_browser.setHtml(html)
def css():
@ -64,9 +63,8 @@ def css():
return _css
def copy_all(web_view):
web_view = getattr(web_view, 'details', web_view)
mf = web_view.page().mainFrame()
def copy_all(text_browser):
mf = getattr(text_browser, 'details', text_browser)
c = QApplication.clipboard()
md = QMimeData()
md.setText(mf.toPlainText())
@ -193,119 +191,124 @@ def render_data(mi, use_roman_numbers=True, all_fields=False, pref_name='book_di
# }}}
# Context menu {{{
def details_context_menu_event(view, ev, book_info): # {{{
p = view.page()
mf = p.mainFrame()
r = mf.hitTestContent(ev.pos())
url = unicode_type(r.linkUrl().toString(NO_URL_FORMATTING)).strip()
menu = p.createStandardContextMenu()
ca = view.pageAction(p.Copy)
for action in list(menu.actions()):
if action is not ca:
menu.removeAction(action)
def add_format_entries(menu, data, book_info):
from calibre.ebooks.oeb.polish.main import SUPPORTED
from calibre.gui2.ui import get_gui
book_id = int(data['book_id'])
fmt = data['fmt']
db = get_gui().current_db.new_api
ofmt = fmt.upper() if fmt.startswith('ORIGINAL_') else 'ORIGINAL_' + fmt
nfmt = ofmt[len('ORIGINAL_'):]
fmts = {x.upper() for x in db.formats(book_id)}
for a, t in [
('remove', _('Delete the %s format')),
('save', _('Save the %s format to disk')),
('restore', _('Restore the %s format')),
('compare', ''),
('set_cover', _('Set the book cover from the %s file')),
]:
if a == 'restore' and not fmt.startswith('ORIGINAL_'):
continue
if a == 'compare':
if ofmt not in fmts or nfmt not in SUPPORTED:
continue
t = _('Compare to the %s format') % (fmt[9:] if fmt.startswith('ORIGINAL_') else ofmt)
else:
t = t % fmt
ac = getattr(book_info, '%s_format_action'%a)
ac.current_fmt = (book_id, fmt)
ac.setText(t)
menu.addAction(ac)
if not fmt.upper().startswith('ORIGINAL_'):
from calibre.gui2.open_with import populate_menu, edit_programs
m = QMenu(_('Open %s with...') % fmt.upper())
def connect_action(ac, entry):
connect_lambda(ac.triggered, book_info, lambda book_info: book_info.open_with(book_id, fmt, entry))
populate_menu(m, connect_action, fmt)
if len(m.actions()) == 0:
menu.addAction(_('Open %s with...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt))
else:
m.addSeparator()
m.addAction(_('Add other application for %s files...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt))
m.addAction(_('Edit Open With applications...'), partial(edit_programs, fmt, book_info))
menu.addMenu(m)
menu.ow = m
if fmt.upper() in SUPPORTED:
menu.addSeparator()
menu.addAction(_('Edit %s...') % fmt.upper(), partial(book_info.edit_fmt, book_id, fmt))
path = data['path']
if path:
ac = book_info.copy_link_action
ac.current_url = path
ac.setText(_('&Copy path to file'))
menu.addAction(ac)
def add_item_specific_entries(menu, data, book_info):
search_internet_added = False
dt = data['type']
if dt == 'format':
add_format_entries(menu, data, book_info)
elif dt == 'author':
author = data['name']
menu.addAction(init_manage_action(book_info.manage_action, 'authors', author))
if hasattr(book_info, 'search_internet'):
menu.sia = sia = create_search_internet_menu(book_info.search_internet, author)
menu.addMenu(sia)
search_internet_added = True
if hasattr(book_info, 'search_requested'):
menu.addAction(_('Search calibre for %s') % author,
lambda : book_info.search_requested('authors:"={}"'.format(author.replace('"', r'\"'))))
elif dt in ('path', 'devpath'):
from calibre.gui2.ui import get_gui
path = data['loc']
ac = book_info.copy_link_action
if isinstance(path, int):
path = get_gui().library_view.model().db.abspath(path, index_is_id=True)
ac.current_url = path
ac.setText(_('Copy path'))
menu.addAction(ac)
else:
field = data.get('field')
if field is not None:
book_id = int(data['book_id'])
value = data['value']
if field == 'identifiers':
menu.addAction(book_info.edit_identifiers_action)
elif field in ('tags', 'series', 'publisher') or is_category(field):
menu.addAction(init_manage_action(book_info.manage_action, field, value))
ac = book_info.remove_item_action
ac.data = (field, value, book_id)
ac.setText(_('Remove %s from this book') % value)
menu.addAction(ac)
return search_internet_added
def details_context_menu_event(view, ev, book_info):
url = view.anchorAt(ev.pos())
menu = view.createStandardContextMenu()
menu.addAction(QIcon(I('edit-copy.png')), _('Copy &all'), partial(copy_all, book_info))
search_internet_added = False
if not r.isNull():
from calibre.ebooks.oeb.polish.main import SUPPORTED
if url.startswith('format:'):
parts = url.split(':')
try:
book_id, fmt = int(parts[1]), parts[2].upper()
except:
import traceback
traceback.print_exc()
else:
from calibre.gui2.ui import get_gui
db = get_gui().current_db.new_api
ofmt = fmt.upper() if fmt.startswith('ORIGINAL_') else 'ORIGINAL_' + fmt
nfmt = ofmt[len('ORIGINAL_'):]
fmts = {x.upper() for x in db.formats(book_id)}
for a, t in [
('remove', _('Delete the %s format')),
('save', _('Save the %s format to disk')),
('restore', _('Restore the %s format')),
('compare', ''),
('set_cover', _('Set the book cover from the %s file')),
]:
if a == 'restore' and not fmt.startswith('ORIGINAL_'):
continue
if a == 'compare':
if ofmt not in fmts or nfmt not in SUPPORTED:
continue
t = _('Compare to the %s format') % (fmt[9:] if fmt.startswith('ORIGINAL_') else ofmt)
else:
t = t % fmt
ac = getattr(book_info, '%s_format_action'%a)
ac.current_fmt = (book_id, fmt)
ac.setText(t)
menu.addAction(ac)
if not fmt.upper().startswith('ORIGINAL_'):
from calibre.gui2.open_with import populate_menu, edit_programs
m = QMenu(_('Open %s with...') % fmt.upper())
def connect_action(ac, entry):
connect_lambda(ac.triggered, book_info, lambda book_info: book_info.open_with(book_id, fmt, entry))
populate_menu(m, connect_action, fmt)
if len(m.actions()) == 0:
menu.addAction(_('Open %s with...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt))
else:
m.addSeparator()
m.addAction(_('Add other application for %s files...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt))
m.addAction(_('Edit Open With applications...'), partial(edit_programs, fmt, book_info))
menu.addMenu(m)
menu.ow = m
if fmt.upper() in SUPPORTED:
menu.addSeparator()
menu.addAction(_('Edit %s...') % fmt.upper(), partial(book_info.edit_fmt, book_id, fmt))
ac = book_info.copy_link_action
ac.current_url = r.linkElement().attribute('data-full-path')
if ac.current_url:
ac.setText(_('&Copy path to file'))
menu.addAction(ac)
else:
el = r.linkElement()
data = el.attribute('data-item')
author = el.toPlainText() if unicode_type(el.attribute('calibre-data')) == 'authors' else None
if url and not url.startswith('search:'):
for a, t in [('copy', _('&Copy link')),
]:
ac = getattr(book_info, '%s_link_action'%a)
ac.current_url = url
if url.startswith('path:'):
ac.current_url = el.attribute('title')
ac.setText(t)
menu.addAction(ac)
if author is not None:
menu.addAction(init_manage_action(book_info.manage_action, 'authors', author))
if hasattr(book_info, 'search_internet'):
menu.sia = sia = create_search_internet_menu(book_info.search_internet, author)
menu.addMenu(sia)
search_internet_added = True
if hasattr(book_info, 'search_requested'):
menu.addAction(_('Search calibre for %s') % author,
lambda : book_info.search_requested('authors:"={}"'.format(author.replace('"', r'\"'))))
if data:
try:
field, value, book_id = json_loads(from_hex_bytes(data))
except Exception:
field = value = book_id = None
if field:
if author is None:
if field in ('tags', 'series', 'publisher') or is_category(field):
menu.addAction(init_manage_action(book_info.manage_action, field, value))
elif field == 'identifiers':
menu.addAction(book_info.edit_identifiers_action)
ac = book_info.remove_item_action
ac.data = (field, value, book_id)
ac.setText(_('Remove %s from this book') % value)
menu.addAction(ac)
if url and url.startswith('action:'):
data = json_loads(from_hex_bytes(url.split(':', 1)[1]))
search_internet_added = add_item_specific_entries(menu, data, book_info)
elif url and not url.startswith('#'):
ac = book_info.copy_link_action
ac.current_url = url
ac.setText(_('Copy link location'))
menu.addAction(ac)
if not search_internet_added and hasattr(book_info, 'search_internet'):
menu.addSeparator()
menu.si = create_search_internet_menu(book_info.search_internet)
menu.addMenu(menu.si)
for ac in tuple(menu.actions()):
if not ac.isEnabled():
menu.removeAction(ac)
if len(menu.actions()) > 0:
menu.exec_(ev.globalPos())
# }}}
@ -539,7 +542,7 @@ class CoverView(QWidget): # {{{
# Book Info {{{
class BookInfo(QWebView):
class BookInfo(HTMLDisplay):
link_clicked = pyqtSignal(object)
remove_format = pyqtSignal(int, object)
@ -555,18 +558,9 @@ class BookInfo(QWebView):
edit_identifiers = pyqtSignal()
def __init__(self, vertical, parent=None):
QWebView.__init__(self, parent)
s = self.settings()
s.setAttribute(s.JavascriptEnabled, False)
HTMLDisplay.__init__(self, parent)
self.vertical = vertical
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.linkClicked.connect(self.link_activated)
self._link_clicked = False
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
self.setAcceptDrops(False)
palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette)
self.anchor_clicked.connect(self.link_activated)
for x, icon in [
('remove_format', 'trash.png'), ('save_format', 'save.png'),
('restore_format', 'edit-undo.png'), ('copy_link','edit-copy.png'),
@ -625,28 +619,21 @@ class BookInfo(QWebView):
self.manage_category.emit(*self.manage_action.current_fmt)
def link_activated(self, link):
self._link_clicked = True
if unicode_type(link.scheme()) in ('http', 'https'):
return safe_open_url(link)
link = unicode_type(link.toString(NO_URL_FORMATTING))
self.link_clicked.emit(link)
def turnoff_scrollbar(self, *args):
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
def show_data(self, mi):
html = render_html(mi, css(), self.vertical, self.parent())
set_html(mi, html, self)
def mouseDoubleClickEvent(self, ev):
swidth = self.page().mainFrame().scrollBarGeometry(Qt.Vertical).width()
sheight = self.page().mainFrame().scrollBarGeometry(Qt.Horizontal).height()
if self.width() - ev.x() < swidth or \
self.height() - ev.y() < sheight:
# Filter out double clicks on the scroll bar
ev.accept()
else:
v = self.viewport()
if v.rect().contains(self.mapFromGlobal(ev.globalPos())):
ev.ignore()
else:
return HTMLDisplay.mouseDoubleClickEvent(self, ev)
def contextMenuEvent(self, ev):
details_context_menu_event(self, ev, self)
@ -867,23 +854,42 @@ class BookDetails(QWidget): # {{{
safe_open_url(url)
def handle_click(self, link):
typ, val = link.partition(':')[0::2]
if typ == 'path':
self.open_containing_folder.emit(int(val))
elif typ == 'format':
id_, fmt = val.split(':')
self.view_specific_format.emit(int(id_), fmt)
elif typ == 'devpath':
self.view_device_book.emit(val)
elif typ == 'search':
self.search_requested.emit(from_hex_unicode(val))
else:
typ, val = link.partition(':')[::2]
def search_term(field, val):
self.search_requested.emit('{}:="{}"'.format(field, val.replace('"', '\\"')))
def browse(url):
try:
safe_open_url(QUrl(link, QUrl.TolerantMode))
except:
safe_open_url(QUrl(url, QUrl.TolerantMode))
except Exception:
import traceback
traceback.print_exc()
if typ == 'action':
data = json_loads(from_hex_bytes(val))
dt = data['type']
if dt == 'search':
search_term(data['term'], data['value'])
elif dt == 'author':
url = data['url']
if url == 'calibre':
search_term('authors', data['name'])
else:
browse(url)
elif dt == 'format':
book_id, fmt = data['book_id'], data['fmt']
self.view_specific_format.emit(int(book_id), fmt)
elif dt == 'identifier':
if data['url']:
browse(data['url'])
elif dt == 'path':
self.open_containing_folder.emit(int(data['loc']))
elif dt == 'devpath':
self.view_device_book.emit(data['loc'])
else:
browse(link)
def mouseDoubleClickEvent(self, ev):
ev.accept()
self.show_book_info.emit()

View File

@ -8,14 +8,15 @@ from PyQt5.Qt import (
QShortcut, QSize, QSplitter, Qt, QTimer, QToolButton, QVBoxLayout, QWidget,
pyqtSignal
)
from PyQt5.QtWebKitWidgets import QWebView
from calibre import fit_image
from calibre.gui2 import NO_URL_FORMATTING, gprefs
from calibre.gui2.book_details import css, details_context_menu_event, render_html, set_html
from calibre.gui2.book_details import (
css, details_context_menu_event, render_html, set_html
)
from calibre.gui2.ui import get_gui
from calibre.gui2.widgets import CoverView
from calibre.gui2.widgets2 import Dialog
from calibre.gui2.widgets2 import Dialog, HTMLDisplay
from polyglot.builtins import unicode_type
@ -77,10 +78,10 @@ class Configure(Dialog):
return Dialog.accept(self)
class Details(QWebView):
class Details(HTMLDisplay):
def __init__(self, book_info, parent=None):
QWebView.__init__(self, parent)
HTMLDisplay.__init__(self, parent)
self.book_info = book_info
def sizeHint(self):
@ -113,20 +114,18 @@ class BookInfo(QDialog):
self.splitter.addWidget(self.cover)
self.details = Details(parent.book_details.book_info, self)
self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks)
self.details.linkClicked.connect(self.link_clicked)
s = self.details.page().settings()
s.setAttribute(s.JavascriptEnabled, False)
self.details.anchor_clicked.connect(self.on_link_clicked)
self.css = css()
self.link_delegate = link_delegate
self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.details.palette()
self.details.setAcceptDrops(False)
palette.setBrush(QPalette.Base, Qt.transparent)
self.details.page().setPalette(palette)
self.details.setPalette(palette)
self.c = QWidget(self)
self.c.l = l2 = QGridLayout(self.c)
l2.setContentsMargins(0, 0, 0, 0)
self.c.setLayout(l2)
l2.addWidget(self.details, 0, 0, 1, -1)
self.splitter.addWidget(self.c)
@ -179,7 +178,7 @@ class BookInfo(QDialog):
if mi is not None:
self.refresh(self.current_row, mi=mi)
def link_clicked(self, qurl):
def on_link_clicked(self, qurl):
link = unicode_type(qurl.toString(NO_URL_FORMATTING))
self.link_delegate(link)

View File

@ -30,6 +30,7 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.opf2 import OPF
from calibre.gui2 import error_dialog, rating_font, gprefs
from calibre.gui2.progress_indicator import draw_snake_spinner
from calibre.gui2.widgets2 import HTMLDisplay
from calibre.utils.date import (utcnow, fromordinal, format_date,
UNDEFINED_DATE, as_utc)
from calibre.library.comments import comments_to_html
@ -312,10 +313,10 @@ class ResultsView(QTableView): # {{{
# }}}
class Comments(QTextBrowser): # {{{
class Comments(HTMLDisplay): # {{{
def __init__(self, parent=None):
QTextBrowser.__init__(self, parent)
HTMLDisplay.__init__(self, parent)
self.setAcceptDrops(False)
self.setMaximumWidth(300)
self.setMinimumWidth(300)
@ -323,14 +324,9 @@ class Comments(QTextBrowser): # {{{
self.wait_timer.timeout.connect(self.update_wait)
self.wait_timer.setInterval(800)
self.dots_count = 0
self.anchor_clicked.connect(self.link_activated)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.setPalette(palette)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
self.anchorClicked.connect(self.link_clicked)
def link_clicked(self, url):
def link_activated(self, url):
from calibre.gui2 import open_url
if url.scheme() in {'http', 'https'}:
open_url(url)

View File

@ -1,22 +1,22 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
from __future__ import absolute_import, division, print_function, unicode_literals
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from __future__ import absolute_import, division, print_function, unicode_literals
import weakref
from PyQt5.Qt import (
QPushButton, QPixmap, QIcon, QColor, Qt, QColorDialog, pyqtSignal,
QKeySequence, QToolButton, QDialog, QDialogButtonBox, QComboBox, QFont,
QAbstractListModel, QModelIndex, QApplication, QStyledItemDelegate,
QUndoCommand, QUndoStack, QLayout, QRect, QSize, QStyle, QSizePolicy,
QPoint, QWidget, QLabel, QCheckBox)
QAbstractListModel, QApplication, QCheckBox, QColor, QColorDialog, QComboBox,
QDialog, QDialogButtonBox, QFont, QIcon, QKeySequence, QLabel, QLayout,
QModelIndex, QPalette, QPixmap, QPoint, QPushButton, QRect, QSize, QSizePolicy,
QStyle, QStyledItemDelegate, Qt, QTextBrowser, QToolButton, QUndoCommand,
QUndoStack, QWidget, pyqtSignal
)
from calibre.ebooks.metadata import rating_to_stars
from calibre.gui2 import gprefs, rating_font
from calibre.gui2.complete2 import LineEdit, EditWithComplete
from calibre.gui2.complete2 import EditWithComplete, LineEdit
from calibre.gui2.widgets import history
from polyglot.builtins import unicode_type
@ -427,6 +427,30 @@ class FlowLayout(QLayout): # {{{
# }}}
class HTMLDisplay(QTextBrowser):
anchor_clicked = pyqtSignal(object)
def __init__(self, parent=None):
QTextBrowser.__init__(self, parent)
self.setFrameShape(self.NoFrame)
self.setOpenLinks(False)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.setPalette(palette)
self.setAcceptDrops(False)
self.anchorClicked.connect(self.on_anchor_clicked)
def on_anchor_clicked(self, qurl):
if not qurl.scheme() and qurl.hasFragment() and qurl.toString().startswith('#'):
frag = qurl.fragment(qurl.FullyDecoded)
if frag:
self.scrollToAnchor(frag)
return
self.anchor_clicked.emit(qurl)
if __name__ == '__main__':
from calibre.gui2 import Application
app = Application([])