[gtk+/wip/feborges/listboxdnd] listbox: Introduce Drag and Drop
- From: Felipe Borges <felipeborges src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/feborges/listboxdnd] listbox: Introduce Drag and Drop
- Date: Wed, 4 Oct 2017 15:37:20 +0000 (UTC)
commit 4aa8ac57ceb32d32b3c2e643620eef70d825ebc4
Author: Felipe Borges <felipeborges gnome org>
Date: Wed Oct 4 17:32:24 2017 +0200
listbox: Introduce Drag and Drop
This is a very preliminary [wip] work to support drag and drop in
GtkListBox.
Stay tuned for this feature in this same wip branch (be aware of
non-fast-forward updates).
gtk/gtklistbox.c | 305 ++++++++++++++++++++++++++++++++++-
gtk/gtklistbox.h | 3 +
gtk/theme/Adwaita/gtk-contained.css | 17 ++
3 files changed, 323 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtklistbox.c b/gtk/gtklistbox.c
index b1305d7..d4bf7b6 100644
--- a/gtk/gtklistbox.c
+++ b/gtk/gtklistbox.c
@@ -106,6 +106,7 @@ typedef struct
GtkGesture *multipress_gesture;
/* DnD */
+ gboolean reorderable;
GtkListBoxRow *drag_highlighted_row;
int n_visible_rows;
@@ -149,6 +150,7 @@ enum {
PROP_0,
PROP_SELECTION_MODE,
PROP_ACTIVATE_ON_SINGLE_CLICK,
+ PROP_REORDERABLE,
LAST_PROPERTY
};
@@ -299,6 +301,9 @@ gtk_list_box_get_property (GObject *obj,
case PROP_ACTIVATE_ON_SINGLE_CLICK:
g_value_set_boolean (value, priv->activate_single_click);
break;
+ case PROP_REORDERABLE:
+ g_value_set_boolean (value, priv->reorderable);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
@@ -321,6 +326,9 @@ gtk_list_box_set_property (GObject *obj,
case PROP_ACTIVATE_ON_SINGLE_CLICK:
gtk_list_box_set_activate_on_single_click (box, g_value_get_boolean (value));
break;
+ case PROP_REORDERABLE:
+ gtk_list_box_set_reorderable (box, g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
@@ -405,6 +413,13 @@ gtk_list_box_class_init (GtkListBoxClass *klass)
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+ properties[PROP_REORDERABLE] =
+ g_param_spec_boolean ("reorderable",
+ P_("Reorderable"),
+ P_("Can reorder rows"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
/**
@@ -1330,6 +1345,206 @@ gtk_list_box_got_row_changed (GtkListBox *box,
}
}
+static GtkListBoxRow *
+get_last_row (GtkListBox *box)
+{
+ gint pos = g_sequence_get_length (BOX_PRIV (box)->children);
+
+ return gtk_list_box_get_row_at_index (box, pos - 1);
+}
+
+static GtkListBoxRow *
+get_row_before (GtkListBox *box,
+ GtkListBoxRow *row)
+{
+ int pos = gtk_list_box_row_get_index (row);
+
+ return gtk_list_box_get_row_at_index (box, pos - 1);
+}
+
+static GtkListBoxRow *
+get_row_after (GtkListBox *box,
+ GtkListBoxRow *row)
+{
+ int pos = gtk_list_box_row_get_index (row);
+
+ return gtk_list_box_get_row_at_index (box, pos + 1);
+}
+
+static void
+drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ GtkListBox *box = GTK_LIST_BOX (widget);
+ GtkWidget *row_before;
+ GtkWidget *row_after;
+ GtkWidget *row;
+ GtkWidget *source;
+ gboolean success = TRUE;
+ int pos;
+
+ row_before = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-before"));
+ row_after = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-after"));
+
+ g_object_set_data (G_OBJECT (box), "row-before", NULL);
+ g_object_set_data (G_OBJECT (box), "row-after", NULL);
+
+ if (row_before)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_before), "drag-hover-bottom");
+ if (row_after)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_after), "drag-hover-top");
+
+ row = (gpointer)* (gpointer*)gtk_selection_data_get_data (selection_data);
+ source = gtk_widget_get_ancestor (row, GTK_TYPE_LIST_BOX_ROW);
+
+ if (source == row_after)
+ {
+ success = FALSE;
+ goto out;
+ }
+
+ g_object_ref (source);
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (source)), source);
+
+ if (row_after)
+ pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row_after));
+ else
+ pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row_before)) + 1;
+
+ gtk_list_box_insert (box, source, pos);
+ g_object_unref (source);
+
+out:
+ gtk_drag_finish (context, success, TRUE, time);
+}
+
+static gboolean
+drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint time,
+ gpointer data)
+{
+ GtkListBox *box = GTK_LIST_BOX (widget);
+ GtkAllocation alloc;
+ GtkWidget *row;
+ int hover_row_y;
+ int hover_row_height;
+ GtkWidget *drag_row;
+ GtkWidget *row_before;
+ GtkWidget *row_after;
+
+ row = GTK_WIDGET (gtk_list_box_get_row_at_y (box, y));
+
+ drag_row = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "drag-row"));
+ row_before = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-before"));
+ row_after = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-after"));
+
+ gtk_style_context_remove_class (gtk_widget_get_style_context (drag_row), "drag-hover");
+ if (row_before)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_before), "drag-hover-bottom");
+ if (row_after)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_after), "drag-hover-top");
+
+ if (row)
+ {
+ gtk_widget_get_allocation (row, &alloc);
+ hover_row_y = alloc.y;
+ hover_row_height = alloc.height;
+
+ if (y < hover_row_y + hover_row_height/2)
+ {
+ row_after = row;
+ row_before = GTK_WIDGET (get_row_before (box, GTK_LIST_BOX_ROW (row)));
+ }
+ else
+ {
+ row_before = row;
+ row_after = GTK_WIDGET (get_row_after (box, GTK_LIST_BOX_ROW (row)));
+ }
+ }
+ else
+ {
+ row_before = GTK_WIDGET (get_last_row (box));
+ row_after = NULL;
+ }
+
+ g_object_set_data (G_OBJECT (widget), "row-before", row_before);
+ g_object_set_data (G_OBJECT (widget), "row-after", row_after);
+
+ if (drag_row == row_before || drag_row == row_after)
+ {
+ gtk_style_context_add_class (gtk_widget_get_style_context (drag_row), "drag-hover");
+ return FALSE;
+ }
+
+ if (row_before)
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_before), "drag-hover-bottom");
+ if (row_after)
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_after), "drag-hover-top");
+
+ return TRUE;
+}
+
+static void
+drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time)
+{
+ GtkWidget *drag_row;
+ GtkWidget *row_before;
+ GtkWidget *row_after;
+
+ drag_row = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "drag-row"));
+ row_before = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-before"));
+ row_after = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "row-after"));
+
+ gtk_style_context_remove_class (gtk_widget_get_style_context (drag_row), "drag-hover");
+ if (row_before)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_before), "drag-hover-bottom");
+ if (row_after)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row_after), "drag-hover-top");
+}
+
+static GtkTargetEntry drag_target_entries[] = {
+ { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 }
+};
+
+void
+gtk_list_box_set_reorderable (GtkListBox *box,
+ gboolean reorderable)
+{
+ g_return_if_fail (GTK_IS_LIST_BOX (box));
+
+ reorderable = reorderable != FALSE;
+
+ if (BOX_PRIV (box)->reorderable == reorderable)
+ return;
+
+ if (reorderable)
+ {
+ gtk_drag_dest_set (GTK_WIDGET (box),
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
+ drag_target_entries, 1,
+ GDK_ACTION_MOVE);
+
+ g_signal_connect (box, "drag-data-received", G_CALLBACK (drag_data_received), NULL);
+ g_signal_connect (box, "drag-motion", G_CALLBACK (drag_motion), NULL);
+ g_signal_connect (box, "drag-leave", G_CALLBACK (drag_leave), NULL);
+ }
+
+ BOX_PRIV (box)->reorderable = reorderable;
+
+ g_object_notify_by_pspec (G_OBJECT (box), properties[PROP_REORDERABLE]);
+}
+
/**
* gtk_list_box_set_activate_on_single_click:
* @box: a #GtkListBox
@@ -2554,6 +2769,92 @@ gtk_list_box_insert_css_node (GtkListBox *box,
sibling);
}
+static void
+drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ GtkWidget *row;
+ GtkAllocation alloc;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int x, y;
+
+ row = gtk_widget_get_ancestor (widget, GTK_TYPE_LIST_BOX_ROW);
+ gtk_widget_get_allocation (row, &alloc);
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ alloc.width, alloc.height);
+ cr = cairo_create (surface);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (row), "drag-icon");
+ gtk_widget_draw (row, cr);
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row), "drag-icon");
+
+ gtk_widget_translate_coordinates (widget, row, 0, 0, &x, &y);
+ cairo_surface_set_device_offset (surface, -x, -y);
+ gtk_drag_set_icon_surface (context, surface);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ g_object_set_data (G_OBJECT (gtk_widget_get_parent (row)), "drag-row", row);
+ gtk_style_context_add_class (gtk_widget_get_style_context (row), "draw-row");
+}
+
+static void
+drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GtkWidget *row;
+
+ row = gtk_widget_get_ancestor (widget, GTK_TYPE_LIST_BOX_ROW);
+ g_object_set_data (G_OBJECT (gtk_widget_get_parent (row)), "drag-row", NULL);
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row), "drag-row");
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row), "drag-hover");
+}
+
+static void
+drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ gpointer data)
+{
+ gtk_selection_data_set (selection_data,
+ gdk_atom_intern_static_string ("GTK_LIST_BOX_ROW"),
+ 32,
+ (const guchar *)&widget,
+ sizeof (gpointer));
+}
+
+static GtkListBoxRow *
+attach_handler (GtkWidget *child)
+{
+ GtkWidget *row;
+ GtkWidget *handle;
+ GtkWidget *box;
+
+ row = gtk_list_box_row_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (row)), "row");
+
+ handle = gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_drag_source_set (handle, GDK_BUTTON1_MASK, drag_target_entries, 1, GDK_ACTION_MOVE);
+ g_signal_connect (handle, "drag-begin", G_CALLBACK (drag_begin), NULL);
+ g_signal_connect (handle, "drag-end", G_CALLBACK (drag_end), NULL);
+ g_signal_connect (handle, "drag-data-get", G_CALLBACK (drag_data_get), NULL);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (box), handle);
+ gtk_container_add (GTK_CONTAINER (box), child);
+
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ return GTK_LIST_BOX_ROW (row);
+}
+
/**
* gtk_list_box_insert:
* @box: a #GtkListBox
@@ -2585,8 +2886,8 @@ gtk_list_box_insert (GtkListBox *box,
row = GTK_LIST_BOX_ROW (child);
else
{
- row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
- gtk_container_add (GTK_CONTAINER (row), child);
+ //row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
+ row = attach_handler (child);
}
if (priv->sort_func != NULL)
diff --git a/gtk/gtklistbox.h b/gtk/gtklistbox.h
index 72ee8df..6f1c30a 100644
--- a/gtk/gtklistbox.h
+++ b/gtk/gtklistbox.h
@@ -308,6 +308,9 @@ void gtk_list_box_bind_model (GtkListBox
gpointer user_data,
GDestroyNotify user_data_free_func);
+void gtk_list_box_set_reorderable (GtkListBox *box,
+ gboolean reorderable);
+
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListBox, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListBoxRow, g_object_unref)
diff --git a/gtk/theme/Adwaita/gtk-contained.css b/gtk/theme/Adwaita/gtk-contained.css
index a5f18f3..0ee441a 100644
--- a/gtk/theme/Adwaita/gtk-contained.css
+++ b/gtk/theme/Adwaita/gtk-contained.css
@@ -1917,6 +1917,23 @@ stackswitcher button.text-button { min-width: 100px; }
stackswitcher button.circular, stackswitcher button.text-button.circular { min-width: 32px; min-height:
32px; padding: 0; }
+/********** GtkListBox Drag and Drop * */
+.row:not(:first-child) { border-top: 1px solid alpha(gray,0.5); border-bottom: 1px solid transparent; }
+
+.row:first-child { border-top: 1px solid transparent; border-bottom: 1px solid transparent; }
+
+.row:last-child { border-top: 1px solid alpha(gray,0.5); border-bottom: 1px solid alpha(gray,0.5); }
+
+.row.drag-icon { background: @theme_base_color; border: 1px solid @borders; }
+
+.row.drag-row { color: gray; background: alpha(gray,0.2); }
+
+.row.drag-hover image, .row.drag-hover label { color: @theme_text_color; }
+
+.row.drag-hover-top { border-top: 48px solid @theme_bg_color; }
+
+.row.drag-hover-bottom { border-bottom: 1px solid @theme_bg_color; }
+
/********* Emoji * */
popover.emoji-picker { padding-left: 0; padding-right: 0; }
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]