[gnome-todo/vyasgiridhar/markdown: 3/3] markdown: Introduce Markdown support for task descriptions



commit 12542a32e6192f8131781f3252a3ec5914221f31
Author: Vyas Giridharan <vyasgiridhar27 gmail com>
Date:   Mon Jan 29 21:41:35 2018 +0530

    markdown: Introduce Markdown support for task descriptions
    
    This commit adds a Markdown parser to task descriptions. Each
    GtdTaskListView contains one Markdown parser, that is then
    shared between the rows.
    
    The Markdown parser keeps a weak reference every GtkTextBuffer
    added, so it can update a map of buffers that were populated
    with the appropriate tags.
    
    Issue: #157

 data/ui/edit-pane.ui        |   2 +
 src/gtd-edit-pane.c         | 161 ++++++++++++++++++++
 src/gtd-edit-pane.h         |   3 +
 src/gtd-markdown-renderer.c | 353 ++++++++++++++++++++++++++++++++++++++++++++
 src/gtd-markdown-renderer.h |  35 +++++
 src/gtd-task-list-view.c    |  14 +-
 src/gtd-task-row.c          |  34 ++++-
 src/gtd-task-row.h          |   3 +-
 src/gtd-types.h             |   1 +
 src/meson.build             |   1 +
 10 files changed, 600 insertions(+), 7 deletions(-)
---
diff --git a/data/ui/edit-pane.ui b/data/ui/edit-pane.ui
index 7e2e01b..3e096fd 100644
--- a/data/ui/edit-pane.ui
+++ b/data/ui/edit-pane.ui
@@ -70,6 +70,8 @@
                     <property name="buffer">text_buffer</property>
                     <signal name="button-press-event" handler="on_trap_textview_clicks_cb" swapped="no" 
after="yes" />
                     <signal name="button-release-event" handler="on_trap_textview_clicks_cb" swapped="no" 
after="yes" />
+                    <signal name="motion-notify-event" handler="on_hyperlink_hover_cb" swapped="no" />
+                    <signal name="event-after" handler="on_hyperlink_clicked_cb" swapped="no" />
                   </object>
                 </child>
               </object>
diff --git a/src/gtd-edit-pane.c b/src/gtd-edit-pane.c
index 3d8f2c9..7d8079e 100644
--- a/src/gtd-edit-pane.c
+++ b/src/gtd-edit-pane.c
@@ -20,6 +20,7 @@
 
 #include "gtd-edit-pane.h"
 #include "gtd-manager.h"
+#include "gtd-markdown-renderer.h"
 #include "gtd-task.h"
 #include "gtd-task-list.h"
 
@@ -218,6 +219,150 @@ on_trap_textview_clicks_cb (GtkWidget   *textview,
   return GDK_EVENT_STOP;
 }
 
