mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Popup footnotes now work
This commit is contained in:
parent
d559fad378
commit
f8e54c26e3
@ -29,17 +29,21 @@ inline_styles = (node) ->
|
||||
|
||||
return cnode
|
||||
|
||||
is_footnote_link = (node, url) ->
|
||||
if not url or url.substr(0, 'file://'.length).toLowerCase() != 'file://'
|
||||
return false # Ignore non-local links
|
||||
# Check for epub:type="noteref", a little complex as we dont operate in XML
|
||||
# mode
|
||||
get_epub_type = (node, possible_values) ->
|
||||
# Try to get the value of the epub:type attribute. Complex as we dont
|
||||
# operate in XML mode
|
||||
epub_type = node.getAttributeNS("http://www.idpf.org/2007/ops", 'type') or node.getAttribute('epub:type')
|
||||
if not epub_type
|
||||
for x in node.attributes # consider any xxx:type="noteref" attribute as marking a note
|
||||
if x.nodeName and x.nodeValue == 'noteref' and x.nodeName.slice(-':type'.length) == ':type'
|
||||
epub_type = 'noteref'
|
||||
if x.nodeName and x.nodeValue in possible_values and x.nodeName.slice(-':type'.length) == ':type'
|
||||
epub_type = x.nodeValue
|
||||
break
|
||||
return epub_type
|
||||
|
||||
is_footnote_link = (node, url) ->
|
||||
if not url or url.substr(0, 'file://'.length).toLowerCase() != 'file://'
|
||||
return false # Ignore non-local links
|
||||
epub_type = get_epub_type(node, ['noteref'])
|
||||
if epub_type and epub_type.toLowerCase() == 'noteref'
|
||||
return true
|
||||
|
||||
@ -61,6 +65,35 @@ is_footnote_link = (node, url) ->
|
||||
|
||||
return false
|
||||
|
||||
is_epub_footnote = (node) ->
|
||||
pv = ['note', 'footnote', 'rearnote']
|
||||
epub_type = get_epub_type(node, pv)
|
||||
if epub_type and epub_type.toLowerCase() in pv
|
||||
return true
|
||||
return false
|
||||
|
||||
get_parents_and_self = (node) ->
|
||||
ans = []
|
||||
while node and node isnt document.body
|
||||
ans.push(node)
|
||||
node = node.parentNode
|
||||
return ans
|
||||
|
||||
get_page_break = (node) ->
|
||||
style = getComputedStyle(node)
|
||||
ans = {}
|
||||
for x in ['before', 'after']
|
||||
ans[x] = style.getPropertyValue('page-break-'.concat(x)) in ['always', 'left', 'right']
|
||||
return ans
|
||||
|
||||
hide_children = (node) ->
|
||||
for child in node.childNodes
|
||||
if child.nodeType == Node.ELEMENT_NODE
|
||||
if child.do_not_hide
|
||||
hide_children(child)
|
||||
else
|
||||
child.style.display = 'none'
|
||||
|
||||
class CalibreExtract
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.calibre_extract object.
|
||||
@ -88,6 +121,39 @@ class CalibreExtract
|
||||
ans[url] = 1
|
||||
return JSON.stringify(ans)
|
||||
|
||||
show_footnote: (target, known_targets) ->
|
||||
if not target
|
||||
return
|
||||
start_elem = document.getElementById(target)
|
||||
if not start_elem
|
||||
return
|
||||
in_note = false
|
||||
is_footnote_container = is_epub_footnote(start_elem)
|
||||
for elem in get_parents_and_self(start_elem)
|
||||
elem.do_not_hide = true
|
||||
for elem in document.body.getElementsByTagName('*')
|
||||
if in_note
|
||||
if known_targets.hasOwnProperty(elem.getAttribute('id'))
|
||||
in_note = false
|
||||
continue
|
||||
pb = get_page_break(elem)
|
||||
if pb['before']
|
||||
in_note = false
|
||||
else if pb['after']
|
||||
in_note = false
|
||||
for child in elem.getElementsByTagName('*')
|
||||
child.do_not_hide = true
|
||||
else
|
||||
elem.do_not_hide = true
|
||||
else
|
||||
if elem is start_elem
|
||||
in_note = not is_footnote_container and not get_page_break(elem)['after']
|
||||
if not in_note
|
||||
for child in elem.getElementsByTagName('*')
|
||||
child.do_not_hide = true
|
||||
hide_children(document.body)
|
||||
location.hash = '#' + target
|
||||
|
||||
if window?
|
||||
window.calibre_extract = new CalibreExtract()
|
||||
|
||||
|
@ -49,7 +49,8 @@ def apply_basic_settings(settings):
|
||||
settings.setAttribute(QWebSettings.PluginsEnabled, False)
|
||||
settings.setAttribute(QWebSettings.JavascriptCanOpenWindows, False)
|
||||
settings.setAttribute(QWebSettings.JavascriptCanAccessClipboard, False)
|
||||
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
|
||||
# PrivateBrowsing disables console messages
|
||||
# settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
|
||||
settings.setAttribute(QWebSettings.NotificationsEnabled, False)
|
||||
settings.setThirdPartyCookiePolicy(QWebSettings.AlwaysBlockThirdPartyCookies)
|
||||
|
||||
@ -1319,6 +1320,7 @@ class DocumentView(QWebView): # {{{
|
||||
if url.isValid() and self.manager is not None:
|
||||
fd = self.footnotes.get_footnote_data(url)
|
||||
if fd:
|
||||
self.footnotes.show_footnote(fd)
|
||||
self.manager.show_footnote_view()
|
||||
ev.accept()
|
||||
return
|
||||
|
@ -9,20 +9,55 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
from PyQt5.Qt import QUrl, QWidget, QHBoxLayout, QSize
|
||||
from PyQt5.Qt import QUrl, QWidget, QHBoxLayout, QSize, pyqtSlot
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
|
||||
class FootnotesPage(QWebPage):
|
||||
|
||||
def __init__(self, parent):
|
||||
QWebPage.__init__(self, parent)
|
||||
self.js_loader = None
|
||||
self._footnote_data = ''
|
||||
from calibre.gui2.viewer.documentview import apply_basic_settings
|
||||
settings = self.settings()
|
||||
apply_basic_settings(settings)
|
||||
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, False)
|
||||
self.setLinkDelegationPolicy(self.DelegateAllLinks)
|
||||
self.mainFrame().javaScriptWindowObjectCleared.connect(self.add_window_objects)
|
||||
self.add_window_objects()
|
||||
|
||||
def add_window_objects(self, add_ready_listener=True):
|
||||
self.mainFrame().addToJavaScriptWindowObject("py_bridge", self)
|
||||
evaljs = self.mainFrame().evaluateJavaScript
|
||||
if self.js_loader is not None:
|
||||
for x in 'utils extract'.split():
|
||||
evaljs(self.js_loader.get(x))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def debug(self, msg):
|
||||
prints(msg)
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def footnote_data(self):
|
||||
return self._footnote_data
|
||||
|
||||
def set_footnote_data(self, target, known_targets):
|
||||
self._footnote_data = json.dumps({'target':target, 'known_targets':known_targets})
|
||||
if self._footnote_data:
|
||||
self.mainFrame().evaluateJavaScript(
|
||||
'data = JSON.parse(py_bridge.footnote_data()); calibre_extract.show_footnote(data["target"], data["known_targets"])')
|
||||
|
||||
def javaScriptAlert(self, frame, msg):
|
||||
prints('FootnoteView:alert::', msg)
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, lineno, source_id):
|
||||
if DEBUG:
|
||||
prints('FootnoteView:%s:%s:'%(unicode(source_id), lineno), unicode(msg))
|
||||
|
||||
|
||||
class FootnotesView(QWidget):
|
||||
@ -31,10 +66,12 @@ class FootnotesView(QWidget):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QHBoxLayout(self)
|
||||
self.view = v = QWebView(self)
|
||||
self._page = FootnotesPage(v)
|
||||
v.setPage(self._page)
|
||||
l.addWidget(v)
|
||||
|
||||
def page(self):
|
||||
return self.view.page()
|
||||
return self._page
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(400, 200)
|
||||
@ -49,6 +86,8 @@ class Footnotes(object):
|
||||
def set_footnotes_view(self, fv):
|
||||
self.footnotes_view = fv
|
||||
self.clone_settings()
|
||||
fv.page().linkClicked.connect(self.view.link_clicked)
|
||||
fv.page().js_loader = self.view.document.js_loader
|
||||
|
||||
def clone_settings(self):
|
||||
source = self.view.document.settings()
|
||||
@ -61,26 +100,28 @@ class Footnotes(object):
|
||||
def clear(self):
|
||||
self.footnote_data_cache = {}
|
||||
self.known_footnote_targets = defaultdict(set)
|
||||
self.showing_url = None
|
||||
|
||||
def spine_index(self, path):
|
||||
def spine_path(self, path):
|
||||
try:
|
||||
return self.view.manager.iterator.spine.index(path)
|
||||
si = self.view.manager.iterator.spine.index(path)
|
||||
return self.view.manager.iterator.spine[si]
|
||||
except (AttributeError, ValueError):
|
||||
return -1
|
||||
pass
|
||||
|
||||
def load_footnote_data(self, current_url):
|
||||
fd = self.footnote_data_cache[current_url] = {}
|
||||
try:
|
||||
raw = self.view.document.javascript('window.calibre_extract.get_footnote_data()', typ='string')
|
||||
for x in json.loads(raw):
|
||||
for x in json.loads(raw or '{}'):
|
||||
if x not in fd:
|
||||
qu = QUrl(x)
|
||||
path = qu.toLocalFile()
|
||||
si = self.spine_index(path)
|
||||
if si > -1:
|
||||
spath = self.spine_path(path)
|
||||
if spath is not None:
|
||||
target = qu.fragment(QUrl.FullyDecoded)
|
||||
fd[qu.toString()] = (path, target)
|
||||
self.known_footnote_targets[path].add(target)
|
||||
fd[qu.toString()] = (spath, target, qu)
|
||||
self.known_footnote_targets[spath].add(target)
|
||||
except Exception:
|
||||
prints('Failed to get footnote data, with error:')
|
||||
import traceback
|
||||
@ -95,3 +136,11 @@ class Footnotes(object):
|
||||
if fd is None:
|
||||
fd = self.load_footnote_data(current_url)
|
||||
return fd.get(qurl.toString())
|
||||
|
||||
def show_footnote(self, fd):
|
||||
path, target, self.showing_url = fd
|
||||
|
||||
if hasattr(self, 'footnotes_view'):
|
||||
if load_html(path, self.footnotes_view.view, codec=getattr(path, 'encoding', 'utf-8'),
|
||||
mime_type=getattr(path, 'mime_type', 'text/html')):
|
||||
self.footnotes_view.page().set_footnote_data(target, {k:True for k in self.known_footnote_targets[path]})
|
||||
|
Loading…
x
Reference in New Issue
Block a user