using Gtk; using Adw; using GtkCommonMark; namespace Publicate.Editors { public class MarkdownEditor : Box, EditorWidget, Savable { private Box leaflet; // For now, a box private GtkCommonMark.MarkdownView markdown_view; private Ppub.Publication publication; private GtkSource.View text_view; private GtkSource.Buffer source_buffer; private GtkSource.LanguageManager language_manager; private ScrolledWindow source_scroller; private ScrolledWindow markdown_scroller; private ViewerWindow window; private TabPage page; public Adw.TabPage tab_page { get { return page; } } public string asset_name { get{ return asset.name; } } private bool unsaved = false; public bool has_unsaved_changes { get{ return unsaved; } } private Ppub.Asset asset; public MarkdownEditor(ViewerWindow win, TabView tab_view) { window = win; orientation = Orientation.VERTICAL; source_scroller = new ScrolledWindow (); markdown_scroller = new ScrolledWindow (); leaflet = new Box(Orientation.HORIZONTAL, 0); text_view = new GtkSource.View (); language_manager = new GtkSource.LanguageManager (); text_view.monospace = true; text_view.show_line_numbers = true; text_view.auto_indent = true; source_buffer = (GtkSource.Buffer) text_view.buffer; source_buffer.language = language_manager.guess_language ("file.md", "text/markdown"); text_view.hexpand = true; text_view.set_wrap_mode (WrapMode.WORD_CHAR); text_view.top_margin = 18; text_view.bottom_margin = 18; text_view.left_margin = 18; text_view.right_margin = 18; text_view.buffer.changed.connect (update_preview); source_scroller.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC); source_scroller.vexpand = true; markdown_scroller.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC); markdown_scroller.vexpand = true; source_scroller.vadjustment.value_changed.connect(sync_scroll); markdown_view = new GtkCommonMark.MarkdownView (); markdown_view.hexpand = true; markdown_view.set_wrap_mode (WrapMode.WORD_CHAR); markdown_view.widget_embedded.connect(widget_embedded); source_scroller.child = text_view; markdown_scroller.child = markdown_view; leaflet.append(source_scroller); leaflet.append(new Separator(Orientation.VERTICAL)); leaflet.append (markdown_scroller); Gtk.Settings.get_default().notify["gtk-application-prefer-dark-theme"].connect(() => configure_tags()); configure_tags(); append(leaflet); page = tab_view.add_page (this, null); } public void set_zoom_percentage (int percent) { var scale = (float)percent / 100f; markdown_view.tag_manager.font_scale = scale; } public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) { this.publication = publication; markdown_view.buffer.set_text("", 0); page.title = asset.name; this.asset = asset; text_view.buffer.set_text("", 0); MemoryOutputStream os = new MemoryOutputStream (null, GLib.realloc, GLib.free); yield os.splice_async (publication.read_asset (asset.name), OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET); var text = os.steal_data (); text.length = (int) os.get_data_size (); text_view.buffer.set_text ((string)text, text.length); set_tab_name(false); } public override async void save_asset(Ppub.Builder builder) { var article_data = get_text().to_utf8(); var article_stream = new MemoryInputStream.from_data((uint8[])article_data); var compression_info = new Ppub.CompressionInfo(article_stream); article_stream.seek(0, SeekType.SET); builder.add_asset(asset.name, asset.mimetype, article_stream, Invercargill.empty(), compression_info); set_tab_name(false); } protected async void widget_embedded(GtkCommonMark.MarkdownViewEmbeddedWidgetHost widget, string file, string title) { var image = new Gtk.Picture(); image.content_fit = Gtk.ContentFit.FILL; widget.append (image); var pixbuf = yield new Gdk.Pixbuf.from_stream_async (publication.read_asset (file), null); image.set_pixbuf (pixbuf); widget.available_width_changed.connect(wid => { image.width_request = wid; var ratio = image.get_paintable().get_intrinsic_aspect_ratio (); image.height_request = (int)(wid / ratio); }); } private void configure_tags() { var link = new LinkButton(""); markdown_view.tag_manager.update_link_colour(link.get_color()); } private void update_preview() { var text = get_text(); markdown_view.buffer.set_text("", 0); markdown_view.load_from_string((string)text); set_tab_name(true); } public string get_text() { TextIter start_iter; TextIter end_iter; text_view.buffer.get_start_iter (out start_iter); text_view.buffer.get_end_iter (out end_iter); var text = text_view.buffer.get_text (start_iter, end_iter, true); return text; } private void sync_scroll() { set_scroll_from_frac(markdown_scroller, get_scroll_frac(source_scroller)); } private static double get_scroll_frac(ScrolledWindow scroller) { return (scroller.vadjustment.value/(scroller.vadjustment.upper - scroller.vadjustment.page_size)); } private void set_scroll_from_frac(ScrolledWindow scroller, double frac) { scroller.vadjustment.value = (scroller.vadjustment.upper - scroller.vadjustment.page_size)*frac; } private void set_tab_name(bool unsaved) { this.unsaved = unsaved; if(unsaved) { tab_page.title = @"⏺ $(asset.name)"; } else { tab_page.title = asset.name; } } } }