diff --git a/src/calibre/ebooks/oeb/display/extract.coffee b/src/calibre/ebooks/oeb/display/extract.coffee new file mode 100644 index 0000000000..92ed91a24f --- /dev/null +++ b/src/calibre/ebooks/oeb/display/extract.coffee @@ -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 + 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() + + diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 5fc4de3e38..69704de1c7 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -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(): diff --git a/src/calibre/gui2/viewer/javascript.py b/src/calibre/gui2/viewer/javascript.py index 179139d082..b9584562c3 100644 --- a/src/calibre/gui2/viewer/javascript.py +++ b/src/calibre/gui2/viewer/javascript.py @@ -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): diff --git a/src/calibre/gui2/viewer/table_popup.py b/src/calibre/gui2/viewer/table_popup.py new file mode 100644 index 0000000000..d466d63def --- /dev/null +++ b/src/calibre/gui2/viewer/table_popup.py @@ -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 ' +__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( + '%s'%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) +