[gnome-todo/gbsneto/listview2: 5/5] Initial port to GtkListView




commit e6b4270fcfa00b2947cfff61cba8137131bb722c
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sun Sep 27 14:43:04 2020 -0300

    Initial port to GtkListView
    
    Various APIs needed to be revisited or simply removed to support this.

 src/gui/gtd-edit-pane.c                   |  10 +-
 src/gui/gtd-new-task-row.ui               |  11 +-
 src/gui/gtd-task-list-popover.c           |   4 +-
 src/gui/gtd-task-list-view-model.c        | 210 +++++++
 src/gui/gtd-task-list-view-model.h        |  43 ++
 src/gui/gtd-task-list-view.c              | 885 ++++--------------------------
 src/gui/gtd-task-list-view.h              |  22 -
 src/gui/gtd-task-list-view.ui             | 102 ++--
 src/gui/gtd-task-row.c                    |  91 +--
 src/gui/gtd-task-row.h                    |  10 -
 src/gui/gtd-task-row.ui                   |  14 +
 src/meson.build                           |   1 +
 src/plugins/inbox-panel/gtd-inbox-panel.c |   2 -
 13 files changed, 483 insertions(+), 922 deletions(-)
---
diff --git a/src/gui/gtd-edit-pane.c b/src/gui/gtd-edit-pane.c
index 9f9dabc..07612fc 100644
--- a/src/gui/gtd-edit-pane.c
+++ b/src/gui/gtd-edit-pane.c
@@ -74,25 +74,25 @@ static void
 update_date_widgets (GtdEditPane *self)
 {
   g_autoptr (GDateTime) dt = NULL;
-  gchar *text;
+  g_autofree gchar *text = NULL;
 
   g_return_if_fail (GTD_IS_EDIT_PANE (self));
 
-  dt = self->task ? g_date_time_ref (gtd_task_get_due_date (self->task)) : NULL;
-  text = dt ? g_date_time_format (dt, "%x") : NULL;
+  dt = self->task ? gtd_task_get_due_date (self->task) : NULL;
 
   g_signal_handlers_block_by_func (self->calendar, on_date_selected_cb, self);
 
   if (!dt)
     dt = g_date_time_new_now_local ();
+  else
+    g_date_time_ref (dt);
 
   gtk_calendar_select_day (self->calendar, dt);
 
   g_signal_handlers_unblock_by_func (self->calendar, on_date_selected_cb, self);
 
+  text = dt ? g_date_time_format (dt, "%x") : NULL;
   gtk_label_set_label (self->date_label, text ? text : _("No date set"));
-
-  g_free (text);
 }
 
 
diff --git a/src/gui/gtd-new-task-row.ui b/src/gui/gtd-new-task-row.ui
index b28a126..12a0df0 100644
--- a/src/gui/gtd-new-task-row.ui
+++ b/src/gui/gtd-new-task-row.ui
@@ -3,9 +3,18 @@
   <requires lib="gtk+" version="3.16"/>
   <template class="GtdNewTaskRow" parent="GtkWidget">
     <property name="can_focus">1</property>
-    <property name="margin-top">12</property>
+    <property name="margin-start">12</property>
+    <property name="margin-end">12</property>
+    <property name="margin-top">6</property>
+    <property name="margin-bottom">6</property>
     <property name="height-request">42</property>
     <property name="css-name">newtaskrow</property>
+    <property name="halign">center</property>
+    <property name="layout-manager">
+      <object class="GtdMaxSizeLayout">
+        <property name="max-width">700</property>
+      </object>
+    </property>
     <child>
       <object class="GtkEntry" id="entry">
         <property name="can_focus">1</property>
diff --git a/src/gui/gtd-task-list-popover.c b/src/gui/gtd-task-list-popover.c
index 176c0e5..a81d461 100644
--- a/src/gui/gtd-task-list-popover.c
+++ b/src/gui/gtd-task-list-popover.c
@@ -243,12 +243,12 @@ gtd_task_list_popover_init (GtdTaskListPopover *self)
   GtdManager *manager = gtd_manager_get_default ();
   GtkCustomFilter *filter;
 
+  gtk_widget_init_template (GTK_WIDGET (self));
+
   filter = gtk_custom_filter_new (filter_listbox_cb, self, NULL);
   self->filter_model = gtk_filter_list_model_new (gtd_manager_get_task_lists_model (manager),
                                                   GTK_FILTER (filter));
 
