diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py
index 0cb91f5fc3..f8cea2cf91 100644
--- a/src/calibre/ebooks/metadata/book/render.py
+++ b/src/calibre/ebooks/metadata/book/render.py
@@ -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 = '%s' % (
- 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 = ['%s' % (
- 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 = '
%s'%(
prepare_string_for_xml(durl))
- link = u'%s%s' % (scheme, url,
+ link = '%s%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'{fmt}'.format(**x)
+ fmts = ['{fmt}'.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'%s' % (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 = [
+ '%s' % (
+ 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'%s'%(a(lt), a(link), aut))
+ authors.append('%s'%(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 = ['%s' % (search_href('languages', n), _(
+ names = ['%s' % (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 = '%s' % (
- 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 = '%s' % (
+ 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 '
+ '%(sidx)s of '
'%(series)s') % 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 = ['%s' % (
- 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 = ['%s' % (
+ 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 = '%s' % (
- 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 = '%s' % (
+ 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)))
diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index d8cfb3bd51..16a032d4e5 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -3,7 +3,6 @@
# License: GPLv3 Copyright: 2010, Kovid Goyal
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()
diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py
index aec75c3133..ec5820b3cf 100644
--- a/src/calibre/gui2/dialogs/book_info.py
+++ b/src/calibre/gui2/dialogs/book_info.py
@@ -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)
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index d984ecceef..e07d726cf6 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -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)
diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py
index dd9e600b35..cb063ba526 100644
--- a/src/calibre/gui2/widgets2.py
+++ b/src/calibre/gui2/widgets2.py
@@ -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
-__license__ = 'GPL v3'
-__copyright__ = '2013, Kovid Goyal '
+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([])