diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 4e5beed957..15b8e39c20 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -228,6 +228,7 @@ class Boss(QObject): for name in tuple(editors): self.close_editor(name) self.gui.preview.clear() + self.gui.live_css.clear() self.container_count = -1 if self.tdir: shutil.rmtree(self.tdir, ignore_errors=True) @@ -311,6 +312,7 @@ class Boss(QObject): self.close_editor(name) if not editors: self.gui.preview.clear() + self.gui.live_css.clear() if remove_names_from_toc(current_container(), spine_names + list(other_items)): self.gui.toc_view.update_if_visible() toc = find_existing_toc(current_container()) @@ -1028,11 +1030,19 @@ class Boss(QObject): if name is not None and getattr(ed, 'syntax', None) == 'html': self.gui.preview.sync_to_editor(name, ed.current_line) + def sync_live_css_to_editor(self): + ed = self.gui.central.current_editor + if ed is not None: + name = editor_name(ed) + if name is not None and getattr(ed, 'syntax', None) == 'html': + self.gui.live_css.sync_to_editor(name) + def init_editor(self, name, editor, data=None, use_template=False): editor.undo_redo_state_changed.connect(self.editor_undo_redo_state_changed) editor.data_changed.connect(self.editor_data_changed) editor.copy_available_state_changed.connect(self.editor_copy_available_state_changed) editor.cursor_position_changed.connect(self.sync_preview_to_editor) + editor.cursor_position_changed.connect(self.sync_live_css_to_editor) editor.cursor_position_changed.connect(self.update_cursor_position) if hasattr(editor, 'word_ignored'): editor.word_ignored.connect(self.word_ignored) @@ -1153,6 +1163,7 @@ class Boss(QObject): # focused. This is not inefficient since multiple requests # to sync are de-bounced with a 100 msec wait. self.sync_preview_to_editor() + self.sync_live_css_to_editor() if name is not None: self.gui.file_list.mark_name_as_current(name) if ed.has_line_numbers: @@ -1182,6 +1193,7 @@ class Boss(QObject): editor.break_cycles() if not editors or getattr(self.gui.central.current_editor, 'syntax', None) != 'html': self.gui.preview.clear() + self.gui.live_css.clear() def insert_character(self): self.gui.insert_char.show() @@ -1251,6 +1263,7 @@ class Boss(QObject): def shutdown(self): self.gui.preview.stop_refresh_timer() + self.gui.live_css.stop_update_timer() self.save_state() [x.reject() for x in _diff_dialogs] del _diff_dialogs[:] diff --git a/src/calibre/gui2/tweak_book/editor/smart/__init__.py b/src/calibre/gui2/tweak_book/editor/smart/__init__.py index b5f54e5ccb..7b5352d78c 100644 --- a/src/calibre/gui2/tweak_book/editor/smart/__init__.py +++ b/src/calibre/gui2/tweak_book/editor/smart/__init__.py @@ -20,3 +20,6 @@ class NullSmarts(object): def verify_for_spellcheck(self, cursor, highlighter): return False + def cursor_position_with_sourceline(self, cursor): + return None, None + diff --git a/src/calibre/gui2/tweak_book/editor/smart/html.py b/src/calibre/gui2/tweak_book/editor/smart/html.py index 243beb7bd8..9ffe7ea697 100644 --- a/src/calibre/gui2/tweak_book/editor/smart/html.py +++ b/src/calibre/gui2/tweak_book/editor/smart/html.py @@ -312,3 +312,26 @@ class HTMLSmarts(NullSmarts): return False + def cursor_position_with_sourceline(self, cursor): + ''' Return the tag containing the current cursor as a source line + number and a list of tags defined on that line upto and including the + containing tag. ''' + block = cursor.block() + offset = cursor.position() - block.position() + block, boundary = next_tag_boundary(block, offset, forward=False) + if block is None: + return None, None + if boundary.is_start: + # We are inside a tag, use this tag + start_block, start_offset = block, boundary.offset + else: + tag = find_closest_containing_tag(block, offset) + if tag is None: + return None, None + start_block, start_offset = tag.start_block, tag.start_offset + sourceline = start_block.blockNumber() + ud = start_block.userData() + if ud is None: + return None, None + all_tags = [t.name for t in ud.tags if (t.is_start and not t.closing and t.offset <= start_offset)] + return sourceline, all_tags diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index 96cf4d5b1b..a7ab7c02c6 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -749,3 +749,6 @@ class TextEdit(PlainTextEdit): if hasattr(self.smarts, 'rename_block_tag'): self.smarts.rename_block_tag(self, new_name) + def current_tag(self): + return self.smarts.cursor_position_with_sourceline(self.textCursor()) + diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index 4e9cdfd53a..a60e1418a8 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -112,6 +112,9 @@ class Editor(QMainWindow): self.editor.go_to_line(val) return property(fget=fget, fset=fset) + def current_tag(self): + return self.editor.current_tag() + @property def number_of_lines(self): return self.editor.blockCount() diff --git a/src/calibre/gui2/tweak_book/live_css.py b/src/calibre/gui2/tweak_book/live_css.py new file mode 100644 index 0000000000..74e1f526f6 --- /dev/null +++ b/src/calibre/gui2/tweak_book/live_css.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +from PyQt4.Qt import (QWidget, QTimer) + +from calibre.gui2.tweak_book import editors, actions + +class LiveCSS(QWidget): + + def __init__(self, preview, parent=None): + QWidget.__init__(self, parent) + self.preview = preview + preview.refreshed.connect(self.update_data) + self.update_timer = QTimer(self) + self.update_timer.timeout.connect(self.update_data) + self.update_timer.setSingleShot(True) + + def clear(self): + pass # TODO: Implement this + + def show_data(self, editor_name, sourceline, tags): + if sourceline is None: + self.clear() + else: + pass # TODO: Do update + + @property + def current_name(self): + return self.preview.current_name + + @property + def is_visible(self): + return self.isVisible() + + def showEvent(self, ev): + self.update_timer.start() + actions['auto-reload-preview'].setEnabled(True) + return QWidget.showEvent(self, ev) + + def sync_to_editor(self, name): + self.start_update_timer() + + def update_data(self): + if not self.is_visible: + return + editor_name = self.current_name + ed = editors.get(editor_name, None) + if self.update_timer.isActive() or (ed is None and editor_name is not None): + return QTimer.singleShot(100, self.update_data) + if ed is not None: + sourceline, tags = ed.current_tag() + self.show_data(editor_name, sourceline, tags) + + def start_update_timer(self): + if self.is_visible: + self.update_timer.start(1000) + + def stop_update_timer(self): + self.update_timer.stop() + diff --git a/src/calibre/gui2/tweak_book/preview.py b/src/calibre/gui2/tweak_book/preview.py index 876033545b..5e0c7fb11f 100644 --- a/src/calibre/gui2/tweak_book/preview.py +++ b/src/calibre/gui2/tweak_book/preview.py @@ -435,6 +435,7 @@ class Preview(QWidget): split_requested = pyqtSignal(object, object, object) split_start_requested = pyqtSignal() link_clicked = pyqtSignal(object, object) + refreshed = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -558,6 +559,7 @@ class Preview(QWidget): self.view.setUrl(current_url) else: self.view.refresh() + self.refreshed.emit() def clear(self): self.view.clear() @@ -567,14 +569,26 @@ class Preview(QWidget): def is_visible(self): return actions['preview-dock'].isChecked() + @property + def live_css_is_visible(self): + try: + return actions['live-css-dock'].isChecked() + except KeyError: + return False + def start_refresh_timer(self): - if self.is_visible and actions['auto-reload-preview'].isChecked(): + if self.live_css_is_visible or (self.is_visible and actions['auto-reload-preview'].isChecked()): self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000) def stop_refresh_timer(self): self.refresh_timer.stop() def auto_reload_toggled(self, checked): + if self.live_css_is_visible and not actions['auto-reload-preview'].isChecked(): + actions['auto-reload-preview'].setChecked(True) + error_dialog(self, _('Cannot disable'), _( + 'Auto reloading of the preview panel cannot be disabled while the' + ' Live CSS panel is open.'), show=True) actions['auto-reload-preview'].setToolTip(_( 'Auto reload preview when text changes in editor') if not checked else _( 'Disable auto reload of preview')) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index c43084f54c..e882d78bf0 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -33,6 +33,7 @@ from calibre.gui2.tweak_book.spell import SpellCheck from calibre.gui2.tweak_book.search import SavedSearches from calibre.gui2.tweak_book.toc import TOCViewer from calibre.gui2.tweak_book.char_select import CharSelect +from calibre.gui2.tweak_book.live_css import LiveCSS from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions from calibre.gui2.tweak_book.editor.insert_resource import InsertImage from calibre.utils.icu import character_name @@ -595,6 +596,13 @@ class Main(MainWindow): d.setWidget(self.preview) self.addDockWidget(Qt.RightDockWidgetArea, d) + d = create(_('Live CSS'), 'live-css') + d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea) + self.live_css = LiveCSS(self.preview, parent=d) + d.setWidget(self.live_css) + self.addDockWidget(Qt.RightDockWidgetArea, d) + d.close() # Hidden by default + d = create(_('Check Book'), 'check-book') d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea) d.setWidget(self.check_book)