/* Chrysalide - Outil d'analyse de fichiers binaires
 * gtkgraphdisplay.c - affichage de morceaux de code sous forme graphique
 *
 * Copyright (C) 2016-2017 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chrysalide 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "gtkgraphdisplay.h"


#include <assert.h>


#include "gtkblockdisplay.h"
#include "gtkbufferdisplay.h"
#include "gtkdisplaypanel-int.h"
#include "graph/cluster.h"
#include "../analysis/routine.h"
#include "../format/format.h"
#include "../glibext/gbinarycursor.h"
#include "../glibext/gloadedpanel.h"
#include "../gui/core/items.h"



/* Composant d'affichage sous forme graphique (instance) */
struct _GtkGraphDisplay
{
    GtkDisplayPanel parent;                 /* A laisser en premier        */
    GtkWidget *support;                     /* Support des vues en bloc    */
    GtkWidget *extender;                    /* Force la taille du support  */

    GBinSymbol *routine;                    /* Routine en cours d'affichage*/

    segcnt_list *highlighted;               /* Segments mis en évidence    */

    GGraphCluster *cluster;                 /* Disposition en graphique    */

    GGraphEdge **edges;                     /* Liens entre les noeuds      */
    size_t edges_count;                     /* Quantité de ces liens       */

    gdouble start_x;                        /* Abscisse du point de souris */
    gdouble start_y;                        /* Ordonnée du point de souris */
    bool big_enough;                        /* Capacités de déplacement ?  */
    gdouble ref_h;                          /* Position horizontale de ref.*/
    gdouble ref_v;                          /* Position verticale de ref.  */

};

/* Composant d'affichage sous forme graphique (classe) */
struct _GtkGraphDisplayClass
{
    GtkDisplayPanelClass parent;            /* A laisser en premier        */

};


/* Profondeur de l'ombre */
#define SHADOW_SIZE 4

/* Marges en bordure de graphique */
#define GRAPH_MARGIN 23


/* Initialise la classe générique des graphiques de code. */
static void gtk_graph_display_class_init(GtkGraphDisplayClass *);

/* Initialise une instance d'afficheur de code en graphique. */
static void gtk_graph_display_init(GtkGraphDisplay *);

/* Supprime toutes les références externes. */
static void gtk_graph_display_dispose(GtkGraphDisplay *);

/* Procède à la libération totale de la mémoire. */
static void gtk_graph_display_finalize(GtkGraphDisplay *);

/* S'adapte à la surface concédée par le composant parent. */
static void gtk_graph_display_size_allocate(GtkWidget *, GtkAllocation *);

/* Centre si possible le contenu du panneau d'affichage. */
static void gtk_graph_display_update_support_margins(GtkGraphDisplay *, const GtkAllocation *);

/* Indique les dimensions de travail du composant d'affichage. */
static void gtk_graph_display_compute_requested_size(GtkGraphDisplay *, gint *, gint *);

/* Réagit à un défilement chez une barre associée au composant. */
static void gtk_graph_display_adjust_scroll_value(GtkGraphDisplay *, GtkAdjustment *, GtkOrientation);

/*  Met à jour l'affichage de la vue sous forme graphique. */
static gboolean gtk_graph_display_draw(GtkWidget *, cairo_t *, GtkGraphDisplay *);

/* Assure la gestion des clics de souris sur le composant. */
static gboolean gtk_graph_display_button_press(GtkWidget *, GdkEventButton *, GtkGraphDisplay *);

/* Assure la gestion des clics de souris sur le composant. */
static gboolean gtk_graph_display_button_release(GtkWidget *, GdkEventButton *, GtkGraphDisplay *);

/* Assure la suivi des déplacements de souris sur le composant. */
static gboolean gtk_graph_display_motion_notify(GtkWidget *, GdkEventMotion *, GtkGraphDisplay *);

/* Réagit à la sélection externe d'une adresse. */
static void gtk_graph_display_define_main_address(GtkGraphDisplay *, const vmpa2t *);

/* Indique la position courante du curseur. */
static const vmpa2t *gtk_graph_display_get_caret_location(const GtkGraphDisplay *);

