From 72bebbd9dc7d59f69e23442b6c5b5526feb2a1a9 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Fri, 1 Mar 2019 15:57:19 +0100
Subject: Drawn a preview of blocks to collapse in graph view.

---
 src/gtkext/blockbar.ui            |   4 +-
 src/gtkext/graph/cluster.c        |  70 +++++++++++++++++-
 src/gtkext/graph/cluster.h        |   3 +
 src/gtkext/gtkbufferdisplay-int.h |   2 +
 src/gtkext/gtkbufferdisplay.c     |  62 ++++++++++++++++
 src/gtkext/gtkgraphdisplay.c      | 150 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 289 insertions(+), 2 deletions(-)

diff --git a/src/gtkext/blockbar.ui b/src/gtkext/blockbar.ui
index b8f07cd..9659aec 100644
--- a/src/gtkext/blockbar.ui
+++ b/src/gtkext/blockbar.ui
@@ -36,7 +36,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkButton">
+          <object class="GtkButton" id="collapse">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="focus_on_click">False</property>
@@ -44,7 +44,9 @@
             <property name="tooltip_text" translatable="yes">Collapse the block with all its dominated blocks</property>
             <property name="opacity">0.59999999999999998</property>
             <property name="relief">none</property>
+            <signal name="enter-notify-event" handler="on_block_bar_collapsing_enter" swapped="no"/>
             <signal name="enter-notify-event" handler="on_block_bar_enter_notify" swapped="no"/>
+            <signal name="leave-notify-event" handler="on_block_bar_collapsing_leave" swapped="no"/>
             <signal name="leave-notify-event" handler="on_block_bar_leave_notify" swapped="no"/>
             <child>
               <object class="GtkImage">
diff --git a/src/gtkext/graph/cluster.c b/src/gtkext/graph/cluster.c
index 7872000..8220bfe 100644
--- a/src/gtkext/graph/cluster.c
+++ b/src/gtkext/graph/cluster.c
@@ -264,6 +264,8 @@ static void set_y_for_graph_rank(const graph_rank_t *, gint *);
 /* Détermine les ordonnées de tous les liens en place. */
 static void compute_loop_link_with_graph_rank(const graph_rank_t *, const GtkAllocation *);
 
+/* Recherche le groupe de blocs avec un composant comme chef. */
+static GGraphCluster *find_cluster_by_widget_in_graph_rank(const graph_rank_t *, GtkWidget *);
 
 
 /* -------------------------- DEFINITION D'UN CHEF DE FILE -------------------------- */
@@ -1776,7 +1778,7 @@ static void set_y_for_graph_rank(const graph_rank_t *grank, gint *base)
 
 static void compute_loop_link_with_graph_rank(const graph_rank_t *grank, const GtkAllocation *needed)
 {
-    size_t i;                               /* Boucle de parcours #1       */
+    size_t i;                               /* Boucle de parcours          */
 
     for (i = 0; i < grank->count; i++)
         g_graph_cluster_compute_link_y_positions(grank->clusters[i]);
@@ -1786,6 +1788,34 @@ static void compute_loop_link_with_graph_rank(const graph_rank_t *grank, const G
 }
 
 
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : grank = ensemble de blocs de même rang à analyser.           *
+*                widget  = composant graphique à retrouver.                   *
+*                                                                             *
+*  Description : Recherche le groupe de blocs avec un composant comme chef.   *
+*                                                                             *
+*  Retour      : Groupe trouvé ou NULL en cas d'échec.                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static GGraphCluster *find_cluster_by_widget_in_graph_rank(const graph_rank_t *grank, GtkWidget *widget)
+{
+    GGraphCluster *result;                  /* Trouvaille à retourner      */
+    size_t i;                               /* Boucle de parcours          */
+
+    result = NULL;
+
+    for (i = 0; i < grank->count && result == NULL; i++)
+        result = g_graph_cluster_find_by_widget(grank->clusters[i], widget);
+
+    return result;
+
+}
+
+
 
 /* ---------------------------------------------------------------------------------- */
 /*                            DEFINITION D'UN CHEF DE FILE                            */
@@ -3361,6 +3391,44 @@ static void g_graph_cluster_resolve_links(const GGraphCluster *cluster)
 }
 
 
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : cluster = graphique de blocs à analyser.                     *
+*                widget  = composant graphique à retrouver.                   *
+*                                                                             *
+*  Description : Recherche le groupe de blocs avec un composant comme chef.   *
+*                                                                             *
+*  Retour      : Groupe trouvé ou NULL en cas d'échec.                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GGraphCluster *g_graph_cluster_find_by_widget(GGraphCluster *cluster, GtkWidget *widget)
+{
+    GGraphCluster *result;                  /* Trouvaille à retourner      */
+    size_t i;                               /* Boucle de parcours          */
+
+    if (cluster->display == widget)
+    {
+        result = cluster;
+        g_object_ref(G_OBJECT(result));
+    }
+
+    else
+    {
+        result = NULL;
+
+        for (i = 0; i < cluster->ranks_count && result == NULL; i++)
+            result = find_cluster_by_widget_in_graph_rank(&cluster->ranks[i], widget);
+
+    }
+
+    return result;
+
+}
+
+
 
 /* ---------------------------------------------------------------------------------- */
 /*                           CALCUL DE REPARTITION DE BLOCS                           */