-  gtk_widget_init_template (GTK_WIDGET (self));
-
   gtk_list_box_bind_model (self->listbox,
                            G_LIST_MODEL (self->filter_model),
                            create_list_row_cb,
diff --git a/src/gui/gtd-task-list-view-model.c b/src/gui/gtd-task-list-view-model.c
new file mode 100644
index 0000000..87b6f41
--- /dev/null
+++ b/src/gui/gtd-task-list-view-model.c
@@ -0,0 +1,210 @@
+/* gtd-task-list-view-model.c
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges stavracas 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "gtd-task-list-view-model.h"
+
+
+/*
+ * Sentinel
+ */
+
+struct _GtdSentinel
+{
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE (GtdSentinel, gtd_sentinel, G_TYPE_OBJECT);
+
+static void
+gtd_sentinel_init (GtdSentinel *self)
+{
+}
+
+static void
+gtd_sentinel_class_init (GtdSentinelClass *klass)
+{
+}
+
+
+
+struct _GtdTaskListViewModel
+{
+  GObject             parent_instance;
+
+  GtdSentinel        *sentinel;
+
+  GListModel         *model;
+  guint               n_items;
+};
+
+static void g_list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtdTaskListViewModel, gtd_task_list_view_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+update_n_items (GtdTaskListViewModel *self,
+                guint                 position,
+                guint                 removed,
+                guint                 added)
+{
+  self->n_items = self->n_items - removed + added;
+  g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+on_model_items_changed_cb (GListModel           *model,
+                           guint                 position,
+                           guint                 removed,
+                           guint                 added,
+                           GtdTaskListViewModel *self)
+{
+  update_n_items (self, position, removed, added);
+}
+
+
+/*
+ * GListModel interface
+ */
+
+static guint
+gtd_task_list_view_model_get_n_items (GListModel *model)
+{
+  GtdTaskListViewModel *self = (GtdTaskListViewModel *)model;
+
+  return self->n_items + 1;
+}
+
+static GType
+gtd_task_list_view_model_get_item_type (GListModel *model)
+{
+  return G_TYPE_OBJECT;
+}
+
+static gpointer
+gtd_task_list_view_model_get_item (GListModel *model,
+                                   guint       position)
+{
+  GtdTaskListViewModel *self = (GtdTaskListViewModel *)model;
+
+  if (gtd_task_list_view_model_is_sentinel (self, position))
+    return g_object_ref (self->sentinel);
+
+  return g_list_model_get_item (self->model, position);
+}
+
+static void
+g_list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = gtd_task_list_view_model_get_n_items;
+  iface->get_item_type = gtd_task_list_view_model_get_item_type;
+  iface->get_item = gtd_task_list_view_model_get_item;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_list_view_model_finalize (GObject *object)
+{
+  GtdTaskListViewModel *self = (GtdTaskListViewModel *)object;
+
+  g_clear_object (&self->model);
+  g_clear_object (&self->sentinel);
+
+  G_OBJECT_CLASS (gtd_task_list_view_model_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_list_view_model_class_init (GtdTaskListViewModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtd_task_list_view_model_finalize;
+}
+
+static void
+gtd_task_list_view_model_init (GtdTaskListViewModel *self)
+{
+  self->sentinel = g_object_new (GTD_TYPE_SENTINEL, NULL);
+}
+
+GtdTaskListViewModel *
+gtd_task_list_view_model_new (void)
+{
+  return g_object_new (GTD_TYPE_TASK_LIST_VIEW_MODEL, NULL);
+}
+
+GListModel *
+gtd_task_list_view_model_get_model (GtdTaskListViewModel *self)
+{
+  return self->model;
+}
+
+void
+gtd_task_list_view_model_set_model (GtdTaskListViewModel *self,
+                                    GListModel           *model)
+{
+  guint old_n_items = self->n_items;
+  guint new_n_items = 0;
+
+  if (self->model)
+    {
+      g_signal_handlers_disconnect_by_func (self->model,
+                                            on_model_items_changed_cb,
+                                            self);
+    }
+
+  g_clear_object (&self->model);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+
+      g_signal_connect_object (model,
+                               "items-changed",
+                               G_CALLBACK (on_model_items_changed_cb),
+                               self,
+                               0);
+
+      new_n_items = g_list_model_get_n_items (model);
+    }
+
+  update_n_items (self, 0, old_n_items, new_n_items);
+}
+
+gboolean
+gtd_task_list_view_model_is_sentinel (GtdTaskListViewModel *self,
+                                      guint                 position)
+{
+  return position == self->n_items;
+}
diff --git a/src/gui/gtd-task-list-view-model.h b/src/gui/gtd-task-list-view-model.h
new file mode 100644
index 0000000..7323f3a
--- /dev/null
+++ b/src/gui/gtd-task-list-view-model.h
@@ -0,0 +1,43 @@
+/* gtd-task-list-view-model.h
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges stavracas 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_SENTINEL (gtd_sentinel_get_type ())
+G_DECLARE_FINAL_TYPE (GtdSentinel, gtd_sentinel, GTD, SENTINEL, GObject)
+
+#define GTD_TYPE_TASK_LIST_VIEW_MODEL (gtd_task_list_view_model_get_type())
+G_DECLARE_FINAL_TYPE (GtdTaskListViewModel, gtd_task_list_view_model, GTD, TASK_LIST_VIEW_MODEL, GObject)
+
+GtdTaskListViewModel* gtd_task_list_view_model_new (void);
+
+GListModel* gtd_task_list_view_model_get_model (GtdTaskListViewModel *self);
+
+void gtd_task_list_view_model_set_model (GtdTaskListViewModel *self,
+                                         GListModel           *model);
+
+gboolean gtd_task_list_view_model_is_sentinel (GtdTaskListViewModel *self,
+                                               guint                 position);
+
+G_END_DECLS
diff --git a/src/gui/gtd-task-list-view.c b/src/gui/gtd-task-list-view.c
index bf89e02..d76edf5 100644
--- a/src/gui/gtd-task-list-view.c
+++ b/src/gui/gtd-task-list-view.c
@@ -30,6 +30,7 @@
 #include "gtd-provider.h"
 #include "gtd-task.h"
 #include "gtd-task-list.h"
+#include "gtd-task-list-view-model.h"
 #include "gtd-task-row.h"
 #include "gtd-utils-private.h"
 #include "gtd-widget.h"
@@ -58,9 +59,6 @@
  *
  * gtd_task_list_view_set_model (view, model);
  *
- * // Hide the '+ New task' row
- * gtd_task_list_view_set_show_new_task_row (view, FALSE);
- *
  * // Date which tasks will be automatically assigned
  * gtd_task_list_view_set_default_date (view, now);
  * ]|
@@ -69,8 +67,7 @@
 
 typedef struct
 {
-  GtkListBox            *listbox;
-  GtkListBoxRow         *new_task_row;
+  GtkListView           *listview;
   GtkWidget             *scrolled_window;
   GtkStack              *stack;
 
@@ -79,12 +76,13 @@ typedef struct
   gboolean               show_due_date;
   gboolean               show_list_name;
   gboolean               handle_subtasks;
-  GListModel            *model;
   GDateTime             *default_date;
 
-  guint                  scroll_to_bottom_handler_id;
+  GtdTaskListViewModel  *model;
+  gpointer               active_item;
+  gint64                 active_position;
 
-  GHashTable            *task_to_row;
+  guint                  scroll_to_bottom_handler_id;
 
   /* Markup renderer*/
   GtdMarkdownRenderer   *renderer;
@@ -94,10 +92,6 @@ typedef struct
   guint                  scroll_timeout_id;
   gboolean               scroll_up;
 
-  /* color provider */
-  GtkCssProvider        *color_provider;
-  GdkRGBA               *color;
-
   /* action */
   GActionGroup          *action_group;
 
@@ -105,10 +99,8 @@ typedef struct
   GtdTaskListViewHeaderFunc header_func;
   gpointer                  header_user_data;
 
-  GtdTaskRow             *active_row;
   GtkSizeGroup           *due_date_sizegroup;
   GtkSizeGroup           *tasklist_name_sizegroup;
-  GtdTaskListSelectorBehavior task_list_selector_behavior;
 } GtdTaskListViewPrivate;
 
 struct _GtdTaskListView
@@ -119,23 +111,6 @@ struct _GtdTaskListView
   GtdTaskListViewPrivate *priv;
 };
 
-typedef enum
-{
-  GTD_IDLE_STATE_STARTED,
-  GTD_IDLE_STATE_LOADING,
-  GTD_IDLE_STATE_COMPLETE,
-  GTD_IDLE_STATE_FINISHED,
-} GtdIdleState;
-
-typedef struct
-{
-  GtdTaskListView      *self;
-  GtdIdleState          state;
-  GPtrArray            *added;
-  GPtrArray            *removed;
-  guint32               current_item;
-} GtdIdleData;
-
 #define COLOR_TEMPLATE               "tasklistview {background-color: %s;}"
 #define DND_SCROLL_OFFSET            24 //px
 #define LUMINANCE(c)                 (0.299 * c->red + 0.587 * c->green + 0.114 * c->blue)
@@ -149,12 +124,6 @@ static void          on_clear_completed_tasks_activated_cb       (GSimpleAction
 static void          on_remove_task_row_cb                       (GtdTaskRow         *row,
                                                                   GtdTaskListView    *self);
 
-static void          on_task_row_entered_cb                      (GtdTaskListView    *self,
-                                                                  GtdTaskRow         *row);
-
-static void          on_task_row_exited_cb                       (GtdTaskListView    *self,
-                                                                  GtdTaskRow         *row);
-
 static gboolean      scroll_to_bottom_cb                         (gpointer            data);
 
 
@@ -172,49 +141,19 @@ typedef struct
 
 enum {
   PROP_0,
-  PROP_COLOR,
   PROP_HANDLE_SUBTASKS,
   PROP_SHOW_LIST_NAME,
   PROP_SHOW_DUE_DATE,
-  PROP_SHOW_NEW_TASK_ROW,
   LAST_PROP
 };
 
 typedef gboolean     (*IterateSubtaskFunc)                       (GtdTaskListView    *self,
                                                                   GtdTask            *task);
 
-
 /*
  * Auxiliary methods
  */
 
-static inline GtdTaskRow*
-task_row_from_row (GtkListBoxRow *row)
-{
-  return GTD_TASK_ROW (gtk_list_box_row_get_child (row));
-}
-
-static void
-set_active_row (GtdTaskListView *self,
-                GtdTaskRow      *row)
-{
-  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
-
-  if (priv->active_row == row)
-    return;
-
-  if (priv->active_row)
-    gtd_task_row_set_active (priv->active_row, FALSE);
-
-  priv->active_row = row;
-
-  if (row)
-    {
-      gtd_task_row_set_active (row, TRUE);
-      gtk_widget_grab_focus (GTK_WIDGET (row));
-    }
-}
-
 static gboolean
 iterate_subtasks (GtdTaskListView    *self,
                   GtdTask            *task,
@@ -236,35 +175,6 @@ iterate_subtasks (GtdTaskListView    *self,
   return TRUE;
 }
 
-static void
-update_font_color (GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv;
-  GtkStyleContext *context;
-  GdkRGBA *color;
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  if (!priv->model || !GTD_IS_TASK_LIST (priv->model))
-    return;
-
-  context = gtk_widget_get_style_context (GTK_WIDGET (self));
-  color = gtd_task_list_get_color (GTD_TASK_LIST (priv->model));
-
-  if (LUMINANCE (color) < 0.5)
-    {
-      gtk_style_context_add_class (context, "dark");
-      gtk_style_context_remove_class (context, "light");
-    }
-  else
-    {
-      gtk_style_context_add_class (context, "light");
-      gtk_style_context_remove_class (context, "dark");
-    }
-
-  gdk_rgba_free (color);
-}
-
 static void
 schedule_scroll_to_bottom (GtdTaskListView *self)
 {
@@ -276,24 +186,13 @@ schedule_scroll_to_bottom (GtdTaskListView *self)
   priv->scroll_to_bottom_handler_id = g_timeout_add (250, scroll_to_bottom_cb, self);
 }
 
-
-/*
- * Callbacks
- */
-
 static GtkWidget*
-create_row_for_task_cb (gpointer item,
-                        gpointer user_data)
+create_task_row (GtdTaskListView *self)
 {
-  GtdTaskListViewPrivate *priv;
-  GtdTaskListView *self;
-  GtkWidget *listbox_row;
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
   GtkWidget *row;
 
-  self = GTD_TASK_LIST_VIEW (user_data);
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  row = gtd_task_row_new (item, priv->renderer);
+  row = gtd_task_row_new (NULL, priv->renderer);
 
   g_object_bind_property (self,
                           "handle-subtasks",
@@ -303,21 +202,19 @@ create_row_for_task_cb (gpointer item,
 
   gtd_task_row_set_list_name_visible (GTD_TASK_ROW (row), priv->show_list_name);
   gtd_task_row_set_due_date_visible (GTD_TASK_ROW (row), priv->show_due_date);
-
-  g_signal_connect_swapped (row, "enter", G_CALLBACK (on_task_row_entered_cb), self);
-  g_signal_connect_swapped (row, "exit", G_CALLBACK (on_task_row_exited_cb), self);
+  gtd_task_row_set_sizegroups (GTD_TASK_ROW (row),
+                               priv->tasklist_name_sizegroup,
+                               priv->due_date_sizegroup);
 
   g_signal_connect (row, "remove-task", G_CALLBACK (on_remove_task_row_cb), self);
 
-  listbox_row = gtk_list_box_row_new ();
-  gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (listbox_row), row);
-
-  g_object_bind_property (row, "visible", listbox_row, "visible", G_BINDING_BIDIRECTIONAL);
+  return row;
+}
 
-  g_hash_table_insert (priv->task_to_row, item, row);
 
-  return listbox_row;
-}
+/*
+ * Callbacks
+ */
 
 static gboolean
 scroll_to_bottom_cb (gpointer data)
@@ -346,7 +243,6 @@ scroll_to_bottom_cb (gpointer data)
     {
       gboolean ignored;
 
-      gtk_widget_grab_focus (GTK_WIDGET (priv->new_task_row));
       g_signal_emit_by_name (priv->scrolled_window, "scroll-child", GTK_SCROLL_END, FALSE, &ignored);
     }
 
@@ -388,7 +284,7 @@ on_clear_completed_tasks_activated_cb (GSimpleAction *simple,
   guint i;
 
   self = GTD_TASK_LIST_VIEW (user_data);
-  model = self->priv->model;
+  model = gtd_task_list_view_model_get_model (self->priv->model);
 
   for (i = 0; i < g_list_model_get_n_items (model); i++)
     {
@@ -483,86 +379,36 @@ on_remove_task_row_cb (GtdTaskRow      *row,
 
 
   /* Clear the active row */
-  set_active_row (self, NULL);
+  gtd_task_row_set_active (row, FALSE);
 }
 
 static void
-on_task_list_color_changed_cb (GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv = GTD_TASK_LIST_VIEW (self)->priv;
-  gchar *color_str;
-  gchar *parsed_css;
-
-  /* Add the color to provider */
-  if (priv->color)
-    {
-      color_str = gdk_rgba_to_string (priv->color);
-    }
-  else
-    {
-      GdkRGBA *color;
-
-      color = gtd_task_list_get_color (GTD_TASK_LIST (priv->model));
-      color_str = gdk_rgba_to_string (color);
-
-      gdk_rgba_free (color);
-    }
-
-  parsed_css = g_strdup_printf (COLOR_TEMPLATE, color_str);
-
-  gtk_css_provider_load_from_data (priv->color_provider, parsed_css, -1);
-
-  update_font_color (self);
-
-  g_free (color_str);
-}
-static void
-on_new_task_row_entered_cb (GtdTaskListView *self,
-                            GtdNewTaskRow   *row)
-{
-  set_active_row (self, NULL);
-}
-
-static void
-on_new_task_row_exited_cb (GtdTaskListView *self,
-                           GtdNewTaskRow   *row)
+on_listview_activate_cb (GtkListBox      *listbox,
+                         guint            position,
+                         GtdTaskListView *self)
 {
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+  gpointer item;
+  gint64 old_active_position;
 
-}
+  GTD_ENTRY;
 
-static void
-on_task_row_entered_cb (GtdTaskListView *self,
-                        GtdTaskRow      *row)
-{
-  set_active_row (self, row);
-}
+  if (priv->active_position == position)
+    GTD_RETURN ();
 
-static void
-on_task_row_exited_cb (GtdTaskListView *self,
-                       GtdTaskRow      *row)
-{
-  GtdTaskListViewPrivate *priv = self->priv;
+  item = g_list_model_get_item (G_LIST_MODEL (priv->model), position);
 
-  if (row == priv->active_row)
-    set_active_row (self, NULL);
-}
+  old_active_position = priv->active_position;
 
-static void
-on_listbox_row_activated_cb (GtkListBox      *listbox,
-                             GtkListBoxRow   *row,
-                             GtdTaskListView *self)
-{
-  GtdTaskRow *task_row;
+  priv->active_item = item;
+  priv->active_position = position;
 
-  GTD_ENTRY;
+  GTD_TRACE_MSG ("Activating %s at %u", G_OBJECT_TYPE_NAME (item), position);
 
-  task_row = task_row_from_row (row);
+  if (old_active_position != -1)
+    g_list_model_items_changed (G_LIST_MODEL (priv->model), old_active_position, 1, 1);
 
-  /* Toggle the row */
-  if (gtd_task_row_get_active (task_row))
-    set_active_row (self, NULL);
-  else
-    set_active_row (self, task_row);
+  g_clear_object (&item);
 
   GTD_EXIT;
 }
@@ -573,362 +419,76 @@ on_listbox_row_activated_cb (GtkListBox      *listbox,
  */
 
 static void
-internal_header_func (GtkListBoxRow   *row,
-                      GtkListBoxRow   *before,
-                      GtdTaskListView *view)
-{
-  GtkWidget *header;
-  GtdTask *row_task;
-  GtdTask *before_task;
-
-  if (!view->priv->header_func)
-    return;
-
-  row_task = before_task = NULL;
-
-  if (row)
-    row_task = gtd_task_row_get_task (task_row_from_row (row));
-
-  if (before)
-      before_task = gtd_task_row_get_task (task_row_from_row (before));
-
-  header = view->priv->header_func (row_task, before_task, view->priv->header_user_data);
-
-  if (header)
-    {
-      GtkWidget *real_header = gtd_widget_new ();
-      gtk_widget_insert_before (header, real_header, NULL);
-
-      header = real_header;
-    }
-
-  gtk_list_box_row_set_header (row, header);
-}
-
-
-/*
- * Drag n' Drop functions
- */
-
-static gboolean
-row_is_subtask_of (GtdTaskRow *row_a,
-                   GtdTaskRow *row_b)
+on_listview_setup_cb (GtkSignalListItemFactory *factory,
+                      GtkListItem              *list_item,
+                      GtdTaskListView          *self)
 {
-  GtdTask *task_a;
-  GtdTask *task_b;
+  GtkWidget *row;
 
-  task_a = gtd_task_row_get_task (row_a);
-  task_b = gtd_task_row_get_task (row_b);
+  /* Use a task row here, even if it's the sentinel */
+  row = create_task_row (self);
 
-  return gtd_task_is_subtask (task_a, task_b);
+  gtk_list_item_set_child (list_item, row);
 }
 
-static GtkListBoxRow*
-get_drop_row_at_y (GtdTaskListView *self,
-                   gdouble          y)
+static void
+on_listview_bind_cb (GtkSignalListItemFactory *factory,
+                     GtkListItem              *list_item,
+                     GtdTaskListView          *self)
 {
-  GtdTaskListViewPrivate *priv;
-  GtkListBoxRow *hovered_row;
-  GtkListBoxRow *task_row;
-  GtkListBoxRow *drop_row;
-  gdouble row_y, row_height;
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  hovered_row = gtk_list_box_get_row_at_y (priv->listbox, y);
-
-  /* Small optimization when hovering the first row */
-  if (gtk_list_box_row_get_index (hovered_row) == 0)
-    return hovered_row;
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+  GtkWidget *row;
+  gpointer item;
 
-  drop_row = NULL;
-  task_row = hovered_row;
-  row_height = gtk_widget_get_allocated_height (GTK_WIDGET (hovered_row));
-  gtk_widget_translate_coordinates (GTK_WIDGET (priv->listbox),
-                                    GTK_WIDGET (hovered_row),
-                                    0,
-                                    y,
-                                    NULL,
-                                    &row_y);
+  item = gtk_list_item_get_item (list_item);
+  row = gtk_list_item_get_child (list_item);
 
-  /*
-   * If the pointer if in the top part of the row, move the DnD row to
-   * the previous row.
-   */
-  if (row_y < row_height / 2)
+  if (GTD_IS_TASK (item))
     {
-      gint row_index, i;
+      GtdTaskRow *task_row;
 
-      row_index = gtk_list_box_row_get_index (hovered_row);
-
-      /* Search for a valid task row */
-      for (i = row_index - 1; i >= 0; i--)
+      if (!GTD_IS_TASK_ROW (row))
         {
-          GtkListBoxRow *aux;
-
-          aux = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->listbox), i);
-
-          /* Skip DnD, New task and hidden rows */
-          if (aux && !gtk_widget_get_visible (GTK_WIDGET (aux)))
-            continue;
-
-          drop_row = aux;
-          break;
+          row = create_task_row (self);
+          gtk_list_item_set_child (list_item, GTK_WIDGET (row));
         }
-    }
-  else
-    {
-      drop_row = task_row;
-    }
-
-  return drop_row ? drop_row : NULL;
-}
-
-static void
-unset_previously_highlighted_row (GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
-  if (priv->highlighted_row)
-    {
-      gtd_task_row_unset_drag_offset (task_row_from_row (priv->highlighted_row));
-      priv->highlighted_row = NULL;
-    }
-}
 
-static inline gboolean
-scroll_to_dnd (gpointer user_data)
-{
-  GtdTaskListViewPrivate *priv;
-  GtkAdjustment *vadjustment;
-  gint value;
+      task_row = GTD_TASK_ROW (row);
 
-  priv = gtd_task_list_view_get_instance_private (user_data);
-  vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolled_window));
-  value = gtk_adjustment_get_value (vadjustment) + (priv->scroll_up ? -6 : 6);
-
-  gtk_adjustment_set_value (vadjustment,
-                            CLAMP (value, 0, gtk_adjustment_get_upper (vadjustment)));
-
-  return G_SOURCE_CONTINUE;
-}
+      gtd_task_row_set_task (task_row, GTD_TASK (item));
 
-static void
-check_dnd_scroll (GtdTaskListView *self,
-                  gboolean         should_cancel,
-                  gdouble          y)
-{
-  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
-  gdouble current_y, height;
+      if (gtk_list_item_get_item (list_item) == priv->active_item)
+        {
+          gboolean active;
 
-  if (should_cancel)
-    {
-      if (priv->scroll_timeout_id > 0)
+          /* Toggle the active state if this is the active item */
+          active = gtd_task_row_get_active (task_row);
+          g_message ("Row active: %d", active);
+          gtd_task_row_set_active (task_row, !active);
+        }
+      else
         {
-          g_source_remove (priv->scroll_timeout_id);
-          priv->scroll_timeout_id = 0;
+          gtd_task_row_set_active (task_row, FALSE);
         }
 
-      return;
     }
-
-  height = gtk_widget_get_allocated_height (priv->scrolled_window);
-  gtk_widget_translate_coordinates (GTK_WIDGET (priv->listbox),
-                                    priv->scrolled_window,
-                                    0, y,
-                                    NULL, &current_y);
-
-  if (current_y < DND_SCROLL_OFFSET || current_y > height - DND_SCROLL_OFFSET)
-    {
-      if (priv->scroll_timeout_id > 0)
-        return;
-
-      /* Start the autoscroll */
-      priv->scroll_up = current_y < DND_SCROLL_OFFSET;
-      priv->scroll_timeout_id = g_timeout_add (25,
-                                               scroll_to_dnd,
-                                               self);
-    }
-  else
+  else if (GTD_IS_SENTINEL (item))
     {
-      if (priv->scroll_timeout_id == 0)
-        return;
-
-      /* Cancel the autoscroll */
-      g_source_remove (priv->scroll_timeout_id);
-      priv->scroll_timeout_id = 0;
-    }
-}
-
-static void
-on_drop_target_drag_leave_cb (GtkDropTarget   *drop_target,
-                              GtdTaskListView *self)
-{
-  unset_previously_highlighted_row (self);
-  check_dnd_scroll (self, TRUE, -1);
-}
-
-static GdkDragAction
-on_drop_target_drag_motion_cb (GtkDropTarget   *drop_target,
-                               gdouble          x,
-                               gdouble          y,
-                               GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv;
-  GtkListBoxRow *highlighted_row;
-  GtdTaskRow *highlighted_task_row;
-  GtdTaskRow *source_task_row;
-  const GValue *value;
-  GdkDrop *drop;
-  GtdTask *task;
-  GdkDrag *drag;
-  gdouble x_offset;
-
-  GTD_ENTRY;
-
-  priv = gtd_task_list_view_get_instance_private (self);
-  drop = gtk_drop_target_get_drop (drop_target);
-  drag = gdk_drop_get_drag (drop);
-
-  if (!drag)
-    {
-      g_info ("Only dragging task rows is supported");
-      GTD_GOTO (fail);
-    }
-
-  value = gtk_drop_target_get_value (drop_target);
-  task = g_value_get_object (value);
-
-  source_task_row = g_hash_table_lookup (priv->task_to_row, task);
-
-  /* Update the x value according to the current offset */
-  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
-    x += gtd_task_row_get_x_offset (source_task_row);
-  else
-    x -= gtd_task_row_get_x_offset (source_task_row);
+      GListModel *model = gtd_task_list_view_model_get_model (priv->model);
 
-  unset_previously_highlighted_row (self);
-
-  highlighted_row = get_drop_row_at_y (self, y);
-  if (!highlighted_row)
-    GTD_GOTO (success);
-
-  highlighted_task_row = task_row_from_row (highlighted_row);
-
-  /* Forbid dropping a row over a subtask row */
-  if (row_is_subtask_of (source_task_row, highlighted_task_row))
-    GTD_GOTO (fail);
-
-  gtk_widget_translate_coordinates (GTK_WIDGET (priv->listbox),
-                                    GTK_WIDGET (highlighted_task_row),
-                                    x,
-                                    0,
-                                    &x_offset,
-                                    NULL);
-
-  gtd_task_row_set_drag_offset (highlighted_task_row, source_task_row, x_offset);
-  priv->highlighted_row = highlighted_row;
-
-success:
-  check_dnd_scroll (self, FALSE, y);
-  GTD_RETURN (GDK_ACTION_MOVE);
-
-fail:
-  GTD_RETURN (0);
-}
-
-static gboolean
-on_drop_target_drag_drop_cb (GtkDropTarget   *drop_target,
-                             const GValue    *value,
-                             gdouble          x,
-                             gdouble          y,
-                             GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv;
-  GtkListBoxRow *drop_row;
-  GtdProvider *provider;
-  GtdTaskRow *hovered_row;
-  GtkWidget *row;
-  GtdTask *new_parent_task;
-  GtdTask *hovered_task;
-  GtdTask *source_task;
-  GdkDrop *drop;
-  GdkDrag *drag;
-  gint64 current_position;
-  gint64 new_position;
-
-  GTD_ENTRY;
-
-  priv = gtd_task_list_view_get_instance_private (self);
-  drop = gtk_drop_target_get_drop (drop_target);
-  drag = gdk_drop_get_drag (drop);
-
-  if (!drag)
-    {
-      g_info ("Only dragging task rows is supported");
-      GTD_RETURN (FALSE);
-    }
-
-  unset_previously_highlighted_row (self);
-
-  source_task = g_value_get_object (value);
-
-  /*
-   * When the drag operation began, the source row was hidden. Now is the time
-   * to show it again.
-   */
-  row = g_hash_table_lookup (priv->task_to_row, source_task);
-  gtk_widget_show (row);
-
-  drop_row = get_drop_row_at_y (self, y);
-  hovered_row = task_row_from_row (drop_row);
-  hovered_task = gtd_task_row_get_task (hovered_row);
-  new_parent_task = gtd_task_row_get_dnd_drop_task (hovered_row);
-
-  g_assert (source_task != NULL);
-  g_assert (source_task != new_parent_task);
-
-  if (new_parent_task)
-    {
-      /* Forbid adding the parent task as a subtask */
-      if (gtd_task_is_subtask (source_task, new_parent_task))
+      if (!GTD_IS_NEW_TASK_ROW (row))
         {
-          gdk_drop_finish (drop, 0);
-          GTD_RETURN (FALSE);
+          row = gtd_new_task_row_new ();
+          gtk_list_item_set_child (list_item, GTK_WIDGET (row));
         }
 
-      GTD_TRACE_MSG ("Making '%s' (%s) subtask of '%s' (%s)",
-                     gtd_task_get_title (source_task),
-                     gtd_object_get_uid (GTD_OBJECT (source_task)),
-                     gtd_task_get_title (new_parent_task),
-                     gtd_object_get_uid (GTD_OBJECT (new_parent_task)));
-
-      gtd_task_add_subtask (new_parent_task, source_task);
+      gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (row),
+                                               !GTD_IS_TASK_LIST (model));
     }
   else
     {
-      GtdTask *current_parent_task = gtd_task_get_parent (source_task);
-      if (current_parent_task)
-        gtd_task_remove_subtask (current_parent_task, source_task);
+      g_assert_not_reached ();
     }
-
-  /*
-   * FIXME: via DnD, we only support moving the task to below another
-   * task, thus the "+ 1"
-   */
-  new_position = gtd_task_get_position (hovered_task) + 1;
-  current_position = gtd_task_get_position (source_task);
-
-  if (new_position != current_position)
-    gtd_task_list_move_task_to_position (GTD_TASK_LIST (priv->model), source_task, new_position);
-
-  /* Finally, save the task */
-  provider = gtd_task_list_get_provider (gtd_task_get_list (source_task));
-  gtd_provider_update_task (provider, source_task, NULL, NULL, NULL);
-
-  check_dnd_scroll (self, TRUE, -1);
-  gdk_drop_finish (drop, GDK_ACTION_MOVE);
-
-  GTD_RETURN (TRUE);
 }
 
 
@@ -942,7 +502,6 @@ gtd_task_list_view_finalize (GObject *object)
   GtdTaskListViewPrivate *priv = GTD_TASK_LIST_VIEW (object)->priv;
 
   g_clear_handle_id (&priv->scroll_to_bottom_handler_id, g_source_remove);
-  g_clear_pointer (&priv->task_to_row, g_hash_table_destroy);
   g_clear_pointer (&priv->default_date, g_date_time_unref);
   g_clear_object (&priv->renderer);
   g_clear_object (&priv->model);
@@ -960,10 +519,6 @@ gtd_task_list_view_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_COLOR:
-      g_value_set_boxed (value, self->priv->color);
-      break;
-
     case PROP_HANDLE_SUBTASKS:
       g_value_set_boolean (value, self->priv->handle_subtasks);
       break;
@@ -976,10 +531,6 @@ gtd_task_list_view_get_property (GObject    *object,
       g_value_set_boolean (value, self->priv->show_list_name);
       break;
 
-    case PROP_SHOW_NEW_TASK_ROW:
-      g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (self->priv->new_task_row)));
-      break;
-
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -995,10 +546,6 @@ gtd_task_list_view_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_COLOR:
-      gtd_task_list_view_set_color (self, g_value_get_boxed (value));
-      break;
-
     case PROP_HANDLE_SUBTASKS:
       gtd_task_list_view_set_handle_subtasks (self, g_value_get_boolean (value));
       break;
@@ -1011,10 +558,6 @@ gtd_task_list_view_set_property (GObject      *object,
       gtd_task_list_view_set_show_list_name (self, g_value_get_boolean (value));
       break;
 
-    case PROP_SHOW_NEW_TASK_ROW:
-      gtd_task_list_view_set_show_new_task_row (self, g_value_get_boolean (value));
-      break;
-
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -1034,13 +577,6 @@ gtd_task_list_view_constructed (GObject *object)
                                    gtd_task_list_view_entries,
                                    G_N_ELEMENTS (gtd_task_list_view_entries),
                                    object);
-
-  /* css provider */
-  self->priv->color_provider = gtk_css_provider_new ();
-
-  gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self)),
-                                  GTK_STYLE_PROVIDER (self->priv->color_provider),
-                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2);
 }
 
 
