diff --git a/data/de.wolfvollprecht.UberWriter.gschema.xml b/data/de.wolfvollprecht.UberWriter.gschema.xml
index 7ac4dbd..c9e45ec 100644
--- a/data/de.wolfvollprecht.UberWriter.gschema.xml
+++ b/data/de.wolfvollprecht.UberWriter.gschema.xml
@@ -10,6 +10,13 @@
+
+
+
+
+
+
+
@@ -76,11 +83,11 @@
Maximum number of characters per line within the editor.
-
- false
- Side-by-side preview
+
+ "full-width"
+ Preview mode
- Show the preview side by side, instead of full-width.
+ How to display the preview.
diff --git a/data/media/css/gtk/base.css b/data/media/css/gtk/base.css
index 41323d8..5d2711a 100644
--- a/data/media/css/gtk/base.css
+++ b/data/media/css/gtk/base.css
@@ -96,7 +96,7 @@
padding: 0;
}
-.stats-button {
+.toggle-button {
color: alpha(@theme_fg_color, 0.6);
background-color: @theme_base_color;
text-shadow: inherit;
@@ -116,8 +116,8 @@
transition: 100ms ease-in;
}
-.stats-button :hover,
-.stats-button :checked {
+.toggle-button:hover,
+.toggle-button:checked {
color: @theme_fg_color;
background-color: mix(@theme_base_color, @theme_bg_color, 0.5);
}
diff --git a/data/ui/Preview.ui b/data/ui/Preview.ui
index 7b8c934..a084329 100644
--- a/data/ui/Preview.ui
+++ b/data/ui/Preview.ui
@@ -11,6 +11,7 @@
diff --git a/data/ui/Window.ui b/data/ui/Window.ui
index fc9c411..7f6e72c 100644
--- a/data/ui/Window.ui
+++ b/data/ui/Window.ui
@@ -115,6 +115,9 @@
pan-down
right
True
+
diff --git a/uberwriter/application.py b/uberwriter/application.py
index 4a0f34c..62e38f1 100644
--- a/uberwriter/application.py
+++ b/uberwriter/application.py
@@ -120,10 +120,18 @@ class Application(Gtk.Application):
stat_default = self.settings.get_string("stat-default")
action = Gio.SimpleAction.new_stateful(
- "stat_default", GLib.VariantType.new('s'), GLib.Variant.new_string(stat_default))
+ "stat_default", GLib.VariantType.new("s"), GLib.Variant.new_string(stat_default))
action.connect("activate", self.on_stat_default)
self.add_action(action)
+ # Preview Menu
+
+ preview_mode = self.settings.get_string("preview-mode")
+ action = Gio.SimpleAction.new_stateful(
+ "preview_mode", GLib.VariantType.new("s"), GLib.Variant.new_string(preview_mode))
+ action.connect("activate", self.on_preview_mode)
+ self.add_action(action)
+
# Shortcuts
# TODO: be aware that a couple of shortcuts are defined in base.css
@@ -180,6 +188,8 @@ class Application(Gtk.Application):
self.window.reload_preview()
elif key == "stat-default":
self.window.update_default_stat()
+ elif key == "preview-mode":
+ self.window.update_preview_mode()
def on_new(self, _action, _value):
self.window.new_document()
@@ -250,6 +260,10 @@ class Application(Gtk.Application):
action.set_state(value)
self.settings.set_string("stat-default", value.get_string())
+ def on_preview_mode(self, action, value):
+ action.set_state(value)
+ self.settings.set_string("preview-mode", value.get_string())
+
# ~ if __name__ == "__main__":
# ~ app = Application()
# ~ app.run(sys.argv)
diff --git a/uberwriter/previewer.py b/uberwriter/preview_handler.py
similarity index 58%
rename from uberwriter/previewer.py
rename to uberwriter/preview_handler.py
index 309db20..cf9afa3 100644
--- a/uberwriter/previewer.py
+++ b/uberwriter/preview_handler.py
@@ -5,12 +5,12 @@ from enum import auto, IntEnum
import gi
from uberwriter.helpers import get_builder
+from uberwriter.preview_renderer import PreviewRenderer
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
@@ -20,38 +20,31 @@ class Step(IntEnum):
RENDER = auto()
-class Previewer:
- def __init__(self, content, editor, text_view):
- self.content = content
- self.editor = editor
+class PreviewHandler:
+ def __init__(self, window, content, editor, text_view):
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')
+ preview = builder.get_object("preview")
+ mode_button = builder.get_object("preview_mode_button")
self.preview_converter = PreviewConverter()
+ self.preview_renderer = PreviewRenderer(
+ window, content, editor, text_view, preview, mode_button)
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
+ self.shown = 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.
@@ -80,41 +73,37 @@ class Previewer:
self.web_view.load_html(html, 'file://localhost/')
elif step == Step.RENDER:
- # Last and one-time step: show the web view.
- if self.showing:
+ # Last and one-time step: show the preview.
+ if self.shown:
return
- self.showing = True
+ self.shown = 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.web_view.set_scroll_scale(self.text_view.get_scroll_scale())
- self.preview.pack_start(self.web_view, True, True, 0)
- self.content.add(self.preview)
- self.web_view.show()
+ self.text_changed_handler_id =\
+ self.text_view.get_buffer().connect("changed", self.__show)
+ 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.preview_renderer.show(self.web_view)
+
+ def reload(self):
+ if self.shown:
+ self.show()
def hide(self):
- if self.showing:
- self.showing = False
+ if self.shown:
+ self.shown = 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.text_view.set_scroll_scale(self.web_view.get_scroll_scale())
- self.content.remove(self.preview)
- self.preview.remove(self.web_view)
+ self.text_view.get_buffer().disconnect(self.text_changed_handler_id)
+ self.text_view.disconnect(self.text_scroll_handler_id)
+ self.web_view.disconnect(self.web_scroll_handler_id)
+
+ self.preview_renderer.hide(self.web_view)
if self.loading:
self.loading = False
@@ -122,6 +111,9 @@ class Previewer:
self.web_view.destroy()
self.web_view = None
+ def update_preview_mode(self):
+ self.preview_renderer.update_mode(self.web_view)
+
def on_load_changed(self, _web_view, event):
if event == WebKit2.LoadEvent.FINISHED:
self.loading = False
@@ -132,11 +124,11 @@ class Previewer:
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):
+ if self.shown and 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):
+ if self.shown and not math.isclose(scale, self.text_view.get_scroll_scale(), rel_tol=1e-5):
self.text_view.set_scroll_scale(scale)
@staticmethod
diff --git a/uberwriter/preview_renderer.py b/uberwriter/preview_renderer.py
new file mode 100644
index 0000000..5283cac
--- /dev/null
+++ b/uberwriter/preview_renderer.py
@@ -0,0 +1,127 @@
+from gettext import gettext as _
+
+from gi.repository import Gtk, Gio, GLib
+
+from uberwriter.settings import Settings
+
+
+class PreviewRenderer:
+ """Renders the preview according to the user selected mode."""
+
+ # Must match the order/index defined in gschema.xml
+ FULL_WIDTH = 0
+ HALF_WIDTH = 1
+ HALF_HEIGHT = 2
+ WINDOWED = 3
+
+ def __init__(self, main_window, content, editor, text_view, preview, mode_button):
+ self.content = content
+ self.editor = editor
+ self.text_view = text_view
+ self.preview = preview
+ self.mode_button = mode_button
+ self.mode_button.connect("clicked", self.show_mode_popover)
+ self.popover = None
+ self.settings = Settings.new()
+ self.main_window = main_window
+ self.window = None
+ self.mode = self.settings.get_enum("preview-mode")
+
+ def show(self, web_view):
+ self.preview.pack_start(web_view, True, True, 0)
+
+ if self.mode == self.FULL_WIDTH:
+ self.content.remove(self.editor)
+ self.content.add(self.preview)
+
+ elif self.mode == self.HALF_WIDTH:
+ self.content.set_orientation(Gtk.Orientation.HORIZONTAL)
+ self.content.set_size_request(self.text_view.get_min_width() * 2, -1)
+ self.content.add(self.preview)
+
+ elif self.mode == self.HALF_HEIGHT:
+ self.content.set_orientation(Gtk.Orientation.VERTICAL)
+ self.content.set_size_request(-1, 800)
+ self.content.add(self.preview)
+
+ elif self.mode == self.WINDOWED:
+ self.window = Gtk.Window(title=_("Preview"))
+ self.window.set_application(self.main_window.get_application())
+ self.window.set_default_size(
+ self.main_window.get_allocated_width(), self.main_window.get_allocated_height())
+ self.window.set_transient_for(self.main_window)
+ self.window.set_modal(False)
+ self.window.add(self.preview)
+ self.window.connect("delete-event", self.on_window_closed)
+ self.window.show()
+
+ else:
+ raise ValueError("Unknown preview mode {}".format(self.mode))
+
+ web_view.show()
+
+ def hide(self, web_view):
+ if self.mode == self.FULL_WIDTH:
+ self.content.remove(self.preview)
+ self.content.add(self.editor)
+
+ elif self.mode == self.HALF_WIDTH or self.mode == self.HALF_HEIGHT:
+ self.content.remove(self.preview)
+ self.content.set_size_request(-1, -1)
+
+ elif self.mode == self.WINDOWED:
+ self.window.remove(self.preview)
+ self.window.destroy()
+ self.window = None
+
+ else:
+ raise ValueError("Unknown preview mode {}".format(self.mode))
+
+ self.preview.remove(web_view)
+
+ def update_mode(self, web_view):
+ mode = self.settings.get_enum("preview-mode")
+ if mode != self.mode:
+ if web_view:
+ self.hide(web_view)
+ self.mode = mode
+ self.show(web_view)
+ text = self.get_text_for_preview_mode(self.mode)
+ self.mode_button.set_label(text)
+ if self.popover:
+ self.popover.popdown()
+
+ def show_mode_popover(self, _button):
+ self.mode_button.set_state_flags(Gtk.StateFlags.CHECKED, False)
+
+ menu = Gio.Menu()
+ modes = self.settings.props.settings_schema.get_key("preview-mode").get_range()[1]
+ for i, mode in enumerate(modes):
+ menu_item = Gio.MenuItem.new(self.get_text_for_preview_mode(i), None)
+ menu_item.set_action_and_target_value("app.preview_mode", GLib.Variant.new_string(mode))
+ menu.append_item(menu_item)
+ self.popover = Gtk.Popover.new_from_model(self.mode_button, menu)
+ self.popover.connect('closed', self.on_popover_closed)
+ self.popover.popup()
+
+ def on_popover_closed(self, _popover):
+ self.mode_button.unset_state_flags(Gtk.StateFlags.CHECKED)
+
+ self.popover = None
+ self.text_view.grab_focus()
+
+ def on_window_closed(self, window, _event):
+ preview_action = window.get_application().lookup_action("preview")
+ preview_action.change_state(GLib.Variant.new_boolean(False))
+
+ def get_text_for_preview_mode(self, mode):
+ if mode == self.FULL_WIDTH:
+ return _("Full-Width")
+ elif mode == self.HALF_WIDTH:
+ return _("Half-Width")
+ elif mode == self.HALF_HEIGHT:
+ return _("Half-Height")
+ elif mode == self.WINDOWED:
+ return _("Windowed")
+ else:
+ raise ValueError("Unknown preview mode {}".format(mode))
diff --git a/uberwriter/stats_handler.py b/uberwriter/stats_handler.py
index 3129d85..1709366 100644
--- a/uberwriter/stats_handler.py
+++ b/uberwriter/stats_handler.py
@@ -9,6 +9,13 @@ from uberwriter.stats_counter import StatsCounter
class StatsHandler:
"""Shows a default statistic on the stats button, and allows the user to toggle which one."""
+ # Must match the order/index defined in gschema.xml
+ CHARACTERS = 0
+ WORDS = 1
+ SENTENCES = 2
+ PARAGRAPHS = 3
+ READ_TIME = 4
+
def __init__(self, stats_button, text_view):
super().__init__()
@@ -28,7 +35,6 @@ class StatsHandler:
self.read_time = (0, 0, 0)
self.settings = Settings.new()
- self.default_stat = self.settings.get_enum("stat-default")
self.stats_counter = StatsCounter()
@@ -59,15 +65,15 @@ class StatsHandler:
self.update_stats)
def get_text_for_stat(self, stat):
- if stat == 0:
+ if stat == self.CHARACTERS:
return _("{:n} Characters").format(self.characters)
- elif stat == 1:
+ elif stat == self.WORDS:
return _("{:n} Words").format(self.words)
- elif stat == 2:
+ elif stat == self.SENTENCES:
return _("{:n} Sentences").format(self.sentences)
- elif stat == 3:
+ elif stat == self.PARAGRAPHS:
return _("{:n} Paragraphs").format(self.paragraphs)
- elif stat == 4:
+ elif stat == self.READ_TIME:
return _("{:d}:{:02d}:{:02d} Read Time").format(*self.read_time)
else:
raise ValueError("Unknown stat {}".format(stat))
diff --git a/uberwriter/web_view.py b/uberwriter/web_view.py
index ca69c7a..cdfb9d5 100644
--- a/uberwriter/web_view.py
+++ b/uberwriter/web_view.py
@@ -16,7 +16,7 @@ class WebView(WebKit2.WebView):
GET_SCROLL_SCALE_JS = """
e = document.documentElement;
-e.scrollHeight > e.clientHeight ? e.scrollTop / (e.scrollHeight - e.clientHeight) : 0;
+e.scrollHeight > e.clientHeight ? e.scrollTop / (e.scrollHeight - e.clientHeight) : -1;
"""
SET_SCROLL_SCALE_JS = """
@@ -37,10 +37,10 @@ e.scrollTop = (e.scrollHeight - e.clientHeight) * scale;
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_dirty = False
self.state_waiting = False
self.timeout_id = None
@@ -49,13 +49,13 @@ e.scrollTop = (e.scrollHeight - e.clientHeight) * scale;
return self.scroll_scale
def set_scroll_scale(self, scale):
- self.pending_scroll_scale = scale
+ self.state_dirty = True
+ self.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):
@@ -72,9 +72,10 @@ e.scrollTop = (e.scrollHeight - e.clientHeight) * scale;
self.run_javascript(
self.GET_SCROLL_SCALE_JS, None, self.sync_scroll_scale)
- def write_scroll_scale(self, scroll_scale):
+ def write_scroll_scale(self):
+ self.state_dirty = False
self.run_javascript(
- self.SET_SCROLL_SCALE_JS.format(scroll_scale), None, None)
+ self.SET_SCROLL_SCALE_JS.format(self.scroll_scale), None, None)
def sync_scroll_scale(self, _web_view, result):
self.state_waiting = False
@@ -88,16 +89,15 @@ e.scrollTop = (e.scrollHeight - e.clientHeight) * scale;
self.timeout_id = None
# Set scroll scale if specified, and the state is not dirty
- if scroll_scale not in (None, self.scroll_scale):
+ if scroll_scale not in (None, -1, 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 self.state_dirty:
+ self.write_scroll_scale()
if delay > 0:
self.timeout_id = GLib.timeout_add(delay, self.state_loop, None, 0)
else:
diff --git a/uberwriter/window.py b/uberwriter/window.py
index fbdfb40..0f44e68 100644
--- a/uberwriter/window.py
+++ b/uberwriter/window.py
@@ -15,7 +15,6 @@
# END LICENSE
import codecs
-import locale
import logging
import os
import urllib
@@ -24,7 +23,7 @@ from gettext import gettext as _
import gi
from uberwriter.export_dialog import Export
-from uberwriter.previewer import Previewer
+from uberwriter.preview_handler import PreviewHandler
from uberwriter.stats_handler import StatsHandler
from uberwriter.text_view import TextView
@@ -112,13 +111,12 @@ class Window(Gtk.ApplicationWindow):
# 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
content = builder.get_object('content')
editor = builder.get_object('editor')
- self.previewer = Previewer(content, editor, self.text_view)
+ self.preview_handler = PreviewHandler(self, content, editor, self.text_view)
# Setup header/stats bar hide after 3 seconds
self.top_bottom_bars_visible = True
@@ -419,6 +417,9 @@ class Window(Gtk.ApplicationWindow):
def update_default_stat(self):
self.stats_handler.update_default_stat()
+ def update_preview_mode(self):
+ self.preview_handler.update_preview_mode()
+
def menu_toggle_sidebar(self, _widget=None):
"""WIP
"""
@@ -455,14 +456,14 @@ class Window(Gtk.ApplicationWindow):
"""
if state.get_boolean():
- self.previewer.show()
+ self.preview_handler.show()
else:
- self.previewer.hide()
+ self.preview_handler.hide()
return True
def reload_preview(self):
- self.previewer.reload()
+ self.preview_handler.reload()
def load_file(self, filename=None):
"""Open File from command line or open / open recent etc."""