diff --git a/src/gtkext/graph/cluster.h b/src/gtkext/graph/cluster.h
index 00c6d82..2a48b25 100644
--- a/src/gtkext/graph/cluster.h
+++ b/src/gtkext/graph/cluster.h
@@ -64,6 +64,9 @@ GtkWidget *g_graph_cluster_get_widget(GGraphCluster *);
 /* Dispose chaque noeud sur la surface de destination donnée. */
 void g_graph_cluster_place(GGraphCluster *, GtkGraphDisplay *);
 
+/* Recherche le groupe de blocs avec un composant comme chef. */
+GGraphCluster *g_graph_cluster_find_by_widget(GGraphCluster *, GtkWidget *);
+
 
 
 /* ------------------------- CALCUL DE REPARTITION DE BLOCS ------------------------- */
diff --git a/src/gtkext/gtkbufferdisplay-int.h b/src/gtkext/gtkbufferdisplay-int.h
index b4b64d0..1d98332 100644
--- a/src/gtkext/gtkbufferdisplay-int.h
+++ b/src/gtkext/gtkbufferdisplay-int.h
@@ -65,6 +65,8 @@ struct _GtkBufferDisplayClass
 
     void (* reach_limit) (GtkBufferDisplay *, GdkScrollDirection);
 
+    void (* prepare_collapsing) (GtkBufferDisplay *, gboolean);
+
 };
 
 
diff --git a/src/gtkext/gtkbufferdisplay.c b/src/gtkext/gtkbufferdisplay.c
index 5ad808f..0794dd4 100644
--- a/src/gtkext/gtkbufferdisplay.c
+++ b/src/gtkext/gtkbufferdisplay.c
@@ -120,6 +120,12 @@ static gboolean on_block_bar_enter_notify(GtkWidget *, GdkEventCrossing *, GtkBu
 /* Accompagne la fin du survol d'un élément de barre d'outils. */
 static gboolean on_block_bar_leave_notify(GtkWidget *, GdkEventCrossing *, GtkBufferDisplay *);
 
+/* Accompagne le début du survol du bouton de compression. */
+static gboolean on_block_bar_collapsing_enter(GtkWidget *, GdkEventCrossing *, GtkBufferDisplay *);
+
+/* Accompagne la fin du survol du bouton de compression. */
+static gboolean on_block_bar_collapsing_leave(GtkWidget *, GdkEventCrossing *, GtkBufferDisplay *);
+
 
 
 /* ---------------------------------------------------------------------------------- */
@@ -184,6 +190,14 @@ static void gtk_buffer_display_class_init(GtkBufferDisplayClass *class)
                  g_cclosure_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1, GTK_TYPE_SCROLL_TYPE);
 
+    g_signal_new("prepare-collapsing",
+                 GTK_TYPE_BUFFER_DISPLAY,
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(GtkBufferDisplayClass, prepare_collapsing),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__BOOLEAN,
+                 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
 }
 
 
@@ -1257,6 +1271,8 @@ void gtk_buffer_display_add_block_bar(GtkBufferDisplay *display)
     gtk_builder_add_callback_symbols(display->builder,
                                      "on_block_bar_enter_notify", G_CALLBACK(on_block_bar_enter_notify),
                                      "on_block_bar_leave_notify", G_CALLBACK(on_block_bar_leave_notify),
+                                     "on_block_bar_collapsing_enter", G_CALLBACK(on_block_bar_collapsing_enter),
+                                     "on_block_bar_collapsing_leave", G_CALLBACK(on_block_bar_collapsing_leave),
                                      NULL);
 
     gtk_builder_connect_signals(display->builder, display);