@@ -1085,21 +621,6 @@ gtd_task_list_view_class_init (GtdTaskListViewClass *klass)
   g_type_ensure (GTD_TYPE_DND_ROW);
   g_type_ensure (GTD_TYPE_EMPTY_LIST_WIDGET);
 
-  /**
-   * GtdTaskListView::color:
-   *
-   * The custom color of this list. If there is a custom color set,
-   * the tasklist's color is ignored.
-   */
-  g_object_class_install_property (
-        object_class,
-        PROP_COLOR,
-        g_param_spec_boxed ("color",
-                            "Color of the task list view",
-                            "The custom color of this task list view",
-                            GDK_TYPE_RGBA,
-                            G_PARAM_READWRITE));
-
   /**
    * GtdTaskListView::handle-subtasks:
    *
@@ -1114,20 +635,6 @@ gtd_task_list_view_class_init (GtdTaskListViewClass *klass)
                               TRUE,
                               G_PARAM_READWRITE));
 
-  /**
-   * GtdTaskListView::show-new-task-row:
-   *
-   * Whether the list shows the "New Task" row or not.
-   */
-  g_object_class_install_property (
-        object_class,
-        PROP_SHOW_NEW_TASK_ROW,
-        g_param_spec_boolean ("show-new-task-row",
-                              "Whether it shows the New Task row",
-                              "Whether the list shows the New Task row, or not",
-                              TRUE,
-                              G_PARAM_READWRITE));
-
   /**
    * GtdTaskListView::show-list-name:
    *
@@ -1159,17 +666,14 @@ gtd_task_list_view_class_init (GtdTaskListViewClass *klass)
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/gtd-task-list-view.ui");
 
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, due_date_sizegroup);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, listbox);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, new_task_row);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, listview);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, tasklist_name_sizegroup);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, scrolled_window);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, stack);
 
-  gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated_cb);
-  gtk_widget_class_bind_template_callback (widget_class, on_new_task_row_entered_cb);
-  gtk_widget_class_bind_template_callback (widget_class, on_new_task_row_exited_cb);
-  gtk_widget_class_bind_template_callback (widget_class, on_task_row_entered_cb);
-  gtk_widget_class_bind_template_callback (widget_class, on_task_row_exited_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_listview_activate_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_listview_setup_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_listview_bind_cb);
 
   gtk_widget_class_set_css_name (widget_class, "tasklistview");
 }
@@ -1178,15 +682,13 @@ static void
 gtd_task_list_view_init (GtdTaskListView *self)
 {
   GtdTaskListViewPrivate *priv;
-  GtkDropTarget *target;
+  GtkNoSelection *no_selection;
 
   priv = gtd_task_list_view_get_instance_private (self);
 
   self->priv = priv;
 
-  priv->task_list_selector_behavior = GTD_TASK_LIST_SELECTOR_BEHAVIOR_AUTOMATIC;
-  priv->task_to_row = g_hash_table_new (NULL, NULL);
-
+  priv->active_position = -1;
   priv->can_toggle = TRUE;
   priv->handle_subtasks = TRUE;
   priv->show_due_date = TRUE;
@@ -1194,15 +696,11 @@ gtd_task_list_view_init (GtdTaskListView *self)
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
-  target = gtk_drop_target_new (GTD_TYPE_TASK, GDK_ACTION_MOVE);
-  gtk_drop_target_set_preload (target, TRUE);
-  g_signal_connect (target, "drop", G_CALLBACK (on_drop_target_drag_drop_cb), self);
-  g_signal_connect (target, "leave", G_CALLBACK (on_drop_target_drag_leave_cb), self);
-  g_signal_connect (target, "motion", G_CALLBACK (on_drop_target_drag_motion_cb), self);
-
-  gtk_widget_add_controller (GTK_WIDGET (priv->listbox), GTK_EVENT_CONTROLLER (target));
-
   priv->renderer = gtd_markdown_renderer_new ();
+
+  priv->model = gtd_task_list_view_model_new ();
+  no_selection = gtk_no_selection_new (G_LIST_MODEL (priv->model));
+  gtk_list_view_set_model (priv->listview, GTK_SELECTION_MODEL (no_selection));
 }
 
 /**
@@ -1218,38 +716,6 @@ gtd_task_list_view_new (void)
   return g_object_new (GTD_TYPE_TASK_LIST_VIEW, NULL);
 }
 
-/**
- * gtd_task_list_view_get_show_new_task_row:
- * @view: a #GtdTaskListView
- *
- * Gets whether @view shows the new task row or not.
- *
- * Returns: %TRUE if @view is shows the new task row, %FALSE otherwise
- */
-gboolean
-gtd_task_list_view_get_show_new_task_row (GtdTaskListView *self)
-{
-  g_return_val_if_fail (GTD_IS_TASK_LIST_VIEW (self), FALSE);
-
-  return gtk_widget_get_visible (GTK_WIDGET (self->priv->new_task_row));
-}
-
-/**
- * gtd_task_list_view_set_show_new_task_row:
- * @view: a #GtdTaskListView
- *
- * Sets the #GtdTaskListView:show-new-task-mode property of @view.
- */
-void
-gtd_task_list_view_set_show_new_task_row (GtdTaskListView *view,
-                                          gboolean         show_new_task_row)
-{
-  g_return_if_fail (GTD_IS_TASK_LIST_VIEW (view));
-
-  gtk_widget_set_visible (GTK_WIDGET (view->priv->new_task_row), show_new_task_row);
-  g_object_notify (G_OBJECT (view), "show-new-task-row");
-}
-
 /**
  * gtd_task_list_view_get_model:
  * @view: a #GtdTaskListView
@@ -1262,9 +728,12 @@ gtd_task_list_view_set_show_new_task_row (GtdTaskListView *view,
 GListModel*
 gtd_task_list_view_get_model (GtdTaskListView *view)
 {
+  GtdTaskListViewPrivate *priv;
+
   g_return_val_if_fail (GTD_IS_TASK_LIST_VIEW (view), NULL);
 
-  return view->priv->model;
+  priv = gtd_task_list_view_get_instance_private (view);
+  return gtd_task_list_view_model_get_model (priv->model);
 }
 
 /**
@@ -1280,45 +749,17 @@ gtd_task_list_view_set_model (GtdTaskListView *view,
                               GListModel      *model)
 {
   GtdTaskListViewPrivate *priv;
-  g_autoptr (GdkRGBA) color = NULL;
-  g_autofree gchar *parsed_css = NULL;
-  g_autofree gchar *color_str = NULL;
-  GtdTaskList *list;
 
   g_return_if_fail (GTD_IS_TASK_LIST_VIEW (view));
   g_return_if_fail (G_IS_LIST_MODEL (model));
 
   priv = gtd_task_list_view_get_instance_private (view);
 
-  if (!g_set_object (&priv->model, model))
-    return;
-
-  gtk_list_box_bind_model (priv->listbox,
-                           model,
-                           create_row_for_task_cb,
-                           view,
-                           NULL);
+  priv->active_item = NULL;
+  priv->active_position = -1;
 
+  gtd_task_list_view_model_set_model (priv->model, model);
   schedule_scroll_to_bottom (view);
-
-  if (priv->task_list_selector_behavior == GTD_TASK_LIST_SELECTOR_BEHAVIOR_AUTOMATIC)
-    gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (priv->new_task_row), !GTD_IS_TASK_LIST 
(model));
-
-  if (!GTD_IS_TASK_LIST (model))
-    return;
-
-  list = GTD_TASK_LIST (model);
-
-  g_debug ("%p: Setting task list to '%s'", view, gtd_task_list_get_name (list));
-
-  /* Add the color to provider */
-  color = gtd_task_list_get_color (list);
-  color_str = gdk_rgba_to_string (color);
-  parsed_css = g_strdup_printf (COLOR_TEMPLATE, color_str);
-
-  //gtk_css_provider_load_from_data (priv->color_provider, parsed_css, -1);
-
-  update_font_color (view);
 }
 
 /**
@@ -1352,22 +793,7 @@ gtd_task_list_view_set_show_list_name (GtdTaskListView *view,
 
   if (view->priv->show_list_name != show_list_name)
     {
-      GtkWidget *child;
-
       view->priv->show_list_name = show_list_name;
-
-      for (child = gtk_widget_get_first_child (GTK_WIDGET (view->priv->listbox));
-           child;
-           child = gtk_widget_get_next_sibling (child))
-        {
-          GtkWidget *row_child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (child));
-
-          if (!GTD_IS_TASK_ROW (row_child))
-            continue;
-
-          gtd_task_row_set_list_name_visible (GTD_TASK_ROW (row_child), show_list_name);
-        }
-
       g_object_notify (G_OBJECT (view), "show-list-name");
     }
 }
@@ -1401,7 +827,6 @@ gtd_task_list_view_set_show_due_date (GtdTaskListView *self,
                                       gboolean         show_due_date)
 {
   GtdTaskListViewPrivate *priv;
-  GtkWidget *child;
 
   g_return_if_fail (GTD_IS_TASK_LIST_VIEW (self));
 
@@ -1411,19 +836,6 @@ gtd_task_list_view_set_show_due_date (GtdTaskListView *self,
     return;
 
   priv->show_due_date = show_due_date;
-
-  for (child = gtk_widget_get_first_child (GTK_WIDGET (priv->listbox));
-       child;
-       child = gtk_widget_get_next_sibling (child))
-    {
-      GtkWidget *row_child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (child));
-
-      if (!GTD_IS_TASK_ROW (row_child))
-        continue;
-
-      gtd_task_row_set_due_date_visible (GTD_TASK_ROW (row_child), show_due_date);
-    }
-
   g_object_notify (G_OBJECT (self), "show-due-date");
 }
 
@@ -1453,21 +865,11 @@ gtd_task_list_view_set_header_func (GtdTaskListView           *view,
     {
       priv->header_func = func;
       priv->header_user_data = user_data;
-
-      gtk_list_box_set_header_func (priv->listbox,
-                                    (GtkListBoxUpdateHeaderFunc) internal_header_func,
-                                    view,
-                                    NULL);
     }
   else
     {
       priv->header_func = NULL;
       priv->header_user_data = NULL;
-
-      gtk_list_box_set_header_func (priv->listbox,
-                                    NULL,
-                                    NULL,
-                                    NULL);
     }
 }
 
@@ -1515,57 +917,6 @@ gtd_task_list_view_set_default_date   (GtdTaskListView *self,
   priv->default_date = default_date ? g_date_time_ref (default_date) : NULL;
 }
 
-/**
- * gtd_task_list_view_get_color:
- * @self: a #GtdTaskListView
- *
- * Retrieves the custom color of @self.
- *
- * Returns: (nullable): a #GdkRGBA, or %NULL if none is set.
- */
-GdkRGBA*
-gtd_task_list_view_get_color (GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv;
-
-  g_return_val_if_fail (GTD_IS_TASK_LIST_VIEW (self), NULL);
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  return priv->color;
-}
-
-/**
- * gtd_task_list_view_set_color:
- * @self: a #GtdTaskListView
- * @color: (nullable): a #GdkRGBA
- *
- * Sets the custom color of @self to @color. If a custom color is set,
- * the tasklist's color is ignored. Passing %NULL makes the tasklist's
- * color apply again.
- */
-void
-gtd_task_list_view_set_color (GtdTaskListView *self,
-                              GdkRGBA         *color)
-{
-  GtdTaskListViewPrivate *priv;
-
-  g_return_if_fail (GTD_IS_TASK_LIST_VIEW (self));
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  if (priv->color != color ||
-      (color && priv->color && !gdk_rgba_equal (color, priv->color)))
-    {
-      g_clear_pointer (&priv->color, gdk_rgba_free);
-      priv->color = gdk_rgba_copy (color);
-
-      on_task_list_color_changed_cb (self);
-
-      g_object_notify (G_OBJECT (self), "color");
-    }
-}
-
 /**
  * gtd_task_list_view_get_handle_subtasks:
  * @self: a #GtdTaskListView
@@ -1616,51 +967,3 @@ gtd_task_list_view_set_handle_subtasks (GtdTaskListView *self,
 
   g_object_notify (G_OBJECT (self), "handle-subtasks");
 }
-
-
-GtdTaskListSelectorBehavior
-gtd_task_list_view_get_task_list_selector_behavior (GtdTaskListView *self)
-{
-  GtdTaskListViewPrivate *priv;
-
-  g_return_val_if_fail (GTD_IS_TASK_LIST_VIEW (self), -1);
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  return priv->task_list_selector_behavior;
-}
-
-void
-gtd_task_list_view_set_task_list_selector_behavior (GtdTaskListView             *self,
-                                                    GtdTaskListSelectorBehavior  behavior)
-{
-  GtdTaskListViewPrivate *priv;
-
-  g_return_if_fail (GTD_IS_TASK_LIST_VIEW (self));
-
-  priv = gtd_task_list_view_get_instance_private (self);
-
-  if (priv->task_list_selector_behavior == behavior)
-    return;
-
-  priv->task_list_selector_behavior = behavior;
-
-  switch (behavior)
-    {
-    case GTD_TASK_LIST_SELECTOR_BEHAVIOR_AUTOMATIC:
-      if (priv->model)
-        {
-          gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (priv->new_task_row),
-                                                   !GTD_IS_TASK_LIST (priv->model));
-        }
-      break;
-
-    case GTD_TASK_LIST_SELECTOR_BEHAVIOR_ALWAYS_SHOW:
-      gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (priv->new_task_row), TRUE);
-      break;
-
-    case GTD_TASK_LIST_SELECTOR_BEHAVIOR_ALWAYS_HIDE:
-      gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (priv->new_task_row), FALSE);
-      break;
-    }
-}
diff --git a/src/gui/gtd-task-list-view.h b/src/gui/gtd-task-list-view.h
index 92f7b60..f69c8f1 100644
--- a/src/gui/gtd-task-list-view.h
+++ b/src/gui/gtd-task-list-view.h
@@ -29,13 +29,6 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GtdTaskListView, gtd_task_list_view, GTD, TASK_LIST_VIEW, GtkBox)
 
-typedef enum
-{
-  GTD_TASK_LIST_SELECTOR_BEHAVIOR_AUTOMATIC,
-  GTD_TASK_LIST_SELECTOR_BEHAVIOR_ALWAYS_SHOW,
-  GTD_TASK_LIST_SELECTOR_BEHAVIOR_ALWAYS_HIDE,
-} GtdTaskListSelectorBehavior;
-
 /**
  * GtdTaskListViewHeaderFunc:
  * @task: the #GtdTask that @row represents
@@ -71,31 +64,16 @@ void                      gtd_task_list_view_set_header_func    (GtdTaskListView
                                                                  GtdTaskListViewHeaderFunc  func,
                                                                  gpointer                   user_data);
 
-gboolean                  gtd_task_list_view_get_show_new_task_row (GtdTaskListView        *view);
-
-void                      gtd_task_list_view_set_show_new_task_row (GtdTaskListView        *view,
-                                                                    gboolean                
show_new_task_row);
-
 GDateTime*                gtd_task_list_view_get_default_date   (GtdTaskListView        *self);
 
 void                      gtd_task_list_view_set_default_date   (GtdTaskListView        *self,
                                                                  GDateTime              *default_date);
 
-GdkRGBA*                  gtd_task_list_view_get_color          (GtdTaskListView        *self);
-
-void                      gtd_task_list_view_set_color          (GtdTaskListView        *self,
-                                                                 GdkRGBA                *color);
-
 gboolean                  gtd_task_list_view_get_handle_subtasks (GtdTaskListView       *self);
 
 void                      gtd_task_list_view_set_handle_subtasks (GtdTaskListView       *self,
                                                                   gboolean               handle_subtasks);
 
-GtdTaskListSelectorBehavior gtd_task_list_view_get_task_list_selector_behavior (GtdTaskListView             
*self);
-
-void                        gtd_task_list_view_set_task_list_selector_behavior (GtdTaskListView             
*self,
-                                                                                GtdTaskListSelectorBehavior  
behavior);
-
 G_END_DECLS
 
 #endif /* GTD_TASK_LIST_VIEW_H */