+static gboolean
+on_hyperlink_hover_cb (GtkTextView    *text_view,
+                       GdkEventMotion *event)
+{
+  g_autoptr (GdkCursor) cursor = NULL;
+  GdkDisplay *display = NULL;
+  GtkTextIter iter;
+  gboolean hovering;
+  gdouble ex, ey;
+  gint x, y;
+
+  gdk_event_get_coords ((GdkEvent *)event, &ex, &ey);
+  gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_WIDGET, ex, ey, &x, &y);
+
+  hovering = FALSE;
+
+  if (gtk_text_view_get_iter_at_location (text_view, &iter, x, y))
+    {
+      GSList *tags = NULL;
+      GSList *l = NULL;
+
+      tags = gtk_text_iter_get_tags (&iter);
+
+      for (l = tags; l; l = l->next)
+        {
+          g_autofree gchar *tag_name = NULL;
+          GtkTextTag *tag;
+
+          tag = l->data;
+
+          g_object_get (tag, "name", &tag_name, NULL);
+
+          if (g_strcmp0 (tag_name, "url") == 0)
+            {
+              hovering = TRUE;
+              break;
+            }
+        }
+    }
+
+  display = gtk_widget_get_display (GTK_WIDGET (text_view));
+  cursor = gdk_cursor_new_from_name (display, hovering ? "pointer" : "text");
+
+  gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), cursor);
+
+  return GDK_EVENT_STOP;
+}
+
+static gboolean
+on_hyperlink_clicked_cb (GtkTextView *text_view,
+                         GdkEvent    *event)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter end_iter;
+  GtkTextIter iter;
+  GSList *tags = NULL;
+  GSList *l = NULL;
+  gdouble ex;
+  gdouble ey;
+  gint x;
+  gint y;
+
+  /* Ignore events that are not button or touch release */
+  if (event->type != GDK_BUTTON_RELEASE && event->type != GDK_TOUCH_END)
+    return GDK_EVENT_PROPAGATE;
+
+  if (event->type == GDK_BUTTON_RELEASE)
+    {
+      GdkEventButton *event_button;
+
+      event_button = (GdkEventButton *)event;
+      if (event_button->button != GDK_BUTTON_PRIMARY)
+        return GDK_EVENT_PROPAGATE;
+
+      ex = event_button->x;
+      ey = event_button->y;
+    }
+  else if (event->type == GDK_TOUCH_END)
+    {
+      GdkEventTouch *event_touch;
+
+      event_touch = (GdkEventTouch *)event;
+
+      ex = event_touch->x;
+      ey = event_touch->y;
+    }
+
+  buffer = gtk_text_view_get_buffer (text_view);
+
+  /* We shouldn't follow a link if the user has selected something */
+  if (gtk_text_buffer_get_has_selection (buffer))
+    return GDK_EVENT_PROPAGATE;
+
+  gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_WIDGET, ex, ey, &x, &y);
+
+  if (!gtk_text_view_get_iter_at_location (text_view, &iter, x, y))
+    return GDK_EVENT_PROPAGATE;
+
+  tags = gtk_text_iter_get_tags (&iter);
+
+  for (l = tags; l; l = l->next)
+    {
+      g_autoptr (GError) error = NULL;
+      g_autofree gchar *tag_name = NULL;
+      g_autofree gchar *url = NULL;
+      GtkTextIter url_start;
+      GtkTextIter url_end;
+      GtkTextTag *tag;
+      GtkWindow *window;
+
+      tag = l->data;
+
+      g_object_get (tag, "name", &tag_name, NULL);
+
+      if (g_strcmp0 (tag_name, "url") != 0)
+        continue;
+
+      gtk_text_buffer_get_iter_at_line (buffer, &iter, gtk_text_iter_get_line (&iter));
+      end_iter = iter;
+      gtk_text_iter_forward_to_line_end (&end_iter);
+
+      /* Find the beginning... */
+      if (!gtk_text_iter_forward_search (&iter, "(", GTK_TEXT_SEARCH_TEXT_ONLY, NULL, &url_start, NULL))
+        continue;
+
+      /* ... and the end of the URL */
+      if (!gtk_text_iter_forward_search (&iter, ")", GTK_TEXT_SEARCH_TEXT_ONLY, &url_end, NULL, &end_iter))
+        continue;
+
+      url = gtk_text_iter_get_text (&url_start, &url_end);
+      window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (text_view)));
+
+      gtk_show_uri_on_window (window, url, GDK_CURRENT_TIME, &error);
+
+      if (error)
+        {
+          g_warning ("%s", error->message);
+          return GDK_EVENT_PROPAGATE;
+        }
+    }
+
+  return GDK_EVENT_STOP;
+}
+
 
 /*
  * GObject overrides
@@ -348,6 +493,8 @@ gtd_edit_pane_class_init (GtdEditPaneClass *klass)
 
   gtk_widget_class_bind_template_callback (widget_class, on_date_selected_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_delete_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_hyperlink_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_hyperlink_hover_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_no_date_button_clicked_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_priority_changed_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_text_buffer_changed_cb);
@@ -434,3 +581,17 @@ gtd_edit_pane_set_task (GtdEditPane *self,
 
   g_object_notify (G_OBJECT (self), "task");
 }
+
+void
+gtd_edit_pane_set_markdown_renderer (GtdEditPane         *self,
+                                     GtdMarkdownRenderer *renderer)
+{
+  GtkTextBuffer *buffer;
+
+  g_assert (GTD_IS_EDIT_PANE (self));
+  g_assert (GTD_IS_MARKDOWN_RENDERER (renderer));
+
+  buffer = gtk_text_view_get_buffer (self->notes_textview);
+
+  gtd_markdown_renderer_add_buffer (renderer, buffer);
+}
diff --git a/src/gtd-edit-pane.h b/src/gtd-edit-pane.h
index ad07b68..3f07de6 100644
--- a/src/gtd-edit-pane.h
+++ b/src/gtd-edit-pane.h
@@ -37,6 +37,9 @@ GtdTask*             gtd_edit_pane_get_task                      (GtdEditPane
 void                 gtd_edit_pane_set_task                      (GtdEditPane        *self,
                                                                   GtdTask            *task);
 
+void                 gtd_edit_pane_set_markdown_renderer         (GtdEditPane         *self,
+                                                                  GtdMarkdownRenderer *renderer);
+
 G_END_DECLS
 
 #endif /* GTD_EDIT_PANE_H */