@@ -1339,3 +1355,49 @@ static gboolean on_block_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *e
     return FALSE;
 
 }
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : widget  = composant graphique concerné par l'opération.      *
+*                event   = informations liées à l'événement.                  *
+*                display = panneau d'affichage impliqué par l'action.         *
+*                                                                             *
+*  Description : Accompagne le début du survol du bouton de compression.      *
+*                                                                             *
+*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static gboolean on_block_bar_collapsing_enter(GtkWidget *widget, GdkEventCrossing *event, GtkBufferDisplay *display)
+{
+    g_signal_emit_by_name(display, "prepare-collapsing", FALSE);
+
+    return FALSE;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : widget  = composant graphique concerné par l'opération.      *
+*                event   = informations liées à l'événement.                  *
+*                display = panneau d'affichage impliqué par l'action.         *
+*                                                                             *
+*  Description : Accompagne la fin du survol du bouton de compression.        *
+*                                                                             *
+*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static gboolean on_block_bar_collapsing_leave(GtkWidget *widget, GdkEventCrossing *event, GtkBufferDisplay *display)
+{
+    g_signal_emit_by_name(display, "prepare-collapsing", TRUE);
+
+    return FALSE;
+
+}
diff --git a/src/gtkext/gtkgraphdisplay.c b/src/gtkext/gtkgraphdisplay.c
index 72519a2..fb89b2e 100644
--- a/src/gtkext/gtkgraphdisplay.c
+++ b/src/gtkext/gtkgraphdisplay.c
@@ -25,6 +25,7 @@
 
 
 #include <assert.h>
+#include <math.h>
 
 
 #include <i18n.h>
@@ -55,6 +56,8 @@ struct _GtkGraphDisplay
     segcnt_list *highlighted;               /* Segments mis en évidence    */
 
     GGraphCluster *cluster;                 /* Disposition en graphique    */
+    GtkAllocation collapsing_area;          /* Aire à compresser           */
+    bool may_collapsing;                    /* Validité de cette aire      */
 
     GGraphEdge **edges;                     /* Liens entre les noeuds      */
     size_t edges_count;                     /* Quantité de ces liens       */
@@ -82,6 +85,9 @@ struct _GtkGraphDisplayClass
 /* Marges en bordure de graphique */
 #define GRAPH_MARGIN 23
 
+/* Taille du cadrillage pour l'aperçu des compressions */
+#define COLLAPSING_GRID_SIZE 4
+
 
 /* Initialise la classe générique des graphiques de code. */
 static void gtk_graph_display_class_init(GtkGraphDisplayClass *);
@@ -149,6 +155,9 @@ static void gtk_graph_display_changed_highlights(GtkBlockDisplay *, GtkGraphDisp
 /* Notifie une incapacité de déplacement au sein d'un noeud. */
 static void gtk_graph_display_reach_caret_limit(GtkBufferDisplay *, GdkScrollDirection, GtkGraphDisplay *);
 
+/* Prend note de la proximité d'une compression de blocs. */
+static void gtk_graph_display_prepare_collasping(GtkBufferDisplay *, gboolean, GtkGraphDisplay *);
+
 
 
 /* Détermine le type du composant d'affichage en graphique. */
@@ -457,6 +466,93 @@ static void gtk_graph_display_adjust_scroll_value(GtkGraphDisplay *display, GtkA
 static gboolean gtk_graph_display_draw(GtkWidget *widget, cairo_t *cr, GtkGraphDisplay *display)
 {
     size_t i;                               /* Boucle de parcours          */
+    cairo_surface_t *pat_image;             /* Fond du futur pinceau       */
+    cairo_t *pat_cr;                        /* Pinceau pour le pinceau     */
+    cairo_pattern_t *pattern;               /* Patron de remplissage       */
+    double degrees;                         /* Conversion en degrés        */
+
+    /* Eventuel fond pour la zone de compression */
+
+    if (display->may_collapsing)
+    {
+        /* Préparation du pinceau */
+
+        pat_image = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+                                               2 * COLLAPSING_GRID_SIZE, 2 * COLLAPSING_GRID_SIZE);
+
+        pat_cr = cairo_create(pat_image);
+
+        cairo_set_source_rgba(pat_cr, 1.0, 1.0, 1.0, 0.05);
+
+        cairo_rectangle(pat_cr,
+                        0, 0,
+                        COLLAPSING_GRID_SIZE, COLLAPSING_GRID_SIZE);
+
+        cairo_fill(pat_cr);
+
+        cairo_rectangle(pat_cr,
+                        COLLAPSING_GRID_SIZE, COLLAPSING_GRID_SIZE,
+                        COLLAPSING_GRID_SIZE, COLLAPSING_GRID_SIZE);
+
+        cairo_fill(pat_cr);
+
+        pattern = cairo_pattern_create_for_surface(pat_image);
+        cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
+
+
+        /* Dessin de la zone */
+
+        degrees = M_PI / 180.0;
+
+        cairo_arc(cr,
+                  display->collapsing_area.x + BORDER_CORNER_RADIUS,
+                  display->collapsing_area.y + BORDER_CORNER_RADIUS,
+                  BORDER_CORNER_RADIUS, 180 * degrees, 270 * degrees);
+
+        cairo_line_to(cr,
+                      display->collapsing_area.x + display->collapsing_area.width - BORDER_CORNER_RADIUS,
+                      display->collapsing_area.y);
+
+        cairo_arc(cr,
+                  display->collapsing_area.x + display->collapsing_area.width - BORDER_CORNER_RADIUS,
+                  display->collapsing_area.y + BORDER_CORNER_RADIUS,
+                  BORDER_CORNER_RADIUS, 270 * degrees, 360 * degrees);
+
+        cairo_line_to(cr,
+                      display->collapsing_area.x + display->collapsing_area.width,
+                      display->collapsing_area.y + display->collapsing_area.height - BORDER_CORNER_RADIUS);
+
+        cairo_arc(cr,
+                  display->collapsing_area.x + display->collapsing_area.width - BORDER_CORNER_RADIUS,
+                  display->collapsing_area.y + display->collapsing_area.height - BORDER_CORNER_RADIUS,
+                  BORDER_CORNER_RADIUS, 0 * degrees, 90 * degrees);
+
+        cairo_line_to(cr,
+                      display->collapsing_area.x + BORDER_CORNER_RADIUS,
+                      display->collapsing_area.y + display->collapsing_area.height);
+
+        cairo_arc(cr,
+                  display->collapsing_area.x + BORDER_CORNER_RADIUS,
+                  display->collapsing_area.y + display->collapsing_area.height - BORDER_CORNER_RADIUS,
+                  BORDER_CORNER_RADIUS, 90 * degrees, 180 * degrees);
+
+        cairo_close_path(cr);
+
+        cairo_set_source(cr, pattern);
+
+        cairo_fill(cr);
+
+        /* Sortie propre */
+
+        cairo_pattern_destroy(pattern);
+
+        cairo_destroy(pat_cr);
+
+        cairo_surface_destroy(pat_image);
+
+    }
+
+    /* Dessin des ombres */
 
     void draw_shadow(GtkWidget *child, gpointer unused)
     {
@@ -1098,6 +1194,7 @@ GtkWidget *gtk_graph_display_new(void)
 void gtk_graph_display_put(GtkGraphDisplay *display, GtkWidget *widget, const GtkAllocation *alloc)
 {
     g_signal_connect(widget, "reach-limit", G_CALLBACK(gtk_graph_display_reach_caret_limit), display);
+    g_signal_connect(widget, "prepare-collapsing", G_CALLBACK(gtk_graph_display_prepare_collasping), display);
     g_signal_connect(widget, "highlight-changed", G_CALLBACK(gtk_graph_display_changed_highlights), display);
 
     gtk_fixed_put(GTK_FIXED(display->support), widget, GRAPH_MARGIN + alloc->x, GRAPH_MARGIN + alloc->y);
@@ -1180,6 +1277,8 @@ static void gtk_graph_display_reset(GtkGraphDisplay *display, bool dispose)
         display->cluster = NULL;
     }
 
+    display->may_collapsing = false;
+
     for (i = 0; i < display->edges_count; i++)
         g_object_unref(G_OBJECT(display->edges[i]));
 
@@ -1422,3 +1521,54 @@ static void gtk_graph_display_reach_caret_limit(GtkBufferDisplay *node, GdkScrol
     /* TODO : scrolling... */
 #endif
 }
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : node    = composant d'affichage impliqué dans la procédure.  *
+*                done    = indique si la préparation est à jeter.             *
+*                display = support graphique de tous les noeuds.              *
+*                                                                             *
+*  Description : Prend note de la proximité d'une compression de blocs.       *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void gtk_graph_display_prepare_collasping(GtkBufferDisplay *node, gboolean done, GtkGraphDisplay *display)
+{
+    GGraphCluster *cluster;                 /* Ensemble à priori concerné  */
+
+    if (!done)
+    {
+        cluster = g_graph_cluster_find_by_widget(display->cluster, GTK_WIDGET(node));
+
+        if (cluster == NULL)
+            done = TRUE;
+
+        else
+        {
+            g_graph_cluster_compute_needed_alloc(cluster, &display->collapsing_area);
+            g_object_unref(G_OBJECT(cluster));
+
+            display->collapsing_area.x += GRAPH_MARGIN;
+            display->collapsing_area.y += GRAPH_MARGIN;
+
+            assert(BORDER_CORNER_RADIUS < GRAPH_MARGIN);
+
+            display->collapsing_area.x -= BORDER_CORNER_RADIUS;
+            display->collapsing_area.y -= BORDER_CORNER_RADIUS;
+            display->collapsing_area.width += 2 * BORDER_CORNER_RADIUS;
+            display->collapsing_area.height += 2 * BORDER_CORNER_RADIUS;
+
+        }
+
+    }
+
+    display->may_collapsing = !done;
+
+    gtk_widget_queue_draw(GTK_WIDGET(display));
+
+}
-- 
cgit v0.11.2-87-g4458