/* Indique la position d'affichage d'un emplacement donné. */
static bool gtk_graph_display_get_cursor_coordinates(const GtkGraphDisplay *, const GLineCursor *, gint *, gint *, ScrollPositionTweak);

/* Déplace le curseur à un emplacement défini. */
static bool gtk_graph_display_move_caret_to(GtkGraphDisplay *, gint, gint);

/* Fournit le position courante dans un panneau de chargement. */
static GLineCursor *gtk_graph_display_get_cursor(const GtkGraphDisplay *);

/* Définit le position courante dans un panneau de chargement. */
static void gtk_graph_display_set_cursor(GtkGraphDisplay *, const GLineCursor *);

/* Place en cache un rendu destiné à l'aperçu graphique rapide. */
static void gtk_graph_display_cache_glance(GtkGraphDisplay *, cairo_t *, const GtkAllocation *, double);

/* Supprime tout contenu de l'afficheur de code en graphique. */
static void gtk_graph_display_reset(GtkGraphDisplay *, bool);

/* Notifie un changement de surbrillance au sein d'un noeud. */
static void gtk_graph_display_changed_highlights(GtkBlockDisplay *, GtkGraphDisplay *);

/* Notifie une incapacité de déplacement au sein d'un noeud. */
static void gtk_graph_display_reach_caret_limit(GtkBufferDisplay *, GdkScrollDirection, GtkGraphDisplay *);



/* Détermine le type du composant d'affichage en graphique. */
G_DEFINE_TYPE(GtkGraphDisplay, gtk_graph_display, GTK_TYPE_DISPLAY_PANEL)


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe GTK à initialiser.                            *
*                                                                             *
*  Description : Initialise la classe générique des graphiques de code.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_class_init(GtkGraphDisplayClass *class)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GtkWidgetClass *widget_class;           /* Classe de haut niveau       */
    GtkDisplayPanelClass *panel_class;      /* Classe parente              */

    object = G_OBJECT_CLASS(class);

    object->dispose = (GObjectFinalizeFunc/* ! */)gtk_graph_display_dispose;
    object->finalize = (GObjectFinalizeFunc)gtk_graph_display_finalize;

    widget_class = GTK_WIDGET_CLASS(class);

    widget_class->size_allocate = gtk_graph_display_size_allocate;

    panel_class = GTK_DISPLAY_PANEL_CLASS(class);

    panel_class->compute_size = (compute_requested_size_fc)gtk_graph_display_compute_requested_size;
    panel_class->adjust = (adjust_scroll_value_fc)gtk_graph_display_adjust_scroll_value;
    panel_class->define = (define_address_fc)gtk_graph_display_define_main_address;

    panel_class->get_caret_loc = (get_caret_location_fc)gtk_graph_display_get_caret_location;
    panel_class->get_coordinates = (get_coordinates_fc)gtk_graph_display_get_cursor_coordinates;
    panel_class->move_caret_to = (move_caret_to_fc)gtk_graph_display_move_caret_to;
    panel_class->get_cursor = (get_cursor_fc)gtk_graph_display_get_cursor;
    panel_class->set_cursor = (set_cursor_fc)gtk_graph_display_set_cursor;
    panel_class->cache_glance = (cache_glance_fc)gtk_graph_display_cache_glance;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = instance GTK à initialiser.                        *
