Parcourir la source

Embedded widget improvements

Billy Barrow il y a 2 ans
Parent
commit
fb772b20a9

+ 31 - 0
src/lib/EmbeddedWidgetLayoutManager.vala

@@ -0,0 +1,31 @@
+namespace GtkCommonMark {
+
+    public class MarkdownViewEmbeddedWidgetHost : Gtk.Box {
+
+        public signal void available_width_changed(int width);
+
+        internal void set_available_width(int width) {
+            _available_width = width;
+            available_width_changed(width);
+        }
+
+        private int _available_width;
+
+        public int available_width {get {
+            return _available_width;
+        }}
+
+        public MarkdownViewEmbeddedWidgetHost() {
+            orientation = Gtk.Orientation.VERTICAL;
+        }
+
+        public override void measure(Gtk.Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
+            base.measure (orientation, for_size, out minimum, out natural, out minimum_baseline, out natural_baseline);
+            if(orientation == Gtk.Orientation.VERTICAL) {
+                minimum = natural;
+            }
+        }
+
+    }
+
+}

+ 137 - 0
src/lib/MarkdownView.vala

@@ -0,0 +1,137 @@
+namespace GtkCommonMark {
+
+    public class MarkdownView : Gtk.TextView {
+
+        private const int MIN_WIDTH = 236;
+
+        public TagManager tag_manager {get; private set;}
+
+        private Gtk.EventControllerMotion motion_controller = new Gtk.EventControllerMotion();
+        private Gtk.GestureClick click_controller = new Gtk.GestureClick();
+
+        private int allocated_width = MIN_WIDTH;
+
+        public signal void widget_embedded(MarkdownViewEmbeddedWidgetHost widget_host, CMark.Node node);
+        public signal void link_activated(string url);
+
+        public MarkdownView() {
+            associate_tag_manager(new TagManager());
+            setup();
+        }
+
+        public MarkdownView.with_tag_manager(TagManager manager) {
+            associate_tag_manager(manager);
+            setup();
+        }
+
+        private void associate_tag_manager(TagManager manager) {
+            editable = false;
+            cursor_visible = false;
+            tag_manager = manager;
+            tag_manager.associate(buffer);
+            top_margin = tag_manager.page_margin;
+            bottom_margin = tag_manager.page_margin;
+            left_margin = tag_manager.page_margin;
+            right_margin = tag_manager.page_margin;
+        }
+
+        private void setup() {
+            add_controller(motion_controller);
+            motion_controller.motion.connect(on_motion);
+            
+            add_controller(click_controller);
+            click_controller.released.connect(on_click);
+
+        }
+
+        public void load_from_file_stream(FileStream stream) {
+            var reader = new GtkCommonMark.Reader(buffer, this, tag_manager);
+            reader.widget_embedded.connect(on_widget_embedded);
+            var root = CMark.Node.parse_file (stream, CMark.OPT.DEFAULT);
+            reader.append_node (root);
+            print("Read!\n");
+        }
+
+        public void load_from_file(File file) {
+            var stream = FileStream.open(file.get_path(), "r");
+            load_from_file_stream(stream);
+        }
+
+        protected string? link_at_buffer_location(int x, int y) {
+            Gtk.TextIter iter;
+            get_iter_at_location(out iter, x, y);
+
+            var tags = iter.get_tags();
+            string url = null;
+            foreach(var tag in tags) {
+                var u = tag.get_data<string>("href");
+                if(u != null) {
+                    url = u;
+                    break;
+                }
+            }
+
+            return url;
+        }
+
+        protected virtual void on_motion(double x, double y) {
+            int bx;
+            int by;
+            window_to_buffer_coords(Gtk.TextWindowType.WIDGET, (int)x, (int)y, out bx, out by);
+
+            var url = link_at_buffer_location(bx, by);
+            set_cursor_from_name(url == null ? "text" : "pointer");
+        }
+
+        protected virtual void on_click(int nclicks, double x, double y) {
+            int bx;
+            int by;
+            window_to_buffer_coords(Gtk.TextWindowType.WIDGET, (int)x, (int)y, out bx, out by);
+            var url = link_at_buffer_location(bx, by);
+
+            if(url != null) {
+                link_activated(url);
+            }
+        }
+
+        protected virtual void on_widget_embedded(MarkdownViewEmbeddedWidgetHost host, CMark.Node node) {
+            host.set_available_width(calculate_embed_size(allocated_width));
+            available_size_for_embed_changed.connect(w => host.set_available_width(w));
+            if(node.get_type() == CMark.NODE_TYPE.IMAGE) {
+                widget_embedded(host, node);
+            }
+        }
+
+        public override void measure(Gtk.Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
+
+            print(@"MEASURE (S): orientation: $orientation; for_size: $for_size;\n");
+            if(orientation == Gtk.Orientation.HORIZONTAL) {
+                minimum = MIN_WIDTH;
+                natural = MIN_WIDTH;
+                minimum_baseline = 0;
+                natural_baseline = 0;
+            }
+            else {
+                base.measure (orientation, for_size, out minimum, out natural, out minimum_baseline, out natural_baseline);
+            }
+
+            print(@"MEASURE (E): minimum: $minimum; natural: $natural; minimum_baseline: $minimum_baseline; natural_baseline: $natural_baseline;\n");
+
+        }
+
+        public override void size_allocate(int width, int height, int baseline) {
+            print(@"SIZE_ALLOCATE: $width, $height, $baseline\n");
+            allocated_width = width;
+            available_size_for_embed_changed(calculate_embed_size(width));
+            base.size_allocate(width, height, baseline);
+        }
+
+        private int calculate_embed_size(int width) {
+            return width - tag_manager.page_margin * 2;
+        }
+
+        private signal void available_size_for_embed_changed(int width);
+
+    }
+
+}

