From 6a2a14683ab86a680a429bc0c6754ba351764839 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Wed, 17 Feb 2016 02:04:21 +0100
Subject: Introduced a new window for destinaton previews in DragAndDrop
 operations.

---
 ChangeLog                |  12 ++
 src/gtkext/gtkdockable.c | 493 +++++++++++++++++++++++++++++++++++++++++++++++
 src/gtkext/gtkdockable.h |  11 ++
 src/gui/editor.c         |   4 +
 src/gui/panels/panel.c   |   2 +
 5 files changed, 522 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 055a93f..feca285 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+16-02-17  Cyrille Bagard <nocbos@gmail.com>
+
+	* src/gtkext/gtkdockable.c:
+	* src/gtkext/gtkdockable.h:
+	Introduce a new window for destinaton previews in DragAndDrop operations.
+
+	* src/gui/editor.c:
+	Create the new window.
+
+	* src/gui/panels/panel.c:
+	Update code.
+
 16-02-10  Cyrille Bagard <nocbos@gmail.com>
 
 	* src/gtkext/gtkstatusstack.c:
diff --git a/src/gtkext/gtkdockable.c b/src/gtkext/gtkdockable.c
index 8819108..954b651 100644
--- a/src/gtkext/gtkdockable.c
+++ b/src/gtkext/gtkdockable.c
@@ -55,6 +55,61 @@ static void on_dockable_search_changed(GtkSearchEntry *, GtkDockable *);
 
 
 
+/* ----------------------- PROCEDURES POUR LE GLISSER-DEPOSER ----------------------- */
+
+
+/* Enumération des types de données à déplacer */
+
+enum
+{
+    TARGET_ROOTWIN
+};
+
+static GtkTargetEntry target_list[] = {
+    { "application/x-rootwindow-drop", 0, TARGET_ROOTWIN }
+};
+
+
+/* Fenêtre d'aperçu de destination */
+static GtkWidget *_drag_window = NULL;
+
+/* Dimensions maximales de l'aperçu du contenu déplacé */
+#define DND_WND_MAX_WIDTH 250
+#define DND_WND_MAX_HEIGHT 150
+
+/* Emplacements de la fenêtre de DragAndDrop */
+
+typedef enum _DNDWindowPosition
+{
+    DWP_NONE,                               /* Position non définie        */
+    DWP_CENTER,                             /* Position centrale           */
+    DWP_LEFT,                               /* Position à gauche           */
+    DWP_TOP,                                /* Position supérieure         */
+    DWP_RIGHT,                              /* Position à droite           */
+    DWP_BOTTOM                              /* Position inférieure         */
+
+} DNDWindowPosition;
+
+static DNDWindowPosition _dnd_position = DWP_NONE;
+
+/* Seuil de basculement entre zones */
+#define BORDER_THRESHOLD 0.20
+
+
+/* Amorce un démarrage de DragAndDrop. */
+static void on_dockable_drag_begin(GtkWidget *, GdkDragContext *, void *);
+
+/* Adapte le dessin de fond à de nouvelles dimensions. */
+static void update_drag_window_background(GtkWidget *, gint, gint);
+
+/* Suit le déplacement d'un contenu déposable. */
+static gboolean on_dockable_drag_motion(GtkWidget *, GdkDragContext *, gint, gint, guint, void *);
+
+/* Accommpagne la sortie de la souris d'un composant graphique. */
+static void on_dockable_drag_leave(GtkWidget *, GdkDragContext *, guint, void *);
+
+
+
 /* ---------------------------------------------------------------------------------- */
 /*                       DEFINITIONS PRINCIPALES DE L'INTERFACE                       */
 /* ---------------------------------------------------------------------------------- */
