[gnome-builder/wip/chergert/todo-glistmodel] plugins/todo: port to GListModel/GtkListView
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/todo-glistmodel] plugins/todo: port to GListModel/GtkListView
- Date: Mon, 25 Jul 2022 23:36:52 +0000 (UTC)
commit 842a6dd0eb5d1e5abdefc066b6d7e9dbe7247963
Author: Christian Hergert <chergert redhat com>
Date: Mon Jul 25 15:18:31 2022 -0700
plugins/todo: port to GListModel/GtkListView
This was one of the last holdouts on using TreeView, which we can remove
now in favor of GtkListView/GListModel.
src/plugins/todo/gbp-todo-item.c | 89 ++++++++--
src/plugins/todo/gbp-todo-item.h | 9 +
src/plugins/todo/gbp-todo-model.c | 317 +++++++++++++++++++++---------------
src/plugins/todo/gbp-todo-model.h | 3 +-
src/plugins/todo/gbp-todo-panel.c | 187 +++------------------
src/plugins/todo/gbp-todo-panel.ui | 96 +++++++++++
src/plugins/todo/todo.gresource.xml | 1 +
7 files changed, 390 insertions(+), 312 deletions(-)
---
diff --git a/src/plugins/todo/gbp-todo-item.c b/src/plugins/todo/gbp-todo-item.c
index c36007aa5..83a4f49bf 100644
--- a/src/plugins/todo/gbp-todo-item.c
+++ b/src/plugins/todo/gbp-todo-item.c
@@ -22,18 +22,49 @@
#include "gbp-todo-item.h"
-#define MAX_TODO_LINES 5
+G_DEFINE_FINAL_TYPE (GbpTodoItem, gbp_todo_item, G_TYPE_OBJECT)
-struct _GbpTodoItem
-{
- GObject parent_instance;
- GBytes *bytes;
- const gchar *path;
- guint lineno;
- const gchar *lines[MAX_TODO_LINES];
+enum {
+ PROP_0,
+ PROP_SUBTITLE,
+ PROP_TITLE,
+ N_PROPS
};
-G_DEFINE_FINAL_TYPE (GbpTodoItem, gbp_todo_item, G_TYPE_OBJECT)
+static GParamSpec *properties[N_PROPS];
+
+static char *
+gbp_todo_item_dup_title (GbpTodoItem *self)
+{
+ const char *path;
+ guint lineno;
+
+ g_assert (GBP_IS_TODO_ITEM (self));
+
+ path = gbp_todo_item_get_path (self);
+ lineno = gbp_todo_item_get_lineno (self);
+
+ return g_strdup_printf ("%s:%u", path, lineno);
+}
+
+static const char *
+gbp_todo_item_get_subtitle (GbpTodoItem *self)
+{
+ const char *message;
+
+ g_assert (GBP_IS_TODO_ITEM (self));
+
+ message = gbp_todo_item_get_line (self, 0);
+ /*
+ * We don't trim the whitespace from lines so that we can keep
+ * them in tact when showing tooltips. So we need to truncate
+ * here for display in the pane.
+ */
+ while (g_ascii_isspace (*message))
+ message++;
+
+ return message;
+}
static void
gbp_todo_item_finalize (GObject *object)
@@ -45,12 +76,48 @@ gbp_todo_item_finalize (GObject *object)
G_OBJECT_CLASS (gbp_todo_item_parent_class)->finalize (object);
}
+static void
+gbp_todo_item_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbpTodoItem *self = GBP_TODO_ITEM (object);
+
+ switch (prop_id)
+ {
+ case PROP_SUBTITLE:
+ g_value_set_string (value, gbp_todo_item_get_subtitle (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_take_string (value, gbp_todo_item_dup_title (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
static void
gbp_todo_item_class_init (GbpTodoItemClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gbp_todo_item_finalize;
+ object_class->get_property = gbp_todo_item_get_property;
+
+ properties [PROP_SUBTITLE] =
+ g_param_spec_string ("subtitle", NULL, NULL,
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title", NULL, NULL,
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
@@ -134,7 +201,7 @@ gbp_todo_item_set_lineno (GbpTodoItem *self,
self->lineno = lineno;
}
-const gchar *
+const char *
gbp_todo_item_get_path (GbpTodoItem *self)
{
g_return_val_if_fail (GBP_IS_TODO_ITEM (self), NULL);
@@ -144,7 +211,7 @@ gbp_todo_item_get_path (GbpTodoItem *self)
void
gbp_todo_item_set_path (GbpTodoItem *self,
- const gchar *path)
+ const char *path)
{
g_return_if_fail (GBP_IS_TODO_ITEM (self));
diff --git a/src/plugins/todo/gbp-todo-item.h b/src/plugins/todo/gbp-todo-item.h
index 4ef12a786..9ca4faa2f 100644
--- a/src/plugins/todo/gbp-todo-item.h
+++ b/src/plugins/todo/gbp-todo-item.h
@@ -28,6 +28,15 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GbpTodoItem, gbp_todo_item, GBP, TODO_ITEM, GObject)
+struct _GbpTodoItem
+{
+ GObject parent_instance;
+ GBytes *bytes;
+ const char *path;
+ guint lineno;
+ const char *lines[5];
+};
+
GbpTodoItem *gbp_todo_item_new (GBytes *bytes);
const gchar *gbp_todo_item_get_path (GbpTodoItem *self);
void gbp_todo_item_set_path (GbpTodoItem *self,
diff --git a/src/plugins/todo/gbp-todo-model.c b/src/plugins/todo/gbp-todo-model.c
index a4ceb728b..d4a5bb09d 100644
--- a/src/plugins/todo/gbp-todo-model.c
+++ b/src/plugins/todo/gbp-todo-model.c
@@ -20,6 +20,7 @@
#define G_LOG_DOMAIN "gbp-todo-model"
+#include <gtk/gtk.h>
#include <string.h>
#include <libide-code.h>
@@ -40,9 +41,11 @@
* even when deleted, is more than fine.
*/
-struct _GbpTodoModel {
- GtkListStore parent_instance;
- IdeVcs *vcs;
+struct _GbpTodoModel
+{
+ GObject parent_instance;
+ GSequence *items;
+ IdeVcs *vcs;
};
typedef struct
@@ -55,10 +58,46 @@ typedef struct
typedef struct
{
GbpTodoModel *self;
- GPtrArray *items;
+ GSequence *items;
+ GFile *file;
+ guint single_file : 1;
} ResultInfo;
-G_DEFINE_FINAL_TYPE (GbpTodoModel, gbp_todo_model, GTK_TYPE_LIST_STORE)
+static GType
+gbp_todo_model_get_item_type (GListModel *model)
+{
+ return GBP_TYPE_TODO_ITEM;
+}
+
+static guint
+gbp_todo_model_get_n_items (GListModel *model)
+{
+ return g_sequence_get_length (GBP_TODO_MODEL (model)->items);
+}
+
+static gpointer
+gbp_todo_model_get_item (GListModel *model,
+ guint position)
+{
+ GbpTodoModel *self = GBP_TODO_MODEL (model);
+ GSequenceIter *iter = g_sequence_get_iter_at_pos (self->items, position);
+
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+
+ return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gbp_todo_model_get_item_type;
+ iface->get_n_items = gbp_todo_model_get_n_items;
+ iface->get_item = gbp_todo_model_get_item;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpTodoModel, gbp_todo_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
enum {
PROP_0,
@@ -70,7 +109,7 @@ static GParamSpec *properties [N_PROPS];
static GRegex *line1;
static GRegex *line2;
-static const gchar *exclude_dirs[] = {
+static const char *exclude_dirs[] = {
".bzr",
".flatpak-builder",
".git",
@@ -82,7 +121,7 @@ static const gchar *exclude_dirs[] = {
* we know we'll discard, rather than wait until we query the IdeVcs
* for that information.
*/
-static const gchar *exclude_files[] = {
+static const char *exclude_files[] = {
"*~",
"*.swp",
"*.m4",
@@ -93,7 +132,7 @@ static const gchar *exclude_files[] = {
"Makecache",
};
-static const gchar *keywords[] = {
+static const char *keywords[] = {
"FIXME",
"XXX",
"TODO",
@@ -114,99 +153,11 @@ result_info_free (gpointer data)
ResultInfo *info = data;
g_clear_object (&info->self);
- g_clear_pointer (&info->items, g_ptr_array_unref);
+ g_clear_object (&info->file);
+ g_clear_pointer (&info->items, g_sequence_free);
g_slice_free (ResultInfo, info);
}
-static void
-gbp_todo_model_clear (GbpTodoModel *self,
- const gchar *path)
-{
- GtkTreeIter iter;
- gboolean matched = FALSE;
-
- g_assert (GBP_IS_TODO_MODEL (self));
- g_assert (path != NULL);
-
- if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self), &iter))
- {
- do
- {
- g_autoptr(GbpTodoItem) item = NULL;
- const gchar *item_path;
-
- again:
-
- gtk_tree_model_get (GTK_TREE_MODEL (self), &iter, 0, &item, -1);
- item_path = gbp_todo_item_get_path (item);
-
- if (g_strcmp0 (path, item_path) == 0)
- {
- if (!gtk_list_store_remove (GTK_LIST_STORE (self), &iter))
- break;
- matched = TRUE;
- g_clear_object (&item);
- goto again;
- }
- else if (matched)
- {
- /* We skipped past our last match, so we might as well
- * short-circuit and avoid looking at more rows.
- */
- break;
- }
- }
- while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self), &iter));
- }
-}
-
-static void
-gbp_todo_model_merge (GbpTodoModel *self,
- GbpTodoItem *item)
-{
- GtkTreeIter iter;
-
- g_assert (GBP_IS_TODO_MODEL (self));
- g_assert (GBP_IS_TODO_ITEM (item));
-
- gtk_list_store_prepend (GTK_LIST_STORE (self), &iter);
- gtk_list_store_set (GTK_LIST_STORE (self), &iter, 0, item, -1);
-}
-
-static gboolean
-gbp_todo_model_merge_results (gpointer user_data)
-{
- ResultInfo *info = user_data;
- const gchar *last_path = NULL;
- gboolean needs_clear = FALSE;
-
- g_assert (info != NULL);
- g_assert (GBP_IS_TODO_MODEL (info->self));
- g_assert (info->items != NULL);
-
- /* Try to avoid clearing on the initial build of the model */
- if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (info->self), NULL) > 0)
- needs_clear = TRUE;
-
- /* Walk backwards to preserve ordering, as merging will always prepend
- * the item to the store.
- */
- for (guint i = info->items->len; i > 0; i--)
- {
- GbpTodoItem *item = g_ptr_array_index (info->items, i - 1);
- const gchar *path = gbp_todo_item_get_path (item);
-
- if (needs_clear && (g_strcmp0 (path, last_path) != 0))
- gbp_todo_model_clear (info->self, path);
-
- gbp_todo_model_merge (info->self, item);
-
- last_path = path;
- }
-
- return G_SOURCE_REMOVE;
-}
-
static void
gbp_todo_model_get_property (GObject *object,
guint prop_id,
@@ -250,7 +201,7 @@ gbp_todo_model_dispose (GObject *object)
{
GbpTodoModel *self = (GbpTodoModel *)object;
- g_clear_object (&self->vcs);
+ g_clear_pointer (&self->items, g_sequence_free);
G_OBJECT_CLASS (gbp_todo_model_parent_class)->dispose (object);
}
@@ -286,11 +237,7 @@ gbp_todo_model_class_init (GbpTodoModelClass *klass)
static void
gbp_todo_model_init (GbpTodoModel *self)
{
- GType column_types[] = { GBP_TYPE_TODO_ITEM };
-
- gtk_list_store_set_column_types (GTK_LIST_STORE (self),
- G_N_ELEMENTS (column_types),
- column_types);
+ self->items = g_sequence_new (g_object_unref);
}
/**
@@ -309,6 +256,98 @@ gbp_todo_model_new (IdeVcs *vcs)
NULL);
}
+static int
+gbp_todo_model_compare_func (const GbpTodoItem *a,
+ const GbpTodoItem *b)
+{
+ return g_strcmp0 (a->path, b->path);
+}
+
+static gboolean
+result_info_merge (gpointer user_data)
+{
+ ResultInfo *r = user_data;
+ guint added;
+
+ g_assert (r != NULL);
+ g_assert (GBP_IS_TODO_MODEL (r->self));
+ g_assert (G_IS_FILE (r->file));
+ g_assert (r->items != NULL);
+
+ added = g_sequence_get_length (r->items);
+
+ if (!r->single_file)
+ {
+ g_autoptr(GSequence) old_seq = g_steal_pointer (&r->self->items);
+ guint old_len = g_sequence_get_length (old_seq);
+
+ /* If we are not in single-file mode, then we just indexed the entire
+ * project directory tree. Just swap out our items lists and notify
+ * the list model interface of changes.
+ */
+ r->self->items = g_steal_pointer (&r->items);
+
+ if (old_len || added)
+ g_list_model_items_changed (G_LIST_MODEL (r->self), 0, old_len, added);
+
+ return G_SOURCE_REMOVE;
+ }
+ else
+ {
+ GbpTodoItem *first = g_sequence_get (g_sequence_get_begin_iter (r->items));
+ GSequenceIter *iter;
+ GSequenceIter *prev;
+ GSequenceIter *next;
+ guint position;
+ guint removed = 0;
+
+ /* We parsed a single file for TODOs, so we need to remove all of the old
+ * items first. We search for a GSequenceIter then walk backwards to the
+ * first matching position (since multiple iters returning TRUE from the
+ * compare func do not guarantee we're given the first). After the last
+ * iter is removed, we can start inserting our sorted result set.
+ */
+
+ iter = g_sequence_search (r->self->items,
+ first,
+ (GCompareDataFunc)gbp_todo_model_compare_func,
+ NULL);
+
+ g_assert (iter != NULL);
+
+ /* Walk backwards to first match. Necessary because GSequence does not
+ * guarantee our binary search will find the first matching iter when
+ * multiple iters may compare equally.
+ */
+ while ((prev = g_sequence_iter_prev (iter)) &&
+ (prev != iter) &&
+ gbp_todo_model_compare_func (g_sequence_get (prev), first) == 0)
+ iter = prev;
+
+ position = g_sequence_iter_get_position (iter);
+
+ while ((next = g_sequence_iter_next (iter)) &&
+ (next != iter) &&
+ gbp_todo_model_compare_func (g_sequence_get (iter), first) == 0)
+ {
+ g_sequence_remove (iter);
+ iter = next;
+ removed++;
+ }
+
+ if (added > 0)
+ g_sequence_move_range (iter,
+ g_sequence_get_begin_iter (r->items),
+ g_sequence_iter_prev (g_sequence_get_end_iter (r->items)));
+
+ g_list_model_items_changed (G_LIST_MODEL (r->self), position, removed, added);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ g_assert_not_reached ();
+}
+
static void
gbp_todo_model_mine_worker (IdeTask *task,
gpointer source_object,
@@ -318,17 +357,19 @@ gbp_todo_model_mine_worker (IdeTask *task,
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(IdeSubprocess) subprocess = NULL;
g_autoptr(GError) error = NULL;
- g_autoptr(GPtrArray) items = NULL;
+ g_autoptr(GSequence) items = NULL;
g_autoptr(GbpTodoItem) item = NULL;
+ g_autoptr(GListStore) store = NULL;
g_autoptr(GBytes) bytes = NULL;
g_autoptr(GTimer) timer = g_timer_new ();
- g_autofree gchar *workpath = NULL;
+ g_autofree char *workpath = NULL;
GbpTodoModel *self = source_object;
Mine *m = task_data;
IdeLineReader reader;
ResultInfo *info;
- gchar *stdoutstr = NULL;
- gchar *line;
+ gboolean single_file = FALSE;
+ char *stdoutstr = NULL;
+ char *line;
gsize pathlen = 0;
gsize stdoutstr_len;
gsize len;
@@ -341,7 +382,8 @@ gbp_todo_model_mine_worker (IdeTask *task,
launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
- if (!(workpath = g_file_get_path (m->workdir)))
+ if (!g_file_is_native (m->workdir) ||
+ !(workpath = g_file_get_path (m->workdir)))
{
ide_task_return_new_error (task,
G_IO_ERROR,
@@ -382,8 +424,8 @@ gbp_todo_model_mine_worker (IdeTask *task,
{
for (guint i = 0; i < G_N_ELEMENTS (exclude_files); i++)
{
- const gchar *exclude_file = exclude_files[i];
- g_autofree gchar *arg = NULL;
+ const char *exclude_file = exclude_files[i];
+ g_autofree char *arg = NULL;
arg = g_strdup_printf ("--exclude=%s", exclude_file);
ide_subprocess_launcher_push_argv (launcher, arg);
@@ -391,8 +433,8 @@ gbp_todo_model_mine_worker (IdeTask *task,
for (guint i = 0; i < G_N_ELEMENTS (exclude_dirs); i++)
{
- const gchar *exclude_dir = exclude_dirs[i];
- g_autofree gchar *arg = NULL;
+ const char *exclude_dir = exclude_dirs[i];
+ g_autofree char *arg = NULL;
arg = g_strdup_printf ("--exclude-dir=%s", exclude_dir);
ide_subprocess_launcher_push_argv (launcher, arg);
@@ -401,8 +443,8 @@ gbp_todo_model_mine_worker (IdeTask *task,
for (guint i = 0; i < G_N_ELEMENTS (keywords); i++)
{
- const gchar *keyword = keywords[i];
- g_autofree gchar *arg = NULL;
+ const char *keyword = keywords[i];
+ g_autofree char *arg = NULL;
arg = g_strdup_printf ("%s(:| )", keyword);
ide_subprocess_launcher_push_argv (launcher, "-e");
@@ -425,14 +467,12 @@ gbp_todo_model_mine_worker (IdeTask *task,
if (g_file_query_file_type (m->file, 0, NULL) != G_FILE_TYPE_DIRECTORY)
{
- g_autofree gchar *path = NULL;
-
- path = g_file_get_path (m->workdir);
- ide_subprocess_launcher_push_argv (launcher, path);
+ ide_subprocess_launcher_push_argv (launcher, g_file_peek_path (m->file));
+ single_file = TRUE;
}
/* Spawn our grep process */
- if (NULL == (subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error)))
+ if (!(subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error)))
{
ide_task_return_error (task, g_steal_pointer (&error));
return;
@@ -455,7 +495,7 @@ gbp_todo_model_mine_worker (IdeTask *task,
stdoutstr_len = strlen (stdoutstr);
bytes = g_bytes_new_take (stdoutstr, stdoutstr_len);
- items = g_ptr_array_new_with_free_func (g_object_unref);
+ items = g_sequence_new (g_object_unref);
/*
* Process the STDOUT string iteratively, while trying to be tidy
@@ -463,7 +503,7 @@ gbp_todo_model_mine_worker (IdeTask *task,
* a \0, we can use it as a C string.
*/
ide_line_reader_init (&reader, stdoutstr, stdoutstr_len);
- while (NULL != (line = ide_line_reader_next (&reader, &len)))
+ while ((line = ide_line_reader_next (&reader, &len)))
{
g_autoptr(GMatchInfo) match_info1 = NULL;
g_autoptr(GMatchInfo) match_info2 = NULL;
@@ -480,11 +520,11 @@ gbp_todo_model_mine_worker (IdeTask *task,
{
if (m->use_git_grep)
{
- g_ptr_array_add (items, g_steal_pointer (&item));
+ g_sequence_append (items, g_steal_pointer (&item));
}
else
{
- const gchar *pathstr = gbp_todo_item_get_path (item);
+ const char *pathstr = gbp_todo_item_get_path (item);
/*
* self->vcs is only set at construction, so safe to
@@ -492,7 +532,7 @@ gbp_todo_model_mine_worker (IdeTask *task,
* is expected to be thread-safe as well.
*/
if (!ide_vcs_path_is_ignored (self->vcs, pathstr, NULL))
- g_ptr_array_add (items, g_steal_pointer (&item));
+ g_sequence_append (items, g_steal_pointer (&item));
else
g_clear_object (&item);
}
@@ -523,7 +563,7 @@ gbp_todo_model_mine_worker (IdeTask *task,
/* Get the path */
if (g_match_info_fetch_pos (match_info1, 1, &begin, &end))
{
- const gchar *pathstr;
+ const char *pathstr;
line[end] = '\0';
pathstr = &line[begin];
@@ -581,11 +621,11 @@ gbp_todo_model_mine_worker (IdeTask *task,
{
if (m->use_git_grep)
{
- g_ptr_array_add (items, g_steal_pointer (&item));
+ g_sequence_append (items, g_steal_pointer (&item));
}
else
{
- const gchar *pathstr = gbp_todo_item_get_path (item);
+ const char *pathstr = gbp_todo_item_get_path (item);
/*
* self->vcs is only set at construction, so safe to
@@ -593,29 +633,36 @@ gbp_todo_model_mine_worker (IdeTask *task,
* is expected to be thread-safe as well.
*/
if (!ide_vcs_path_is_ignored (self->vcs, pathstr, NULL))
- g_ptr_array_add (items, g_steal_pointer (&item));
+ g_sequence_append (items, g_steal_pointer (&item));
else
g_clear_object (&item);
}
}
- g_debug ("Located %u TODO items in %0.4lf seconds",
- items->len, g_timer_elapsed (timer, NULL));
+ g_debug ("Located %d TODO items in %0.4lf seconds",
+ g_sequence_get_length (items),
+ g_timer_elapsed (timer, NULL));
info = g_slice_new0 (ResultInfo);
info->self = g_object_ref (source_object);
info->items = g_steal_pointer (&items);
+ info->file = g_object_ref (m->file);
+ info->single_file = single_file;
+
+ /* Sort our result set to help reduce how much sorting
+ * needs to be done on the main thread later.
+ */
+ g_sequence_sort (info->items, (GCompareDataFunc)gbp_todo_model_compare_func, NULL);
g_idle_add_full (G_PRIORITY_LOW + 100,
- gbp_todo_model_merge_results,
- info, result_info_free);
+ result_info_merge, info, result_info_free);
ide_task_return_boolean (task, TRUE);
}
static gboolean
-is_typed (IdeVcs *vcs,
- const gchar *name)
+is_typed (IdeVcs *vcs,
+ const char *name)
{
return g_strcmp0 (G_OBJECT_TYPE_NAME (vcs), name) == 0;
}
diff --git a/src/plugins/todo/gbp-todo-model.h b/src/plugins/todo/gbp-todo-model.h
index 95e04ea79..ac93dccd2 100644
--- a/src/plugins/todo/gbp-todo-model.h
+++ b/src/plugins/todo/gbp-todo-model.h
@@ -20,14 +20,13 @@
#pragma once
-#include <gtk/gtk.h>
#include <libide-vcs.h>
G_BEGIN_DECLS
#define GBP_TYPE_TODO_MODEL (gbp_todo_model_get_type())
-G_DECLARE_FINAL_TYPE (GbpTodoModel, gbp_todo_model, GBP, TODO_MODEL, GtkListStore)
+G_DECLARE_FINAL_TYPE (GbpTodoModel, gbp_todo_model, GBP, TODO_MODEL, GObject)
GbpTodoModel *gbp_todo_model_new (IdeVcs *vcs);
void gbp_todo_model_mine_async (GbpTodoModel *self,
diff --git a/src/plugins/todo/gbp-todo-panel.c b/src/plugins/todo/gbp-todo-panel.c
index a2fb3845f..47554e2f6 100644
--- a/src/plugins/todo/gbp-todo-panel.c
+++ b/src/plugins/todo/gbp-todo-panel.c
@@ -30,12 +30,12 @@
struct _GbpTodoPanel
{
- IdePane parent_instance;
+ IdePane parent_instance;
- GbpTodoModel *model;
+ GbpTodoModel *model;
- GtkTreeView *tree_view;
- GtkStack *stack;
+ GtkNoSelection *selection;
+ GtkStack *stack;
};
G_DEFINE_FINAL_TYPE (GbpTodoPanel, gbp_todo_panel, IDE_TYPE_PANE)
@@ -49,68 +49,24 @@ enum {
static GParamSpec *properties [N_PROPS];
static void
-gbp_todo_panel_cell_data_func (GtkCellLayout *cell_layout,
- GtkCellRenderer *cell,
- GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- gpointer data)
-{
- g_autoptr(GbpTodoItem) item = NULL;
- const gchar *message;
-
- gtk_tree_model_get (tree_model, iter, 0, &item, -1);
-
- message = gbp_todo_item_get_line (item, 0);
-
- if (message != NULL)
- {
- g_autofree gchar *title = NULL;
- const gchar *path;
- guint lineno;
-
- /*
- * We don't trim the whitespace from lines so that we can keep
- * them in tact when showing tooltips. So we need to truncate
- * here for display in the pane.
- */
- while (g_ascii_isspace (*message))
- message++;
-
- path = gbp_todo_item_get_path (item);
- lineno = gbp_todo_item_get_lineno (item);
- title = g_strdup_printf ("%s:%u", path, lineno);
- ide_cell_renderer_fancy_take_title (IDE_CELL_RENDERER_FANCY (cell),
- g_steal_pointer (&title));
- ide_cell_renderer_fancy_set_body (IDE_CELL_RENDERER_FANCY (cell), message);
- }
- else
- {
- ide_cell_renderer_fancy_set_body (IDE_CELL_RENDERER_FANCY (cell), NULL);
- ide_cell_renderer_fancy_set_title (IDE_CELL_RENDERER_FANCY (cell), NULL);
- }
-}
-
-static void
-gbp_todo_panel_row_activated (GbpTodoPanel *self,
- GtkTreePath *tree_path,
- GtkTreeViewColumn *column,
- GtkTreeView *tree_view)
+gbp_todo_panel_activate_cb (GbpTodoPanel *self,
+ guint position,
+ GtkListView *list_view)
{
g_autoptr(GbpTodoItem) item = NULL;
g_autoptr(GFile) file = NULL;
IdeWorkbench *workbench;
- GtkTreeModel *model;
- const gchar *path;
- GtkTreeIter iter;
+ GListModel *model;
+ const char *path;
guint lineno;
g_assert (GBP_IS_TODO_PANEL (self));
- g_assert (tree_path != NULL);
- g_assert (GTK_IS_TREE_VIEW (tree_view));
+ g_assert (GTK_IS_LIST_VIEW (list_view));
- model = gtk_tree_view_get_model (tree_view);
- gtk_tree_model_get_iter (model, &iter, tree_path);
- gtk_tree_model_get (model, &iter, 0, &item, -1);
+ model = G_LIST_MODEL (gtk_list_view_get_model (list_view));
+ g_assert (G_IS_LIST_MODEL (model));
+
+ item = g_list_model_get_item (model, position);
g_assert (GBP_IS_TODO_ITEM (item));
workbench = ide_widget_get_workbench (GTK_WIDGET (self));
@@ -145,68 +101,13 @@ gbp_todo_panel_row_activated (GbpTodoPanel *self,
ide_workbench_open_at_async (workbench,
file,
- "editor",
+ "editorui",
lineno,
-1,
IDE_BUFFER_OPEN_FLAGS_NONE,
NULL, NULL, NULL, NULL);
}
-static gboolean
-gbp_todo_panel_query_tooltip (GbpTodoPanel *self,
- gint x,
- gint y,
- gboolean keyboard_mode,
- GtkTooltip *tooltip,
- GtkTreeView *tree_view)
-{
- g_autoptr(GtkTreePath) path = NULL;
- GtkTreeModel *model;
-
- g_assert (GBP_IS_TODO_PANEL (self));
- g_assert (GTK_IS_TOOLTIP (tooltip));
- g_assert (GTK_IS_TREE_VIEW (tree_view));
-
- if (NULL == (model = gtk_tree_view_get_model (tree_view)))
- return FALSE;
-
- if (gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, NULL, NULL, NULL))
- {
- GtkTreeIter iter;
-
- if (gtk_tree_model_get_iter (model, &iter, path))
- {
- g_autoptr(GbpTodoItem) item = NULL;
- g_autoptr(GString) str = g_string_new ("<tt>");
-
- gtk_tree_model_get (model, &iter, 0, &item, -1);
- g_assert (GBP_IS_TODO_ITEM (item));
-
- /* only 5 lines stashed */
- for (guint i = 0; i < 5; i++)
- {
- const gchar *line = gbp_todo_item_get_line (item, i);
- g_autofree gchar *escaped = NULL;
-
- if (!line)
- break;
-
- escaped = g_markup_escape_text (line, -1);
- g_string_append (str, escaped);
- g_string_append_c (str, '\n');
- }
-
- g_string_append (str, "</tt>");
- gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
- gtk_tooltip_set_markup (tooltip, str->str);
-
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
static void
gbp_todo_panel_dispose (GObject *object)
{
@@ -214,9 +115,6 @@ gbp_todo_panel_dispose (GObject *object)
g_assert (GBP_IS_TODO_PANEL (self));
- if (self->tree_view != NULL)
- gtk_tree_view_set_model (self->tree_view, NULL);
-
g_clear_object (&self->model);
G_OBJECT_CLASS (gbp_todo_panel_parent_class)->dispose (object);
@@ -264,6 +162,7 @@ static void
gbp_todo_panel_class_init (GbpTodoPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = gbp_todo_panel_dispose;
object_class->get_property = gbp_todo_panel_get_property;
@@ -277,53 +176,17 @@ gbp_todo_panel_class_init (GbpTodoPanelClass *klass)
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/plugins/todo/gbp-todo-panel.ui");
+ gtk_widget_class_bind_template_child (widget_class, GbpTodoPanel, selection);
+ gtk_widget_class_bind_template_child (widget_class, GbpTodoPanel, stack);
+ gtk_widget_class_bind_template_callback (widget_class, gbp_todo_panel_activate_cb);
}
static void
gbp_todo_panel_init (GbpTodoPanel *self)
{
- GtkWidget *scroller;
- GtkWidget *empty;
- GtkTreeSelection *selection;
-
- self->stack = g_object_new (GTK_TYPE_STACK,
- "transition-duration", 333,
- "transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
- NULL);
- panel_widget_set_child (PANEL_WIDGET (self), GTK_WIDGET (self->stack));
-
- empty = g_object_new (ADW_TYPE_STATUS_PAGE,
- "title", _("Loading TODOs…"),
- "description", _("Please wait while we scan your project"),
- "icon-name", "builder-todo-symbolic",
- NULL);
- gtk_stack_add_named (self->stack, empty, "empty");
-
- scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
- "vexpand", TRUE,
- NULL);
- gtk_stack_add_named (self->stack, scroller, "todos");
-
- self->tree_view = g_object_new (IDE_TYPE_FANCY_TREE_VIEW,
- "has-tooltip", TRUE,
- NULL);
- g_signal_connect_swapped (self->tree_view,
- "row-activated",
- G_CALLBACK (gbp_todo_panel_row_activated),
- self);
- g_signal_connect_swapped (self->tree_view,
- "query-tooltip",
- G_CALLBACK (gbp_todo_panel_query_tooltip),
- self);
- gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroller), GTK_WIDGET (self->tree_view));
-
- selection = gtk_tree_view_get_selection (self->tree_view);
- gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
-
- ide_fancy_tree_view_set_data_func (IDE_FANCY_TREE_VIEW (self->tree_view),
- gbp_todo_panel_cell_data_func, NULL, NULL);
-
- gtk_widget_add_css_class (GTK_WIDGET (self->tree_view), "navigation-sidebar");
+ gtk_widget_init_template (GTK_WIDGET (self));
}
/**
@@ -351,11 +214,7 @@ gbp_todo_panel_set_model (GbpTodoPanel *self,
if (g_set_object (&self->model, model))
{
- if (self->model != NULL)
- gtk_tree_view_set_model (self->tree_view, GTK_TREE_MODEL (self->model));
- else
- gtk_tree_view_set_model (self->tree_view, NULL);
-
+ gtk_no_selection_set_model (self->selection, G_LIST_MODEL (model));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODEL]);
}
}
diff --git a/src/plugins/todo/gbp-todo-panel.ui b/src/plugins/todo/gbp-todo-panel.ui
new file mode 100644
index 000000000..04c74c5f5
--- /dev/null
+++ b/src/plugins/todo/gbp-todo-panel.ui
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GbpTodoPanel" parent="IdePane">
+ <property name="title" translatable="yes">TODO/FIXMEs</property>
+ <property name="icon-name">builder-todo-symbolic</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">loading</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">builder-todo-symbolic</property>
+ <property name="title" translatable="yes">Loading TODOs…</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">todos</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">true</property>
+ <child>
+ <object class="GtkListView" id="list_view">
+ <signal name="activate" handler="gbp_todo_panel_activate_cb" swapped="true"
object="GbpTodoPanel"/>
+ <property name="orientation">vertical</property>
+ <property name="single-click-activate">true</property>
+ <property name="model">
+ <object class="GtkNoSelection" id="selection">
+ </object>
+ </property>
+ <property name="factory">
+ <object class="GtkBuilderListItemFactory">
+ <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GtkListItem">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkInscription">
+ <property name="text-overflow">ellipsize-start</property>
+ <property name="xalign">0</property>
+ <binding name="text">
+ <lookup name="title" type="GbpTodoItem">
+ <lookup name="item">GtkListItem</lookup>
+ </lookup>
+ </binding>
+ <style>
+ <class name="heading"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkInscription">
+ <property name="wrap-mode">char</property>
+ <property name="xalign">0</property>
+ <binding name="text">
+ <lookup name="subtitle" type="GbpTodoItem">
+ <lookup name="item">GtkListItem</lookup>
+ </lookup>
+ </binding>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
+]]>
+ </property>
+ </object>
+ </property>
+ <style>
+ <class name="navigation-sidebar"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/plugins/todo/todo.gresource.xml b/src/plugins/todo/todo.gresource.xml
index 991ecb4a8..c854509b0 100644
--- a/src/plugins/todo/todo.gresource.xml
+++ b/src/plugins/todo/todo.gresource.xml
@@ -2,5 +2,6 @@
<gresources>
<gresource prefix="/plugins/todo">
<file>todo.plugin</file>
+ <file preprocess="xml-stripblanks">gbp-todo-panel.ui</file>
</gresource>
</gresources>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]