mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
E-book viewer: Allow vieweing tables in a separate popup window by right clicking on the table and selecting 'View table'. Useful for reference books that have lots of large tables. See #1080710
This commit is contained in:
parent
92a97abaf8
commit
0800dafcb7
53
src/calibre/ebooks/oeb/display/extract.coffee
Normal file
53
src/calibre/ebooks/oeb/display/extract.coffee
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env coffee
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
###
|
||||
Copyright 2012, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
if window?.calibre_utils
|
||||
log = window.calibre_utils.log
|
||||
|
||||
merge = (node, cnode) ->
|
||||
rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '')
|
||||
if rules
|
||||
for rule in rules
|
||||
style = rule.style
|
||||
for name in style
|
||||
val = style.getPropertyValue(name)
|
||||
if val and not cnode.style.getPropertyValue(name)
|
||||
cnode.style.setProperty(name, val)
|
||||
|
||||
inline_styles = (node) ->
|
||||
cnode = node.cloneNode(true)
|
||||
merge(node, cnode)
|
||||
nl = node.getElementsByTagName('*')
|
||||
cnl = cnode.getElementsByTagName('*')
|
||||
for node, i in nl
|
||||
merge(node, cnl[i])
|
||||
|
||||
return cnode
|
||||
|
||||
class CalibreExtract
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.calibre_extract object.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
throw new Error('CalibreExtract constructor called as function')
|
||||
this.marked_node = null
|
||||
|
||||
mark: (node) =>
|
||||
this.marked_node = node
|
||||
|
||||
extract: (node=null) =>
|
||||
if node == null
|
||||
node = this.marked_node
|
||||
cnode = inline_styles(node)
|
||||
return cnode.outerHTML
|
||||
|
||||
if window?
|
||||
window.calibre_extract = new CalibreExtract()
|
||||
|
||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, pyqtProperty,
|
||||
QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion,
|
||||
QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal,
|
||||
QSwipeGesture, QApplication, pyqtSlot)
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings, QWebElement
|
||||
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts
|
||||
@ -24,6 +24,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
|
||||
from calibre.gui2.viewer.position import PagePosition
|
||||
from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
|
||||
from calibre.gui2.viewer.image_popup import ImagePopup
|
||||
from calibre.gui2.viewer.table_popup import TablePopup
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.constants import isxp, iswindows
|
||||
# }}}
|
||||
@ -31,6 +32,7 @@ from calibre.constants import isxp, iswindows
|
||||
class Document(QWebPage): # {{{
|
||||
|
||||
page_turn = pyqtSignal(object)
|
||||
mark_element = pyqtSignal(QWebElement)
|
||||
|
||||
def set_font_settings(self, opts):
|
||||
settings = self.settings()
|
||||
@ -182,6 +184,7 @@ class Document(QWebPage): # {{{
|
||||
ensure_ascii=False)))
|
||||
for pl in self.all_viewer_plugins:
|
||||
pl.load_javascript(evaljs)
|
||||
evaljs('py_bridge.mark_element.connect(window.calibre_extract.mark)')
|
||||
|
||||
@pyqtSignature("")
|
||||
def animated_scroll_done(self):
|
||||
@ -448,6 +451,10 @@ class Document(QWebPage): # {{{
|
||||
self.height+amount)
|
||||
self.setPreferredContentsSize(s)
|
||||
|
||||
def extract_node(self):
|
||||
return unicode(self.mainFrame().evaluateJavaScript(
|
||||
'window.calibre_extract.extract()').toString())
|
||||
|
||||
# }}}
|
||||
|
||||
class DocumentView(QWebView): # {{{
|
||||
@ -496,8 +503,11 @@ class DocumentView(QWebView): # {{{
|
||||
self.dictionary_action.triggered.connect(self.lookup)
|
||||
self.addAction(self.dictionary_action)
|
||||
self.image_popup = ImagePopup(self)
|
||||
self.table_popup = TablePopup(self)
|
||||
self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
|
||||
self.view_image_action.triggered.connect(self.image_popup)
|
||||
self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self)
|
||||
self.view_table_action.triggered.connect(self.popup_table)
|
||||
self.search_action = QAction(QIcon(I('dictionary.png')),
|
||||
_('&Search for next occurrence'), self)
|
||||
self.search_action.setShortcut(Qt.CTRL+Qt.Key_S)
|
||||
@ -603,10 +613,26 @@ class DocumentView(QWebView): # {{{
|
||||
t = t.replace(u'&', u'&&')
|
||||
return _("S&earch Google for '%s'")%t
|
||||
|
||||
def popup_table(self):
|
||||
html = self.document.extract_node()
|
||||
self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path),
|
||||
self.document.font_magnification_step)
|
||||
|
||||
def contextMenuEvent(self, ev):
|
||||
mf = self.document.mainFrame()
|
||||
r = mf.hitTestContent(ev.pos())
|
||||
img = r.pixmap()
|
||||
elem = r.element()
|
||||
if elem.isNull():
|
||||
elem = r.enclosingBlockElement()
|
||||
table = None
|
||||
parent = elem
|
||||
while not parent.isNull():
|
||||
if (unicode(parent.tagName()) == u'table' or
|
||||
unicode(parent.localName()) == u'table'):
|
||||
table = parent
|
||||
break
|
||||
parent = parent.parent()
|
||||
self.image_popup.current_img = img
|
||||
self.image_popup.current_url = r.imageUrl()
|
||||
menu = self.document.createStandardContextMenu()
|
||||
@ -615,6 +641,9 @@ class DocumentView(QWebView): # {{{
|
||||
|
||||
if not img.isNull():
|
||||
menu.addAction(self.view_image_action)
|
||||
if table is not None:
|
||||
self.document.mark_element.emit(table)
|
||||
menu.addAction(self.view_table_action)
|
||||
|
||||
text = self._selectedText()
|
||||
if text and img.isNull():
|
||||
|
@ -34,11 +34,12 @@ class JavaScriptLoader(object):
|
||||
'utils':'ebooks.oeb.display.utils',
|
||||
'fs':'ebooks.oeb.display.full_screen',
|
||||
'math': 'ebooks.oeb.display.mathjax',
|
||||
'extract': 'ebooks.oeb.display.extract',
|
||||
}
|
||||
|
||||
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
|
||||
'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged',
|
||||
'fs', 'math')
|
||||
'fs', 'math', 'extract')
|
||||
|
||||
|
||||
def __init__(self, dynamic_coffeescript=False):
|
||||
|
83
src/calibre/gui2/viewer/table_popup.py
Normal file
83
src/calibre/gui2/viewer/table_popup.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QApplication,
|
||||
QSize, QIcon, Qt)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre.gui2 import gprefs, error_dialog
|
||||
|
||||
class TableView(QDialog):
|
||||
|
||||
def __init__(self, parent, font_magnification_step):
|
||||
QDialog.__init__(self, parent)
|
||||
self.font_magnification_step = font_magnification_step
|
||||
dw = QApplication.instance().desktop()
|
||||
self.avail_geom = dw.availableGeometry(parent)
|
||||
|
||||
self.view = QWebView(self)
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole)
|
||||
self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole)
|
||||
zi.setIcon(QIcon(I('plus.png')))
|
||||
zo.setIcon(QIcon(I('minus.png')))
|
||||
zi.clicked.connect(self.zoom_in)
|
||||
zo.clicked.connect(self.zoom_out)
|
||||
|
||||
self.l = l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
l.addWidget(self.view)
|
||||
l.addWidget(bb)
|
||||
|
||||
def zoom_in(self):
|
||||
self.view.setZoomFactor(self.view.zoomFactor() +
|
||||
self.font_magnification_step)
|
||||
|
||||
def zoom_out(self):
|
||||
self.view.setZoomFactor(max(0.1, self.view.zoomFactor()
|
||||
-self.font_magnification_step))
|
||||
|
||||
def __call__(self, html, baseurl):
|
||||
self.view.setHtml(
|
||||
'<!DOCTYPE html><html><body bgcolor="white">%s<body></html>'%html,
|
||||
baseurl)
|
||||
geom = self.avail_geom
|
||||
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
|
||||
geom = gprefs.get('viewer_table_popup_geometry', None)
|
||||
if geom is not None:
|
||||
self.restoreGeometry(geom)
|
||||
self.setWindowTitle(_('View Table'))
|
||||
self.show()
|
||||
|
||||
def done(self, e):
|
||||
gprefs['viewer_table_popup_geometry'] = bytearray(self.saveGeometry())
|
||||
return QDialog.done(self, e)
|
||||
|
||||
class TablePopup(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.dialogs = []
|
||||
|
||||
def __call__(self, html, baseurl, font_magnification_step):
|
||||
if not html:
|
||||
return error_dialog(self.parent, _('No table found'),
|
||||
_('No table was found'), show=True)
|
||||
d = TableView(self.parent, font_magnification_step)
|
||||
self.dialogs.append(d)
|
||||
d.finished.connect(self.cleanup, type=Qt.QueuedConnection)
|
||||
d(html, baseurl)
|
||||
|
||||
def cleanup(self):
|
||||
for d in tuple(self.dialogs):
|
||||
if not d.isVisible():
|
||||
self.dialogs.remove(d)
|
||||
|
Loading…
x
Reference in New Issue
Block a user