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:
Kovid Goyal 2012-11-22 12:59:04 +05:30
parent 92a97abaf8
commit 0800dafcb7
4 changed files with 168 additions and 2 deletions

View 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()

View File

@ -12,7 +12,7 @@ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, pyqtProperty,
QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion, QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion,
QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal, QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal,
QSwipeGesture, QApplication, pyqtSlot) 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.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts 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.position import PagePosition
from calibre.gui2.viewer.config import config, ConfigDialog, load_themes from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
from calibre.gui2.viewer.image_popup import ImagePopup 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.ebooks.oeb.display.webview import load_html
from calibre.constants import isxp, iswindows from calibre.constants import isxp, iswindows
# }}} # }}}
@ -31,6 +32,7 @@ from calibre.constants import isxp, iswindows
class Document(QWebPage): # {{{ class Document(QWebPage): # {{{
page_turn = pyqtSignal(object) page_turn = pyqtSignal(object)
mark_element = pyqtSignal(QWebElement)
def set_font_settings(self, opts): def set_font_settings(self, opts):
settings = self.settings() settings = self.settings()
@ -182,6 +184,7 @@ class Document(QWebPage): # {{{
ensure_ascii=False))) ensure_ascii=False)))
for pl in self.all_viewer_plugins: for pl in self.all_viewer_plugins:
pl.load_javascript(evaljs) pl.load_javascript(evaljs)
evaljs('py_bridge.mark_element.connect(window.calibre_extract.mark)')
@pyqtSignature("") @pyqtSignature("")
def animated_scroll_done(self): def animated_scroll_done(self):
@ -448,6 +451,10 @@ class Document(QWebPage): # {{{
self.height+amount) self.height+amount)
self.setPreferredContentsSize(s) self.setPreferredContentsSize(s)
def extract_node(self):
return unicode(self.mainFrame().evaluateJavaScript(
'window.calibre_extract.extract()').toString())
# }}} # }}}
class DocumentView(QWebView): # {{{ class DocumentView(QWebView): # {{{
@ -496,8 +503,11 @@ class DocumentView(QWebView): # {{{
self.dictionary_action.triggered.connect(self.lookup) self.dictionary_action.triggered.connect(self.lookup)
self.addAction(self.dictionary_action) self.addAction(self.dictionary_action)
self.image_popup = ImagePopup(self) 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 = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
self.view_image_action.triggered.connect(self.image_popup) 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')), self.search_action = QAction(QIcon(I('dictionary.png')),
_('&Search for next occurrence'), self) _('&Search for next occurrence'), self)
self.search_action.setShortcut(Qt.CTRL+Qt.Key_S) self.search_action.setShortcut(Qt.CTRL+Qt.Key_S)
@ -603,10 +613,26 @@ class DocumentView(QWebView): # {{{
t = t.replace(u'&', u'&&') t = t.replace(u'&', u'&&')
return _("S&earch Google for '%s'")%t 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): def contextMenuEvent(self, ev):
mf = self.document.mainFrame() mf = self.document.mainFrame()
r = mf.hitTestContent(ev.pos()) r = mf.hitTestContent(ev.pos())
img = r.pixmap() 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_img = img
self.image_popup.current_url = r.imageUrl() self.image_popup.current_url = r.imageUrl()
menu = self.document.createStandardContextMenu() menu = self.document.createStandardContextMenu()
@ -615,6 +641,9 @@ class DocumentView(QWebView): # {{{
if not img.isNull(): if not img.isNull():
menu.addAction(self.view_image_action) 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() text = self._selectedText()
if text and img.isNull(): if text and img.isNull():

View File

@ -34,11 +34,12 @@ class JavaScriptLoader(object):
'utils':'ebooks.oeb.display.utils', 'utils':'ebooks.oeb.display.utils',
'fs':'ebooks.oeb.display.full_screen', 'fs':'ebooks.oeb.display.full_screen',
'math': 'ebooks.oeb.display.mathjax', 'math': 'ebooks.oeb.display.mathjax',
'extract': 'ebooks.oeb.display.extract',
} }
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images', ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged', 'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged',
'fs', 'math') 'fs', 'math', 'extract')
def __init__(self, dynamic_coffeescript=False): def __init__(self, dynamic_coffeescript=False):

View 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)