From 16242be5838a77690946cbb2f30b2e89f2df0b94 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Fri, 15 Feb 2019 23:58:40 +0100
Subject: Displayed tooltips for graph view edges.

---
 src/analysis/block-int.h     |   4 +
 src/analysis/block.c         |  26 ++++
 src/analysis/block.h         |   3 +
 src/analysis/disass/block.c  | 349 +++++++++++++++++++++++++++++++++++++++++++
 src/analysis/routine.c       |   2 +-
 src/glibext/gbuffercache.c   |  12 +-
 src/gtkext/graph/cluster.c   |  25 +++-
 src/gtkext/graph/edge.c      |  39 ++++-
 src/gtkext/graph/edge.h      |  36 +++--
 src/gtkext/gtkgraphdisplay.c |  71 +++++++++
 10 files changed, 539 insertions(+), 28 deletions(-)

diff --git a/src/analysis/block-int.h b/src/analysis/block-int.h
index 6759e7b..9eb99d7 100644
--- a/src/analysis/block-int.h
+++ b/src/analysis/block-int.h
@@ -47,6 +47,9 @@ typedef block_link_t * (* block_get_links_fc) (const GCodeBlock *, const GBlockL
 /* Fournit la représentation graphique d'un bloc de code. */
 typedef GBufferView * (* block_build_view_fc) (const GCodeBlock *, segcnt_list *);
 
+/* Construit un ensemble d'indications pour bloc. */
+typedef char *(* block_build_tooltip_fc) (const GCodeBlock *);
+
 
 /* Description d'un bloc de code (instance) */
 struct _GCodeBlock
@@ -75,6 +78,7 @@ struct _GCodeBlockClass
     block_get_links_fc get_src;             /* Obtention des origines      */
     block_get_links_fc get_dest;            /* Obtention des destinations  */
     block_build_view_fc build;              /* Construction d'une vue      */
+    block_build_tooltip_fc build_tooltip;   /* Construction d'une bulle    */
 
 };
 
diff --git a/src/analysis/block.c b/src/analysis/block.c
index a7172af..85c8017 100644
--- a/src/analysis/block.c
+++ b/src/analysis/block.c
@@ -357,6 +357,32 @@ GBufferView *g_code_block_get_view(GCodeBlock *block, segcnt_list *highlighted)
 }
 
 
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : block = bloc de code à consulter.                            *
+*                                                                             *
+*  Description : Construit un ensemble d'indications pour bloc.               *
+*                                                                             *
+*  Retour      : Informations à présenter sous forme de bulle d'aide.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+char *g_code_block_build_tooltip(const GCodeBlock *block)
+{
+    char *result;                           /* Description à retourner     */
+    GCodeBlockClass *class;                 /* Classe des blocs de code    */
+
+    class = G_CODE_BLOCK_GET_CLASS(block);
+
+    result = class->build_tooltip(block);
+
+    return result;
+
+}
+
+
 
 /* ---------------------------------------------------------------------------------- */
 /*                     DEFINITION DE LIAISONS ENTRE BLOCS DE CODE                     */
diff --git a/src/analysis/block.h b/src/analysis/block.h
index 71092b2..bb3db3e 100644
--- a/src/analysis/block.h
+++ b/src/analysis/block.h
@@ -77,6 +77,9 @@ void g_code_block_set_rank(GCodeBlock *, size_t);
 /* Fournit la représentation graphique d'un bloc de code. */
 GBufferView *g_code_block_get_view(GCodeBlock *, segcnt_list *);
 
+/* Construit un ensemble d'indications pour bloc. */
+char *g_code_block_build_tooltip(const GCodeBlock *);
+
 
 
 /* ------------------- DEFINITION DE LIAISONS ENTRE BLOCS DE CODE ------------------- */
diff --git a/src/analysis/disass/block.c b/src/analysis/disass/block.c
index 9fce202..3077dc2 100644
--- a/src/analysis/disass/block.c
+++ b/src/analysis/disass/block.c
@@ -28,7 +28,13 @@
 #include <malloc.h>
 
 