@@ -379,3 +434,441 @@ static void on_dockable_search_changed(GtkSearchEntry *entry, GtkDockable *docka
     iface->update_filtered(dockable, filter);
 
 }
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                         PROCEDURES POUR LE GLISSER-DEPOSER                         */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Prépare en sous-main la fenêtre de prédiction du déposer.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void prepare_drag_and_drop_window(void)
+{
+    GdkScreen *screen;                      /* Ecran d'affichage           */
+    GdkVisual *rgba_visual;                 /* Configuration visuelle      */
+    gboolean has_rgba;                      /* Support de la transparence ?*/
+
+    screen = gdk_screen_get_default();
+    rgba_visual = gdk_screen_get_rgba_visual(screen);
+
+    has_rgba = (rgba_visual != NULL && gdk_screen_is_composited(screen));
+
+    _drag_window = gtk_window_new(GTK_WINDOW_POPUP);
+
+    if (has_rgba)
+        gtk_widget_set_visual(_drag_window, rgba_visual);
+
+    gtk_window_set_type_hint(GTK_WINDOW(_drag_window), GDK_WINDOW_TYPE_HINT_DND);
+    gtk_window_set_screen(GTK_WINDOW(_drag_window), screen);
+
+    gtk_widget_set_app_paintable(_drag_window, TRUE);
+
+    gtk_widget_set_size_request(_drag_window, 1, 1);
+    gtk_widget_realize(_drag_window);
+
+    /**
+     * Cf. commentaires de on_dockable_drag_leave().
+     */
+    gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
+    gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
+
+    gtk_widget_show(_drag_window);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : dockable = instance de composant à intégrer pleinement.      *
+*                                                                             *
+*  Description : Initialise les fonctions de glisser/déposer pour un élément. *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void gtk_dockable_setup_dnd(GtkDockable *dockable)
+{
+    GtkDockableIface *iface;                /* Interface utilisée          */
+    GtkWidget *widget;                      /* Composant graphique interne */
+
+    iface = GTK_DOCKABLE_GET_IFACE(dockable);
+
+    widget = iface->get_widget(dockable);
+
+    gtk_drag_source_set(widget, GDK_BUTTON1_MASK,
+                        target_list, G_N_ELEMENTS(target_list),
+                        GDK_ACTION_MOVE);
+
+    g_signal_connect_after(widget, "drag-begin", G_CALLBACK(on_dockable_drag_begin), NULL);
+
+    gtk_drag_dest_set(widget, 0, target_list, G_N_ELEMENTS(target_list), GDK_ACTION_MOVE);
+
+    g_signal_connect(widget, "drag-motion", G_CALLBACK(on_dockable_drag_motion), NULL);
+    g_signal_connect(widget, "drag-leave", G_CALLBACK(on_dockable_drag_leave), NULL);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : widget  = composant graphique à l'origine de l'opération.    *
+*                context = contexte assurant la gestion de l'opération.       *
+*                unused  = adresse non utilisée ici.                          *
+*                                                                             *
+*  Description : Amorce un démarrage de DragAndDrop.                          *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void on_dockable_drag_begin(GtkWidget *widget, GdkDragContext *context, void *unused)
+{
+    GtkAllocation alloc;                    /* Espace graphique occupé     */
+    gint width;                             /* Largeur de l'aperçu         */
+    gint height;                            /* Hauteur de l'aperçu         */
+    cairo_surface_t *surface;               /* Représentation du contenu   */
+    cairo_t *cr;                            /* Pinceau de dessin           */
+    GtkStyleContext *style;                 /* Contexte du thème actuel    */
+
+    gtk_widget_get_allocation(widget, &alloc);
+
+    if (alloc.width > alloc.height)
+    {
+        width = DND_WND_MAX_WIDTH;
+        height = alloc.height * ((1.0 * DND_WND_MAX_WIDTH) / alloc.width);
+    }
+    else
+    {
+        height = DND_WND_MAX_HEIGHT;
+        width = alloc.width * ((1.0 * DND_WND_MAX_HEIGHT) / alloc.height);
+    }
+
+    surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+
+    cr = cairo_create(surface);
+
+    /* Fond */
+
+    style = gtk_widget_get_style_context(widget);
+
+    gtk_style_context_save(style);
+
+    gtk_style_context_add_class(style, GTK_STYLE_CLASS_VIEW);
+
+    gtk_render_background(style, cr, 0, 0, width, height);
+    gtk_render_frame(style, cr, 0, 0, width, height);
+
+    gtk_style_context_restore(style);
+
+    /* Aperçu */
+
+    cairo_save(cr);
+
+    cairo_scale(cr, (1.0 * width) / alloc.width, (1.0 * height) / alloc.height);
+
+    gtk_widget_draw(widget, cr);
+
+    cairo_restore(cr);
+
+    /* Application */
+
+    gtk_drag_set_icon_surface(context, surface);
+
+    cairo_destroy(cr);
+    cairo_surface_destroy(surface);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : dnd_widget = composant GTK assurant l'affichage du fond.     *
+*                width      = nouvelle largeur à remplir.                     *
+*                height     = nouvelle hauteur à remplir.                     *
+*                                                                             *
+*  Description : Adapte le dessin de fond à de nouvelles dimensions.          *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void update_drag_window_background(GtkWidget *dnd_window, gint width, gint height)
+{
+    GdkWindow *window;                      /* Représentation de bas niveau*/
+    cairo_surface_t *surface;               /* Surface à appliquer au fond */
+    cairo_t *cr;                            /* Pinceau de dessin           */
+    GdkScreen *screen;                      /* Ecran d'affichage           */
+    gboolean has_rgba;                      /* Support de la transparence ?*/
+    cairo_surface_t *saturated;             /* Surface de remplacement     */
+    cairo_region_t *region;                 /* Limites du dessin           */
+    cairo_pattern_t *pattern;               /* Modèle de dessin final      */
+    cairo_matrix_t matrix;                  /* Matrice de transformation   */
+
+    window = gtk_widget_get_window(dnd_window);
+
+    /**
+     * Code inspiré de celui de la fonction gtk_drag_set_icon_surface().
+     */
+
+    /* Création d'un nouveau modèle */
+
+    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+    cr = cairo_create(surface);
+
+    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.05);
+
+    cairo_rectangle(cr, 0, 0, width, height);
+    cairo_fill(cr);
+
+    cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.05);
+
+    cairo_rectangle(cr, 0, 0, width, height);
+    cairo_stroke(cr);
+
+    cairo_destroy(cr);
+
+    /* Application */
+
+    screen = gdk_window_get_screen(window);
+
+    has_rgba = (gdk_screen_get_rgba_visual(screen) != NULL
+                && gdk_screen_is_composited(screen));
+
+    if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR && !has_rgba)
+    {
+        region = gdk_cairo_region_create_from_surface(surface);
+
+        gtk_widget_shape_combine_region(dnd_window, region);
+        cairo_region_destroy(region);
+
+        saturated = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR, width, height);
+
+        cr = cairo_create(saturated);
+        cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR_ALPHA);
+        cairo_set_source_surface(cr, surface, 0, 0);
+        cairo_paint(cr);
+        cairo_set_operator(cr, CAIRO_OPERATOR_SATURATE);
+        cairo_paint(cr);
+        cairo_pop_group_to_source(cr);
+        cairo_paint(cr);
+        cairo_destroy(cr);
+
+        pattern = cairo_pattern_create_for_surface(saturated);
+
+        cairo_surface_destroy(saturated);
+
+    }
+    else
+    {
+        pattern = cairo_pattern_create_for_surface(surface);
+        cairo_matrix_init_translate(&matrix, 0, 0);
+        cairo_pattern_set_matrix(pattern, &matrix);
+    }
+
+    cairo_surface_destroy(surface);
+
+    gdk_window_set_background_pattern(window, pattern);
+    cairo_pattern_destroy(pattern);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : widget  = composant graphique à l'origine de l'opération.    *
+*                context = contexte assurant la gestion de l'opération.       *
+*                x       = abscisse courante du pointeur de la souris.        *
+*                y       = ordonnée courante du pointeur de la souris.        *
+*                time    = horodatage associé à l'événement.                  *
+*                unused  = adresse non utilisée ici.                          *
+*                                                                             *
+*  Description : Suit le déplacement d'un contenu déposable.                  *
+*                                                                             *
+*  Retour      : TRUE pour un contenu acceptable, FALSE sinon.                *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static gboolean on_dockable_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, void *unused)
+{
+    GtkAllocation alloc;                    /* Espace graphique occupé     */
+    DNDWindowPosition wpos;                 /* Direction actuelle          */
+    gint border_width;                      /* Largeur d'une bordure       */
+    gint border_height;                     /* Hauteur d'une bordure       */
+    gint corner_left;                       /* Bordure à gauche            */
+    gint corner_top;                        /* Bordure en haut             */
+    gint corner_right;                      /* Bordure à droite            */
+    gint corner_bottom;                     /* Bordure en bas              */
+    GtkAllocation update;                   /* Nouvelle position           */
+
+    /* Détermination de la position idéale */
+
+    gtk_widget_get_allocation(widget, &alloc);
+
+    wpos = DWP_NONE;
+
+    border_width = alloc.width * BORDER_THRESHOLD;
+    border_height = alloc.height * BORDER_THRESHOLD;
+
+    corner_left = border_width;
+    corner_top = border_height;
+    corner_right = alloc.width - border_width;
+    corner_bottom = alloc.height - border_height;
+
+    if (alloc.width > alloc.height)
+    {
+        if (x < corner_left)
+            wpos = DWP_LEFT;
+        else if (x > corner_right)
+            wpos = DWP_RIGHT;
+        else if (y < corner_top)
+            wpos = DWP_TOP;
+        else if (y > corner_bottom)
+            wpos = DWP_BOTTOM;
+    }
+    else
+    {
+        if (y < corner_top)
+            wpos = DWP_TOP;
+        else if (y > corner_bottom)
+            wpos = DWP_BOTTOM;
+        else if (x < corner_left)
+            wpos = DWP_LEFT;
+        else if (x > corner_right)
+            wpos = DWP_RIGHT;
+    }
+
+    if (x >= corner_left && x <= corner_right && y >= corner_top && y <= corner_bottom)
+    {
+        wpos = DWP_CENTER;
+    }
+
+    /* Mise à jour de la fenêtre de portée ? */
+
+    if (_dnd_position != wpos)
+    {
+        if (wpos != DWP_NONE)
+            gdk_window_get_origin(gtk_widget_get_window(widget), &update.x, &update.y);
+
+        update.x += alloc.x;
+        update.y += alloc.y;
+
+        switch (wpos)
+        {
+            case DWP_NONE:
+                break;
+
+            case DWP_CENTER:
+                update.x += border_width;
+                update.y += border_height;
+                update.width = alloc.width - 2 * border_width;
+                update.height = alloc.height - 2 * border_height;
+                break;
+
+            case DWP_LEFT:
+                update.width = border_width;
+                update.height = alloc.height;
+                break;
+
+            case DWP_TOP:
+                update.width = alloc.width;
+                update.height = border_height;
+                break;
+
+            case DWP_RIGHT:
+                update.x += corner_right;
+                update.width = border_width;
+                update.height = alloc.height;
+                break;
+
+            case DWP_BOTTOM:
+                update.y += corner_bottom;
+                update.width = alloc.width;
+                update.height = border_height;
+                break;
+
+        }
+
+        if (wpos == DWP_NONE)
+        {
+            /**
+             * Cf. commentaires de on_dockable_drag_leave().
+             */
+            gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
+            gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
+        }
+
+        else
+        {
+            update_drag_window_background(_drag_window, update.width, update.height);
+
+            gtk_window_move(GTK_WINDOW(_drag_window), update.x, update.y);
+            gtk_window_resize(GTK_WINDOW(_drag_window), update.width, update.height);
+
+        }
+
+        _dnd_position = wpos;
+
+    }
+
+    gdk_drag_status(context, GDK_ACTION_MOVE, time);
+
+    return TRUE;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : widget  = composant graphique qui était survolé.             *
+*                context = contexte assurant la gestion de l'opération.       *
+*                time    = horodatage associé à l'événement.                  *
+*                unused  = adresse non utilisée ici.                          *
+*                                                                             *
+*  Description : Accommpagne la sortie de la souris d'un composant graphique. *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void on_dockable_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, void *unused)
+{
+    /**
+     * Visiblement, cacher et réafficher la fenêtre d'aperçu de la zone de
+     * destination provoque un enchaînement ininterrompu d'événements
+     * "motion" & "leave".
+     *
+     * Conséquence : la fenêtre scintille.
+     *
+     * On contourne le bogue visuel en ne cachant plus la fenêtre.
+     */
+
+    _dnd_position = DWP_NONE;
+
+    gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
+    gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
+
+}
diff --git a/src/gtkext/gtkdockable.h b/src/gtkext/gtkdockable.h
index 91a6bda..e939fad 100644
--- a/src/gtkext/gtkdockable.h
+++ b/src/gtkext/gtkdockable.h
@@ -69,4 +69,15 @@ void gtk_dockable_toggle_revealer(GtkDockable *, GtkWidget *, gboolean);
 
 
 
+/* ----------------------- PROCEDURES POUR LE GLISSER-DEPOSER ----------------------- */
+
+
+/* Prépare en sous-main la fenêtre de prédiction du déposer. */
+void prepare_drag_and_drop_window(void);
+
+/* Initialise les fonctions de glisser/déposer pour un élément. */
+void gtk_dockable_setup_dnd(GtkDockable *);
+
+
+
 #endif  /* _GTKEXT_GTKDOCKABLE_H */
diff --git a/src/gui/editor.c b/src/gui/editor.c
index bc4f144..6f5ef7a 100644
--- a/src/gui/editor.c
+++ b/src/gui/editor.c
@@ -36,6 +36,7 @@
 #include "../analysis/project.h"
 #include "../core/params.h"
 #include "../gtkext/easygtk.h"
+#include "../gtkext/gtkdockable.h"
 #include "../gtkext/gtkdockstation.h"
 #include "../gtkext/support.h"
 
@@ -225,6 +226,9 @@ GtkWidget *create_editor(void)
     gtk_box_pack_start(GTK_BOX(vbox1), widget, FALSE, FALSE, 0);
 
 
+    /* Autre */
+
+    prepare_drag_and_drop_window();
 
 
 
diff --git a/src/gui/panels/panel.c b/src/gui/panels/panel.c
index a9e3028..01307cb 100644
--- a/src/gui/panels/panel.c
+++ b/src/gui/panels/panel.c
@@ -232,6 +232,8 @@ void g_panel_item_init_ext(GPanelItem *item, GObject *ref, const char *name, con
 
     panels_list_add_tail(item, &_panels_list);
 
+    gtk_dockable_setup_dnd(GTK_DOCKABLE(item));
+
 }
 
 
-- 
cgit v0.11.2-87-g4458