[gnome-todo/wip/gbsneto/subtasks] dnd-row: introduce GtdDndRow



commit a1d8a872efa0ec4190783dfacd77e5b64f2ea2ba
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Oct 14 01:29:16 2016 -0300

    dnd-row: introduce GtdDndRow
    
    A row specialized in creating chaos with DnD

 data/theme/Adwaita.css    |    6 +
 data/todo.gresource.xml   |    1 +
 data/ui/dnd-row.ui        |   45 ++++++
 doc/reference/Makefile.am |    1 +
 src/Makefile.am           |    8 +-
 src/gtd-dnd-row.c         |  331 +++++++++++++++++++++++++++++++++++++++++++++
 src/gtd-dnd-row.h         |   49 +++++++
 7 files changed, 440 insertions(+), 1 deletions(-)
---
diff --git a/data/theme/Adwaita.css b/data/theme/Adwaita.css
index 3b45d67..3e9959f 100644
--- a/data/theme/Adwaita.css
+++ b/data/theme/Adwaita.css
@@ -103,6 +103,12 @@ taskrow.complete label {
     text-decoration-line: line-through;
 }
 
+/* dnd row */
+dndrow frame {
+    background: alpha(#333333, 0.5);
+    min-height: 36px;
+}
+
 /* extension list */
 list.extension-list row {
     border-bottom: solid 1px @borders
diff --git a/data/todo.gresource.xml b/data/todo.gresource.xml
index a4d8302..e4a4c04 100644
--- a/data/todo.gresource.xml
+++ b/data/todo.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/todo">
     <file alias="gtk/menus.ui">gtk/menus.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/dnd-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/edit-pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/initial-setup.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/list-selector-grid-item.ui</file>
diff --git a/data/ui/dnd-row.ui b/data/ui/dnd-row.ui
new file mode 100644
index 0000000..c94544f
--- /dev/null
+++ b/data/ui/dnd-row.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="GtdDndRow" parent="GtkListBoxRow">
+    <property name="can_focus">True</property>
+    <property name="activatable">False</property>
+    <property name="selectable">False</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_start">24</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkImage" id="icon">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="icon_name">open-menu-symbolic</property>
+            <property name="pixel-size">12</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkFrame">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="label_xalign">0</property>
+            <property name="shadow_type">none</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="margin_end">24</property>
+                <property name="margin_top">24</property>
+                <property name="margin_bottom">24</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
index 09dfedc..41159c5 100644
--- a/doc/reference/Makefile.am
+++ b/doc/reference/Makefile.am
@@ -56,6 +56,7 @@ CFILE_GLOB = \
 IGNORE_HFILES = \
        gtd-application.h \
        gtd-arrow-frame.h \
+       gtd-dnd-row.h \
        gtd-edit-pane.h \
        gtd-enum-types.h \
        gtd-initial-setup-window.h \
diff --git a/src/Makefile.am b/src/Makefile.am
index bfa8ef7..749435d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,8 @@ gnome_todo_SOURCES = \
        gtd-application.h \
        gtd-arrow-frame.c \
        gtd-arrow-frame.h \
+       gtd-dnd-row.c \
+       gtd-dnd-row.h \
        gtd-edit-pane.c \
        gtd-edit-pane.h \
        gtd-enums.h \
@@ -96,6 +98,7 @@ gnome_todo_LDFLAGS = \
        -Wl,--undefined=gtd_plugin_dark_theme_register_types \
        -Wl,--undefined=gtd_plugin_scheduled_panel_register_types \
        -Wl,--undefined=gtd_plugin_today_panel_register_types \
+       -lm \
        $(GNOME_TODO_WARN_LDFLAGS)
 
 gnome_todo_LDADD = \
@@ -206,6 +209,8 @@ libgtd_la_SOURCES = \
        gtd-application.h \
        gtd-arrow-frame.c \
        gtd-arrow-frame.h \
+       gtd-dnd-row.c \
+       gtd-dnd-row.h \
        gtd-edit-pane.c \
        gtd-edit-pane.h \
        gtd-enum-types.c \
@@ -236,7 +241,8 @@ libgtd_la_CFLAGS = \
        $(GNOME_TODO_WARN_CFLAGS)
 
 libgtd_la_LDFLAGS = \
-       -Wl,--export-dynamic
+       -Wl,--export-dynamic \
+       -lm
 
 endif
 
diff --git a/src/gtd-dnd-row.c b/src/gtd-dnd-row.c
new file mode 100644
index 0000000..d6aa245
--- /dev/null
+++ b/src/gtd-dnd-row.c
@@ -0,0 +1,331 @@
+/* gtd-dnd-row.c
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#include "gtd-dnd-row.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+#include "gtd-task-row.h"
+
+#include <math.h>
+
+struct _GtdDndRow
+{
+  GtkListBoxRow       parent;
+
+  GtkWidget          *icon;
+
+  GtdTaskRow         *row_above;
+  GtdTask            *source_task;
+  gint                depth;
+  gboolean            has_dnd : 1;
+};
+
+G_DEFINE_TYPE (GtdDndRow, gtd_dnd_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+  PROP_0,
+  PROP_ROW_ABOVE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static GtdTask*
+get_real_task_for_depth (GtdDndRow *self)
+{
+  GtdTask *task;
+  gint i, task_depth;
+
+  task = self->row_above ? gtd_task_row_get_task (self->row_above) : NULL;
+  task_depth = task ? gtd_task_get_depth (task) : -1;
+
+  /* Find the real parent */
+  for (i = task_depth - self->depth; i >= 0; i--)
+    task = gtd_task_get_parent (task);
+
+  return task;
+}
+
+static void
+update_row_padding (GtdDndRow *self)
+{
+  gtk_widget_set_margin_start (self->icon, self->depth * 32);
+}
+
+static void
+gtd_dnd_row_finalize (GObject *object)
+{
+  GtdDndRow *self = (GtdDndRow *)object;
+
+  g_clear_object (&self->row_above);
+
+  G_OBJECT_CLASS (gtd_dnd_row_parent_class)->finalize (object);
+}
+
+static void
+gtd_dnd_row_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GtdDndRow *self = GTD_DND_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ROW_ABOVE:
+      g_value_set_object (value, self->row_above);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtd_dnd_row_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  GtdDndRow *self = GTD_DND_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ROW_ABOVE:
+      gtd_dnd_row_set_row_above (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtd_dnd_row_drag_leave (GtkWidget      *widget,
+                        GdkDragContext *context,
+                        guint           time)
+{
+  GtdDndRow *self = GTD_DND_ROW (widget);
+
+  self->has_dnd = FALSE;
+}
+
+static gboolean
+gtd_dnd_row_drag_motion (GtkWidget      *widget,
+                         GdkDragContext *context,
+                         gint            x,
+                         gint            y,
+                         guint           time)
+{
+  GtdDndRow *self;
+
+  self = GTD_DND_ROW (widget);
+
+  if (self->row_above)
+    {
+      GtdTask *task;
+
+      task = gtd_task_row_get_task (self->row_above);
+      self->depth = CLAMP (floor (x / 32), 0, gtd_task_get_depth (task) + 1);
+    }
+  else
+    {
+      self->depth = 0;
+    }
+
+  self->has_dnd = TRUE;
+
+  update_row_padding (self);
+
+  gdk_drag_status (context, GDK_ACTION_COPY, time);
+
+  return TRUE;
+}
+
+static gboolean
+gtd_dnd_row_drag_drop (GtkWidget      *widget,
+                       GdkDragContext *context,
+                       gint            x,
+                       gint            y,
+                       guint           time)
+{
+  GtdProvider *provider;
+  GtdDndRow *self;
+  GtkWidget *source_widget, *row;
+  GtdTask *row_task, *target_task;
+
+  self = GTD_DND_ROW (widget);
+
+  /* Reset padding */
+  update_row_padding (self);
+
+  gtk_widget_hide (widget);
+
+  row = NULL;
+  source_widget = gtk_drag_get_source_widget (context);
+
+  if (!source_widget)
+    {
+      gdk_drag_status (context, 0, time);
+      return FALSE;
+    }
+
+  /*
+   * When the drag operation began, the source row was hidden. Now is the time
+   * to show it again.
+   */
+  row = gtk_widget_get_ancestor (source_widget, GTD_TYPE_TASK_ROW);
+  gtk_widget_show (row);
+
+  /* Do not allow dropping on itself, nor on the new task row */
+  if (!row || row == widget || gtd_task_row_get_new_task_mode (GTD_TASK_ROW (row)))
+    {
+      gdk_drag_status (context, 0, time);
+      return FALSE;
+    }
+
+  row_task = gtd_task_row_get_task (GTD_TASK_ROW (row));
+  target_task = get_real_task_for_depth (self);
+
+  if (target_task)
+    {
+      /* Forbid adding the parent task as a subtask */
+      if (gtd_task_is_subtask (row_task, target_task))
+        {
+          gdk_drag_status (context, 0, time);
+          return FALSE;
+        }
+
+      gtd_task_add_subtask (target_task, row_task);
+    }
+  else
+    {
+      /*
+       * If the user moved to depth == 0, or the first row,
+       * remove the task from it's parent (if any).
+       */
+      if (gtd_task_get_parent (row_task))
+        gtd_task_remove_subtask (gtd_task_get_parent (row_task), row_task);
+    }
+
+  /* Save the task */
+  provider = gtd_task_list_get_provider (gtd_task_get_list (row_task));
+
+  gtd_task_save (row_task);
+  gtd_provider_update_task (provider, row_task);
+
+  gtk_list_box_invalidate_sort (GTK_LIST_BOX (gtk_widget_get_parent (widget)));
+
+  return TRUE;
+}
+
+static void
+gtd_dnd_row_class_init (GtdDndRowClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtd_dnd_row_finalize;
+  object_class->get_property = gtd_dnd_row_get_property;
+  object_class->set_property = gtd_dnd_row_set_property;
+
+  widget_class->drag_drop = gtd_dnd_row_drag_drop;
+  widget_class->drag_leave = gtd_dnd_row_drag_leave;
+  widget_class->drag_motion = gtd_dnd_row_drag_motion;
+
+  properties[PROP_ROW_ABOVE] = g_param_spec_object ("row-above",
+                                                    "Row above",
+                                                    "The task row above this row",
+                                                    GTD_TYPE_TASK_ROW,
+                                                    G_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/dnd-row.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GtdDndRow, icon);
+
+  gtk_widget_class_set_css_name (widget_class, "dndrow");
+}
+
+static void
+gtd_dnd_row_init (GtdDndRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_drag_dest_set (GTK_WIDGET (self),
+                     0,
+                     NULL,
+                     0,
+                     GDK_ACTION_MOVE);
+}
+
+GtkWidget*
+gtd_dnd_row_new (void)
+{
+  return g_object_new (GTD_TYPE_DND_ROW, NULL);
+}
+
+GtdTaskRow*
+gtd_dnd_row_get_row_above (GtdDndRow *self)
+{
+  g_return_val_if_fail (GTD_IS_DND_ROW (self), NULL);
+
+  return self->row_above;
+}
+
+void
+gtd_dnd_row_set_row_above (GtdDndRow  *self,
+                           GtdTaskRow *row)
+{
+  g_return_if_fail (GTD_IS_DND_ROW (self));
+
+  if (g_set_object (&self->row_above, row))
+    {
+      update_row_padding (self);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROW_ABOVE]);
+    }
+}
+
+GtdTask*
+gtd_dnd_row_get_source_task (GtdDndRow *self)
+{
+  g_return_val_if_fail (GTD_IS_DND_ROW (self), NULL);
+
+  return self->source_task;
+}
+
+void
+gtd_dnd_row_set_source_task (GtdDndRow *self,
+                             GtdTask   *source_task)
+{
+  g_return_if_fail (GTD_IS_DND_ROW (self));
+
+  g_set_object (&self->source_task, source_task);
+}
+
+gboolean
+gtd_dnd_row_has_dnd (GtdDndRow *self)
+{
+  g_return_val_if_fail (GTD_IS_DND_ROW (self), FALSE);
+
+  return self->has_dnd;
+}
+
diff --git a/src/gtd-dnd-row.h b/src/gtd-dnd-row.h
new file mode 100644
index 0000000..e55d466
--- /dev/null
+++ b/src/gtd-dnd-row.h
@@ -0,0 +1,49 @@
+/* gtd-dnd-row.h
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#ifndef GTD_DND_ROW_H
+#define GTD_DND_ROW_H
+
+#include "gtd-types.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_DND_ROW (gtd_dnd_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdDndRow, gtd_dnd_row, GTD, DND_ROW, GtkListBoxRow)
+
+GtkWidget*           gtd_dnd_row_new                             (void);
+
+GtdTaskRow*          gtd_dnd_row_get_row_above                   (GtdDndRow          *self);
+
+void                 gtd_dnd_row_set_row_above                   (GtdDndRow          *self,
+                                                                  GtdTaskRow         *row);
+
+GtdTask*             gtd_dnd_row_get_source_task                 (GtdDndRow          *self);
+
+void                 gtd_dnd_row_set_source_task                 (GtdDndRow          *self,
+                                                                  GtdTask            *source_task);
+
+gboolean             gtd_dnd_row_has_dnd                         (GtdDndRow          *self);
+
+G_END_DECLS
+
+#endif /* GTD_DND_ROW_H */
+


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