diff --git a/src/calibre/ebooks/oeb/polish/preview.coffee b/src/calibre/ebooks/oeb/polish/preview.coffee index f218cc26bd..e596e1c6bd 100644 --- a/src/calibre/ebooks/oeb/polish/preview.coffee +++ b/src/calibre/ebooks/oeb/polish/preview.coffee @@ -17,6 +17,21 @@ is_hidden = (elem) -> elem = elem.parentNode return false +previous_sibling = (node) -> + node = node.previousSibling + while node and node.nodeType != Node.ELEMENT_NODE + node = node.previousSibling + return node + +is_block = (elem) -> + style = window.getComputedStyle(elem) + return style.display in ['block', 'flex-box', 'box'] + +find_containing_block = (elem) -> + while elem and elem.getAttribute('data-is-block') != '1' + elem = elem.parentNode + return elem + class PreviewIntegration ### @@ -27,18 +42,20 @@ class PreviewIntegration constructor: () -> if not this instanceof arguments.callee throw new Error('PreviewIntegration constructor called as function') + this.blocks_found = false + this.in_split_mode = false - go_to_line: (lnum) -> + go_to_line: (lnum) => for node in document.querySelectorAll('[data-lnum="' + lnum + '"]') if is_hidden(node) continue top = window.calibre_utils.abstop(node) - (window.innerHeight / 2) - if (top < 0) + if top < 0 top = 0 window.scrollTo(0, top) return - line_numbers: () -> + line_numbers: () => found_body = false ans = [] for node in document.getElementsByTagName('*') @@ -49,18 +66,43 @@ class PreviewIntegration return ans find_blocks: () => + if this.blocks_found + return for elem in document.body.getElementsByTagName('*') - style = window.getComputedStyle(elem) - if style.display in ['block', 'flex-box', 'box'] + if is_block(elem) elem.setAttribute('data-is-block', '1') - elem.onclick = this.onclick + this.blocks_found = true - onload: () -> - window.document.body.addEventListener('click', window.calibre_preview_integration.onclick, true) + split_mode: (enabled) => + this.in_split_mode = enabled + document.body.setAttribute('data-in-split-mode', if enabled then '1' else '0') + if enabled + this.find_blocks() - onclick: (event) -> + report_split: (node) => + loc = [] + parent = find_containing_block(node) + while parent and parent.tagName.toLowerCase() != 'body' + num = 0 + sibling = previous_sibling(parent) + while sibling + num += 1 + sibling = previous_sibling(sibling) + loc.push(num) + parent = parent.parentNode + loc.reverse() + window.py_bridge.request_split(loc) + + onload: () => + window.document.body.addEventListener('click', this.onclick, true) + + onclick: (event) => event.preventDefault() - window.py_bridge.request_sync(event.target.getAttribute("data-lnum")) + if this.in_split_mode + this.report_split(event.target) + else + window.py_bridge.request_sync(event.target.getAttribute("data-lnum")) + return false window.calibre_preview_integration = new PreviewIntegration() window.onload = window.calibre_preview_integration.onload diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 12660743c8..68078066e6 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -57,6 +57,7 @@ class Boss(QObject): self.gui.central.close_requested.connect(self.editor_close_requested) self.gui.central.search_panel.search_triggered.connect(self.search) self.gui.preview.sync_requested.connect(self.sync_editor_to_preview) + self.gui.preview.split_start_requested.connect(self.split_start_requested) def mkdtemp(self, prefix=''): self.container_count += 1 @@ -502,6 +503,11 @@ class Boss(QObject): if ok: ed.current_line = num + def split_start_requested(self): + if not self.check_dirtied(): + return self.gui.preview.stop_split() + self.gui.preview.do_start_split() + def sync_editor_to_preview(self, name, lnum): editor = self.edit_file(name, 'html') self.ignore_preview_to_editor_sync = True diff --git a/src/calibre/gui2/tweak_book/preview.py b/src/calibre/gui2/tweak_book/preview.py index 4df502dd81..e28d92cae8 100644 --- a/src/calibre/gui2/tweak_book/preview.py +++ b/src/calibre/gui2/tweak_book/preview.py @@ -6,8 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import time +import time, textwrap from bisect import bisect_right +from base64 import b64encode from future_builtins import map from threading import Thread from Queue import Queue, Empty @@ -23,6 +24,7 @@ from calibre.constants import iswindows, DEBUG from calibre.ebooks.oeb.polish.parsing import parse from calibre.ebooks.oeb.base import serialize, OEB_DOCS from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.gui2 import error_dialog from calibre.gui2.tweak_book import current_container, editors, tprefs, actions from calibre.gui2.viewer.documentview import apply_settings from calibre.gui2.viewer.config import config @@ -237,6 +239,7 @@ def find_le(a, x): class WebPage(QWebPage): sync_requested = pyqtSignal(object) + split_requested = pyqtSignal(object) def __init__(self, parent): QWebPage.__init__(self, parent) @@ -251,6 +254,10 @@ class WebPage(QWebPage): settings.setAttribute(settings.LinksIncludedInFocusChain, False) settings.setAttribute(settings.DeveloperExtrasEnabled, True) settings.setDefaultTextEncoding('utf-8') + data = 'data:text/css;charset=utf-8;base64,' + css = '[data-in-split-mode="1"] [data-is-block="1"]:hover { cursor: pointer !important; border-top: solid 5px green !important }' + data += b64encode(css.encode('utf-8')) + settings.setUserStyleSheetUrl(QUrl(data)) self.setNetworkAccessManager(NetworkAccessManager(self)) self.setLinkDelegationPolicy(self.DelegateAllLinks) @@ -277,6 +284,14 @@ class WebPage(QWebPage): except (TypeError, ValueError, OverflowError, AttributeError): pass + @pyqtSlot('QList') + def request_split(self, loc): + actions['split-in-preview'].setChecked(False) + if not loc: + return error_dialog(self.view(), _('Invalid location'), + _('Cannot split on the body tag'), show=True) + self.split_requested.emit(loc) + @property def line_numbers(self): if self._line_numbers is None: @@ -297,6 +312,12 @@ class WebPage(QWebPage): self.mainFrame().evaluateJavaScript( 'window.calibre_preview_integration.go_to_line(%d)' % lnum) + def split_mode(self, enabled): + self.mainFrame().evaluateJavaScript( + 'window.calibre_preview_integration.split_mode(%s)' % ( + 'true' if enabled else 'false')) + + class WebView(QWebView): def __init__(self, parent=None): @@ -343,6 +364,8 @@ class WebView(QWebView): class Preview(QWidget): sync_requested = pyqtSignal(object, object) + split_requested = pyqtSignal(object, object) + split_start_requested = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -351,6 +374,8 @@ class Preview(QWidget): l.setContentsMargins(0, 0, 0, 0) self.view = WebView(self) self.view.page().sync_requested.connect(self.request_sync) + self.view.page().split_requested.connect(self.request_split) + self.view.page().loadFinished.connect(self.load_finished) self.inspector = self.view.inspector self.inspector.setPage(self.view.page()) l.addWidget(self.view) @@ -373,6 +398,13 @@ class Preview(QWidget): self.bar.addSeparator() + ac = actions['split-in-preview'] + ac.setCheckable(True) + ac.setChecked(False) + ac.toggled.connect(self.split_toggled) + self.split_toggled(ac.isChecked()) + self.bar.addAction(ac) + ac = actions['reload-preview'] ac.triggered.connect(self.refresh) self.bar.addAction(ac) @@ -390,6 +422,10 @@ class Preview(QWidget): if self.current_name: self.sync_requested.emit(self.current_name, lnum) + def request_split(self, loc): + if self.current_name: + self.split_requested.emit(self.current_name, loc) + def sync_to_editor(self, name, lnum): self.current_sync_request = (name, lnum) QTimer.singleShot(100, self._sync_to_editor) @@ -455,3 +491,26 @@ class Preview(QWidget): if is_visible: self.refresh() + def split_toggled(self, checked): + actions['split-in-preview'].setToolTip(textwrap.fill(_( + 'Abort file split') if checked else _( + 'Split this file at a specified location.\n\nAfter clicking this button, click' + ' inside the preview panel above at the location you want the file to be split.'))) + if checked: + self.split_start_requested.emit() + else: + self.view.page().split_mode(False) + + def do_start_split(self): + self.view.page().split_mode(True) + + def stop_split(self): + actions['split-in-preview'].setChecked(False) + + def load_finished(self, ok): + if actions['split-in-preview'].isChecked(): + if ok: + self.do_start_split() + else: + self.stop_split() + diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index d3882fba52..144a8d7593 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -223,6 +223,8 @@ class Main(MainWindow): self.action_auto_sync_preview = reg('sync-right.png', _('Sync preview position to editor position'), None, 'sync-preview-to-editor', (), _( 'Sync preview position to editor position')) self.action_reload_preview = reg('view-refresh.png', _('Refresh preview'), None, 'reload-preview', ('F5',), _('Refresh preview')) + self.action_split_in_preview = reg('auto_author_sort.png', _('Split this file'), None, 'split-in-preview', (), _( + 'Split file in the preview panel')) # Search actions group = _('Search')