forked from Mirrors/apostrophe
168 lines
5.8 KiB
Python
168 lines
5.8 KiB
Python
import math
|
|
import webbrowser
|
|
from enum import auto, IntEnum
|
|
|
|
import gi
|
|
|
|
from uberwriter.preview_renderer import PreviewRenderer
|
|
from uberwriter.settings import Settings
|
|
|
|
gi.require_version('WebKit2', '4.0')
|
|
from gi.repository import WebKit2, GLib, Gtk
|
|
|
|
from uberwriter.preview_converter import PreviewConverter
|
|
from uberwriter.preview_web_view import PreviewWebView
|
|
|
|
|
|
class Step(IntEnum):
|
|
CONVERT_HTML = auto()
|
|
LOAD_WEBVIEW = auto()
|
|
RENDER = auto()
|
|
|
|
|
|
class PreviewHandler:
|
|
"""Handles showing/hiding the preview, and allows the user to toggle between modes.
|
|
|
|
The rendering itself is handled by `PreviewRendered`. This class handles conversion/loading and
|
|
connects it all together (including synchronization, ie. text changes, scroll)."""
|
|
|
|
def __init__(self, window, content, editor, text_view):
|
|
self.text_view = text_view
|
|
|
|
self.web_view = None
|
|
self.web_view_pending_html = None
|
|
|
|
builder = Gtk.Builder()
|
|
builder.add_from_resource(
|
|
"/de/wolfvollprecht/UberWriter/ui/Preview.ui")
|
|
preview = builder.get_object("preview")
|
|
mode_button = builder.get_object("preview_mode_button")
|
|
self.mode_revealer = builder.get_object("preview_mode_revealer")
|
|
|
|
self.preview_converter = PreviewConverter()
|
|
self.preview_renderer = PreviewRenderer(
|
|
window, content, editor, text_view, preview, self.mode_revealer, mode_button)
|
|
|
|
window.connect("style-updated", self.reload)
|
|
|
|
self.text_changed_handler_id = None
|
|
|
|
self.settings = Settings.new()
|
|
self.web_scroll_handler_id = None
|
|
self.text_scroll_handler_id = None
|
|
|
|
self.loading = False
|
|
self.shown = False
|
|
|
|
def show(self):
|
|
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 = PreviewWebView()
|
|
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 step: show the preview. This is a one-time step.
|
|
if self.shown:
|
|
return
|
|
self.shown = True
|
|
|
|
self.text_changed_handler_id = \
|
|
self.text_view.get_buffer().connect("changed", self.__show)
|
|
|
|
GLib.idle_add(self.web_view.set_scroll_scale, self.text_view.get_scroll_scale())
|
|
|
|
self.preview_renderer.show(self.web_view)
|
|
|
|
if self.settings.get_boolean("sync-scroll"):
|
|
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)
|
|
|
|
def reload(self, *_widget, reshow=False):
|
|
if self.shown:
|
|
if reshow:
|
|
self.hide()
|
|
self.show()
|
|
|
|
def hide(self):
|
|
if self.shown:
|
|
self.shown = False
|
|
|
|
self.text_view.get_buffer().disconnect(self.text_changed_handler_id)
|
|
|
|
GLib.idle_add(self.text_view.set_scroll_scale, self.web_view.get_scroll_scale())
|
|
|
|
self.preview_renderer.hide(self.web_view)
|
|
|
|
if self.text_scroll_handler_id:
|
|
self.text_view.disconnect(self.text_scroll_handler_id)
|
|
self.text_scroll_handler_id = None
|
|
if self.web_scroll_handler_id:
|
|
self.web_view.disconnect(self.web_scroll_handler_id)
|
|
self.web_scroll_handler_id = None
|
|
|
|
if self.loading:
|
|
self.loading = False
|
|
|
|
self.web_view.destroy()
|
|
self.web_view = None
|
|
|
|
def update_preview_mode(self):
|
|
self.preview_renderer.update_mode(self.web_view)
|
|
|
|
def get_top_bottom_bar_revealers(self):
|
|
if self.shown and not self.preview_renderer.window:
|
|
return [self.mode_revealer]
|
|
else:
|
|
return []
|
|
|
|
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 self.shown and not math.isclose(scale, self.web_view.get_scroll_scale(), rel_tol=1e-4):
|
|
self.web_view.set_scroll_scale(scale)
|
|
|
|
def on_web_view_scrolled(self, _web_view, scale):
|
|
if self.shown and self.text_view.get_mapped() and \
|
|
not math.isclose(scale, self.text_view.get_scroll_scale(), rel_tol=1e-4):
|
|
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
|