*                                                                             *
*  Description : Initialise une instance d'afficheur de code en graphique.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_init(GtkGraphDisplay *display)
{
    display->support = gtk_fixed_new();
    gtk_widget_set_has_window(display->support, TRUE);
    gtk_widget_set_can_focus(display->support, TRUE);

    g_signal_connect(G_OBJECT(display->support), "draw",
                     G_CALLBACK(gtk_graph_display_draw), display);

    g_signal_connect(G_OBJECT(display->support), "button-press-event",
                      G_CALLBACK(gtk_graph_display_button_press), display);
    g_signal_connect(G_OBJECT(display->support), "button-release-event",
                      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);

    gtk_widget_add_events(display->support,
                          GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);

    gtk_widget_show(display->support);

    gtk_fixed_put(GTK_FIXED(display), display->support, 0, 0);

    display->extender = gtk_fixed_new();

    gtk_widget_set_margin_end(display->extender, 1);
    gtk_widget_set_margin_top(display->extender, 1);

    gtk_widget_show(display->extender);
    gtk_fixed_put(GTK_FIXED(display->support), display->extender, 0, 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = instance d'objet GLib à traiter.                   *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_dispose(GtkGraphDisplay *display)
{
    /**
     * display->support est traité par la version amont du conteneur, propriétaire
     * du composant GTK.
     *
     * Pareil pour display->extender.
     */

    gtk_graph_display_reset(display, true);

    G_OBJECT_CLASS(gtk_graph_display_parent_class)->dispose(G_OBJECT(display));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = instance d'objet GLib à traiter.                   *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_finalize(GtkGraphDisplay *display)
{
    G_OBJECT_CLASS(gtk_graph_display_parent_class)->finalize(G_OBJECT(display));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget     = composant GTK à mettre à jour.                  *
*                allocation = étendue accordée à la vue.                      *
*                                                                             *
*  Description : S'adapte à la surface concédée par le composant parent.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
    GtkGraphDisplay *display;                     /* Autre version du composant  */

    GTK_WIDGET_CLASS(gtk_graph_display_parent_class)->size_allocate(widget, allocation);

    display = GTK_GRAPH_DISPLAY(widget);

    gtk_graph_display_update_support_margins(display, allocation);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display    = panneau dont le contenu est à déplacer.         *
*                allocation = étendue accordée à la vue.                      *
*                                                                             *
*  Description : Centre si possible le contenu du panneau d'affichage.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_update_support_margins(GtkGraphDisplay *display, const GtkAllocation *allocation)
{
    gint width;                             /* Largeur totale du support   */
    gint height;                            /* Hauteur totale du support   */
    gint start;                             /* Bordure horizontale         */
    gint top;                               /* Bordure verticale           */

    gtk_graph_display_compute_requested_size(display, &width, &height);

    if (width > allocation->width)
        start = 0;
    else
        start = (allocation->width - width) / 2;

    if (height > allocation->height)
        top = 0;
    else
        top = (allocation->height - height) / 2;

    gtk_widget_set_margin_start(display->support, start);
    gtk_widget_set_margin_top(display->support, top);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à consulter.                         *
*                width   = largeur requise à renseigner ou NULL. [OUT]        *
*                height  = hauteur requise à renseigner ou NULL. [OUT]        *
*                                                                             *
*  Description : Indique les dimensions de travail du composant d'affichage.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_compute_requested_size(GtkGraphDisplay *display, gint *width, gint *height)
{
    GtkAllocation needed;                   /* Taille requise              */

    if (display->cluster != NULL)
    {
        g_graph_cluster_compute_needed_alloc(display->cluster, &needed);
        assert(needed.x == 0 && needed.y == 0);

        needed.width += 2 * GRAPH_MARGIN;
        needed.height += 2 * GRAPH_MARGIN;

    }
    else
    {
        needed.width = 0;
        needed.height = 0;
    }

    if (width != NULL) *width = needed.width;
    if (height != NULL) *height = needed.height;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display     = panneau d'affichage concerné.                  *
*                adj         = défilement dont une valeur a changé.           *
*                orientation = indication sur le défilement à traiter.        *
*                                                                             *
*  Description : Réagit à un défilement chez une barre associée au composant. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_adjust_scroll_value(GtkGraphDisplay *display, GtkAdjustment *adj, GtkOrientation orientation)
{
    gint fake_x;                            /* Abscisse virtuelle          */
    gint fake_y;                            /* Ordonnée virtuelle          */

    fake_x = 0;
    fake_y = 0;
    gtk_display_panel_compute_fake_coord(GTK_DISPLAY_PANEL(display), &fake_x, &fake_y);

    gtk_fixed_move(GTK_FIXED(display), display->support, fake_x, -fake_y);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget  = composant GTK à redessiner.                        *
*                cr      = contexte graphique associé à l'événement.          *
*                display = support maître à consulter.                        *
*                                                                             *
*  Description : Met à jour l'affichage de la vue sous forme graphique.       *
*                                                                             *
*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_graph_display_draw(GtkWidget *widget, cairo_t *cr, GtkGraphDisplay *display)
{
    size_t i;                               /* Boucle de parcours          */

    void draw_shadow(GtkWidget *child, gpointer unused)
    {
        GtkAllocation alloc;                /* Emplacement de l'enfant     */
        gint j;                             /* Boucle de parcours          */
        cairo_pattern_t *pattern;           /* Zones d'application         */

        /* On évite l'extenseur de support... */
        if (!GTK_IS_DISPLAY_PANEL(child))
            return;

        gtk_widget_get_allocation(child, &alloc);

        for (j = 1; j < SHADOW_SIZE; j++)
        {
            cairo_push_group(cr);

            gtk_display_panel_define_border_path(GTK_DISPLAY_PANEL(child), cr, alloc.x + j, alloc.y + j);
            cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
            cairo_fill(cr);

            cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);

            gtk_display_panel_define_border_path(GTK_DISPLAY_PANEL(child), cr, alloc.x, alloc.y);
            cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
            cairo_fill(cr);

            pattern = cairo_pop_group(cr);

            cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.3);
            cairo_mask(cr, pattern);
            cairo_fill(cr);

            cairo_pattern_destroy(pattern);

        }

    }

    gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)draw_shadow, NULL);

    for (i = 0; i < display->edges_count; i++)
        g_graph_edge_draw(display->edges[i], cr, true);

    return FALSE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget  = composant GTK visé par l'opération.                *
*                event   = informations liées à l'événement.                  *
*                display = support maître à consulter.                        *
*                                                                             *
*  Description : Assure la gestion des clics de souris sur le composant.      *
*                                                                             *
*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_graph_display_button_press(GtkWidget *widget, GdkEventButton *event, GtkGraphDisplay *display)
{
    gboolean result;                        /* Poursuite à faire suivre    */
    GtkScrolledWindow *support;             /* Support défilant associé    */
    GtkAdjustment *hadj;                    /* Gestionnaire du défilement  */
    GtkAdjustment *vadj;                    /* Gestionnaire du défilement  */
    GdkCursor *cursor;                      /* Pointeur pour la surface    */

    result = FALSE;

    if (event->button == 1)
    {
        support = GTK_SCROLLED_WINDOW(gtk_widget_get_parent(GTK_WIDGET(display)));

        hadj = gtk_scrolled_window_get_hadjustment(support);
        vadj = gtk_scrolled_window_get_vadjustment(support);

        display->big_enough = (gtk_adjustment_get_upper(hadj) > gtk_adjustment_get_page_size(hadj)
                            || gtk_adjustment_get_upper(vadj) > gtk_adjustment_get_page_size(vadj));

        if (display->big_enough)
        {
            display->start_x = event->x_root;
            display->start_y = event->y_root;

            display->ref_h = gtk_adjustment_get_value(hadj);
            display->ref_v = gtk_adjustment_get_value(vadj);

            cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_FLEUR);
            gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
            g_object_unref(G_OBJECT(cursor));

            result = TRUE;

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget  = composant GTK visé par l'opération.                *
*                event   = informations liées à l'événement.                  *
*                display = support maître à consulter.                        *
*                                                                             *
*  Description : Assure la gestion des clics de souris sur le composant.      *
*                                                                             *
*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_graph_display_button_release(GtkWidget *widget, GdkEventButton *event, GtkGraphDisplay *display)
{
    if (event->button == 1 && display->big_enough)
        gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);

    return FALSE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : widget  = composant GTK visé par l'opération.                *
*                event   = informations liées à l'événement.                  *
*                display = support maître à consulter.                        *
*                                                                             *
*  Description : Assure la suivi des déplacements de souris sur le composant. *
*                                                                             *
*  Retour      : FALSE pour poursuivre la propagation de l'événement.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_graph_display_motion_notify(GtkWidget *widget, GdkEventMotion *event, GtkGraphDisplay *display)
{
    gdouble diff_x;                         /* Evolution sur les abscisses */
    gdouble diff_y;                         /* Evolution sur les ordonnées */
    GtkScrolledWindow *support;             /* Support défilant associé    */
    GtkAdjustment *hadj;                    /* Gestionnaire du défilement  */
    GtkAdjustment *vadj;                    /* Gestionnaire du défilement  */
    gdouble value;                          /* Nouvelle valeur bornée      */

    if (event->state & GDK_BUTTON1_MASK && display->big_enough)
    {
        diff_x = display->start_x - event->x_root;
        diff_y = display->start_y - event->y_root;

        support = GTK_SCROLLED_WINDOW(gtk_widget_get_parent(GTK_WIDGET(display)));

        hadj = gtk_scrolled_window_get_hadjustment(support);
        vadj = gtk_scrolled_window_get_vadjustment(support);

        value = CLAMP(display->ref_h + diff_x, gtk_adjustment_get_lower(hadj),
                      gtk_adjustment_get_upper(hadj) - gtk_adjustment_get_page_size(hadj));
        gtk_adjustment_set_value(hadj, value);

        value = CLAMP(display->ref_v + diff_y, gtk_adjustment_get_lower(vadj),
                      gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj));
        gtk_adjustment_set_value(vadj, value);

    }

    return FALSE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à mettre à jour.                     *
*                addr    = adresse sélectionnée de manière externe.           *
*                                                                             *
*  Description : Réagit à la sélection externe d'une adresse.                 *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_define_main_address(GtkGraphDisplay *display, const vmpa2t *addr)
{
    bool need_update;                       /* Mise à jour du contenu ?    */
    const mrange_t *range;                  /* Couverture courante         */
    GExeFormat *format;                     /* Type de fichier chargé      */
    GBinSymbol *symbol;                     /* Symbole présent à l'adresse */
    bool found;                             /* Bilan des recherches        */
    SymbolType type;                        /* Type de symbole rencontré   */
    GBlockList *list;                       /* Liste de blocs basiques     */
    gint right;                             /* Abscisse du coin droit      */
    gint bottom;                            /* Ordonnée du coin inférieur  */
    GtkAllocation allocation;               /* Espace alloué au panneau    */

    if (display->routine == NULL)
        need_update = true;
    else
    {
        range = g_binary_symbol_get_range(display->routine);
        need_update = !mrange_contains_addr(range, addr);
    }

    if (need_update)
    {
        gtk_graph_display_reset(display, false);

        format = g_loaded_binary_get_format(GTK_DISPLAY_PANEL(display)->binary);

        found = g_binary_format_find_symbol_for(G_BIN_FORMAT(format), addr, &symbol);

        if (!found)
            goto ggddma_done;

        type = g_binary_symbol_get_target_type(symbol);

        if (type != STP_ROUTINE && type != STP_ENTRY_POINT)
            goto ggddma_bad_type;

        display->routine = symbol;
        g_object_ref(G_OBJECT(symbol));

        display->highlighted = init_segment_content_list();

        list = g_binary_routine_get_basic_blocks(G_BIN_ROUTINE(symbol));

        display->cluster = bootstrap_graph_cluster(GTK_DISPLAY_PANEL(display)->binary,
                                                   list, display->highlighted);

        g_graph_cluster_place(display->cluster, display);

        /**
         * Comme la taille du support ne peut pas être forcée et
         * étendue pour comprendre les ombres, on place un composant
         * minuscule à l'extrémité de ce support.
         */

        gtk_graph_display_compute_requested_size(display, &right, &bottom);

        gtk_fixed_move(GTK_FIXED(display->support), display->extender, right - 1, bottom - 1);

        /**
         * Si possible, on centre le contenu obtenu.
         */

        gtk_widget_get_allocation(GTK_WIDGET(display), &allocation);

        gtk_graph_display_update_support_margins(display, &allocation);

 ggddma_bad_type:

        g_object_unref(G_OBJECT(symbol));

 ggddma_done:

        update_editor_items_current_view(G_LOADED_PANEL(display));

        g_object_unref(G_OBJECT(format));

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à manipuler.                         *
*                                                                             *
*  Description : Indique la position courante du curseur.                     *
*                                                                             *
*  Retour      : Emplacement courant du curseur ou NULL si aucun.             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static const vmpa2t *gtk_graph_display_get_caret_location(const GtkGraphDisplay *display)
{
    return NULL;    /* FIXME */

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à consulter.                         *
*                cursor  = emplacement à présenter à l'écran.                 *
*                x       = position horizontale au sein du composant. [OUT]   *
*                y       = position verticale au sein du composant. [OUT]     *
*                tweak   = adaptation finale à effectuer.                     *
*                                                                             *
*  Description : Indique la position d'affichage d'un emplacement donné.      *
*                                                                             *
*  Retour      : true si l'adresse fait partie du composant, false sinon.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool gtk_graph_display_get_cursor_coordinates(const GtkGraphDisplay *display, const GLineCursor *cursor, gint *x, gint *y, ScrollPositionTweak tweak)
{
    /* TODO */

    return false;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à manipuler.                         *
*                x       = abscisse proposée pour le nouvel emplacement.      *
*                y       = ordonnée proposée pour le nouvel emplacement.      *
*                                                                             *
*  Description : Déplace le curseur à un emplacement défini.                  *
*                                                                             *
*  Retour      : true si un traitement a été effectué, false sinon.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool gtk_graph_display_move_caret_to(GtkGraphDisplay *display, gint x, gint y)
{
    bool result;                            /* Bilan à retourner           */

    result = false;

    void move_caret_to_sub_block(GtkWidget *child, gpointer unused)
    {
        GtkAllocation alloc;                /* Emplacement réservé         */
        GtkDisplayPanel *panel;             /* Autre vision d'enfance      */
        gint sub_x;                         /* Abscisse relative à l'enfant*/
        gint sub_y;                         /* Ordonnée relative à l'enfant*/

        if (result)
            return;

        if (!GTK_IS_BUFFER_DISPLAY(child))
            return;

        gtk_widget_get_allocation(child, &alloc);

        if (x < alloc.x || x >= (alloc.x + alloc.width)) return;
        if (y < alloc.y || y >= (alloc.y + alloc.height)) return;

        panel = GTK_DISPLAY_PANEL(child);

        sub_x = x - alloc.x;
        sub_y = y - alloc.y;

        result = GTK_DISPLAY_PANEL_GET_CLASS(panel)->move_caret_to(panel, sub_x, sub_y);

    }

    gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)move_caret_to_sub_block, NULL);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à consulter.                         *
*                                                                             *
*  Description : Fournit le position courante dans un panneau de chargement.  *
*                                                                             *
*  Retour      : Informations relatives à la position du curseur.             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GLineCursor *gtk_graph_display_get_cursor(const GtkGraphDisplay *display)
{
    GLineCursor *result;                    /* Contenu à retourner         */

    result = NULL;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à mettre à jour.                     *
*                cursor  = informations relatives à la position du curseur.   *
*                                                                             *
*  Description : Définit le position courante dans un panneau de chargement.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_set_cursor(GtkGraphDisplay *display, const GLineCursor *cursor)
{
    vmpa2t addr;                            /* Adresse ciblée              */

    assert(G_IS_BINARY_CURSOR(cursor));

    g_binary_cursor_get_info(G_BINARY_CURSOR(cursor), &addr);

    gtk_graph_display_define_main_address(display, &addr);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à manipuler.                         *
*                cr      = assistant pour la création de rendus.              *
*                area    = taille de la surface réduite à disposition.        *
*                scale   = échelle vis à vis de la taille réelle.             *
*                                                                             *
*  Description : Place en cache un rendu destiné à l'aperçu graphique rapide. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_cache_glance(GtkGraphDisplay *display, cairo_t *cr, const GtkAllocation *area, double scale)
{
    size_t i;                               /* Boucle de parcours          */

    void draw_child_glance(GtkWidget *child, gpointer unused)
    {
        GtkAllocation sub_area;             /* Emplacement réservé         */

        if (!GTK_IS_BUFFER_DISPLAY(child))
            return;

        gtk_widget_get_allocation(child, &sub_area);

        sub_area.x *= scale;
        sub_area.y *= scale;
        sub_area.width = sub_area.width * scale + 1;
        sub_area.height = sub_area.height * scale + 1;

        g_loaded_panel_cache_glance(G_LOADED_PANEL(child), cr, &sub_area, scale);

    }

    gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)draw_child_glance, NULL);

    cairo_scale(cr, scale, scale);

    for (i = 0; i < display->edges_count; i++)
        g_graph_edge_draw(display->edges[i], cr, false);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Crée un nouveau composant pour l'affichage en graphique.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GtkWidget *gtk_graph_display_new(void)
{
    return g_object_new(GTK_TYPE_GRAPH_DISPLAY, NULL);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à mettre à jour.                     *
*                widget  = composant GTK à insérer.                           *
*                x       = abscisse du point d'insertion.                     *
*                y       = ordonnée du point d'insertion.                     *
*                                                                             *
*  Description : Place une vue sous forme de bloc dans le graphique.          *
*                                                                             *
*  Retour      : Plutôt que de redéfinir *toutes* les méthodes de             *
*                GtkContainer, on étend !                                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

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, "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);

}



/******************************************************************************
*                                                                             *
*  Paramètres  : display = composant GTK à mettre à jour.                     *
*                edge    = lien entre noeuds à conserver.                     *
*                                                                             *
*  Description : Intègre un lien entre blocs graphiques dans l'afficheur.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_graph_display_add_edge(GtkGraphDisplay *display, GGraphEdge *edge)
{
    g_graph_edge_offset(edge, GRAPH_MARGIN, GRAPH_MARGIN);

    display->edges = (GGraphEdge **)realloc(display->edges,
                                         ++display->edges_count * sizeof(GGraphEdge *));

    display->edges[display->edges_count - 1] = edge;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : display = instance GTK à réinitialiser.                      *
*                dispose = indique l'origine de l'appel.                      *
*                                                                             *
*  Description : Supprime tout contenu de l'afficheur de code en graphique.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_reset(GtkGraphDisplay *display, bool dispose)
{
    size_t i;                               /* Boucle de parcours          */

    if (!dispose)
    {
        void detach_all_blocks(GtkWidget *widget, GtkContainer *container)
        {
            if (widget != display->extender)
                gtk_container_remove(container, widget);

        }

        gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)detach_all_blocks, display->support);

    }

    if (display->routine != NULL)
    {
        g_object_unref(G_OBJECT(display->routine));
        display->routine = NULL;
    }

    if (display->highlighted != NULL)
    {
        exit_segment_content_list(display->highlighted);
        display->highlighted = NULL;
    }

    if (display->cluster != NULL)
    {
        g_object_unref(G_OBJECT(display->cluster));
        display->cluster = NULL;
    }

    for (i = 0; i < display->edges_count; i++)
        g_object_unref(G_OBJECT(display->edges[i]));

    if (display->edges_count > 0)
    {
        free(display->edges);
        display->edges = NULL;

        display->edges_count = 0;

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : node    = composant d'affichage impliqué dans la procédure.  *
*                display = support graphique de tous les noeuds.              *
*                                                                             *
*  Description : Notifie un changement de surbrillance au sein d'un noeud.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_changed_highlights(GtkBlockDisplay *node, GtkGraphDisplay *display)
{
    void refresh_highlights(GtkWidget *child, gpointer unused)
    {
        if (!GTK_IS_BUFFER_DISPLAY(child))
            return;

        if (child != GTK_WIDGET(node))
            gtk_widget_queue_draw(child);

    }

    gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)refresh_highlights, NULL);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : node    = composant d'affichage impliqué dans la procédure.  *
*                dir     = direction du déplacement souhaité et impossible.   *
*                display = support graphique de tous les noeuds.              *
*                                                                             *
*  Description : Notifie une incapacité de déplacement au sein d'un noeud.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_graph_display_reach_caret_limit(GtkBufferDisplay *node, GdkScrollDirection dir, GtkGraphDisplay *display)
{
#if 0
    GBufferView *view;                      /* Vue d'un tampon global      */
    vmpa2t first;                           /* Début d'un groupe de lignes */
    vmpa2t last;                            /* Fin d'un groupe de lignes   */
    const mrange_t *range;                  /* Couverture courante         */
    GArchProcessor *proc;                   /* Processeur pour instructions*/
    vmpa2t iaddr;                           /* Position de l'instructin    */
    instr_iter_t *iter;                     /* Boucle de parcours          */
    GArchInstruction *instr;                /* Instruction à venir visiter */
#ifndef NDEBUG
    bool is_return;                         /* Est-ce une instruc. finale ?*/
#endif
    GtkBufferDisplay *target;               /* Bloc suivant pour le focus  */

    /* Détermination de l'instruction à cibler */

    view = gtk_buffer_display_get_view(node);
    g_buffer_view_get_restrictions(view, &first, &last);
    g_object_unref(G_OBJECT(view));

    range = g_binary_symbol_get_range(display->routine);

    proc = g_loaded_binary_get_processor(GTK_DISPLAY_PANEL(display)->binary);

    init_vmpa(&iaddr, VMPA_NO_PHYSICAL, VMPA_NO_VIRTUAL);

#ifndef NDEBUG
    is_return = false;
#endif

    switch (dir)
    {
        case GDK_SCROLL_LEFT:
        case GDK_SCROLL_UP:

            if (cmp_vmpa(get_mrange_addr(range), &first) != 0)
            {
                iter = g_arch_processor_get_iter_from_address(proc, &first);

                if (iter != NULL)
                {
                    instr = get_instruction_iterator_prev(iter);

                    if (instr != NULL)
                    {
                        /* TODO : boucler si !HAS_CODE */

                        if (mrange_contains_addr(range, &iaddr))
                            copy_vmpa(&iaddr, get_mrange_addr(g_arch_instruction_get_range(instr)));

                        g_object_unref(G_OBJECT(instr));

                    }

                    delete_instruction_iterator(iter);

                }

            }

            break;

        case GDK_SCROLL_RIGHT:
        case GDK_SCROLL_DOWN:

            iter = g_arch_processor_get_iter_from_address(proc, &last);

            if (iter != NULL)
            {
#ifndef NDEBUG
                instr = get_instruction_iterator_current(iter);
                if (instr != NULL)
                {
                    is_return = (g_arch_instruction_get_flags(instr) & AIF_RETURN_POINT);
                    g_object_unref(G_OBJECT(instr));
                }
#endif

                instr = get_instruction_iterator_next(iter);

                if (instr != NULL)
                {
                    /* TODO : boucler si !HAS_CODE */

                    copy_vmpa(&iaddr, get_mrange_addr(g_arch_instruction_get_range(instr)));

                    if (!mrange_contains_addr(range, &iaddr))
                        init_vmpa(&iaddr, VMPA_NO_PHYSICAL, VMPA_NO_VIRTUAL);

                    g_object_unref(G_OBJECT(instr));

                }

                delete_instruction_iterator(iter);

            }

            break;

        case GDK_SCROLL_SMOOTH:
            assert(false);  /* Argument jamais généré */
            break;

    }

    g_object_unref(G_OBJECT(proc));

    /* Recherche du bloc parent */

    if (is_invalid_vmpa(&iaddr))
        return;

    target = NULL;

    void find_target_block(GtkWidget *child, gpointer unused)
    {
        GtkBufferDisplay *test;             /* Candidat potentiel à tester */

        if (!GTK_IS_BUFFER_DISPLAY(child))
            return;

        test = GTK_BUFFER_DISPLAY(child);

        view = gtk_buffer_display_get_view(test);
        g_buffer_view_get_restrictions(view, &first, &last);
        g_object_unref(G_OBJECT(view));

        if (cmp_vmpa(&first, &iaddr) <= 0 && cmp_vmpa(&iaddr, &last) <= 0)
        {
            assert(target == NULL);
            assert(node != test);
            target = test;
            return;
        }

    }

    gtk_container_foreach(GTK_CONTAINER(display->support), (GtkCallback)find_target_block, NULL);

    assert(target != NULL || is_return);

    /* Affichage du nouveau curseur */

    /**
     * Il se peut qu'aucune adresse suivante ne soit disponible : c'est typiquement
     * le cas sous ARM, avec les valeurs brutes référencées dans le code. Ces valeurs
     * sont incluses dans la surface couverte par la routine concernée, mais ne sont
     * pas intégrées dans les blocs basiques associés.
     */

    if (target == NULL)
        return;

    gtk_widget_grab_focus(GTK_WIDGET(target));

    switch (dir)
    {
        case GDK_SCROLL_UP:
        case GDK_SCROLL_LEFT:
            gtk_buffer_display_move_caret_to(target, false, NULL);
            break;

            break;

        case GDK_SCROLL_RIGHT:
        case GDK_SCROLL_DOWN:
            gtk_buffer_display_move_caret_to(target, true, NULL);
            break;

            break;

        case GDK_SCROLL_SMOOTH:
            assert(false);  /* Argument jamais généré */
            break;

    }

    /* TODO : scrolling... */
#endif
}