From 2cb161307c17e789eba42a7529b2f9f587dbef2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Silva?= Date: Thu, 25 Apr 2019 15:57:06 +0100 Subject: [PATCH] Improve side-by-side experience Includes multiple improvements to scroll syncing, preview re-render, layout separation, etc --- data/media/css/web/base.css | 2 +- data/ui/Preview.ui | 47 ++++++++++ uberwriter/inline_preview.py | 10 ++- uberwriter/preview_converter.py | 49 +++++++++++ uberwriter/previewer.py | 147 +++++++++++++++++++++++++++++++ uberwriter/search_and_replace.py | 26 +++--- uberwriter/stats_handler.py | 6 -- uberwriter/text_view.py | 8 +- uberwriter/web_view.py | 104 ++++++++++++++++++++++ uberwriter/web_view_scroller.py | 134 ---------------------------- uberwriter/window.py | 112 ++++------------------- 11 files changed, 393 insertions(+), 252 deletions(-) create mode 100644 data/ui/Preview.ui create mode 100644 uberwriter/preview_converter.py create mode 100644 uberwriter/previewer.py create mode 100644 uberwriter/web_view.py delete mode 100644 uberwriter/web_view_scroller.py diff --git a/data/media/css/web/base.css b/data/media/css/web/base.css index 059ba28..ae24b4a 100644 --- a/data/media/css/web/base.css +++ b/data/media/css/web/base.css @@ -59,7 +59,7 @@ body { word-wrap: break-word; max-width: 980px; margin: auto; - padding: 2em; + padding: 4em; } a { diff --git a/data/ui/Preview.ui b/data/ui/Preview.ui new file mode 100644 index 0000000..7b8c934 --- /dev/null +++ b/data/ui/Preview.ui @@ -0,0 +1,47 @@ + + + + + + True + False + pan-down-symbolic + 2 + + + True + False + vertical + + + + + + True + False + crossfade + 750 + True + + + Full-Width + True + True + True + Switch Preview Mode + end + pan-down + right + True + + + + + False + True + end + 1 + + + + diff --git a/uberwriter/inline_preview.py b/uberwriter/inline_preview.py index ae5f9d9..75dbaee 100644 --- a/uberwriter/inline_preview.py +++ b/uberwriter/inline_preview.py @@ -28,9 +28,11 @@ from urllib.parse import unquote import gi +from uberwriter.text_view_markup_handler import MarkupHandler + gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gdk, GdkPixbuf, GObject -from uberwriter import latex_to_PNG, text_view_markup_handler +from gi.repository import Gtk, Gdk, GdkPixbuf +from uberwriter import latex_to_PNG from uberwriter.settings import Settings from uberwriter.fix_table import FixTable @@ -360,8 +362,8 @@ class InlinePreview: text = self.text_buffer.get_text(start_iter, end_iter, False) - math = text_view_markup_handler.regex["MATH"] - link = text_view_markup_handler.regex["LINK"] + math = MarkupHandler.regex["MATH"] + link = MarkupHandler.regex["LINK"] footnote = re.compile(r'\[\^([^\s]+?)\]') image = re.compile(r"!\[(.*?)\]\((.+?)\)") diff --git a/uberwriter/preview_converter.py b/uberwriter/preview_converter.py new file mode 100644 index 0000000..07fba58 --- /dev/null +++ b/uberwriter/preview_converter.py @@ -0,0 +1,49 @@ +from queue import Queue +from threading import Thread + +from gi.repository import GLib + +from uberwriter import helpers +from uberwriter.theme import Theme + + +class PreviewConverter: + """Converts markdown to html using a background thread.""" + + def __init__(self): + super().__init__() + + self.queue = Queue() + worker = Thread(target=self.__do_convert, name="preview-converter") + worker.daemon = True + worker.start() + + def convert(self, text, callback, *user_data): + """Converts text to html, calling callback when done. + + The callback argument contains the result.""" + + self.queue.put((text, callback, user_data)) + + def stop(self): + """Stops the background worker. PreviewConverter shouldn't be used after this.""" + + self.queue.put((None, None)) + + def __do_convert(self): + while True: + while True: + (text, callback, user_data) = self.queue.get() + if text is None and callback is None: + return + if self.queue.empty(): + break + + args = ['--standalone', + '--mathjax', + '--css=' + Theme.get_current().web_css_path, + '--lua-filter=' + helpers.get_script_path('relative_to_absolute.lua'), + '--lua-filter=' + helpers.get_script_path('task-list.lua')] + text = helpers.pandoc_convert(text, to="html5", args=args) + + GLib.idle_add(callback, text, *user_data) diff --git a/uberwriter/previewer.py b/uberwriter/previewer.py new file mode 100644 index 0000000..309db20 --- /dev/null +++ b/uberwriter/previewer.py @@ -0,0 +1,147 @@ +import math +import webbrowser +from enum import auto, IntEnum + +import gi + +from uberwriter.helpers import get_builder + +gi.require_version('WebKit2', '4.0') +from gi.repository import WebKit2 + +from uberwriter.preview_converter import PreviewConverter +from uberwriter.settings import Settings +from uberwriter.web_view import WebView + + +class Step(IntEnum): + CONVERT_HTML = auto() + LOAD_WEBVIEW = auto() + RENDER = auto() + + +class Previewer: + def __init__(self, content, editor, text_view): + self.content = content + self.editor = editor + self.text_view = text_view + + self.web_view = None + self.web_view_pending_html = None + + builder = get_builder("Preview") + self.preview = builder.get_object("preview") + self.preview_mode_button = builder.get_object("preview_mode_button") + self.preview_mode_button.get_style_context().add_class('toggle-button') + + self.preview_converter = PreviewConverter() + + self.web_scroll_handler_id = None + self.text_scroll_handler_id = None + self.text_changed_handler_id = None + + self.settings = Settings.new() + + self.loading = False + self.showing = False + + def show(self): + self.__show() + + def reload(self): + if self.showing: + self.show() + + def __show(self, html=None, step=Step.CONVERT_HTML): + if step == Step.CONVERT_HTML: + # First step: convert text to HTML. + buf = self.text_view.get_buffer() + self.preview_converter.convert( + buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False), + self.__show, Step.LOAD_WEBVIEW) + + elif step == Step.LOAD_WEBVIEW: + # Second step: load HTML. + self.loading = True + + if not self.web_view: + self.web_view = WebView() + self.web_view.get_settings().set_allow_universal_access_from_file_urls(True) + + # Show preview once the load is finished + self.web_view.connect("load-changed", self.on_load_changed) + + # All links will be opened in default browser, but local files are opened in apps. + self.web_view.connect("decide-policy", self.on_click_link) + + if self.web_view.is_loading(): + self.web_view_pending_html = html + else: + self.web_view.load_html(html, 'file://localhost/') + + elif step == Step.RENDER: + # Last and one-time step: show the web view. + if self.showing: + return + self.showing = True + + if self.settings.get_boolean("preview-side-by-side"): + self.content.set_size_request(self.text_view.get_min_width() * 2, -1) + self.web_view.set_scroll_scale(self.text_view.get_scroll_scale()) + self.web_scroll_handler_id = \ + self.web_view.connect("scroll-scale-changed", self.on_web_view_scrolled) + self.text_scroll_handler_id = \ + self.text_view.connect("scroll-scale-changed", self.on_text_view_scrolled) + self.text_changed_handler_id = \ + self.text_view.get_buffer().connect("changed", self.__show) + else: + self.content.remove(self.editor) + + self.preview.pack_start(self.web_view, True, True, 0) + self.content.add(self.preview) + self.web_view.show() + + def hide(self): + if self.showing: + self.showing = False + + if self.settings.get_boolean("preview-side-by-side"): + self.content.set_size_request(-1, -1) + self.web_view.disconnect(self.web_scroll_handler_id) + self.text_view.disconnect(self.text_scroll_handler_id) + self.text_view.get_buffer().disconnect(self.text_changed_handler_id) + else: + self.content.add(self.editor) + + self.content.remove(self.preview) + self.preview.remove(self.web_view) + + if self.loading: + self.loading = False + + self.web_view.destroy() + self.web_view = None + + def on_load_changed(self, _web_view, event): + if event == WebKit2.LoadEvent.FINISHED: + self.loading = False + if self.web_view_pending_html: + self.__show(html=self.web_view_pending_html, step=Step.LOAD_WEBVIEW) + self.web_view_pending_html = None + else: + self.__show(step=Step.RENDER) + + def on_text_view_scrolled(self, _text_view, scale): + if not math.isclose(scale, self.web_view.get_scroll_scale(), rel_tol=1e-5): + self.web_view.set_scroll_scale(scale) + + def on_web_view_scrolled(self, _web_view, scale): + if not math.isclose(scale, self.text_view.get_scroll_scale(), rel_tol=1e-5): + self.text_view.set_scroll_scale(scale) + + @staticmethod + def on_click_link(web_view, decision, _decision_type): + if web_view.get_uri().startswith(("http://", "https://", "www.")): + webbrowser.open(web_view.get_uri()) + decision.ignore() + return True diff --git a/uberwriter/search_and_replace.py b/uberwriter/search_and_replace.py index 24fb1d9..462db2e 100644 --- a/uberwriter/search_and_replace.py +++ b/uberwriter/search_and_replace.py @@ -33,32 +33,32 @@ class SearchAndReplace: uberwriter """ - def __init__(self, parentwindow, textview): + def __init__(self, parentwindow, textview, builder): self.parentwindow = parentwindow self.textview = textview self.textbuffer = textview.get_buffer() - self.box = parentwindow.builder.get_object("searchbar_placeholder") + self.box = builder.get_object("searchbar_placeholder") self.box.set_reveal_child(False) - self.searchbar = parentwindow.builder.get_object("searchbar") - self.searchentry = parentwindow.builder.get_object("searchentrybox") + self.searchbar = builder.get_object("searchbar") + self.searchentry = builder.get_object("searchentrybox") self.searchentry.connect('changed', self.search) self.searchentry.connect('activate', self.scrolltonext) self.searchentry.connect('key-press-event', self.key_pressed) - self.open_replace_button = parentwindow.builder.get_object("replace") + self.open_replace_button = builder.get_object("replace") self.open_replace_button.connect("toggled", self.toggle_replace) - self.nextbutton = parentwindow.builder.get_object("next_result") - self.prevbutton = parentwindow.builder.get_object("previous_result") - self.regexbutton = parentwindow.builder.get_object("regex") - self.casesensitivebutton = parentwindow.builder.get_object("case_sensitive") + self.nextbutton = builder.get_object("next_result") + self.prevbutton = builder.get_object("previous_result") + self.regexbutton = builder.get_object("regex") + self.casesensitivebutton = builder.get_object("case_sensitive") - self.replacebox = parentwindow.builder.get_object("replace_placeholder") + self.replacebox = builder.get_object("replace_placeholder") self.replacebox.set_reveal_child(False) - self.replace_one_button = parentwindow.builder.get_object("replace_one") - self.replace_all_button = parentwindow.builder.get_object("replace_all") - self.replaceentry = parentwindow.builder.get_object("replaceentrybox") + self.replace_one_button = builder.get_object("replace_one") + self.replace_all_button = builder.get_object("replace_all") + self.replaceentry = builder.get_object("replaceentrybox") self.replace_all_button.connect('clicked', self.replace_all) self.replace_one_button.connect('clicked', self.replace_clicked) diff --git a/uberwriter/stats_handler.py b/uberwriter/stats_handler.py index 8fce782..83c4d89 100644 --- a/uberwriter/stats_handler.py +++ b/uberwriter/stats_handler.py @@ -1,13 +1,7 @@ -import math -import re from gettext import gettext as _ -from queue import Queue -from threading import Thread from gi.repository import GLib, Gio, Gtk -from uberwriter import helpers -from uberwriter.helpers import get_builder from uberwriter.settings import Settings from uberwriter.stats_counter import StatsCounter diff --git a/uberwriter/text_view.py b/uberwriter/text_view.py index 7c38176..abbd6ec 100644 --- a/uberwriter/text_view.py +++ b/uberwriter/text_view.py @@ -37,7 +37,8 @@ class TextView(Gtk.TextView): 'insert-header': (GObject.SignalFlags.ACTION, None, ()), 'insert-strikethrough': (GObject.SignalFlags.ACTION, None, ()), 'undo': (GObject.SignalFlags.ACTION, None, ()), - 'redo': (GObject.SignalFlags.ACTION, None, ()) + 'redo': (GObject.SignalFlags.ACTION, None, ()), + 'scroll-scale-changed': (GObject.SIGNAL_RUN_LAST, None, (float,)), } font_sizes = [18, 17, 16, 15, 14] # Must match CSS selectors in gtk/base.css @@ -136,6 +137,8 @@ class TextView(Gtk.TextView): if parent: parent.set_size_request(self.get_min_width(), 500) self.scroller = TextViewScroller(self, parent) + parent.get_vadjustment().connect("changed", self.on_scroll_scale_changed) + parent.get_vadjustment().connect("value-changed", self.on_scroll_scale_changed) else: self.scroller = None @@ -152,6 +155,9 @@ class TextView(Gtk.TextView): self.markup.apply() return False + def on_scroll_scale_changed(self, *_): + self.emit("scroll-scale-changed", self.get_scroll_scale()) + def set_focus_mode(self, focus_mode): """Toggle focus mode. diff --git a/uberwriter/web_view.py b/uberwriter/web_view.py new file mode 100644 index 0000000..ca69c7a --- /dev/null +++ b/uberwriter/web_view.py @@ -0,0 +1,104 @@ +import gi + +gi.require_version('WebKit2', '4.0') +from gi.repository import WebKit2, GLib, GObject + + +class WebView(WebKit2.WebView): + """A WebView that provides read/write access to scroll. + + It does so using JavaScript, by continuously monitoring it while loaded. + The alternative is using a WebExtension and C-bindings (see reference), but that is more + complicated implementation-wise, as well as build-wise until we start building with Meson. + + Reference: https://github.com/aperezdc/webkit2gtk-python-webextension-example + """ + + GET_SCROLL_SCALE_JS = """ +e = document.documentElement; +e.scrollHeight > e.clientHeight ? e.scrollTop / (e.scrollHeight - e.clientHeight) : 0; +""" + + SET_SCROLL_SCALE_JS = """ +scale = {:.16f}; +e = document.documentElement; +e.scrollTop = (e.scrollHeight - e.clientHeight) * scale; +""" + + __gsignals__ = { + "scroll-scale-changed": (GObject.SIGNAL_RUN_LAST, None, (float,)), + } + + def __init__(self): + super().__init__() + + self.connect("load-changed", self.on_load_changed) + self.connect("load-failed", self.on_load_failed) + self.connect("destroy", self.on_destroy) + + self.scroll_scale = 0.0 + self.pending_scroll_scale = None + + self.state_loaded = False + self.state_load_failed = False + self.state_waiting = False + + self.timeout_id = None + + def get_scroll_scale(self): + return self.scroll_scale + + def set_scroll_scale(self, scale): + self.pending_scroll_scale = scale + self.state_loop() + + def on_load_changed(self, _web_view, event): + self.state_loaded = event >= WebKit2.LoadEvent.COMMITTED and not self.state_load_failed + self.state_load_failed = False + self.pending_scroll_scale = self.scroll_scale + self.state_loop() + + def on_load_failed(self, _web_view, _event): + self.state_loaded = False + self.state_load_failed = True + self.state_loop() + + def on_destroy(self, _widget): + self.state_loaded = False + self.state_loop() + + def read_scroll_scale(self): + self.state_waiting = True + self.run_javascript( + self.GET_SCROLL_SCALE_JS, None, self.sync_scroll_scale) + + def write_scroll_scale(self, scroll_scale): + self.run_javascript( + self.SET_SCROLL_SCALE_JS.format(scroll_scale), None, None) + + def sync_scroll_scale(self, _web_view, result): + self.state_waiting = False + result = self.run_javascript_finish(result) + self.state_loop(result.get_js_value().to_double()) + + def state_loop(self, scroll_scale=None, delay=16): # 16ms ~ 60hz + # Remove any pending callbacks + if self.timeout_id: + GLib.source_remove(self.timeout_id) + self.timeout_id = None + + # Set scroll scale if specified, and the state is not dirty + if scroll_scale not in (None, self.scroll_scale): + self.scroll_scale = scroll_scale + self.emit("scroll-scale-changed", self.scroll_scale) + + # Handle the current state + if not self.state_loaded or self.state_load_failed or self.state_waiting: + return + if self.pending_scroll_scale: + self.write_scroll_scale(self.pending_scroll_scale) + self.pending_scroll_scale = None + if delay > 0: + self.timeout_id = GLib.timeout_add(delay, self.state_loop, None, 0) + else: + self.read_scroll_scale() diff --git a/uberwriter/web_view_scroller.py b/uberwriter/web_view_scroller.py deleted file mode 100644 index 2c757c0..0000000 --- a/uberwriter/web_view_scroller.py +++ /dev/null @@ -1,134 +0,0 @@ -import gi - -gi.require_version('Gtk', '3.0') -gi.require_version('WebKit2', '4.0') -from gi.repository import WebKit2, GLib - - -class WebViewScroller: - """Provides read/write scrolling functionality to a WebView. - - It does so using JavaScript, by continuously monitoring it while loaded and focused. - The alternative is using a WebExtension and C-bindings (see reference), but that is more - complicated implementation-wise, and build-wise, at least we start building with Meson. - - Reference: https://github.com/aperezdc/webkit2gtk-python-webextension-example - """ - - SETUP_SROLL_SCALE_JS = """ -const e = document.documentElement; -function get_scroll_scale() { - if (document.readyState !== "complete" || e.clientHeight === 0) { - return null; - } else if (e.scrollHeight <= e.clientHeight) { - return 0; - } else { - return e.scrollTop / (e.scrollHeight - e.clientHeight); - } -} -function set_scroll_scale(scale) { - if (document.readyState !== "complete") { - window.addEventListener("load", function() { set_scroll_scale(scale); }); - } else if (e.clientHeight === 0) { - window.addEventListener("resize", function() { set_scroll_scale(scale); }); - } else if (e.scrollHeight > e.clientHeight) { - e.scrollTop = (e.scrollHeight - e.clientHeight) * scale; - } - return get_scroll_scale(); -} -get_scroll_scale(); -""".strip() - - GET_SCROLL_SCALE_JS = "get_scroll_scale();" - - SET_SCROLL_SCALE_JS = "set_scroll_scale({:.16f});" - - def __init__(self, webview): - super().__init__() - - self.webview = webview - - self.webview.connect("focus-in-event", self.on_focus_changed) - self.webview.connect("focus-out-event", self.on_focus_changed) - self.webview.connect("load-changed", self.on_load_changed) - self.webview.connect("destroy", self.on_destroy) - - self.scroll_scale = 0 - - self.state_loaded = False - self.state_setup = False - self.state_focused = True - self.state_dirty = False - self.state_waiting = False - - self.timeout_id = None - - def get_scroll_scale(self): - return self.scroll_scale - - def set_scroll_scale(self, scale): - self.scroll_scale = scale - self.state_dirty = True - self.state_loop() - - def on_focus_changed(self, _webview, event): - self.state_focused = event.in_ - self.state_loop() - - def on_load_changed(self, _webview, event): - self.state_loaded = event == WebKit2.LoadEvent.FINISHED - self.state_loop() - - def on_destroy(self, _widget): - self.state_loaded = False - self.state_focused = False - self.state_loop() - self.webview = None - - def setup_scroll_state(self): - self.state_waiting = True - self.state_setup = True - self.webview.run_javascript( - self.SETUP_SROLL_SCALE_JS, None, self.sync_scroll_scale) - - def read_scroll_scale(self): - self.state_waiting = True - self.webview.run_javascript( - self.GET_SCROLL_SCALE_JS, None, self.sync_scroll_scale) - - def write_scroll_scale(self): - self.state_waiting = True - self.state_dirty = False - self.webview.run_javascript( - self.SET_SCROLL_SCALE_JS.format(self.scroll_scale), None, self.sync_scroll_scale) - - def sync_scroll_scale(self, _webview, result): - self.state_waiting = False - result = self.webview.run_javascript_finish(result) - self.state_loop(result.get_js_value().to_double()) - - def state_loop(self, scroll_scale=None, read_delay=500): - # Remove any pending callbacks - if self.timeout_id: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - # Set scroll scale if specified, and the state is not dirty - if scroll_scale is not None and not self.state_dirty: - self.scroll_scale = scroll_scale - - # Handle the current state - if self.state_waiting: - return - elif not self.state_loaded: - return - elif not self.state_setup: - self.setup_scroll_state() - elif self.state_dirty: - self.write_scroll_scale() - elif self.state_focused: - if read_delay > 0: - self.timeout_id = GLib.timeout_add(read_delay, self.state_loop, None, 0) - else: - self.read_scroll_scale() - diff --git a/uberwriter/window.py b/uberwriter/window.py index 0ee9b59..fbdfb40 100644 --- a/uberwriter/window.py +++ b/uberwriter/window.py @@ -19,20 +19,17 @@ import locale import logging import os import urllib -import webbrowser from gettext import gettext as _ import gi from uberwriter.export_dialog import Export +from uberwriter.previewer import Previewer from uberwriter.stats_handler import StatsHandler from uberwriter.text_view import TextView -from uberwriter.web_view_scroller import WebViewScroller gi.require_version('Gtk', '3.0') -gi.require_version('WebKit2', '4.0') # pylint: disable=wrong-import-position from gi.repository import Gtk, Gdk, GObject, GLib, Gio -from gi.repository import WebKit2 as WebKit import cairo @@ -74,8 +71,8 @@ class Window(Gtk.ApplicationWindow): title="Uberwriter") # Set UI - self.builder = get_builder('Window') - root = self.builder.get_object("FullscreenOverlay") + builder = get_builder('Window') + root = builder.get_object("FullscreenOverlay") root.connect('style-updated', self.apply_current_theme) self.connect("delete-event", self.on_delete_called) self.add(root) @@ -88,7 +85,7 @@ class Window(Gtk.ApplicationWindow): # Headerbars self.headerbar = headerbars.MainHeaderbar(app) self.set_titlebar(self.headerbar.hb_container) - self.fs_headerbar = headerbars.FullscreenHeaderbar(self.builder, app) + self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app) self.title_end = " – UberWriter" self.set_headerbar_title("New File" + self.title_end) @@ -101,9 +98,7 @@ class Window(Gtk.ApplicationWindow): self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) - self.content = self.builder.get_object('content') - - self.scrolled_window = self.builder.get_object('editor_scrolledwindow') + self.scrolled_window = builder.get_object('editor_scrolledwindow') self.scrolled_window.get_style_context().add_class('uberwriter-scrolled-window') # Setup text editor @@ -114,15 +109,16 @@ class Window(Gtk.ApplicationWindow): self.text_view.grab_focus() self.scrolled_window.add(self.text_view) - # Stats stats counter - self.stats_revealer = self.builder.get_object('editor_stats_revealer') - self.stats_button = self.builder.get_object('editor_stats_button') - self.stats_button.get_style_context().add_class('stats-button') + # Setup stats counter + self.stats_revealer = builder.get_object('editor_stats_revealer') + self.stats_button = builder.get_object('editor_stats_button') + self.stats_button.get_style_context().add_class('toggle-button') self.stats_handler = StatsHandler(self.stats_button, self.text_view) - # Setup preview webview - self.web_view = None - self.web_view_scroller = None + # Setup preview + content = builder.get_object('content') + editor = builder.get_object('editor') + self.previewer = Previewer(content, editor, self.text_view) # Setup header/stats bar hide after 3 seconds self.top_bottom_bars_visible = True @@ -145,8 +141,8 @@ class Window(Gtk.ApplicationWindow): ### # Sidebar initialization test ### - self.paned_window = self.builder.get_object("main_paned") - self.sidebar_box = self.builder.get_object("sidebar_box") + self.paned_window = builder.get_object("main_paned") + self.sidebar_box = builder.get_object("sidebar_box") self.sidebar = Sidebar(self) self.sidebar_box.hide() @@ -154,7 +150,7 @@ class Window(Gtk.ApplicationWindow): # Search and replace initialization # Same interface as Sidebar ;) ### - self.searchreplace = SearchAndReplace(self, self.text_view) + self.searchreplace = SearchAndReplace(self, self.text_view, builder) # Set current theme self.apply_current_theme() @@ -459,70 +455,14 @@ class Window(Gtk.ApplicationWindow): """ if state.get_boolean(): - self.show_web_view() + self.previewer.show() else: - self.show_text_editor() + self.previewer.hide() return True - def show_text_editor(self): - # Remove web view - if self.settings.get_boolean("preview-side-by-side"): - self.set_size_request(-1, -1) - self.content.remove(self.web_view) - else: - self.scrolled_window.remove(self.scrolled_window.get_child()) - self.scrolled_window.add(self.text_view) - self.text_view.show() - self.queue_draw() - - # Sync scroll between web view and text view - self.text_view.set_scroll_scale(self.web_view_scroller.get_scroll_scale()) - - # Destroy web view to clean up resources - self.web_view.destroy() - self.web_view = None - self.web_view_scroller = None - - def show_web_view(self, loaded=False): - if loaded: - # Sync scroll between text view and web view - self.web_view_scroller.set_scroll_scale(self.text_view.get_scroll_scale()) - - # Show web view - if self.settings.get_boolean("preview-side-by-side"): - self.content.add(self.web_view) - self.set_size_request(self.text_view.get_min_width() * 2, -1) - else: - self.scrolled_window.remove(self.scrolled_window.get_child()) - self.scrolled_window.add(self.web_view) - self.web_view.show() - self.queue_draw() - else: - args = ['--standalone', - '--mathjax', - '--css=' + Theme.get_current().web_css_path, - '--lua-filter=' + helpers.get_script_path('relative_to_absolute.lua'), - '--lua-filter=' + helpers.get_script_path('task-list.lua')] - output = helpers.pandoc_convert(self.text_view.get_text(), to="html5", args=args) - - if self.web_view is None: - self.web_view = WebKit.WebView() - self.web_view.get_settings().set_allow_universal_access_from_file_urls(True) - self.web_view_scroller = WebViewScroller(self.web_view) - - # Show preview once the load is finished - self.web_view.connect("load-changed", self.on_preview_load_change) - - # This saying that all links will be opened in default browser, \ - # but local files are opened in appropriate apps: - self.web_view.connect("decide-policy", self.on_click_link) - - self.web_view.load_html(output, 'file://localhost/') - def reload_preview(self): - if self.web_view: - self.show_web_view() + self.previewer.reload() def load_file(self, filename=None): """Open File from command line or open / open recent etc.""" @@ -701,17 +641,3 @@ class Window(Gtk.ApplicationWindow): self.filename = None base_path = "/" self.settings.set_value("open-file-path", GLib.Variant("s", base_path)) - - def on_preview_load_change(self, _web_view, event): - """swaps text editor with preview once the load is complete - """ - if event == WebKit.LoadEvent.FINISHED: - self.show_web_view(loaded=True) - - def on_click_link(self, web_view, decision, _decision_type): - """provide ability for self.webview to open links in default browser - """ - if web_view.get_uri().startswith(("http://", "https://", "www.")): - webbrowser.open(web_view.get_uri()) - decision.ignore() - return True # Don't let the event "bubble up"