mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Do not use WebKit to display book details
We are phasing out WebKit
This commit is contained in:
parent
27c5324269
commit
e3fd6f9bff
@ -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,14 +190,18 @@ 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))
|
||||
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:
|
||||
@ -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)))
|
||||
|
||||
|
@ -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,30 +191,14 @@ 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)
|
||||
menu.addAction(QIcon(I('edit-copy.png')), _('Copy &all'), partial(copy_all, book_info))
|
||||
search_internet_added = False
|
||||
if not r.isNull():
|
||||
|
||||
def add_format_entries(menu, data, book_info):
|
||||
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
|
||||
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_'):]
|
||||
@ -259,25 +241,21 @@ def details_context_menu_event(view, ev, book_info): # {{{
|
||||
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 = r.linkElement().attribute('data-full-path')
|
||||
if ac.current_url:
|
||||
ac.current_url = path
|
||||
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:
|
||||
|
||||
|
||||
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)
|
||||
@ -286,26 +264,51 @@ def details_context_menu_event(view, ev, book_info): # {{{
|
||||
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':
|
||||
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 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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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([])
|
||||
|
Loading…
x
Reference in New Issue
Block a user