From 658e681c38b3c0770cf67a803c1c42614b54c10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20H=C3=BCttel?= Date: Wed, 13 Mar 2019 20:22:11 +0100 Subject: [PATCH] Implement a more convenient drag and drop --- CMakeLists.txt | 2 +- layer-selector-dnd.c | 231 ++++++++++++++++++++++++++++++++++++++++ layer-selector-dnd.h | 33 ++++++ main-window.c | 4 + widgets/layer-element.c | 70 ++++++------ 5 files changed, 300 insertions(+), 40 deletions(-) create mode 100644 layer-selector-dnd.c create mode 100644 layer-selector-dnd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 03b94df..aaacf57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ aux_source_directory("gds-parser" PARSER_SOURCES) aux_source_directory("latex-output" LATEX_SOURCES) aux_source_directory("cairo-output" CAIRO_SOURCES) aux_source_directory("trigonometric" TRIG_SOURCES) -set(SOURCE "main.c" "layer-selector.c" "mapping-parser.c" "command-line.c" "main-window.c" "external-renderer.c") +set(SOURCE "main.c" "layer-selector.c" "layer-selector-dnd.c" "mapping-parser.c" "command-line.c" "main-window.c" "external-renderer.c") set(SOURCE ${SOURCE} diff --git a/layer-selector-dnd.c b/layer-selector-dnd.c new file mode 100644 index 0000000..e581fc6 --- /dev/null +++ b/layer-selector-dnd.c @@ -0,0 +1,231 @@ +/* + * GDSII-Converter + * Copyright (C) 2019 Mario Hüttel + * + * This file is part of GDSII-Converter. + * + * GDSII-Converter is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * GDSII-Converter 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 GDSII-Converter. If not, see . + */ + +/* + * Original Drag and Drop Code taken from: + * https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-22/tests/testlist3.c + */ + +/** + * @file layer-selector-dnd.c + * @brief This file implements the drag and drop functions regarding the list box containing the layer elements + * @author Mario Hüttel + */ + +#include "layer-selector-dnd.h" + +static GtkTargetEntry entries[] = { + { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 } +}; + +static GtkListBoxRow *layer_selector_get_last_row (GtkListBox *list) +{ + int i; + GtkListBoxRow *row; + + row = NULL; + for (i = 0; ; i++) { + GtkListBoxRow *tmp; + tmp = gtk_list_box_get_row_at_index(list, i); + if (tmp == NULL) + break; + row = tmp; + } + + return row; +} + + +static GtkListBoxRow *layer_selector_get_row_before (GtkListBox *list, GtkListBoxRow *row) +{ + int pos; + + pos = gtk_list_box_row_get_index (row); + return gtk_list_box_get_row_at_index (list, pos - 1); +} + +static GtkListBoxRow *layer_selector_get_row_after (GtkListBox *list, GtkListBoxRow *row) +{ + int pos; + + pos = gtk_list_box_row_get_index(row); + return gtk_list_box_get_row_at_index(list, pos + 1); +} + +static void layer_selector_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + GtkSelectionData *selection_data, guint info, guint32 time, + gpointer data) +{ + GtkWidget *row_before, *row_after; + GtkWidget *row; + GtkWidget *source; + 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(widget), "row-before", NULL); + g_object_set_data(G_OBJECT(widget), "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) + return; + + 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(GTK_LIST_BOX(widget), source, pos); + g_object_unref(source); +} + +static gboolean layer_selector_drag_motion(GtkWidget *widget, GdkDragContext *context, int x, int y, guint time) +{ + 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(GTK_LIST_BOX(widget), y)); + + drag_row = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "drag-row")); + row_after = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "row-after")); + row_before = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "row-before")); + + 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(layer_selector_get_row_before(GTK_LIST_BOX(widget), GTK_LIST_BOX_ROW(row))); + } else { + row_before = row; + row_after = GTK_WIDGET(layer_selector_get_row_after(GTK_LIST_BOX(widget), GTK_LIST_BOX_ROW(row))); + } + } else { + row_before = GTK_WIDGET(layer_selector_get_last_row(GTK_LIST_BOX(widget))); + 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 layer_selector_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 const char *dnd_additional_css = + ".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: white; " + " border: 1px solid black; " + "}" + ".row.drag-row { " + " color: gray; " + " background: alpha(gray,0.2); " + "}" + ".row.drag-row.drag-hover { " + " border-top: 1px solid #4e9a06; " + " border-bottom: 1px solid #4e9a06; " + "}" + ".row.drag-hover image, " + ".row.drag-hover label { " + " color: #4e9a06; " + "}" + ".row.drag-hover-top {" + " border-top: 1px solid #4e9a06; " + "}" + ".row.drag-hover-bottom {" + " border-bottom: 1px solid #4e9a06; " + "}"; + +void layer_selector_list_box_setup_dnd(GtkListBox *box) +{ + GtkCssProvider *provider; + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, dnd_additional_css, -1, NULL); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider), 800); + + gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, entries, 1, GDK_ACTION_MOVE); + g_signal_connect(box, "drag-data-received", G_CALLBACK(layer_selector_drag_data_received), NULL); + g_signal_connect(box, "drag-motion", G_CALLBACK(layer_selector_drag_motion), NULL); + g_signal_connect(box, "drag-leave", G_CALLBACK(layer_selector_drag_leave), NULL); + +} diff --git a/layer-selector-dnd.h b/layer-selector-dnd.h new file mode 100644 index 0000000..49ab937 --- /dev/null +++ b/layer-selector-dnd.h @@ -0,0 +1,33 @@ +/* + * GDSII-Converter + * Copyright (C) 2019 Mario Hüttel + * + * This file is part of GDSII-Converter. + * + * GDSII-Converter is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * GDSII-Converter 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 GDSII-Converter. If not, see . + */ + +/** + * @file layer-selector-dnd.h + * @brief Header for drag and drop of layer selector + * @author Mario Hüttel + */ + +#ifndef _LAYER_SELECTOR_DND_H_ +#define _LAYER_SELECTOR_DND_H_ + +#include + +void layer_selector_list_box_setup_dnd(GtkListBox *box); + +#endif /* _LAYER_SELECTOR_DND_H_ */ diff --git a/main-window.c b/main-window.c index cb4dbfb..8279d32 100644 --- a/main-window.c +++ b/main-window.c @@ -40,6 +40,7 @@ #include "version/version.h" #include "tree-renderer/lib-cell-renderer.h" #include "gds-parser/gds-tree-checker.h" +#include "layer-selector-dnd.h" /** * @brief User data supplied to callback function of the open button @@ -441,6 +442,9 @@ GtkWindow *create_main_window() g_signal_connect(conv_button, "clicked", G_CALLBACK(on_convert_clicked), &conv_data); listbox = GTK_WIDGET(gtk_builder_get_object(main_builder, "layer-list")); + /* Set up the list box sided callbacks for drag and drop */ + layer_selector_list_box_setup_dnd(GTK_LIST_BOX(listbox)); + open_data.layer_box = GTK_LIST_BOX(listbox); /* Set buttons fpr layer mapping GUI */ diff --git a/widgets/layer-element.c b/widgets/layer-element.c index dbc2598..965e0de 100644 --- a/widgets/layer-element.c +++ b/widgets/layer-element.c @@ -17,9 +17,16 @@ * along with GDSII-Converter. If not, see . */ +/* + * The drag and drop implementation is adapted from + * https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-22/tests/testlist3.c + * + * Thanks to the GTK3 people for creating these examples. + */ + /** * @file layer-element.c - * @brief Omplementation of the layer element used for configuring layer colors etc. + * @brief Implementation of the layer element used for configuring layer colors etc. * @author Mario Hüttel */ @@ -65,14 +72,14 @@ static void layer_element_drag_begin(GtkWidget *widget, int x, y; (void)data; - row = gtk_widget_get_ancestor (widget, GTK_TYPE_LIST_BOX_ROW); - gtk_widget_get_allocation (row, &alloc); - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, alloc.width, alloc.height); - cr = cairo_create (surface); + row = gtk_widget_get_ancestor(widget, GTK_TYPE_LIST_BOX_ROW); + gtk_widget_get_allocation(row, &alloc); + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, alloc.width, alloc.height); + cr = cairo_create(surface); - gtk_style_context_add_class (gtk_widget_get_style_context (row), "drag-icon"); + 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_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); @@ -80,6 +87,21 @@ static void layer_element_drag_begin(GtkWidget *widget, 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), "drag-row"); +} + +static void layer_element_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer data) +{ + GtkWidget *row; + (void)context; + (void)data; + + 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 layer_element_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, @@ -94,36 +116,6 @@ static void layer_element_drag_data_get(GtkWidget *widget, GdkDragContext *conte 32, (const guchar *)&widget, sizeof(gpointer)); } -static void layer_element_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, - GtkSelectionData *selection_data, guint info, guint32 time, - gpointer data) -{ - GtkWidget *target; - GtkWidget *row; - GtkWidget *source; - int pos; - (void)context; - (void)x; - (void)y; - (void)info; - (void)time; - (void)data; - - target = widget; - - pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (target)); - row = (gpointer)(*(gpointer *)gtk_selection_data_get_data(selection_data)); - source = gtk_widget_get_ancestor (row, GTK_TYPE_LIST_BOX_ROW); - - if (source == target) - return; - - g_object_ref (source); - gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (source)), source); - gtk_list_box_insert (GTK_LIST_BOX (gtk_widget_get_parent (target)), source, pos); - g_object_unref (source); -} - static void layer_element_init(LayerElement *self) { GtkBuilder *builder; @@ -140,11 +132,11 @@ static void layer_element_init(LayerElement *self) self->priv.event_handle = GTK_EVENT_BOX(gtk_builder_get_object(builder, "event-box")); /* Setup drag and drop */ + gtk_style_context_add_class (gtk_widget_get_style_context(GTK_WIDGET(self)), "row"); gtk_drag_source_set(GTK_WIDGET(self->priv.event_handle), GDK_BUTTON1_MASK, entries, 1, GDK_ACTION_MOVE); g_signal_connect(self->priv.event_handle, "drag-begin", G_CALLBACK(layer_element_drag_begin), NULL); g_signal_connect(self->priv.event_handle, "drag-data-get", G_CALLBACK(layer_element_drag_data_get), NULL); - gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, entries, 1, GDK_ACTION_MOVE); - g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(layer_element_drag_data_received), NULL); + g_signal_connect(self->priv.event_handle, "drag-end", G_CALLBACK(layer_element_drag_end), NULL); g_object_unref(builder); }