+ 15 - 0
src/lib/NodeActions/Link.vala

@@ -0,0 +1,15 @@
+namespace GtkCommonMark.NodeActions {
+
+    public class Link : SimpleNodeAction {
+
+        public Link(CMark.Node node) {
+            base(node);
+        }
+
+        public override Gtk.TextTag get_tag(TagManager tags) {
+            return tags.get_link(node.get_url());
+        }
+
+    }
+
+}

+ 9 - 3
src/lib/NodeActions/ThematicBreak.vala

@@ -3,10 +3,12 @@ namespace GtkCommonMark.NodeActions {
     public class ThematicBreak : SimpleNodeAction {
 
         private Gtk.TextView text_view;
+        private MarkdownViewEmbeddedWidgetHost host;
 
-        public ThematicBreak(CMark.Node node, Gtk.TextView text_view) {
+        public ThematicBreak(CMark.Node node, Gtk.TextView text_view, MarkdownViewEmbeddedWidgetHost host) {
             base(node);
             this.text_view = text_view;
+            this.host = host;
         }
 
         public override Gtk.TextTag get_tag(TagManager tags) {
@@ -15,9 +17,13 @@ namespace GtkCommonMark.NodeActions {
 
         public override void at_start(Gtk.TextBuffer buffer, ref Gtk.TextIter iter) {        
             var anchor = buffer.create_child_anchor(iter);
+            
             var seperator = new Gtk.Separator(Gtk.Orientation.HORIZONTAL);
-            seperator.width_request = 200;
-            text_view.add_child_at_anchor(seperator, anchor);
+            host.available_width_changed.connect(w => seperator.width_request = w);
+            seperator.width_request = host.available_width;
+            host.append(seperator);
+
+            text_view.add_child_at_anchor(host, anchor);
             buffer.get_end_iter(out iter);
             buffer.insert(ref iter, "\n", -1);
         }

+ 13 - 6
src/lib/Reader.vala

@@ -12,6 +12,8 @@ namespace GtkCommonMark {
         private Gtk.TextIter tb_iter;
         private List<NodeActions.NodeAction> node_actions;
 
+        public signal void widget_embedded(MarkdownViewEmbeddedWidgetHost widget_host, CMark.Node node);
+
         public Reader(Gtk.TextBuffer buffer, Gtk.TextView text_view, TagManager tag_manager) {
             this.tags = tag_manager;
             this.buffer = buffer;
@@ -142,7 +144,6 @@ namespace GtkCommonMark {
 
         protected virtual NodeActions.NodeAction build_action_for_node(CMark.Node node) {
             var type = node.get_type();
-            print(@"Building type $type\n");
             switch (type) {
                 case CMark.NODE_TYPE.BLOCK_QUOTE:
                     return build_action_for_block_quote(node);
@@ -206,10 +207,12 @@ namespace GtkCommonMark {
         }
 
         protected virtual NodeActions.NodeAction build_action_for_html_block(CMark.Node node) {
+            GLib.warning("An HTML block was detected while reading the markdown file which is unsupported, it will be omitted");
             return build_default_action(node);
         }
 
         protected virtual NodeActions.NodeAction build_action_for_custom_block(CMark.Node node) {
+            GLib.warning("A custom block was detected while reading the markdown file which is unsupported, it will be omitted");
             return build_default_action(node);
         }
 
@@ -222,7 +225,8 @@ namespace GtkCommonMark {
         }
 
         protected virtual NodeActions.NodeAction build_action_for_thematic_break(CMark.Node node) {
-            return new NodeActions.ThematicBreak(node, text_view);
+            var host = build_embed_widget(node);
+            return new NodeActions.ThematicBreak(node, text_view, host);
         }
 
         // Inline Elements
@@ -243,10 +247,12 @@ namespace GtkCommonMark {
         }
 
         protected virtual NodeActions.NodeAction build_action_for_inline_html(CMark.Node node) {
+            GLib.warning("An HTML inline segment was detected while reading the markdown file which is unsupported, it will be omitted");
             return build_default_action(node);
         }
 
         protected virtual NodeActions.NodeAction build_action_for_inline_custom(CMark.Node node) {
+            GLib.warning("A custom inline segment was detected while reading the markdown file which is unsupported, it will be omitted");
             return build_default_action(node);
         }
 
@@ -259,7 +265,7 @@ namespace GtkCommonMark {
         }
 
         protected virtual NodeActions.NodeAction build_action_for_link(CMark.Node node) {
-            return build_default_action(node);
+            return new NodeActions.Link(node);
         }
 
         protected virtual NodeActions.NodeAction build_action_for_embed(CMark.Node node) {
@@ -273,9 +279,10 @@ namespace GtkCommonMark {
             return new NodeActions.SimpleNodeAction(node);
         }
 
-        protected virtual Gtk.Widget build_embed_widget(CMark.Node node) {
-            var widget = new Gtk.Image.from_file(node.get_url());
-            widget.set_size_request(500, 500);
+        protected virtual MarkdownViewEmbeddedWidgetHost build_embed_widget(CMark.Node node) {
+            var widget = new MarkdownViewEmbeddedWidgetHost();
+            widget_embedded(widget, node);
+            widget.show();
             return widget;
         }
         

+ 17 - 3
src/lib/TagManager.vala

@@ -13,8 +13,9 @@ namespace GtkCommonMark {
         private Gee.HashMap<int, Gtk.TextTag> list_tags = new Gee.HashMap<int, Gtk.TextTag>();
         private Gee.HashMap<int, Gtk.TextTag> list_item_tags = new Gee.HashMap<int, Gtk.TextTag>();
         private Gee.HashMap<int, Gtk.TextTag> blockquote_tags = new Gee.HashMap<int, Gtk.TextTag>();
+        private Gee.HashMap<string, Gtk.TextTag> link_tags = new Gee.HashMap<string, Gtk.TextTag>();
 
-        public TagManager(Gtk.TextBuffer buffer) {
+        internal void associate(Gtk.TextBuffer buffer){
             this.buffer = buffer;
             build_tags();
             update_tags();
@@ -205,8 +206,6 @@ namespace GtkCommonMark {
             
             tag.indent = -quote_text_distance;
             tag.left_margin = quote_margin;
-            print(tag.left_margin.to_string());
-            print(@" and QL-$level has margin of $quote_margin px\n");
             var tabs = new Pango.TabArray(2, true);
             tabs.set_tab(0, Pango.TabAlign.LEFT, 0);
             tabs.set_tab(1, Pango.TabAlign.LEFT, quote_text_distance);
@@ -217,6 +216,21 @@ namespace GtkCommonMark {
             tag.accumulative_margin = true;
         }
 
+        public virtual Gtk.TextTag get_link (string url) {
+            if(!link_tags.has_key(url)) {
+                var tag = buffer.create_tag(null);
+                configure_link(url, tag);
+                link_tags.set(url, tag);
+            }
+            return link_tags.get(url);
+        }
+
+
+        protected virtual void configure_link (string url, Gtk.TextTag tag) {
+            tag.set_data<string>("href", url);
+            tag.underline = Pango.Underline.SINGLE;
+            tag.foreground = "blue";
+        }
     }
 
 }

+ 3 - 0
src/lib/meson.build

@@ -14,6 +14,8 @@ dependencies = [
 
 sources = files('Reader.vala')
 sources += files('TagManager.vala')
+sources += files('MarkdownView.vala')
+sources += files('EmbeddedWidgetLayoutManager.vala')
 sources += files('NodeActions/NodeAction.vala')
 sources += files('NodeActions/Heading.vala')
 sources += files('NodeActions/Emphisis.vala')
@@ -28,6 +30,7 @@ sources += files('NodeActions/Quote.vala')
 sources += files('NodeActions/ThematicBreak.vala')
 sources += files('NodeActions/Code.vala')
 sources += files('NodeActions/CodeBlock.vala')
+sources += files('NodeActions/Link.vala')
 
 libgtkcmark = shared_library('gtkcommonmark', sources,
     dependencies: dependencies,

+ 37 - 41
src/viewer/Main.vala

@@ -2,50 +2,46 @@ using Gtk;
 
 int main (string[] argv) {
     // Create a new application
-    var app = new Gtk.Application ("com.example.GtkApplication", GLib.ApplicationFlags.FLAGS_NONE);
+    var app = new Gtk.Application ("com.example.GtkApplication", GLib.ApplicationFlags.HANDLES_OPEN);
     app.activate.connect (() => {
-        var window = new Gtk.ApplicationWindow (app);
-        window.width_request = 250;
-        window.height_request = 100;
-        window.title = "Not Loopy";
 
-        var text_view = new TextView();
-        text_view.set_wrap_mode (WrapMode.WORD_CHAR);
-        //  text_view.set_editable (false);
-
-        var buffer = text_view.get_buffer();
-        var tags = new GtkCommonMark.TagManager(buffer);
-
-        //  buffer.begin_irreversible_action();
-
-        //  TextIter iter;
-        //  buffer.get_iter_at_offset (out iter, 0);
-
-        //  buffer.insert_with_tags (ref iter, "Oh My!\n", -1, tags.heading_l1);
-        //  buffer.insert(ref iter, "Hello world ", -1);
-        //  buffer.insert_with_tags (ref iter, "this is my link", -1, tags.emphisis, tags.strong);
-        //  buffer.insert(ref iter, ", what do you think?\n", -1);
-        //  buffer.insert_with_tags (ref iter, "What a quote though\nI don't know what to think!", -1, tags.strong);
-
-        //  buffer.end_irreversible_action();
-
-        var reader = new GtkCommonMark.Reader(buffer, text_view, tags);
-        //  var file = GLib.FileStream.open ("/home/bbarrow/Documents/Recipes/canned_spaghetti_toast/recipe.md", "r");
-        var file = GLib.FileStream.open ("test.md", "r");
-        var root = CMark.Node.parse_file (file, CMark.OPT.DEFAULT);
-        reader.append_node (root);
-
-        text_view.top_margin = tags.page_margin;
-        text_view.bottom_margin = tags.page_margin;
-        text_view.left_margin = tags.page_margin;
-        text_view.right_margin = tags.page_margin;
-
-        var srcolled_window = new Gtk.ScrolledWindow ();
-        srcolled_window.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
-        srcolled_window.set_child(text_view);
-        window.set_child (srcolled_window);
+    });
 
-        window.present ();
+    app.open.connect ((f) => {
+        foreach(var file in f) {
+            var window = new Gtk.ApplicationWindow (app);
+            window.width_request = 500;
+            window.height_request = 900;
+            window.title = "Gtk Common Mark Demo";
+    
+            var text_view = new GtkCommonMark.MarkdownView();
+            text_view.set_wrap_mode (WrapMode.WORD_CHAR);
+    
+            text_view.link_activated.connect (url => print(@"Url clicked: $url\n"));
+            text_view.widget_embedded.connect ((w, n) => {
+                var url = Path.build_filename (file.get_parent().get_path(), n.get_url ());
+                print(@"Load image: $url\n");
+                var image = new Gtk.Picture.for_filename (url);
+                image.content_fit = Gtk.ContentFit.FILL;
+                w.available_width_changed.connect(wid => {
+                    image.width_request = wid;
+                    var ratio = image.get_paintable().get_intrinsic_aspect_ratio ();
+                    image.height_request = (int)(wid / ratio);
+                });
+                w.append (image);
+            });
+    
+            print(@"Opening $(file.get_path())\n");
+            text_view.load_from_file(file);
+    
+            var srcolled_window = new Gtk.ScrolledWindow ();
+            srcolled_window.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
+            srcolled_window.set_child(text_view);
+            window.set_child (srcolled_window);
+    
+            window.present ();
+        }
     });
+
     return app.run (argv);
 }

+ 1 - 1
test.md

@@ -66,7 +66,7 @@ namespace GtkCommonMark.NodeActions {
     3. Nesting is cool
 5. Hopefully it works
 
-<!-- ![House Photo](/home/bbarrow/Pictures/House/1943301509.jpg) -->
+![House Photo](/home/bbarrow/Pictures/House/1943301509.jpg)
 
 10. This list starts from 10
 10. The MD does not incriement the number