diff --git a/src/gui/gtd-task-list-view.ui b/src/gui/gtd-task-list-view.ui
index 2497f70..cd22cbf 100644
--- a/src/gui/gtd-task-list-view.ui
+++ b/src/gui/gtd-task-list-view.ui
@@ -2,76 +2,68 @@
 <interface>
   <requires lib="gtk+" version="3.16"/>
   <template class="GtdTaskListView" parent="GtkBox">
-    <property name="vexpand">1</property>
+    <property name="vexpand">true</property>
     <property name="orientation">vertical</property>
     <child>
-      <object class="GtkScrolledWindow" id="scrolled_window">
-        <property name="can_focus">1</property>
-        <property name="hexpand">1</property>
-        <property name="vexpand">1</property>
-        <property name="min-content-height">320</property>
-        <property name="hscrollbar-policy">never</property>
+      <object class="GtkStack" id="stack">
+        <property name="hexpand">true</property>
+        <property name="vexpand">true</property>
+        <property name="transition-type">crossfade</property>
         <child>
-          <object class="GtkStack" id="stack">
-            <property name="hexpand">true</property>
-            <property name="vexpand">true</property>
-            <property name="transition-type">crossfade</property>
-            <child>
-              <object class="GtkStackPage">
-                <property name="name">listbox</property>
-                <property name="child">
-                  <object class="GtdWidget">
-                    <property name="hexpand">1</property>
-                    <property name="vexpand">1</property>
-                    <property name="halign">center</property>
-                    <property name="layout-manager">
-                      <object class="GtdMaxSizeLayout">
-                        <property name="max-width">700</property>
-                      </object>
-                    </property>
+          <object class="GtkStackPage">
+            <property name="name">listview</property>
+            <property name="child">
+              <object class="GtdWidget">
+                <property name="hexpand">true</property>
+                <property name="vexpand">true</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="margin-top">6</property>
-                        <property name="margin-bottom">64</property>
-                        <property name="margin-start">18</property>
-                        <property name="margin-end">18</property>
-                        <property name="orientation">vertical</property>
+                      <object class="GtkScrolledWindow" id="scrolled_window">
+                        <property name="can_focus">true</property>
+                        <property name="hexpand">true</property>
+                        <property name="vexpand">true</property>
+                        <property name="min-content-height">320</property>
+                        <property name="hscrollbar-policy">never</property>
                         <child>
