[gnome-todo/vyasgiridhar/markdown: 3/3] markdown: Introduce Markdown support for task descriptions
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-todo/vyasgiridhar/markdown: 3/3] markdown: Introduce Markdown support for task descriptions
- Date: Fri, 30 Mar 2018 11:24:47 +0000 (UTC)
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]