|
@@ -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);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|