-                          <object class="GtkListBox" id="listbox">
-                            <property name="hexpand">1</property>
-                            <property name="selection_mode">none</property>
-                            <signal name="row-activated" handler="on_listbox_row_activated_cb" 
object="GtdTaskListView" swapped="no"/>
+                          <object class="GtkListView" id="listview">
+                            <property name="margin-top">6</property>
+                            <property name="margin-start">18</property>
+                            <property name="margin-end">18</property>
+                            <property name="hexpand">true</property>
+                            <property name="single-click-activate">true</property>
+                            <property name="factory">
+                              <object class="GtkSignalListItemFactory">
+                                <signal name="setup" handler="on_listview_setup_cb" object="GtdTaskListView" 
swapped="no" />
+                                <signal name="bind" handler="on_listview_bind_cb" object="GtdTaskListView" 
swapped="no" />
+                              </object>
+                            </property>
+                            <signal name="activate" handler="on_listview_activate_cb" 
object="GtdTaskListView" swapped="no" />
                             <style>
                               <class name="transparent"/>
                             </style>
                           </object>
                         </child>
-                        <child>
-                          <object class="GtdNewTaskRow" id="new_task_row">
-                            <property name="margin-bottom">24</property>
-                            <signal name="enter" handler="on_new_task_row_entered_cb" 
object="GtdTaskListView" swapped="yes"/>
-                            <signal name="exit" handler="on_new_task_row_exited_cb" object="GtdTaskListView" 
swapped="yes"/>
-                          </object>
-                        </child>
                       </object>
                     </child>
                   </object>