+#include <i18n.h>
+
+
 #include "../block-int.h"
+#include "../../arch/raw.h"
+#include "../../common/extstr.h"
+#include "../../core/params.h"
 #include "../../glibext/gbinarycursor.h"
 
 
@@ -84,6 +90,9 @@ static block_link_t *g_basic_block_get_destinations(const GBasicBlock *, const G
 /* Fournit la représentation graphique d'un bloc de code. */
 static GBufferView *g_basic_block_build_view(const GBasicBlock *, segcnt_list *);
 
+/* Construit un ensemble d'indications pour bloc. */
+static char *g_basic_block_build_tooltip(const GBasicBlock *);
+
 
 
 /* ---------------------------------------------------------------------------------- */
@@ -124,6 +133,7 @@ static void g_basic_block_class_init(GBasicBlockClass *class)
     block->get_src = (block_get_links_fc)g_basic_block_get_sources;
     block->get_dest = (block_get_links_fc)g_basic_block_get_destinations;
     block->build = (block_build_view_fc)g_basic_block_build_view;
+    block->build_tooltip = (block_build_tooltip_fc)g_basic_block_build_tooltip;
 
 }
 
@@ -499,6 +509,345 @@ static GBufferView *g_basic_block_build_view(const GBasicBlock *block, segcnt_li
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : block = bloc de code à consulter.                            *
+*                                                                             *
+*  Description : Construit un ensemble d'indications pour bloc.               *
+*                                                                             *
+*  Retour      : Informations à présenter sous forme de bulle d'aide.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static char *g_basic_block_build_tooltip(const GBasicBlock *block)
+{
+    char *result;                           /* Description à retourner     */
+    const mrange_t *brange[2];              /* Emplacements d'instruction  */
+    phys_t diff;                            /* Espacement entre adresses   */
+    mrange_t range;                         /* Couverture du bloc          */
+    char *name;                             /* Désignation de l'entête     */
+    GBinFormat *format;                     /* Format associé au binaire   */
+    GBinSymbol *symbol;                     /* Symbole lié au bloc         */
+    char *label;                            /* Etiquette à insérer         */
+    GBufferCache *cache;                    /* Tampon d'impression colorée */
+    GBufferLine *line;                      /* Ligne au contenu coloré     */
+    VMPA_BUFFER(loc);                       /* Indication de position      */
+    GGenConfig *config;                     /* Configuration à consulter   */
+    unsigned int max_calls;                 /* Quantité d'appels à afficher*/
+    unsigned int max_strings;               /* Nbre de chaînes à afficher  */
+    unsigned int ins_count;                 /* Quantité d'instructions     */
+    unsigned int call_count;                /* Quantité d'appels           */
+    char *call_info;                        /* Détails des appels          */
+    unsigned int string_count;              /* Quantité de chaînes         */
+    char *string_info;                      /* Détails des chaînes         */
+    GArchProcessor *proc;                   /* Architecture utilisée       */
+    instr_iter_t *iter;                     /* Parcours local d'adresses   */
+    GArchInstruction *instr;                /* Instruction correspondante  */
+    size_t dcount;                          /* Nombre de liens de dest.    */
+    size_t i;                               /* Boucle de parcours          */
+    const instr_link_t *dest;               /* Instr. visée par une autre  */
+    const mrange_t *irange;                 /* Emplacement d'instruction   */
+    GLineCursor *cursor;                    /* Emplacement dans un tampon  */
+    size_t index;                           /* Indice de ligne à traiter   */
+    char *info;                             /* Ligne d'information créée   */
+
+    /* Définition de la couverture du bloc */
+
+    brange[0] = g_arch_instruction_get_range(block->first);
+    brange[1] = g_arch_instruction_get_range(block->last);
+
+    diff = compute_vmpa_diff(get_mrange_addr(brange[0]), get_mrange_addr(brange[1]));
+
+    init_mrange(&range, get_mrange_addr(brange[0]), diff + get_mrange_length(brange[1]));
+
+    /* Recherche d'un symbole de départ */
+
+    name = NULL;
+
+    format = G_BIN_FORMAT(g_loaded_binary_get_format(block->binary));
+
+    if (g_binary_format_find_symbol_at(format, get_mrange_addr(brange[0]), &symbol))
+    {
+        label = g_binary_symbol_get_label(symbol);
+
+        if (label != NULL)
+        {
+            cache = g_buffer_cache_new(NULL);
+            g_buffer_cache_append(cache, G_LINE_GENERATOR(symbol), BLF_NONE);
+
+            line = g_buffer_cache_find_line_by_index(cache, 0);
+            name = g_buffer_line_get_text(line, BLC_ASSEMBLY_LABEL, BLC_COUNT, true);
+            g_object_unref(G_OBJECT(line));
+
+            g_object_unref(G_OBJECT(cache));
+
+            /* Suppression de la fin de l'étiquette... */
+            name = strrpl(name, ":", "");
+
+        }
+
+        else
+            name = NULL;
+
+        free(label);
+
+        g_object_unref(G_OBJECT(symbol));
+
+    }
+
+    if (name == NULL)
+    {
+        proc = g_loaded_binary_get_processor(block->binary);
+
+        if (g_arch_processor_has_virtual_space(proc) && has_virt_addr(get_mrange_addr(&range)))
+            vmpa2_virt_to_string(get_mrange_addr(&range), MDS_UNDEFINED, loc, NULL);
+        else
+            vmpa2_phys_to_string(get_mrange_addr(&range), MDS_UNDEFINED, loc, NULL);
+
+        name = strdup(loc);
+
+        g_object_unref(G_OBJECT(proc));
+
+    }
+
+    result = name;
+
+    /* Lecture des paramètres de configuration */
+
+    config = get_main_configuration();
+
+    if (!g_generic_config_get_value(config, MPK_TOOLTIP_MAX_CALLS, &max_calls))
+        max_calls = 0;
+
+    max_calls++;
+
+    if (!g_generic_config_get_value(config, MPK_TOOLTIP_MAX_STRINGS, &max_strings))
+        max_strings = 0;
+
+    max_strings++;
+
+    /* Parcours des instructions */
+
+    ins_count = 0;
+
+    call_count = 0;
+    call_info = NULL;
+
+    string_count = 0;
+    string_info = NULL;
+
+    proc = g_loaded_binary_get_processor(block->binary);
+    cache = g_loaded_binary_get_disassembled_cache(block->binary);
+
+    iter = g_arch_processor_get_iter_from_address(proc, get_mrange_addr(&range));
+    if (iter == NULL) goto no_iter;
+
+    restrict_instruction_iterator(iter, &range);
+
+    for (instr = get_instruction_iterator_current(iter);
+         instr != NULL;
+         instr = get_instruction_iterator_next(iter))
+    {
+        ins_count ++;
+
+        /* Appels ou références ? */
+
+        g_arch_instruction_lock_dest(instr);
+        dcount = g_arch_instruction_count_destinations(instr);
+
+        for (i = 0; i < dcount; i++)
+        {
+            dest = g_arch_instruction_get_destination(instr, i);
+
+            switch (dest->type)
+            {
+                case ILT_CALL:
+
+                    call_count++;
+
+                    if (call_count > max_calls)
+                        goto next_dest;
+
+                    if (call_count == max_calls)
+                    {
+                        call_info = stradd(call_info, "\n    ...");
+                        goto next_dest;
+                    }
+
+                    irange = g_arch_instruction_get_range(instr);
+
+                    cursor = g_binary_cursor_new();
+                    g_binary_cursor_update(G_BINARY_CURSOR(cursor), get_mrange_addr(irange));
+
+                    index = g_buffer_cache_find_index_by_cursor(cache, cursor, true);
+
+                    g_object_unref(G_OBJECT(cursor));
+
+                    index = g_buffer_cache_look_for_flag(cache, index, BLF_HAS_CODE);
+
+                    line = g_buffer_cache_find_line_by_index(cache, index);
+
+                    if (line != NULL)
+                    {
+                        info = g_buffer_line_get_text(line, BLC_ASSEMBLY_HEAD, BLC_COUNT, true);
+                        g_object_unref(G_OBJECT(line));
+                    }
+
+                    else
+                        info = NULL;
+
+                    if (call_info != NULL)
+                        call_info = stradd(call_info, "\n");
+
+                    if (info != NULL)
+                    {
+                        call_info = stradd(call_info, "    - ");
+                        call_info = stradd(call_info, info);
+                        free(info);
+                    }
+
+                    else
+                        call_info = stradd(call_info, "    - ???");
+
+                    break;
+
+                case ILT_REF:
+
+                    if (!G_IS_RAW_INSTRUCTION(dest->linked))
+                        goto next_dest;
+
+                    if (!g_raw_instruction_is_string(G_RAW_INSTRUCTION(dest->linked)))
+                        goto next_dest;
+
+                    string_count++;
+
+                    if (string_count > max_strings)
+                        goto next_dest;
+
+                    if (string_count == max_strings)
+                    {
+                        string_info = stradd(string_info, "\n    ...");
+                        goto next_dest;
+                    }
+
+                    irange = g_arch_instruction_get_range(dest->linked);
+
+                    cursor = g_binary_cursor_new();
+                    g_binary_cursor_update(G_BINARY_CURSOR(cursor), get_mrange_addr(irange));
+
+                    index = g_buffer_cache_find_index_by_cursor(cache, cursor, true);
+
+                    g_object_unref(G_OBJECT(cursor));
+
+                    index = g_buffer_cache_look_for_flag(cache, index, BLF_HAS_CODE);
+
+                    line = g_buffer_cache_find_line_by_index(cache, index);
+
+                    if (line != NULL)
+                    {
+                        info = g_buffer_line_get_text(line, BLC_ASSEMBLY, BLC_COUNT, true);
+                        g_object_unref(G_OBJECT(line));
+                    }
+
+                    else
+                        info = NULL;
+
+                    if (string_info != NULL)
+                        string_info = stradd(string_info, "\n");
+
+                    if (info != NULL)
+                    {
+                        string_info = stradd(string_info, "    - ");
+                        string_info = stradd(string_info, info);
+                        free(info);
+                    }
+
+                    else
+                        string_info = stradd(string_info, "    - ???");
+
+                    break;
+
+                default:
+                    break;
+
+            }
+
+ next_dest:
+
+            unref_instr_link(dest);
+
+        }
+
+        g_arch_instruction_unlock_dest(instr);
+
+        g_object_unref(G_OBJECT(instr));
+
+    }
+
+    delete_instruction_iterator(iter);
+
+ no_iter:
+
+    g_object_unref(G_OBJECT(cache));
+    g_object_unref(G_OBJECT(proc));
+
+    /* Construction du résumé */
+
+    result = stradd(result, "\n");
+
+    if (ins_count > 1)
+        asprintf(&info, " - %u %s", ins_count, _("instructions"));
+    else
+        asprintf(&info, " - %u %s", ins_count, _("instruction"));
+
+    result = stradd(result, info);
+    free(info);
+
+    result = stradd(result, "\n");
+
+    if (call_count > 1)
+        asprintf(&info, " - %u %s", call_count, _("calls:"));
+    else if (call_count == 1)
+        asprintf(&info, " - 1 %s", _("call:"));
+    else
+        asprintf(&info, " - 0 %s", _("call"));
+
+    if (call_count > 0)
+    {
+        info = stradd(info, "\n");
+        info = stradd(info, call_info);
+        free(call_info);
+    }
+
+    info = stradd(info, "\n");
+
+    result = stradd(result, info);
+    free(info);
+
+    if (string_count > 1)
+        asprintf(&info, " - %u %s", string_count, _("strings:"));
+    else if (string_count == 1)
+        asprintf(&info, " - 1 %s", _("string:"));
+    else
+        asprintf(&info, " - 0 %s", _("string"));
+
+    if (string_count > 0)
+    {
+        info = stradd(info, "\n");
+        info = stradd(info, call_info);
+        free(call_info);
+    }
+
+    result = stradd(result, info);
+    free(info);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : block = bloc d'instructions à consulter.                     *
 *                first = instruction de départ du bloc. [OUT]                 *
 *                last  = dernière instruction du bloc. [OUT]                  *
diff --git a/src/analysis/routine.c b/src/analysis/routine.c
index d206e5e..20b5162 100644
--- a/src/analysis/routine.c
+++ b/src/analysis/routine.c
@@ -1134,7 +1134,7 @@ char *g_binary_routine_build_tooltip(const GBinRoutine *routine, const GLoadedBi
     {
         ins_count ++;
 
-        /* Appels ? */
+        /* Appels ou références ? */
 
         g_arch_instruction_lock_dest(instr);
         dcount = g_arch_instruction_count_destinations(instr);
diff --git a/src/glibext/gbuffercache.c b/src/glibext/gbuffercache.c
index e4ddf06..8d02dd8 100644
--- a/src/glibext/gbuffercache.c
+++ b/src/glibext/gbuffercache.c
@@ -609,26 +609,24 @@ static void g_buffer_cache_dispose(GBufferCache *cache)
     cache_info *info;                       /* Accès direct à une ligne    */
     size_t j;                               /* Boucle de parcours #2       */
 
-    if (cache->content != NULL)
-        g_object_unref(G_OBJECT(cache->content));
+    g_clear_object(&cache->content);
 
     for (i = 0; i < cache->used; i++)
     {
         info = &cache->lines[i];
 
         if (info->count == 1)
-            g_object_unref(G_OBJECT(info->generator.instance));
+            g_clear_object(&info->generator.instance);
 
         else
             for (j = 0; j < info->count; j++)
-                g_object_unref(G_OBJECT(info->generators[j].instance));
+                g_clear_object(&info->generators[j].instance);
 
-        if (info->line)
-            g_object_unref(G_OBJECT(info->line));
+        g_clear_object(&info->line);
 
     }
 
-    g_object_unref(G_OBJECT(cache->tracker));
+    g_clear_object(&cache->tracker);
 
     G_OBJECT_CLASS(g_buffer_cache_parent_class)->dispose(G_OBJECT(cache));
 
diff --git a/src/gtkext/graph/cluster.c b/src/gtkext/graph/cluster.c
index c4deeaa..765bbac 100644
--- a/src/gtkext/graph/cluster.c
+++ b/src/gtkext/graph/cluster.c
@@ -460,6 +460,8 @@ static gint compute_leaving_link_position(const leaving_link_t *link)
 static incoming_link_t *create_incoming_link(GGraphCluster *owner, InstructionLinkType type, leaving_link_t *other)
 {
     incoming_link_t *result;                /* Structure à retourner       */
+    GCodeBlock *src;                        /* Bloc d'origine du lien      */
+    GCodeBlock *dst;                        /* Bloc de destination du lien */
 
     result = malloc(sizeof(incoming_link_t));
 
@@ -467,14 +469,23 @@ static incoming_link_t *create_incoming_link(GGraphCluster *owner, InstructionLi
 
     result->type = type;
 
+    src = other->owner->block;
+    dst = owner->block;
+
     if (type == ILT_JUMP_IF_TRUE)
-        result->edge = g_graph_edge_new_true(&other->start[0], &other->start[1], &result->end[0], &result->end[1]);
+        result->edge = g_graph_edge_new_true(src, dst,
+                                             &other->start[0], &other->start[1],
+                                             &result->end[0], &result->end[1]);
 
     else if (type == ILT_JUMP_IF_FALSE)
-        result->edge = g_graph_edge_new_false(&other->start[0], &other->start[1], &result->end[0], &result->end[1]);
+        result->edge = g_graph_edge_new_false(src, dst,
+                                              &other->start[0], &other->start[1],
+                                              &result->end[0], &result->end[1]);
 
     else
-        result->edge = g_graph_edge_new(&other->start[0], &other->start[1], &result->end[0], &result->end[1]);
+        result->edge = g_graph_edge_new(src, dst,
+                                        &other->start[0], &other->start[1],
+                                        &result->end[0], &result->end[1]);
 
     result->other = other;
 
@@ -499,6 +510,8 @@ static incoming_link_t *create_incoming_link(GGraphCluster *owner, InstructionLi
 static incoming_link_t *create_incoming_loop_link(GGraphCluster *owner, const GdkPoint *midpts, leaving_link_t *other)
 {
     incoming_link_t *result;                /* Structure à retourner       */
+    GCodeBlock *src;                        /* Bloc d'origine du lien      */
+    GCodeBlock *dst;                        /* Bloc de destination du lien */
 
     result = malloc(sizeof(incoming_link_t));
 
@@ -506,7 +519,11 @@ static incoming_link_t *create_incoming_loop_link(GGraphCluster *owner, const Gd
 
     result->type = ILT_LOOP;
 
-    result->edge = g_graph_edge_new_loop(&other->start[0], &other->start[1],
+    src = other->owner->block;
+    dst = owner->block;
+
+    result->edge = g_graph_edge_new_loop(src, dst,
+                                         &other->start[0], &other->start[1],
                                          &midpts[0], &midpts[1],
                                          &result->end[0], &result->end[1]);
 
diff --git a/src/gtkext/graph/edge.c b/src/gtkext/graph/edge.c
index 9a2f848..20f1988 100644
--- a/src/gtkext/graph/edge.c
+++ b/src/gtkext/graph/edge.c
@@ -36,6 +36,9 @@ struct _GGraphEdge
 {
     GObject parent;                         /* A laisser en premier        */
 
+    GCodeBlock *src;                        /* Bloc d'origine du lien      */
+    GCodeBlock *dst;                        /* Bloc de destination du lien */
+
     EdgeColor color;                        /* Couleur du rendu            */
 
     union
@@ -137,6 +140,9 @@ static void g_graph_edge_init(GGraphEdge *edge)
 
 static void g_graph_edge_dispose(GGraphEdge *edge)
 {
+    g_clear_object(&edge->src);
+    g_clear_object(&edge->dst);
+
     G_OBJECT_CLASS(g_graph_edge_parent_class)->dispose(G_OBJECT(edge));
 
 }
@@ -177,12 +183,18 @@ static void g_graph_edge_finalize(GGraphEdge *edge)
 *                                                                             *
 ******************************************************************************/
 
-GGraphEdge *_g_graph_edge_new(const GdkPoint **templates, size_t count, EdgeColor color)
+GGraphEdge *_g_graph_edge_new(GCodeBlock *src, GCodeBlock *dst, const GdkPoint **templates, size_t count, EdgeColor color)
 {
     GGraphEdge *result;                     /* Structure à retourner       */
 
     result = g_object_new(G_TYPE_GRAPH_EDGE, NULL);
 
+    result->src = src;
+    result->dst = dst;
+
+    g_object_ref(G_OBJECT(src));
+    g_object_ref(G_OBJECT(dst));
+
     result->color = color;
 
     assert(count == 4 || count == 6);
@@ -200,6 +212,31 @@ GGraphEdge *_g_graph_edge_new(const GdkPoint **templates, size_t count, EdgeColo
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : edge = ligne de rendu à consulter.                           *
+*                src  = bloc d'origine du lien. [OUT]                         *
+*                dst  = bloc de destination du lien. [OUT]                    *
+*                                                                             *
+*  Description : Fournit les deux blocs aux extrémités d'un lien.             *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_graph_edge_get_boundaries(const GGraphEdge *edge, GCodeBlock **src, GCodeBlock **dst)
+{
+    *src = edge->src;
+    *dst = edge->dst;
+
+    g_object_ref(G_OBJECT(*src));
+    g_object_ref(G_OBJECT(*dst));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : edge = ligne de rendu à consulter.                           *
 *                x1   = abscisse du point de départ de la ligne. [OUT]        *
 *                x2   = abscisse du point d'arrivée de la ligne. [OUT]        *
 *                                                                             *
diff --git a/src/gtkext/graph/edge.h b/src/gtkext/graph/edge.h
index e905685..ec33544 100644
--- a/src/gtkext/graph/edge.h
+++ b/src/gtkext/graph/edge.h
@@ -30,13 +30,16 @@
 #include <gtk/gtk.h>
 
 
+#include "../../analysis/disass/block.h"
 
-#define G_TYPE_GRAPH_EDGE               g_graph_edge_get_type()
-#define G_GRAPH_EDGE(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_GRAPH_EDGE, GGraphEdge))
-#define G_IS_GRAPH_EDGE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_GRAPH_EDGE))
-#define G_GRAPH_EDGE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_GRAPH_EDGE, GGraphEdgeClass))
-#define G_IS_GRAPH_EDGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_GRAPH_EDGE))
-#define G_GRAPH_EDGE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_GRAPH_EDGE, GGraphEdgeClass))
+
+
+#define G_TYPE_GRAPH_EDGE            g_graph_edge_get_type()
+#define G_GRAPH_EDGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_GRAPH_EDGE, GGraphEdge))
+#define G_IS_GRAPH_EDGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_GRAPH_EDGE))
+#define G_GRAPH_EDGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_GRAPH_EDGE, GGraphEdgeClass))
+#define G_IS_GRAPH_EDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_GRAPH_EDGE))
+#define G_GRAPH_EDGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_GRAPH_EDGE, GGraphEdgeClass))
 
 
 /* Lien graphique entre deux noeuds graphiques (instance) */
@@ -68,19 +71,22 @@ typedef enum _EdgeColor
 GType g_graph_edge_get_type(void);
 
 /* Etablit un lien graphique entre deux noeuds graphiques. */
-GGraphEdge *_g_graph_edge_new(const GdkPoint **, size_t, EdgeColor);
+GGraphEdge *_g_graph_edge_new(GCodeBlock *, GCodeBlock *, const GdkPoint **, size_t, EdgeColor);
+
+#define g_graph_edge_new(src, dst, pts0, pts1, pte0, pte1) \
+    _g_graph_edge_new(src, dst, (const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_DEFAULT)
 
-#define g_graph_edge_new(pts0, pts1, pte0, pte1) \
-    _g_graph_edge_new((const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_DEFAULT)
+#define g_graph_edge_new_true(src, dst, pts0, pts1, pte0, pte1) \
+    _g_graph_edge_new(src, dst, (const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_GREEN)
 
-#define g_graph_edge_new_true(pts0, pts1, pte0, pte1) \
-    _g_graph_edge_new((const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_GREEN)
+#define g_graph_edge_new_false(src, dst, pts0, pts1, pte0, pte1) \
+    _g_graph_edge_new(src, dst, (const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_RED)
 
-#define g_graph_edge_new_false(pts0, pts1, pte0, pte1) \
-    _g_graph_edge_new((const GdkPoint *[]) { pts0, pts1, pte0, pte1 }, 4, EGC_RED)
+#define g_graph_edge_new_loop(src, dst, pts0, pts1, ptl0, ptl1, pte0, pte1) \
+    _g_graph_edge_new(src, dst, (const GdkPoint *[]) { pts0, pts1, ptl0, ptl1, pte0, pte1 }, 6, EGC_BLUE)
 
-#define g_graph_edge_new_loop(pts0, pts1, ptl0, ptl1, pte0, pte1) \
-    _g_graph_edge_new((const GdkPoint *[]) { pts0, pts1, ptl0, ptl1, pte0, pte1 }, 6, EGC_BLUE)
+/* Fournit les deux blocs aux extrémités d'un lien. */
+void g_graph_edge_get_boundaries(const GGraphEdge *, GCodeBlock **, GCodeBlock **);
 
 /* Fournit les abscisses des points extrèmes de la ligne. */
 void g_graph_edge_get_x_borders(const GGraphEdge *, gint *, gint *);
diff --git a/src/gtkext/gtkgraphdisplay.c b/src/gtkext/gtkgraphdisplay.c
index 0915f3a..0865e04 100644
--- a/src/gtkext/gtkgraphdisplay.c
+++ b/src/gtkext/gtkgraphdisplay.c
@@ -27,11 +27,15 @@
 #include <assert.h>
 
 
+#include <i18n.h>
+
+
 #include "gtkblockdisplay.h"
 #include "gtkbufferdisplay.h"
 #include "gtkdisplaypanel-int.h"
 #include "graph/cluster.h"
 #include "../analysis/routine.h"
+#include "../common/extstr.h"
 #include "../format/format.h"
 #include "../glibext/gbinarycursor.h"
 #include "../glibext/gloadedpanel.h"
@@ -115,6 +119,9 @@ static gboolean gtk_graph_display_button_release(GtkWidget *, GdkEventButton *,
 /* Assure la suivi des déplacements de souris sur le composant. */
 static gboolean gtk_graph_display_motion_notify(GtkWidget *, GdkEventMotion *, GtkGraphDisplay *);
 
+/* Prépare l'affichage d'une astuce. */
+static gboolean gtk_graph_display_query_tooltip(GtkWidget *, gint, gint, gboolean, GtkTooltip *, GtkGraphDisplay *);
+
 /* Ajuste au besoin la zone affichée pour un curseur. */
 static void gtk_graph_display_prepare_for_cursor(GtkGraphDisplay *, const GLineCursor *);
 
@@ -213,6 +220,10 @@ static void gtk_graph_display_init(GtkGraphDisplay *display)
                       G_CALLBACK(gtk_graph_display_button_release), display);
     g_signal_connect(G_OBJECT(display->support), "motion-notify-event",
                       G_CALLBACK(gtk_graph_display_motion_notify), display);
+    g_signal_connect(G_OBJECT(display->support), "query-tooltip",
+                      G_CALLBACK(gtk_graph_display_query_tooltip), display);
+
+    g_object_set(G_OBJECT(display->support), "has-tooltip", TRUE, NULL);
 
     gtk_widget_add_events(display->support,
                           GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
@@ -649,6 +660,66 @@ static gboolean gtk_graph_display_motion_notify(GtkWidget *widget, GdkEventMotio
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : widget   = composant GTK visé par l'opération.               *
+*                x        = abscisse de la position du message.               *
+*                y        = ordonnée de la position du message.               *
+*                keyboard = indique une demande suite à obtiention du focus.  *
+*                tooltip  = astuce à compléter. [OUT]                         *
+*                display  = support maître à consulter.                       *
+*                                                                             *
+*  Description : Prépare l'affichage d'une astuce.                            *
+*                                                                             *
+*  Retour      : TRUE pour un affichage validé, FALSE sinon.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static gboolean gtk_graph_display_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard, GtkTooltip *tooltip, GtkGraphDisplay *display)
+{
+    gboolean result;                        /* Bilan à retourner           */
+    GCodeBlock *src;                        /* Bloc d'origine du lien      */
+    GCodeBlock *dst;                        /* Bloc de destination du lien */
+    char *info;                             /* Information à faire paraître*/
+    char *desc;                             /* Description d'un bloc       */
+
+    result = FALSE;
+
+    if (display->hl_edge_index < display->edges_count)
+    {
+        g_graph_edge_get_boundaries(display->edges[display->hl_edge_index], &src, &dst);
+
+        info = stradd(NULL, _("<b>Source:</b> "));
+
+        desc = g_code_block_build_tooltip(src);
+        info = stradd(info, desc);
+        free(desc);
+
+        info = stradd(info, "\n\n");
+
+        info = stradd(info, _("<b>Destination:</b> "));
+
+        desc = g_code_block_build_tooltip(dst);
+        info = stradd(info, desc);
+        free(desc);
+
+        gtk_tooltip_set_markup(tooltip, info);
+        free(info);
+
+        g_object_unref(G_OBJECT(src));
+        g_object_unref(G_OBJECT(dst));
+
+        result = TRUE;
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : display = composant GTK à mettre à jour.                     *
 *                cursor  = emplacement à présenter à l'écran.                 *
 *                                                                             *
-- 
cgit v0.11.2-87-g4458