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
|
return cnode
|
||||||
|
|
||||||
is_footnote_link = (node, url) ->
|
get_epub_type = (node, possible_values) ->
|
||||||
if not url or url.substr(0, 'file://'.length).toLowerCase() != 'file://'
|
# Try to get the value of the epub:type attribute. Complex as we dont
|
||||||
return false # Ignore non-local links
|
# operate in XML mode
|
||||||
# Check for epub:type="noteref", a little complex as we dont operate in XML
|
|
||||||
# mode
|
|
||||||
epub_type = node.getAttributeNS("http://www.idpf.org/2007/ops", 'type') or node.getAttribute('epub:type')
|
epub_type = node.getAttributeNS("http://www.idpf.org/2007/ops", 'type') or node.getAttribute('epub:type')
|
||||||
if not epub_type
|
if not epub_type
|
||||||
for x in node.attributes # consider any xxx:type="noteref" attribute as marking a note
|
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'
|
if x.nodeName and x.nodeValue in possible_values and x.nodeName.slice(-':type'.length) == ':type'
|
||||||
epub_type = 'noteref'
|
epub_type = x.nodeValue
|
||||||
break
|
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'
|
if epub_type and epub_type.toLowerCase() == 'noteref'
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@ -61,6 +65,35 @@ is_footnote_link = (node, url) ->
|
|||||||
|
|
||||||
return false
|
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
|
class CalibreExtract
|
||||||
# This class is a namespace to expose functions via the
|
# This class is a namespace to expose functions via the
|
||||||
# window.calibre_extract object.
|
# window.calibre_extract object.
|
||||||
@ -88,6 +121,39 @@ class CalibreExtract
|
|||||||
ans[url] = 1
|
ans[url] = 1
|
||||||
return JSON.stringify(ans)
|
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?
|
if window?
|
||||||
window.calibre_extract = new CalibreExtract()
|
window.calibre_extract = new CalibreExtract()
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@ def apply_basic_settings(settings):
|
|||||||
settings.setAttribute(QWebSettings.PluginsEnabled, False)
|
settings.setAttribute(QWebSettings.PluginsEnabled, False)
|
||||||
settings.setAttribute(QWebSettings.JavascriptCanOpenWindows, False)
|
settings.setAttribute(QWebSettings.JavascriptCanOpenWindows, False)
|
||||||
settings.setAttribute(QWebSettings.JavascriptCanAccessClipboard, 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.setAttribute(QWebSettings.NotificationsEnabled, False)
|
||||||
settings.setThirdPartyCookiePolicy(QWebSettings.AlwaysBlockThirdPartyCookies)
|
settings.setThirdPartyCookiePolicy(QWebSettings.AlwaysBlockThirdPartyCookies)
|
||||||
|
|
||||||
@ -1319,6 +1320,7 @@ class DocumentView(QWebView): # {{{
|
|||||||
if url.isValid() and self.manager is not None:
|
if url.isValid() and self.manager is not None:
|
||||||
fd = self.footnotes.get_footnote_data(url)
|
fd = self.footnotes.get_footnote_data(url)
|
||||||
if fd:
|
if fd:
|
||||||
|
self.footnotes.show_footnote(fd)
|
||||||
self.manager.show_footnote_view()
|
self.manager.show_footnote_view()
|
||||||
ev.accept()
|
ev.accept()
|
||||||
return
|
return
|
||||||
|
@ -9,20 +9,55 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
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.QtWebKitWidgets import QWebView, QWebPage
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
|
from calibre.constants import DEBUG
|
||||||
|
from calibre.ebooks.oeb.display.webview import load_html
|
||||||
|
|
||||||
class FootnotesPage(QWebPage):
|
class FootnotesPage(QWebPage):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QWebPage.__init__(self, parent)
|
QWebPage.__init__(self, parent)
|
||||||
|
self.js_loader = None
|
||||||
|
self._footnote_data = ''
|
||||||
from calibre.gui2.viewer.documentview import apply_basic_settings
|
from calibre.gui2.viewer.documentview import apply_basic_settings
|
||||||
settings = self.settings()
|
settings = self.settings()
|
||||||
apply_basic_settings(settings)
|
apply_basic_settings(settings)
|
||||||
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, False)
|
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):
|
class FootnotesView(QWidget):
|
||||||
@ -31,10 +66,12 @@ class FootnotesView(QWidget):
|
|||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.l = l = QHBoxLayout(self)
|
self.l = l = QHBoxLayout(self)
|
||||||
self.view = v = QWebView(self)
|
self.view = v = QWebView(self)
|
||||||
|
self._page = FootnotesPage(v)
|
||||||
|
v.setPage(self._page)
|
||||||
l.addWidget(v)
|
l.addWidget(v)
|
||||||
|
|
||||||
def page(self):
|
def page(self):
|
||||||
return self.view.page()
|
return self._page
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return QSize(400, 200)
|
return QSize(400, 200)
|
||||||
@ -49,6 +86,8 @@ class Footnotes(object):
|
|||||||
def set_footnotes_view(self, fv):
|
def set_footnotes_view(self, fv):
|
||||||
self.footnotes_view = fv
|
self.footnotes_view = fv
|
||||||
self.clone_settings()
|
self.clone_settings()
|
||||||
|
fv.page().linkClicked.connect(self.view.link_clicked)
|
||||||
|
fv.page().js_loader = self.view.document.js_loader
|
||||||
|
|
||||||
def clone_settings(self):
|
def clone_settings(self):
|
||||||
source = self.view.document.settings()
|
source = self.view.document.settings()
|
||||||
@ -61,26 +100,28 @@ class Footnotes(object):
|
|||||||
def clear(self):
|
def clear(self):
|
||||||
self.footnote_data_cache = {}
|
self.footnote_data_cache = {}
|
||||||
self.known_footnote_targets = defaultdict(set)
|
self.known_footnote_targets = defaultdict(set)
|
||||||
|
self.showing_url = None
|
||||||
|
|
||||||
def spine_index(self, path):
|
def spine_path(self, path):
|
||||||
try:
|
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):
|
except (AttributeError, ValueError):
|
||||||
return -1
|
pass
|
||||||
|
|
||||||
def load_footnote_data(self, current_url):
|
def load_footnote_data(self, current_url):
|
||||||
fd = self.footnote_data_cache[current_url] = {}
|
fd = self.footnote_data_cache[current_url] = {}
|
||||||
try:
|
try:
|
||||||
raw = self.view.document.javascript('window.calibre_extract.get_footnote_data()', typ='string')
|
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:
|
if x not in fd:
|
||||||
qu = QUrl(x)
|
qu = QUrl(x)
|
||||||
path = qu.toLocalFile()
|
path = qu.toLocalFile()
|
||||||
si = self.spine_index(path)
|
spath = self.spine_path(path)
|
||||||
if si > -1:
|
if spath is not None:
|
||||||
target = qu.fragment(QUrl.FullyDecoded)
|
target = qu.fragment(QUrl.FullyDecoded)
|
||||||
fd[qu.toString()] = (path, target)
|
fd[qu.toString()] = (spath, target, qu)
|
||||||
self.known_footnote_targets[path].add(target)
|
self.known_footnote_targets[spath].add(target)
|
||||||
except Exception:
|
except Exception:
|
||||||
prints('Failed to get footnote data, with error:')
|
prints('Failed to get footnote data, with error:')
|
||||||
import traceback
|
import traceback
|
||||||
@ -95,3 +136,11 @@ class Footnotes(object):
|
|||||||
if fd is None:
|
if fd is None:
|
||||||
fd = self.load_footnote_data(current_url)
|
fd = self.load_footnote_data(current_url)
|
||||||
return fd.get(qurl.toString())
|
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