-
-                </property>
+                </child>
               </object>
-            </child>
-            <child>
-              <object class="GtkStackPage">
-                <property name="name">loading</property>
-                <property name="child">
-                  <object class="GtkSpinner">
-                    <property name="spinning">true</property>
-                    <property name="width-request">96</property>
-                    <property name="height-request">96</property>
-                  </object>
-                </property>
+
+            </property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStackPage">
+            <property name="name">loading</property>
+            <property name="child">
+              <object class="GtkSpinner">
+                <property name="spinning">true</property>
+                <property name="width-request">96</property>
+                <property name="height-request">96</property>
               </object>
-            </child>
+            </property>
           </object>
         </child>
       </object>
diff --git a/src/gui/gtd-task-row.c b/src/gui/gtd-task-row.c
index 876df1b..ae6c2a0 100644
--- a/src/gui/gtd-task-row.c
+++ b/src/gui/gtd-task-row.c
@@ -72,6 +72,7 @@ struct _GtdTaskRow
 
   gboolean            active;
   gboolean            changed;
+  gboolean            pressed;
 };
 
 #define PRIORITY_ICON_SIZE 8
@@ -293,8 +294,6 @@ on_drag_end_cb (GtkDragSource *source,
 {
   GTD_ENTRY;
 
-  gtd_task_row_unset_drag_offset (self);
-
   gtk_widget_set_cursor_from_name (GTK_WIDGET (self), NULL);
   gtk_widget_show (GTK_WIDGET (self));
 
@@ -309,8 +308,6 @@ on_drag_cancelled_cb (GtkDragSource       *source,
 {
   GTD_ENTRY;
 
-  gtd_task_row_unset_drag_offset (self);
-
   gtk_widget_set_cursor_from_name (GTK_WIDGET (self), NULL);
   gtk_widget_show (GTK_WIDGET (self));
 
@@ -426,6 +423,54 @@ on_task_changed_cb (GtdTaskRow  *self)
   self->changed = TRUE;
 }
 
+static void
+on_click_gesture_pressed_cb (GtkGestureClick *gesture,
+                             gint             n_press,
+                             gdouble          x,
+                             gdouble          y,
+                             GtdTaskRow      *self)
+{
+  GTD_ENTRY;
+
+  if (self->pressed || n_press != 1)
+    GTD_RETURN ();
+
+  self->pressed = TRUE;
+
+  GTD_EXIT;
+}
+
+
+static void
+on_click_gesture_released_cb (GtkGestureClick *gesture,
+                              gint             n_press,
+                              gdouble          x,
+                              gdouble          y,
+                              GtdTaskRow      *self)
+{
+  GTD_ENTRY;
+
+  if (!self->pressed || n_press != 1)
+    GTD_RETURN ();
+
+  gtd_task_row_set_active (self, !self->active);
+
+  self->pressed = FALSE;
+
+  GTD_EXIT;
+}
+
+static void
+on_click_gesture_stopped_cb (GtkGestureClick *gesture,
+                             GtdTaskRow      *self)
+{
+  GTD_ENTRY;
+
+  self->pressed = FALSE;
+
+  GTD_EXIT;
+}
+
 
 /*
  * GObject overrides
@@ -642,6 +687,9 @@ gtd_task_row_class_init (GtdTaskRowClass *klass)
   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, title_entry);
 
   gtk_widget_class_bind_template_callback (widget_class, on_button_press_event_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_pressed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_released_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_stopped_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_complete_check_toggled_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_key_pressed_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_remove_task_cb);
@@ -695,6 +743,9 @@ gtd_task_row_set_task (GtdTaskRow *self,
 
   g_return_if_fail (GTD_IS_TASK_ROW (self));
 
+  if (task == self->task)
+    return;
+
   old_task = self->task;
 
   if (old_task)
@@ -845,7 +896,8 @@ gtd_task_row_set_handle_subtasks (GtdTaskRow *self,
 
   gtk_widget_set_visible (self->dnd_box, handle_subtasks);
   gtk_widget_set_visible (self->dnd_icon, handle_subtasks);
-  on_depth_changed_cb (self, NULL, self->task);
+  if (self->task)
+    on_depth_changed_cb (self, NULL, self->task);
 
   g_object_notify (G_OBJECT (self), "handle-subtasks");
 }
@@ -962,32 +1014,3 @@ gtd_task_row_set_drag_offset (GtdTaskRow *self,
 
   gtk_widget_show (self->dnd_frame);
 }
-
-void
-gtd_task_row_unset_drag_offset (GtdTaskRow *self)
-{
-  g_return_if_fail (GTD_IS_TASK_ROW (self));
-
-  gtk_widget_hide (self->dnd_frame);
-}
-
-GtdTask*
-gtd_task_row_get_dnd_drop_task (GtdTaskRow *self)
-{
-  GtdTask *task;
-  gint task_depth;
-  gint depth;
-  gint i;
-
-  g_return_val_if_fail (GTD_IS_TASK_ROW (self), NULL);
-
-  task = self->task;
-  task_depth = gtd_task_get_depth (task);
-  depth = (gtk_widget_get_margin_start (self->dnd_frame) - 12) / 32;
-
-  /* Find the real parent */
-  for (i = task_depth - depth; i >= 0; i--)
-    task = gtd_task_get_parent (task);
-
-  return task;
-}
diff --git a/src/gui/gtd-task-row.h b/src/gui/gtd-task-row.h
index b51099b..851c652 100644
--- a/src/gui/gtd-task-row.h
+++ b/src/gui/gtd-task-row.h
@@ -54,16 +54,6 @@ void                      gtd_task_row_set_sizegroups           (GtdTaskRow
                                                                  GtkSizeGroup        *name_group,
                                                                  GtkSizeGroup        *date_group);
 
-gint                      gtd_task_row_get_x_offset             (GtdTaskRow          *self);
-
-void                      gtd_task_row_set_drag_offset          (GtdTaskRow          *self,
-                                                                 GtdTaskRow          *source_row,
-                                                                 gint                 x_offset);
-
-void                      gtd_task_row_unset_drag_offset        (GtdTaskRow          *self);
-
-GtdTask*                  gtd_task_row_get_dnd_drop_task        (GtdTaskRow          *self);
-
 G_END_DECLS
 
 #endif /* GTD_TASK_ROW_H */
diff --git a/src/gui/gtd-task-row.ui b/src/gui/gtd-task-row.ui
index ffa6860..c50349b 100644
--- a/src/gui/gtd-task-row.ui
+++ b/src/gui/gtd-task-row.ui
@@ -3,12 +3,26 @@
   <requires lib="gtk+" version="3.16"/>
   <template class="GtdTaskRow" parent="GtdWidget">
     <property name="hexpand">true</property>
+    <property name="halign">center</property>
+    <property name="layout-manager">
+      <object class="GtdMaxSizeLayout">
+        <property name="max-width">700</property>
+      </object>
+    </property>
     <child>
       <object class="GtkEventControllerKey">
         <property name="propagation-phase">capture</property>
         <signal name="key-pressed" handler="on_key_pressed_cb" object="GtdTaskRow" swapped="no"/>
       </object>
     </child>
+    <child>
+      <object class="GtkGestureClick">
+        <property name="propagation-phase">bubble</property>
+        <signal name="pressed" handler="on_click_gesture_pressed_cb" object="GtdTaskRow" swapped="no"/>
+        <signal name="released" handler="on_click_gesture_released_cb" object="GtdTaskRow" swapped="no"/>
+        <signal name="stopped" handler="on_click_gesture_stopped_cb" object="GtdTaskRow" swapped="no"/>
+      </object>
+    </child>
     <child>
       <object class="GtkBox" id="main_box">
         <property name="orientation">vertical</property>
diff --git a/src/meson.build b/src/meson.build
index bea40f5..e8df568 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -141,6 +141,7 @@ sources += files(
   'gui/gtd-new-task-row.c',
   'gui/gtd-task-list-popover.c',
   'gui/gtd-task-list-view.c',
+  'gui/gtd-task-list-view-model.c',
   'gui/gtd-task-row.c',
   'gui/gtd-color-button.c',
   'gui/gtd-empty-list-widget.c',
diff --git a/src/plugins/inbox-panel/gtd-inbox-panel.c b/src/plugins/inbox-panel/gtd-inbox-panel.c
index 6227278..04e61be 100644
--- a/src/plugins/inbox-panel/gtd-inbox-panel.c
+++ b/src/plugins/inbox-panel/gtd-inbox-panel.c
@@ -254,8 +254,6 @@ gtd_inbox_panel_init (GtdInboxPanel *self)
   gtd_task_list_view_set_handle_subtasks (GTD_TASK_LIST_VIEW (self->view), FALSE);
   gtd_task_list_view_set_show_list_name (GTD_TASK_LIST_VIEW (self->view), FALSE);
   gtd_task_list_view_set_show_due_date (GTD_TASK_LIST_VIEW (self->view), FALSE);
-  gtd_task_list_view_set_task_list_selector_behavior (GTD_TASK_LIST_VIEW (self->view),
-                                                      GTD_TASK_LIST_SELECTOR_BEHAVIOR_ALWAYS_HIDE);
 
   gtk_widget_set_hexpand (GTK_WIDGET (self->view), TRUE);
   gtk_widget_set_vexpand (GTK_WIDGET (self->view), TRUE);


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