diff --git a/src/gtd-markdown-renderer.c b/src/gtd-markdown-renderer.c
new file mode 100644
index 0000000..b7546d8
--- /dev/null
+++ b/src/gtd-markdown-renderer.c
@@ -0,0 +1,353 @@
+/* gtd-markdown-buffer.c
+ *
+ * Copyright © 2018 Vyas Giridharan <vyasgiridhar27 gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdMarkdownRenderer"
+
+#include "gtd-debug.h"
+#include "gtd-markdown-renderer.h"
+
+#define ITALICS_1 "*"
+#define ITALICS_2 "_"
+#define BOLD_1    "__"
+#define BOLD_2    "**"
+#define STRIKE    "~~"
+#define HEAD_1    "#"
+#define HEAD_2    "##"
+#define HEAD_3    "###"
+#define LIST      "+"
+
+struct _GtdMarkdownRenderer
+{
+  GObject              parent_instance;
+
+  GHashTable          *populated_buffers;
+};
+
+
+static void          on_text_buffer_weak_notified_cb             (gpointer            data,
+                                                                  GObject            *where_the_object_was);
+
+static void          on_text_changed_cb                          (GtkTextBuffer       *buffer,
+                                                                  GtdMarkdownRenderer *self);
+
+
+G_DEFINE_TYPE (GtdMarkdownRenderer, gtd_markdown_renderer, G_TYPE_OBJECT)
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+apply_link_tags (GtdMarkdownRenderer *self,
+                 GtkTextBuffer       *buffer,
+                 GtkTextTag          *link_tag,
+                 GtkTextTag          *url_tag,
+                 GtkTextIter         *text_start,
+                 GtkTextIter         *text_end)
+{
+  GtkTextIter symbol_start;
+  GtkTextIter symbol_end;
+  GtkTextIter target_start;
+  GtkTextIter target_end;
+  GtkTextIter iter;
+
+  GTD_ENTRY;
+
+  iter = *text_start;
+
+  /*
+   * We advance in pairs of [...], and inside the loop we check if the very next character
+   * after ']' is '('. No spaces are allowed. Only if this condition is satisfied that we
+   * claim this to be a link, and render it as such.
+   */
+  while (gtk_text_iter_forward_search (&iter, "[", GTK_TEXT_SEARCH_TEXT_ONLY, &symbol_start, &target_start, 
text_end) &&
+         gtk_text_iter_forward_search (&target_start, "]", GTK_TEXT_SEARCH_TEXT_ONLY, &target_end, 
&symbol_end, text_end))
+    {
+      GtkTextIter url_start;
+      GtkTextIter url_end;
+
+      iter = symbol_end;
+
+      /* Advance a single position */
+      url_start = symbol_end;
+
+      /* Only consider valid if the character after ']' is '(' */
+      if (gtk_text_iter_get_char (&url_start) != '(')
+        continue;
+
+      /*
+       * Try and find the matching (...), if it fails, iter is set to the previous ']' so
+       * we don't enter in an infinite loop
+       */
+      if (!gtk_text_iter_forward_search (&iter, "(", GTK_TEXT_SEARCH_TEXT_ONLY, NULL, &url_start, text_end) 
||
+          !gtk_text_iter_forward_search (&iter, ")", GTK_TEXT_SEARCH_TEXT_ONLY, &url_end, NULL, text_end))
+        {
+          continue;
+        }
+
+      /* Apply both the link and url tags */
+      gtk_text_buffer_apply_tag (buffer, link_tag, &target_start, &target_end);
+      gtk_text_buffer_apply_tag (buffer, url_tag, &url_start, &url_end);
+
+      iter = url_end;
+    }
+}
+
+static void
+apply_markdown_tag (GtdMarkdownRenderer *self,
+                    GtkTextBuffer       *buffer,
+                    GtkTextTag          *tag,
+                    const gchar         *symbol,
+                    GtkTextIter         *text_start,
+                    GtkTextIter         *text_end,
+                    gboolean             paired)
+{
+  GtkTextIter symbol_start;
+  GtkTextIter symbol_end;
+  GtkTextIter iter;
+
+  iter = *text_start;
+
+  while (gtk_text_iter_forward_search (&iter, symbol, GTK_TEXT_SEARCH_TEXT_ONLY, &symbol_start, &symbol_end, 
text_end))
+    {
+      GtkTextIter tag_start;
+      GtkTextIter tag_end;
+
+      tag_start = symbol_start;
+      tag_end = symbol_end;
+
+      /* Iter is initially at the end of the found symbol, to avoid infinite loops */
+      iter = symbol_end;
+
+      if (paired)
+        {
+          /*
+           * If the markdown tag is in the form of pairs (e.g. **bold**, __italics__, etc), then we should
+           * search the symbol twice. The first marks the start and the second marks the end of the section
+           * of the text that needs the tag.
+           *
+           * We also ignore the tag if it's not contained in the same line of the start.
+           */
+          if (!gtk_text_iter_forward_search (&tag_end, symbol, GTK_TEXT_SEARCH_TEXT_ONLY, NULL, &tag_end, 
text_end) ||
+              gtk_text_iter_get_line (&tag_end) != gtk_text_iter_get_line (&symbol_start))
+            {
+              continue;
+            }
+        }
+      else
+        {
+          /*
+           * If the markdown tag is not paired (e.g. ## header), then it is just applied at the start of
+           * the line. As such, we must search for the symbol - and this is where the tag starts - but move
+           * straight to the end of the line.
+           */
+          gtk_text_iter_forward_to_line_end (&tag_end);
+
+          /* Only apply this tag if this is the start of the line */
+          if (gtk_text_iter_get_line_offset (&tag_start) != 0)
+            continue;
+        }
+
+      /* Apply the tag */
+      gtk_text_buffer_apply_tag (buffer, tag, &tag_start, &tag_end);
+
+      /*
+       * If we applied the tag, jump the iter to the end of the tag. We are already guaranteed
+       * to not run into infinite loops, but this skips a bigger section of the buffer too and
+       * can save a tiny few cycles
+       */
+      iter = tag_end;
+    }
+}
+
+static void
+populate_tag_table (GtdMarkdownRenderer *self,
+                    GtkTextBuffer       *buffer)
+{
+  gtk_text_buffer_create_tag (buffer,
+                              "italic",
+                              "style",
+                              PANGO_STYLE_ITALIC,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "bold",
+                              "weight",
+                              PANGO_WEIGHT_BOLD,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "head_1",
+                              "weight",
+                              PANGO_WEIGHT_BOLD,
+                              "scale",
+                              PANGO_SCALE_XX_LARGE,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "head_2",
+                              "weight",
+                              PANGO_WEIGHT_BOLD,
+                              "scale",
+                              PANGO_SCALE_SMALL,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "head_3",
+                              "weight",
+                              PANGO_WEIGHT_BOLD,
+                              "scale",
+                              PANGO_SCALE_SMALL,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "strikethrough",
+                              "strikethrough",
+                              TRUE,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "list-indent",
+                              "indent",
+                              20,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "url",
+                              "foreground",
+                              "blue",
+                              "underline",
+                              PANGO_UNDERLINE_SINGLE,
+                              NULL);
+
+  gtk_text_buffer_create_tag (buffer,
+                              "link-text",
+                              "weight",
+                              PANGO_WEIGHT_BOLD,
+                              "foreground",
+                              "#555F61",
+                              NULL);
+
+  /*
+   * Add a weak ref so we can remove from the map of populated buffers when it's
+   * finalized.
+   */
+  g_object_weak_ref (G_OBJECT (buffer), on_text_buffer_weak_notified_cb, self);
+
+  /* Add to the map of populated buffers */
+  g_hash_table_add (self->populated_buffers, buffer);
+  g_signal_connect (buffer, "changed", G_CALLBACK (on_text_changed_cb), self);
+
+  g_debug ("Added buffer %p to markdown renderer", buffer);
+}
+
+static void
+render_markdown (GtdMarkdownRenderer *self,
+                 GtkTextBuffer       *buffer)
+{
+  GtkTextTagTable *tag_table;
+  GtkTextIter start;
+  GtkTextIter end;
+
+  GTD_ENTRY;
+
+  /* Wipe out the previous tags */
+  gtk_text_buffer_get_start_iter (buffer, &start);
+  gtk_text_buffer_get_end_iter (buffer, &end);
+
+  gtk_text_buffer_remove_all_tags (buffer, &start, &end);
+
+  /* Apply the tags */
+  tag_table = gtk_text_buffer_get_tag_table (buffer);
+
+#define TAG(x) gtk_text_tag_table_lookup(tag_table, x)
+
+  apply_markdown_tag (self, buffer, TAG ("bold"), BOLD_2, &start, &end, TRUE);
+  apply_markdown_tag (self, buffer, TAG ("bold"), BOLD_1, &start, &end, TRUE);
+  apply_markdown_tag (self, buffer, TAG ("italic"), ITALICS_2, &start, &end, TRUE);
+  apply_markdown_tag (self, buffer, TAG ("italic"), ITALICS_1, &start, &end, TRUE);
+  apply_markdown_tag (self, buffer, TAG ("head_3"), HEAD_3, &start, &end, FALSE);
+  apply_markdown_tag (self, buffer, TAG ("head_2"), HEAD_2, &start, &end, FALSE);
+  apply_markdown_tag (self, buffer, TAG ("head_1"), HEAD_1, &start, &end, FALSE);
+  apply_markdown_tag (self, buffer, TAG ("strikethrough"), STRIKE, &start, &end, TRUE);
+  apply_markdown_tag (self, buffer, TAG ("list_indent"), LIST, &start, &end, FALSE);
+
+  apply_link_tags (self, buffer, TAG ("link-text"), TAG ("url"), &start, &end);
+
+#undef TAG
+
+  GTD_EXIT;
+}
+
+/*
+ * Callbacks
+ */
+
+static void
+on_text_buffer_weak_notified_cb (gpointer  data,
+                                 GObject  *where_the_object_was)
+{
+  GtdMarkdownRenderer *self = GTD_MARKDOWN_RENDERER (data);
+
+  g_hash_table_remove (self->populated_buffers, where_the_object_was);
+
+  g_debug ("Buffer %p died and was removed from markdown renderer", where_the_object_was);
+}
+
+
+static void
+on_text_changed_cb (GtkTextBuffer       *buffer,
+                    GtdMarkdownRenderer *self)
+{
+  render_markdown (self, buffer);
+}
+
+static void
+gtd_markdown_renderer_class_init (GtdMarkdownRendererClass *klass)
+{
+}
+
+void
+gtd_markdown_renderer_init (GtdMarkdownRenderer *self)
+{
+  self->populated_buffers = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+GtdMarkdownRenderer*
+gtd_markdown_renderer_new (void)
+{
+  return g_object_new (GTD_TYPE_MARKDOWN_RENDERER, NULL);
+}
+
+void
+gtd_markdown_renderer_add_buffer (GtdMarkdownRenderer *self,
+                                  GtkTextBuffer       *buffer)
+{
+  g_return_if_fail (GTD_IS_MARKDOWN_RENDERER (self));
+
+  GTD_ENTRY;
+
+  /* If the text buffer is not poopulated yet, do it now */
+  if (!g_hash_table_contains (self->populated_buffers, buffer))
+    populate_tag_table (self, buffer);
+
+  render_markdown (self, buffer);
+
+  GTD_EXIT;
+}
diff --git a/src/gtd-markdown-renderer.h b/src/gtd-markdown-renderer.h
new file mode 100644
index 0000000..1c2593b
--- /dev/null
+++ b/src/gtd-markdown-renderer.h
@@ -0,0 +1,35 @@
+/* gtd-markdown-buffer.h
+ *
+ * Copyright (C) 2018 Vyas Giridharan <vyasgiridhar27 gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_MARKDOWN_RENDERER (gtd_markdown_renderer_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdMarkdownRenderer, gtd_markdown_renderer, GTD, MARKDOWN_RENDERER, GObject)
+
+GtdMarkdownRenderer* gtd_markdown_renderer_new                   (void);
+
+void                 gtd_markdown_renderer_add_buffer            (GtdMarkdownRenderer*self,
+                                                                  GtkTextBuffer      *buffer);
+
+G_END_DECLS
+
diff --git a/src/gtd-task-list-view.c b/src/gtd-task-list-view.c
index 5f4c77d..900b072 100644
--- a/src/gtd-task-list-view.c
+++ b/src/gtd-task-list-view.c
@@ -25,6 +25,7 @@
 #include "gtd-empty-list-widget.h"
 #include "gtd-task-list-view.h"
 #include "gtd-manager.h"
+#include "gtd-markdown-renderer.h"
 #include "gtd-new-task-row.h"
 #include "gtd-notification.h"
 #include "gtd-provider.h"
@@ -87,6 +88,9 @@ typedef struct
   GtdTaskList           *task_list;
   GDateTime             *default_date;
 
+  /* Markup renderer*/
+  GtdMarkdownRenderer   *renderer;
+
   /* DnD autoscroll */
   guint                  scroll_timeout_id;
   gboolean               scroll_up;
@@ -320,7 +324,7 @@ add_task_row (GtdTaskListView *self,
   GtdTaskListViewPrivate *priv = self->priv;
   GtkWidget *new_row;
 
-  new_row = gtd_task_row_new (task);
+  new_row = gtd_task_row_new (task, priv->renderer);
 
   g_object_bind_property (self,
                           "handle-subtasks",
@@ -480,8 +484,8 @@ remove_task_row (GtdTaskListView *view,
 {
   GtdTaskRow *row;
 
-  g_return_if_fail (GTD_IS_TASK_LIST_VIEW (view));
-  g_return_if_fail (GTD_IS_TASK (task));
+  g_assert (GTD_IS_TASK_LIST_VIEW (view));
+  g_assert (GTD_IS_TASK (task));
 
   row = get_row_for_task (view, task);
 
@@ -687,6 +691,7 @@ on_remove_task_row_cb (GtdTaskRow      *row,
 
   gtd_window_notify (window, notification);
 
+
   /* Clear the active row */
   set_active_row (self, NULL);
 
@@ -1298,6 +1303,7 @@ gtd_task_list_view_finalize (GObject *object)
 
   g_clear_pointer (&priv->default_date, g_date_time_unref);
   g_clear_pointer (&priv->list, g_list_free);
+  g_clear_object (&priv->renderer);
 
   G_OBJECT_CLASS (gtd_task_list_view_parent_class)->finalize (object);
 }
@@ -1582,6 +1588,8 @@ gtd_task_list_view_init (GtdTaskListView *self)
                      NULL,
                      0,
                      GDK_ACTION_MOVE);
+
+  self->priv->renderer = gtd_markdown_renderer_new ();
 }
 
 /**
diff --git a/src/gtd-task-row.c b/src/gtd-task-row.c
index 756d7ed..8b746d3 100644
--- a/src/gtd-task-row.c
+++ b/src/gtd-task-row.c
@@ -22,6 +22,7 @@
 #include "gtd-edit-pane.h"
 #include "gtd-expandable-entry.h"
 #include "gtd-manager.h"
+#include "gtd-markdown-renderer.h"
 #include "gtd-provider.h"
 #include "gtd-rows-common-private.h"
 #include "gtd-task-row.h"
@@ -62,6 +63,8 @@ struct _GtdTaskRow
   /* data */
   GtdTask            *task;
 
+  GtdMarkdownRenderer *renderer;
+
   gboolean            active;
   gboolean            changed;
 };
@@ -103,6 +106,7 @@ enum
 {
   PROP_0,
   PROP_HANDLE_SUBTASKS,
+  PROP_RENDERER,
   PROP_TASK,
   LAST_PROP
 };
@@ -176,7 +180,7 @@ create_transient_row (GtdTaskRow *self)
 {
   GtdTaskRow *new_row;
 
-  new_row = GTD_TASK_ROW (gtd_task_row_new (self->task));
+  new_row = GTD_TASK_ROW (gtd_task_row_new (self->task, self->renderer));
   gtk_revealer_set_transition_duration (new_row->revealer, 0);
   gtk_revealer_set_reveal_child (new_row->revealer, TRUE);
 
@@ -509,7 +513,6 @@ on_task_changed_cb (GtdTaskRow  *self)
   self->changed = TRUE;
 }
 
-
 /*
  * GtkWidget overrides
  */
@@ -585,6 +588,10 @@ gtd_task_row_get_property (GObject    *object,
       g_value_set_boolean (value, self->handle_subtasks);
       break;
 
+    case PROP_RENDERER:
+      g_value_set_object (value, self->renderer);
+      break;
+
     case PROP_TASK:
       g_value_set_object (value, self->task);
       break;
@@ -608,6 +615,11 @@ gtd_task_row_set_property (GObject      *object,
       gtd_task_row_set_handle_subtasks (self, g_value_get_boolean (value));
       break;
 
+    case PROP_RENDERER:
+      self->renderer = g_value_get_object (value);
+      gtd_edit_pane_set_markdown_renderer (GTD_EDIT_PANE (self->edit_panel), self->renderer);
+      break;
+
     case PROP_TASK:
       gtd_task_row_set_task (self, g_value_get_object (value));
       break;
@@ -647,6 +659,20 @@ gtd_task_row_class_init (GtdTaskRowClass *klass)
                                 TRUE,
                                 G_PARAM_READWRITE));
 
+  /**
+   * GtdTaskRow::renderer:
+   *
+   * The internal markdown renderer.
+   */
+  g_object_class_install_property (
+          object_class,
+          PROP_RENDERER,
+          g_param_spec_object ("renderer",
+                               "Renderer",
+                               "Renderer",
+                               GTD_TYPE_MARKDOWN_RENDERER,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_PRIVATE | 
G_PARAM_STATIC_STRINGS));
+
   /**
    * GtdTaskRow::task:
    *
@@ -761,10 +787,12 @@ gtd_task_row_init (GtdTaskRow *self)
 }
 
 GtkWidget*
-gtd_task_row_new (GtdTask *task)
+gtd_task_row_new (GtdTask             *task,
+                  GtdMarkdownRenderer *renderer)
 {
   return g_object_new (GTD_TYPE_TASK_ROW,
                        "task", task,
+                       "renderer", renderer,
                        NULL);
 }
 
diff --git a/src/gtd-task-row.h b/src/gtd-task-row.h
index e8fe1e8..7f4aed4 100644
--- a/src/gtd-task-row.h
+++ b/src/gtd-task-row.h
@@ -30,7 +30,8 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GtdTaskRow, gtd_task_row, GTD, TASK_ROW, GtkListBoxRow)
 
-GtkWidget*                gtd_task_row_new                      (GtdTask             *task);
+GtkWidget*                gtd_task_row_new                      (GtdTask             *task,
+                                                                 GtdMarkdownRenderer *renderer);
 
 GtdTask*                  gtd_task_row_get_task                 (GtdTaskRow          *row);
 
diff --git a/src/gtd-types.h b/src/gtd-types.h
index d2caeeb..ca2addd 100644
--- a/src/gtd-types.h
+++ b/src/gtd-types.h
@@ -31,6 +31,7 @@ typedef struct _GtdDoneButton           GtdDoneButton;
 typedef struct _GtdInitialSetupWindow   GtdInitialSetupWindow;
 typedef struct _GtdListView             GtdListView;
 typedef struct _GtdManager              GtdManager;
+typedef struct _GtdMarkdownRenderer     GtdMarkdownRenderer;
 typedef struct _GtdNotification         GtdNotification;
 typedef struct _GtdNotificationWidget   GtdNotificationWidget;
 typedef struct _GtdObject               GtdObject;
diff --git a/src/meson.build b/src/meson.build
index 79622a9..7f56334 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -66,6 +66,7 @@ sources = files(
   'gtd-edit-pane.c',
   'gtd-empty-list-widget.c',
   'gtd-initial-setup-window.c',
+  'gtd-markdown-renderer.c',
   'gtd-new-task-row.c',
   'gtd-object.c',
   'gtd-plugin-dialog.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]