/* Chrysalide - Outil d'analyse de fichiers binaires
* hexview.c - affichage de contenus de binaire
*
* Copyright (C) 2016-2024 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 .
*/
#include "hexview.h"
#include
#include
#include "area.h"
#include "hexview-int.h"
#include "../glibext/options/hex.h"
/* ------------------------- BASES D'UN COMPOSANT GRAPHIQUE ------------------------- */
/* Procède à l'initialisation de l'afficheur générique. */
static void gtk_hex_view_class_init(GtkHexViewClass *);
/* Procède à l'initialisation de l'afficheur générique. */
static void gtk_hex_view_init(GtkHexView *);
/* Supprime toutes les références externes. */
static void gtk_hex_view_dispose(GtkHexView *);
/* Procède à la libération totale de la mémoire. */
static void gtk_hex_view_finalize(GtkHexView *);
/* Procède à l'actualisation de l'affichage d'un sous-composant. */
static void gtk_hex_view_dispatch_sub_snapshot(GtkWidget *, GtkSnapshot *, GtkWidget *);
/* Adapte le cache de lignes hexadécimales à la taille courante. */
static void gtk_hex_view_populate_cache(GtkHexView *);
void demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent);
/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */
/* Prend acte de la taille allouée au composant d'affichage. */
static void gtk_hex_view_size_allocate(GtkWidget *, int, int, int);
/* Fournit le mode de calcul pour déterminer la taille. */
static GtkSizeRequestMode gtk_hex_view_get_request_mode(GtkWidget *);
/* Fournit les mesures mainimale et idéale du composant. */
static void gtk_hex_view_measure(GtkWidget *, GtkOrientation, int, int *, int *, int *, int *);
/* ---------------------------------------------------------------------------------- */
/* BASES D'UN COMPOSANT GRAPHIQUE */
/* ---------------------------------------------------------------------------------- */
/* Détermine le type du composant d'affichage générique. */
G_DEFINE_TYPE(GtkHexView, gtk_hex_view, GTK_TYPE_BUFFER_VIEW);
/******************************************************************************
* *
* Paramètres : class = classe GTK à initialiser. *
* *
* Description : Procède à l'initialisation de l'afficheur générique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_class_init(GtkHexViewClass *class)
{
GObjectClass *object; /* Plus haut niveau équivalent */
GtkWidgetClass *widget; /* Classe de haut niveau */
object = G_OBJECT_CLASS(class);
object->dispose = (GObjectFinalizeFunc/* ! */)gtk_hex_view_dispose;
object->finalize = (GObjectFinalizeFunc)gtk_hex_view_finalize;
widget = GTK_WIDGET_CLASS(class);
gtk_widget_class_set_css_name(widget, "GtkHexView");
g_type_ensure(GTK_TYPE_COMPOSING_AREA);
gtk_widget_class_set_template_from_resource(widget, "/re/chrysalide/framework/gtkext/hexview.ui");
gtk_widget_class_bind_template_child(widget, GtkHexView, offsets);
gtk_widget_class_bind_template_child(widget, GtkHexView, hex);
gtk_widget_class_bind_template_child(widget, GtkHexView, ascii);
widget->size_allocate = gtk_hex_view_size_allocate;
widget->get_request_mode = gtk_hex_view_get_request_mode;
widget->measure = gtk_hex_view_measure;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à initialiser. *
* *
* Description : Procède à l'initialisation de l'afficheur générique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_init(GtkHexView *view)
{
GtkContentView *base; /* Base d'instance supérieure */
/* Niveau supérieur */
base = GTK_CONTENT_VIEW(view);
base->options = G_DISPLAY_OPTIONS(g_hex_options_new());
base->sub_children = view->children;
base->sub_count = _CHILDREN_COUNT;
/* Instance courante */
gtk_widget_init_template(GTK_WIDGET(view));
g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->offsets),
gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));
g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->hex),
gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));
g_raw_scan_cache_register_snapshot(GTK_COMPOSING_AREA(view->ascii),
gtk_hex_view_dispatch_sub_snapshot, GTK_WIDGET(view));
view->generator = NULL;
}
/******************************************************************************
* *
* Paramètres : view = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_dispose(GtkHexView *view)
{
gtk_widget_dispose_template(GTK_WIDGET(view), GTK_TYPE_HEX_VIEW);
g_clear_object(&view->generator);
G_OBJECT_CLASS(gtk_hex_view_parent_class)->dispose(G_OBJECT(view));
}
/******************************************************************************
* *
* Paramètres : view = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_finalize(GtkHexView *view)
{
G_OBJECT_CLASS(gtk_hex_view_parent_class)->finalize(G_OBJECT(view));
}
/******************************************************************************
* *
* Paramètres : content = contenu binaire à exposer de façon brute. *
* *
* Description : Crée un composant d'affichage d'octets bruts et imprimables. *
* *
* Retour : Centralisateur mis en place pour un composant GTK donné. *
* *
* Remarques : - *
* *
******************************************************************************/
GtkHexView *gtk_hex_view_new(GBinContent *content)
{
GtkHexView *result; /* Nouvelle instance à renvoyer*/
result = g_object_new(GTK_TYPE_HEX_VIEW, NULL);
if (!gtk_hex_view_create(result, content))
g_clear_object(&result);
return result;
}
/******************************************************************************
* *
* Paramètres : view = composant d'affichage à initialiser pleinement. *
* content = contenu binaire à exposer de façon brute. *
* *
* Description : Met en place un nouveau composant d'affichage d'octets bruts.*
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
bool gtk_hex_view_create(GtkHexView *view, GBinContent *content)
{
bool result; /* Bilan à retourner */
GtkBufferView *parent; /* Version parente du composant*/
GBufferCache *cache; /* Tampon à représenter */
result = true;
assert(g_display_options_count(GTK_CONTENT_VIEW(view)->options) == 1);
parent = GTK_BUFFER_VIEW(view);
cache = g_buffer_cache_new(1, 2);
parent->view = g_buffer_view_new(cache, parent->style);
unref_object(cache);
view->generator = g_hex_generator_new(content);
return result;
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à redessiner. *
* snapshot = gestionnaire de noeuds de rendu à solliciter. *
* parent = composant GTK parent et cadre de l'appel. *
* *
* Description : Procède à l'actualisation de l'affichage d'un sous-composant.*
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_dispatch_sub_snapshot(GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent)
{
GtkHexView *view; /* Version spécialisée */
size_t column; /* Indice de colonne à traiter */
int width; /* Largeur à disposition */
int height; /* Hauteur à disposition */
cairo_t *cr; /* Pinceau pour les dessins */
view = GTK_HEX_VIEW(parent);
if (widget == view->offsets)
column = HCO_OFFSET;
else if (widget == view->hex)
column = HCO_COUNT + 0;
else
{
assert(widget == view->ascii);
column = HCO_COUNT + 1;
}
width = gtk_widget_get_width(widget);
height = gtk_widget_get_height(widget);
cr = gtk_snapshot_append_cairo(snapshot, &GRAPHENE_RECT_INIT(0, 0, width, height));
g_buffer_view_draw(GTK_BUFFER_VIEW(parent)->view, cr, column, GTK_BUFFER_VIEW(parent)->virt_top, height);
cairo_destroy(cr);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à mettre à jour. *
* *
* Description : Adapte le cache de lignes hexadécimales à la taille courante.*
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_populate_cache(GtkHexView *view)
{
GBinContent *content; /* Contenu binaire affiché */
phys_t full; /* Taille totale à représenter */
phys_t line; /* Taille représentée par ligne*/
size_t needed; /* Nombre de lignes nécessaires*/
GBufferCache *cache; /* Tampon à représenter */
size_t count; /* Nombre actuel de lignes */
/* Détermination du besoin */
content = g_hex_generator_get_content(view->generator);
full = g_binary_content_compute_size(content);
unref_object(content);
line = g_hex_generator_get_bytes_per_line(view->generator);
needed = full / line;
if (full % line > 0)
needed++;
/* Adaptation du tampon interne ? */
cache = g_buffer_view_get_cache(GTK_BUFFER_VIEW(view)->view);
g_buffer_cache_wlock(cache);
count = g_buffer_cache_count_lines(cache);
if (needed < count)
g_buffer_cache_truncate(cache, needed);
else if (needed > count)
g_buffer_cache_extend_with(cache, needed, G_TOKEN_GENERATOR(view->generator));
g_buffer_cache_wunlock(cache);
unref_object(cache);
/* Mise à jour de l'affichage ? */
if (needed != count)
gtk_widget_queue_resize(GTK_WIDGET(view));
}
void demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot, GtkWidget *parent)
{
GdkRGBA red, green, yellow, blue;
float w, h;
gdk_rgba_parse (&red, "red");
gdk_rgba_parse (&green, "green");
gdk_rgba_parse (&yellow, "yellow");
gdk_rgba_parse (&blue, "blue");
w = gtk_widget_get_width (widget) / 2.0;
h = gtk_widget_get_height (widget) / 2.0;
h /= 2.0;
gtk_snapshot_append_color (snapshot, &red,
&GRAPHENE_RECT_INIT(0, 0, w, h));
gtk_snapshot_append_color (snapshot, &green,
&GRAPHENE_RECT_INIT(w, 0, w, h));
gtk_snapshot_append_color (snapshot, &yellow,
&GRAPHENE_RECT_INIT(0, h, w, h));
gtk_snapshot_append_color (snapshot, &blue,
&GRAPHENE_RECT_INIT(w, h, w, h));
}
/* ---------------------------------------------------------------------------------- */
/* IMPLEMENTATION DES FONCTIONS DE CLASSE */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : widget = composant GTK à examiner. *
* width = largeur affectée au composant graphique. *
* height = hauteur affectée au composant graphique. *
* baseline = ligne de base affectée au composant graphique. *
* *
* Description : Prend acte de la taille allouée au composant d'affichage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_size_allocate(GtkWidget *widget, int width, int height, int baseline)
{
GtkHexView *view; /* Version spécialisée */
int *min_widths; /* Tailles minimales imposées */
int *final_widths; /* Tailles finales retenues */
size_t i; /* Boucle de parcours */
int available; /* Largeur disponible */
bool changed; /* Détection de variation */
GWidthTracker *tracker; /* Collecteur de largeurs */
view = GTK_HEX_VIEW(widget);
min_widths = alloca(_CHILDREN_COUNT * sizeof(int));
final_widths = alloca(_CHILDREN_COUNT * sizeof(int));
for (i = 0; i < _CHILDREN_COUNT; i++)
gtk_widget_measure(view->children[i], GTK_ORIENTATION_HORIZONTAL, -1, &min_widths[i], NULL, NULL, NULL);
/* Passe 1 : tentative sans défilement vertical */
available = width;
for (i = 0; i < _CHILDREN_COUNT; i++)
available -= min_widths[i];
changed = g_hex_generator_allocate(view->generator,
GTK_CONTENT_VIEW(view)->options,
GTK_BUFFER_VIEW(view)->style,
available, final_widths);
/* Application des largeurs calculées */
if (changed)
{
gtk_hex_view_populate_cache(view);
tracker = g_buffer_view_get_tracker(GTK_BUFFER_VIEW(view)->view);
for (i = 0; i < _CHILDREN_COUNT; i++)
{
final_widths[i] += min_widths[i];
g_width_tracker_set_column_min_width(tracker, i, final_widths[i]);
}
unref_object(tracker);
}
/* Mise à jour des éléments plus internes */
GTK_WIDGET_CLASS(gtk_hex_view_parent_class)->size_allocate(widget, width, height, baseline);
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à consulter. *
* *
* Description : Fournit le mode de calcul pour déterminer la taille. *
* *
* Retour : Mode de calcul adapté au composant graphique. *
* *
* Remarques : - *
* *
******************************************************************************/
static GtkSizeRequestMode gtk_hex_view_get_request_mode(GtkWidget *widget)
{
GtkSizeRequestMode result; /* Configuration à remonter */
result = GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
return result;
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à examiner. *
* orientation = direction à observer pour les calculs. *
* for_size = taille de la direction opposée. *
* minimum = taille minimale pour le composant. [OUT] *
* natural = taille idéale pour le composant. [OUT] *
* min_baseline = ligne de base minimale. [OUT] *
* nat_baseline = ligne de base idéale. [OUT] *
* *
* Description : Fournit les mesures mainimale et idéale du composant. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_measure(GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *min_baseline, int *nat_baseline)
{
bool processed; /* Calcul de hauteur effectué */
GtkHexView *view; /* Version spécialisée */
int requested; /* Taille requise à priori */
size_t i; /* Boucle de parcours */
int min; /* Valeur minimale locale */
int nat; /* Valeur idéale locale */
processed = false;
/* Demande de hauteur minimale / idéale */
if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1)
{
view = GTK_HEX_VIEW(widget);
requested = 0;
for (i = 0; i < _CHILDREN_COUNT; i++)
{
gtk_widget_measure(view->children[i], GTK_ORIENTATION_HORIZONTAL, -1, &min, NULL, NULL, NULL);
requested += min;
}
for_size -= requested;
if (for_size > 0)
{
requested = g_hex_generator_mesure_height_for_width(view->generator,
GTK_CONTENT_VIEW(view)->options,
GTK_BUFFER_VIEW(view)->style,
for_size);
if (minimum != NULL) *minimum = 0;
if (natural != NULL) *natural = requested;
for (i = 0; i < _CHILDREN_COUNT; i++)
{
gtk_widget_measure(view->children[i], GTK_ORIENTATION_VERTICAL, -1, &min, &nat, NULL, NULL);
if (minimum != NULL && min > *minimum)
*minimum = min;
if (natural != NULL && nat > *natural)
*natural = nat;
}
processed = true;
}
}
if (!processed)
GTK_WIDGET_CLASS(gtk_hex_view_parent_class)->measure(widget, orientation, for_size,
minimum, natural, min_baseline, nat_baseline);
}