/* 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 ------------------------- */
/* Liste des propriétés */
typedef enum _HexViewProperty {
PROP_0, /* Réservé */
PROP_SHOW_OFFSETS, /* Affichage des positions */
PROP_CONTENT, /* Contenu binaire affiché */
N_PROPERTIES
} HexViewProperty;
static GParamSpec *_hex_view_properties[N_PROPERTIES] = { NULL, };
/* Initialise la classe des afficheurs de tampons bruts. */
static void gtk_hex_view_class_init(GtkHexViewClass *);
/* Initialise une instance d'afficheur de tampons bruts. */
static void gtk_hex_view_init(GtkHexView *);
/* Supprime toutes les références externes. */
static void gtk_hex_view_dispose(GObject *);
/* Procède à la libération totale de la mémoire. */
static void gtk_hex_view_finalize(GObject *);
/* 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 *);
/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */
/* Met à jour une propriété d'instance GObject. */
static void gtk_hex_view_set_property(GObject *, guint, const GValue *, GParamSpec *);
/* Fournit la valeur d'une propriété d'instance GObject. */
static void gtk_hex_view_get_property(GObject *, guint, GValue *, GParamSpec *);
/* 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 : Initialise la classe des afficheurs de tampons bruts. *
* *
* 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 = gtk_hex_view_dispose;
object->finalize = gtk_hex_view_finalize;
object->set_property = gtk_hex_view_set_property;
object->get_property = gtk_hex_view_get_property;
_hex_view_properties[PROP_SHOW_OFFSETS] =
g_param_spec_boolean("show-offsets", NULL, NULL,
TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
_hex_view_properties[PROP_CONTENT] =
g_param_spec_object("content", NULL, NULL,
G_TYPE_BIN_CONTENT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties(object, N_PROPERTIES, _hex_view_properties);
widget = GTK_WIDGET_CLASS(class);
gtk_widget_class_set_css_name(widget, "hexview");
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 : Initialise une instance d'afficheur de tampons bruts. *
* *
* 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;
gtk_hex_view_create(view, NULL);
}
/******************************************************************************
* *
* Paramètres : object = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_dispose(GObject *object)
{
GtkHexView *view; /* Version spécialisée */
gtk_widget_dispose_template(GTK_WIDGET(object), GTK_TYPE_HEX_VIEW);
view = GTK_HEX_VIEW(object);
g_clear_object(&view->generator);
G_OBJECT_CLASS(gtk_hex_view_parent_class)->dispose(object);
}
/******************************************************************************
* *
* Paramètres : object = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_finalize(GObject *object)
{
G_OBJECT_CLASS(gtk_hex_view_parent_class)->finalize(object);
}
/******************************************************************************
* *
* 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 /* opt_count */, 2 /* reg_count */);
parent->view = g_buffer_view_new(cache, parent->style);
unref_object(cache);
gtk_hex_view_set_content(view, content);
return result;
}
/******************************************************************************
* *
* Paramètres : view = composant d'affichage à consulter. *
* *
* Description : Fournit le contenu associé au composant d'affichage. *
* *
* Retour : Contenu dans lequel puise le générateur pour les lignes. *
* *
* Remarques : - *
* *
******************************************************************************/
GBinContent *gtk_hex_view_get_content(const GtkHexView *view)
{
GBinContent *result; /* Référence à retourner */
if (view->generator != NULL)
result = g_hex_generator_get_content(view->generator);
else
result = NULL;
return result;
}
/******************************************************************************
* *
* Paramètres : view = composant d'affichage à modifier. *
* content = nouveau contenu pour source de génération. *
* *
* Description : Définit le contenu associé au composant d'affichage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_hex_view_set_content(GtkHexView *view, GBinContent *content)
{
GBinContent *old; /* Ancienne valeur */
GBufferCache *cache; /* Tampon à représenter */
old = gtk_hex_view_get_content(view);
assert((old == NULL && view->generator == NULL) || (old != NULL && view->generator != NULL));
if (old != content)
{
if (view->generator != NULL)
{
cache = g_buffer_view_get_cache(GTK_BUFFER_VIEW(view)->view);
g_buffer_cache_wlock(cache);
g_buffer_cache_truncate(cache, 0);
g_buffer_cache_wunlock(cache);
unref_object(cache);
g_clear_object(&view->generator);
}
if (content != NULL)
view->generator = g_hex_generator_new(content);
g_object_notify_by_pspec(G_OBJECT(view), _hex_view_properties[PROP_CONTENT]);
assert(content != NULL);
gtk_widget_queue_resize(GTK_WIDGET(view));
}
g_clear_object(&old);
}
/******************************************************************************
* *
* 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));
*/
}
/* ---------------------------------------------------------------------------------- */
/* IMPLEMENTATION DES FONCTIONS DE CLASSE */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : object = instance d'objet GLib à mamnipuler. *
* prop_id = identifiant de la propriété visée. *
* value = valeur à prendre en compte. *
* pspec = définition de la propriété. *
* *
* Description : Met à jour une propriété d'instance GObject. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
GtkHexView *view; /* Version spécialisée */
GObject *content; /* Contenu sous forme simple */
view = GTK_HEX_VIEW(object);
switch (prop_id)
{
case PROP_SHOW_OFFSETS:
g_display_options_set(GTK_CONTENT_VIEW(view)->options, HCO_OFFSET, g_value_get_boolean(value));
gtk_widget_set_visible(view->offsets, g_value_get_boolean(value));
break;
case PROP_CONTENT:
content = g_value_get_object(value);
gtk_hex_view_set_content(view, G_BIN_CONTENT(content));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/******************************************************************************
* *
* Paramètres : object = instance d'objet GLib à mamnipuler. *
* prop_id = identifiant de la propriété visée. *
* value = valeur à transmettre. [OUT] *
* pspec = définition de la propriété. *
* *
* Description : Fournit la valeur d'une propriété d'instance GObject. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_hex_view_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
GtkHexView *view; /* Version spécialisée */
view = GTK_HEX_VIEW(object);
switch (prop_id)
{
case PROP_SHOW_OFFSETS:
g_value_set_boolean(value, gtk_widget_get_visible(view->offsets));
break;
case PROP_CONTENT:
g_value_take_object(value, gtk_hex_view_get_content(view));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/******************************************************************************
* *
* 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++)
{
if (!gtk_widget_get_visible(view->children[i]))
min_widths[i] = 0;
else
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++)
{
if (!gtk_widget_get_visible(view->children[i]))
final_widths[i] = 0;
else
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++)
{
if (!gtk_widget_get_visible(view->children[i]))
continue;
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 = requested;
if (natural != NULL) *natural = requested;
for (i = 0; i < _CHILDREN_COUNT; i++)
{
if (!gtk_widget_get_visible(view->children[i]))
continue;
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);
}