/* Chrysalide - Outil d'analyse de fichiers binaires
* gtkbufferview.c - affichage de tampons de lignes
*
* Copyright (C) 2010-2014 Cyrille Bagard
*
* This file is part of Chrysalide.
*
* OpenIDA 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.
*
* OpenIDA 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 Foobar. If not, see .
*/
#include "gtkbufferview-int.h"
#include
#include "../glibext/chrysamarshal.h"
/* -------------------------- INTERACTION DIRECTE AVEC GTK -------------------------- */
/* Procède à l'initialisation de l'afficheur de tampons. */
static void gtk_buffer_view_class_init(GtkBufferViewClass *);
/* Procède à l'initialisation de l'afficheur de tampons. */
static void gtk_buffer_view_init(GtkBufferView *);
/* Intègre le focus dans le rendu du composant. */
static gboolean gtk_buffer_view_focus(GtkWidget *, GtkDirectionType);
/* Assure la gestion des clics de souris sur le composant. */
static gboolean gtk_buffer_view_button_press(GtkWidget *, GdkEventButton *);
/* Fournit la hauteur de composant requise pour un plein rendu. */
static void gtk_buffer_view_get_preferred_height(GtkWidget *, gint *, gint *);
/* Fournit la largeur de composant requise pour un plein rendu. */
static void gtk_buffer_view_get_preferred_width(GtkWidget *, gint *, gint *);
/* S'adapte à la surface concédée par le composant parent. */
static void gtk_buffer_view_size_allocate(GtkWidget *, GtkAllocation *);
/* Met à jour l'affichage de la visualisation de code buffer. */
static gboolean gtk_buffer_view_draw(GtkWidget *, cairo_t *);
/* Prend en compte une frappe de touche sur le composant. */
static gboolean gtk_buffer_view_key_press(GtkWidget *, GdkEventKey *);
/* Indique la position d'affichage d'une adresse donnée. */
static bool gtk_buffer_view_get_address_coordinates(const GtkBufferView *, vmpa_t, gint *, gint *);
/* Place en cache un rendu destiné à l'aperçu graphique rapide. */
static void gtk_buffer_view_cache_glance(GtkBufferView *, cairo_t *, const GtkAllocation *, double);
/* ------------------------------ ANIMATION DU CURSEUR ------------------------------ */
/* Redémarre l'affichage du curseur à l'emplacement courant. */
static void restart_caret_blinking(GtkBufferView *);
/* Bascule et relance l'affichage du curseur. */
static gboolean gtk_buffer_view_refresh_caret(GtkBufferView *);
/* ---------------------------------------------------------------------------------- */
/* INTERACTION DIRECTE AVEC GTK */
/* ---------------------------------------------------------------------------------- */
/* Détermine le type du composant d'affichage de tampon de lignes. */
G_DEFINE_TYPE(GtkBufferView, gtk_buffer_view, GTK_TYPE_VIEW_PANEL)
/******************************************************************************
* *
* Paramètres : class = classe GTK à initialiser. *
* *
* Description : Procède à l'initialisation de l'afficheur de tampons. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_buffer_view_class_init(GtkBufferViewClass *class)
{
GtkWidgetClass *widget_class; /* Classe version Widget */
widget_class = GTK_WIDGET_CLASS(class);
widget_class->focus = gtk_buffer_view_focus;
widget_class->button_press_event = gtk_buffer_view_button_press;
widget_class->get_preferred_height = gtk_buffer_view_get_preferred_height;
widget_class->get_preferred_width = gtk_buffer_view_get_preferred_width;
widget_class->size_allocate = gtk_buffer_view_size_allocate;
widget_class->draw = gtk_buffer_view_draw;
widget_class->key_press_event = gtk_buffer_view_key_press;
g_signal_new("caret-moved",
GTK_TYPE_BUFFER_VIEW,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GtkBufferViewClass, caret_moved),
NULL, NULL,
g_cclosure_user_marshal_VOID__UINT64,
G_TYPE_NONE, 1, G_TYPE_UINT64);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à initialiser. *
* *
* Description : Procède à l'initialisation de l'afficheur de tampons. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_buffer_view_init(GtkBufferView *view)
{
GtkViewPanel *viewpanel; /* Instance parente */
viewpanel = GTK_VIEW_PANEL(view);
viewpanel->get_coordinates = (get_addr_coordinates_fc)gtk_buffer_view_get_address_coordinates;
viewpanel->cache_glance = (cache_glance_fc)gtk_buffer_view_cache_glance;
view->caret.x = 10;
view->caret.y = 10;
view->caret.width = 100;
view->caret.height = 100;
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK visé par l'opération. *
* dir = sens de l'opération : perte ou gain de focus. *
* *
* Description : Intègre le focus dans le rendu du composant. *
* *
* Retour : FALSE pour poursuivre la propagation de l'événement. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_buffer_view_focus(GtkWidget *widget, GtkDirectionType direction)
{
GtkBufferView *view; /* Autre version du composant */
gboolean has_focus; /* Etat courant */
view = GTK_BUFFER_VIEW(widget);
has_focus = gtk_widget_is_focus(widget);
if (has_focus)
restart_caret_blinking(view);
else if (view->caret_timer != 0)
{
g_source_remove(view->caret_timer);
view->caret_timer = 0;
view->show_caret = true;
gtk_buffer_view_refresh_caret(view);
}
return TRUE;
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK visé par l'opération. *
* event = informations liées à l'événement. *
* *
* 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_buffer_view_button_press(GtkWidget *widget, GdkEventButton *event)
{
GtkBufferView *view; /* Autre version du composant */
gint real_x; /* Abscisse absolue réelle */
gint real_y; /* Ordonnée absolue réelle */
size_t index; /* Indice de ligne de tampon */
GBufferLine *line; /* Ligne à la position courante*/
vmpa_t addr; /* Position mémoire associée */
GdkRectangle new; /* Nouvel emplacement calculé */
view = GTK_BUFFER_VIEW(widget);
gtk_widget_grab_focus(widget);
real_x = event->x;
real_y = event->y;
gtk_buffer_view_compute_real_coord(view, &real_x, &real_y);
printf(" !mouse! :: (%g ; %g) -> (%d ; %d)\n",
event->x, event->y,
real_x, real_y);
line = g_buffer_view_find_line_at(view->buffer_view, real_y, &index);
if (line == NULL) return FALSE;
if (real_x < view->left_margin)
{
printf("Border Line :: %p\n", line);
}
else
{
addr = g_buffer_view_compute_caret(view->buffer_view, line, index, real_x, &new);
if (addr == VMPA_INVALID) return FALSE;
gtk_buffer_view_compute_relative_coords(view, &view->caret.x, &view->caret.y);
printf(" mouse --old-- :: (%d ; %d)\n",
view->caret.x, view->caret.y);
printf(" mouse --new-- :: (%d ; %d)\n",
new.x, new.y);
gtk_widget_queue_draw_area(GTK_WIDGET(view), view->caret.x, view->caret.y,
view->caret.width, view->caret.height);
view->caret = new;
view->caret_addr = addr;
restart_caret_blinking(view);
}
return FALSE;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* event = informations liées à l'événement. *
* *
* Description : Transcrit les coordonnées à l'écran en coordonnées absolues. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_buffer_view_compute_fake_coord(GtkBufferView *view, gint *x, gint *y)
{
if (GTK_VIEW_PANEL(view)->hadjustment != NULL)
*x -= gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->hadjustment);
if (GTK_VIEW_PANEL(view)->vadjustment != NULL)
*y += gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->vadjustment);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* event = informations liées à l'événement. *
* *
* Description : Transcrit les coordonnées absolues en coordonnées à l'écran. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_buffer_view_compute_real_coord(GtkBufferView *view, gint *x, gint *y)
{
if (x != NULL && GTK_VIEW_PANEL(view)->hadjustment != NULL)
*x += gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->hadjustment);
if (y != NULL && GTK_VIEW_PANEL(view)->vadjustment != NULL)
*y += gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->vadjustment);
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à consulter. *
* minimal = taille minimale. [OUT] *
* natural = taille idéale. [OUT] *
* *
* Description : Fournit la hauteur de composant requise pour un plein rendu. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_buffer_view_get_preferred_height(GtkWidget *widget, gint *minimal, gint *natural)
{
GtkBufferView *view; /* Autre version du composant */
view = GTK_BUFFER_VIEW(widget);
if (view->buffer_view != NULL)
*minimal = g_buffer_view_get_height(view->buffer_view);
else
*minimal = 0;
*natural = *minimal;
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à consulter. *
* minimal = taille minimale. [OUT] *
* natural = taille idéale. [OUT] *
* *
* Description : Fournit la largeur de composant requise pour un plein rendu. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_buffer_view_get_preferred_width(GtkWidget *widget, gint *minimal, gint *natural)
{
GtkBufferView *view; /* Autre version du composant */
view = GTK_BUFFER_VIEW(widget);
if (view->buffer_view != NULL)
*minimal = g_buffer_view_get_width(view->buffer_view,
*GTK_VIEW_PANEL(view)->display_addr,
*GTK_VIEW_PANEL(view)->display_code);
else
*minimal = 0;
*natural = *minimal;
}
/******************************************************************************
* *
* Paramètres : view = 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_buffer_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
GtkViewPanel *panel; /* Autre version du composant */
GtkBufferView *view; /* Encore une autre version */
gint width; /* Largeur de l'objet actuelle */
gint height; /* Hauteur de l'objet actuelle */
GtkAllocation valloc; /* Surface utilisable */
gboolean changed; /* Changement de valeur ? */
/* Mise à jour GTK */
gtk_widget_set_allocation(widget, allocation);
if (gtk_widget_get_realized(widget))
gdk_window_move_resize(gtk_widget_get_window(widget),
allocation->x, allocation->y,
allocation->width, allocation->height);
panel = GTK_VIEW_PANEL(widget);
if (panel->hadjustment == NULL || panel->vadjustment == NULL)
return;
view = GTK_BUFFER_VIEW(widget);
width = g_buffer_view_get_width(view->buffer_view, *panel->display_addr, *panel->display_code);
height = g_buffer_view_get_height(view->buffer_view);
gtk_view_panel_compute_allocation(panel, &valloc);
/* Défilement horizontal */
gtk_adjustment_set_page_size(panel->hadjustment, valloc.width);
gtk_adjustment_set_step_increment(panel->hadjustment, valloc.width * 0.1);
gtk_adjustment_set_page_increment(panel->hadjustment, valloc.width * 0.9);
gtk_adjustment_set_upper(panel->hadjustment, MAX(width, valloc.width));
gtk_view_panel_reclamp_adjustment(panel->hadjustment, &changed);
gtk_adjustment_changed(panel->hadjustment);
if (changed)
gtk_adjustment_value_changed(panel->hadjustment);
/* Défilement vertical */
gtk_adjustment_set_page_size(panel->vadjustment, valloc.height);
gtk_adjustment_set_step_increment(panel->vadjustment, view->line_height);
gtk_adjustment_set_page_increment(panel->vadjustment, view->line_height * 10.0);
gtk_adjustment_set_upper(panel->vadjustment, MAX(height, valloc.height));
gtk_view_panel_reclamp_adjustment(panel->vadjustment, &changed);
gtk_adjustment_changed(panel->vadjustment);
if (changed)
gtk_adjustment_value_changed(panel->vadjustment);
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à redessiner. *
* cr = contexte graphique associé à l'événement. *
* *
* Description : Met à jour l'affichage de la visualisation de code buffer. *
* *
* Retour : FALSE pour poursuivre la propagation de l'événement. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_buffer_view_draw(GtkWidget *widget, cairo_t *cr)
{
GtkBufferView *view; /* Autre version du composant */
GtkViewPanel *pview; /* Autre version du composant */
cairo_region_t *region; /* Région visible à redessiner */
cairo_rectangle_int_t area; /* Surface correspondante */
gint fake_x; /* Abscisse virtuelle */
gint fake_y; /* Ordonnée virtuelle */
GtkStyleContext *context; /* Contexte du thème actuel */
GdkRGBA color; /* Couleur du curseur */
view = GTK_BUFFER_VIEW(widget);
widget = GTK_WIDGET(view);
pview = GTK_VIEW_PANEL(widget);
region = gdk_window_get_visible_region(gtk_widget_get_window(widget));
cairo_region_get_extents(region, &area);
cairo_region_destroy(region);
fake_x = 0;
fake_y = 0;
gtk_buffer_view_compute_fake_coord(view, &fake_x, &fake_y);
/* Dessin de la marge gauche */
context = gtk_widget_get_style_context(widget);
gtk_style_context_save(context);
gtk_style_context_add_class(context, GTK_STYLE_CLASS_TROUGH);
gtk_style_context_get_color(context, GTK_STATE_FLAG_BACKDROP | GTK_STATE_FLAG_DIR_LTR, &color);
gtk_style_context_restore(context);
cairo_set_source_rgb(cr, color.red, color.green, color.blue);
cairo_rectangle(cr, fake_x, area.y, view->left_margin, area.y + area.height);
cairo_fill(cr);
/* Eventuelle bordure globale */
GTK_WIDGET_CLASS(gtk_buffer_view_parent_class)->draw(widget, cr);
/* Impression du désassemblage */
if (view->buffer_view != NULL)
g_buffer_view_draw(view->buffer_view, cr, fake_x, fake_y, &area,
*pview->display_addr, *pview->display_code);
return TRUE;
}
/******************************************************************************
* *
* Paramètres : widget = composant visé par l'opération. *
* event = informations liées à l'événement. *
* *
* Description : Prend en compte une frappe de touche sur le composant. *
* *
* Retour : FALSE pour poursuivre la propagation de l'événement. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_buffer_view_key_press(GtkWidget *widget, GdkEventKey *event)
{
gboolean result; /* Suites à renvoyer */
bool ctrl; /* Statut de la touche Contrôle*/
result = FALSE;
ctrl = (event->state & GDK_CONTROL_MASK);
switch (event->keyval)
{
case GDK_KEY_Left:
result = TRUE;
break;
case GDK_KEY_Up:
result = TRUE;
break;
case GDK_KEY_Right:
result = TRUE;
break;
case GDK_KEY_Down:
result = TRUE;
break;
}
printf("ctrl ? %d -- keyval = %d -->> %d\n", ctrl, event->keyval, result);
return result;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* addr = adresse à présenter à l'écran. *
* x = position horizontale au sein du composant. [OUT] *
* y = position verticale au sein du composant. [OUT] *
* *
* Description : Indique la position d'affichage d'une adresse donnée. *
* *
* Retour : true si l'adresse fait partie du composant, false sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool gtk_buffer_view_get_address_coordinates(const GtkBufferView *view, vmpa_t addr, gint *x, gint *y)
{
return g_buffer_view_get_address_coordinates(view->buffer_view, addr, x, y);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à manipuler. *
* cairo = 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_buffer_view_cache_glance(GtkBufferView *view, cairo_t *cairo, const GtkAllocation *area, double scale)
{
cairo_set_line_width(cairo, 1);
cairo_set_source_rgb(cairo, 0.4, 0.4, 0.4);
cairo_rectangle(cairo, area->x + 0.5, area->y + 0.5, area->width - 1, area->height - 1);
cairo_stroke(cairo);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à mettre à jour. *
* buffer = tampon de lignes à encadrer. *
* addr = indique si les positions doivent être affichées. *
* code = indique si le code binaire doit être affiché. *
* *
* Description : Prend acte de l'association d'un tampon de lignes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_buffer_view_attach_buffer(GtkBufferView *view, GBufferView *buffer, bool *addr, bool *code)
{
gint width; /* Largeur de l'objet actuelle */
gint height; /* Hauteur de l'objet actuelle */
if (view->buffer != NULL)
{
g_object_unref(G_OBJECT(view->buffer));
g_object_unref(G_OBJECT(view->buffer_view));
}
view->buffer = g_buffer_view_get_buffer(buffer);
g_object_ref(G_OBJECT(view->buffer));
view->buffer_view = buffer;
/* Taille des marges */
view->line_height = g_buffer_view_get_line_height(view->buffer_view);
view->left_margin = 2 * view->line_height;
view->left_text = -2.5 * view->line_height;
/* Validation finale */
width = g_buffer_view_get_width(view->buffer_view, *addr, *code);
height = g_buffer_view_get_height(view->buffer_view);
width += -view->left_text + 1;
height += 1;
//gtk_widget_set_size_request(GTK_WIDGET(view), width, height);
gtk_widget_queue_draw(GTK_WIDGET(view));
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* *
* Description : Fournit la vue associée au tampon de lignes courant. *
* *
* Retour : Vue mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GBufferView *gtk_buffer_view_get_buffer(const GtkBufferView *view)
{
return view->buffer_view;
}
/* ---------------------------------------------------------------------------------- */
/* CONVERSIONS DE COORDONNEES */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* x = abscisse à ajuster. [OUT] *
* x = ordonnée à ajuster. [OUT] *
* *
* Description : Transcrit les coordonnées absolues en coordonnées à l'écran. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_buffer_view_compute_relative_coords(GtkBufferView *view, gint *x, gint *y)
{
if (x != NULL && GTK_VIEW_PANEL(view)->hadjustment != NULL)
*x -= gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->hadjustment);
if (y != NULL && GTK_VIEW_PANEL(view)->vadjustment != NULL)
*y -= gtk_adjustment_get_value(GTK_VIEW_PANEL(view)->vadjustment);
}
/* ---------------------------------------------------------------------------------- */
/* ANIMATION DU CURSEUR */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : view = composant GTK à manipuler. *
* *
* Description : Redémarre l'affichage du curseur à l'emplacement courant. *
* *
* Retour : TRUE pour poursuivre les basculements automatiques. *
* *
* Remarques : - *
* *
******************************************************************************/
static void restart_caret_blinking(GtkBufferView *view)
{
if (view->caret_addr == VMPA_INVALID)
return;
if (view->caret_timer != 0)
g_source_remove(view->caret_timer);
view->caret_timer = g_timeout_add_seconds(1, (GSourceFunc)gtk_buffer_view_refresh_caret, view);
view->show_caret = false;
gtk_buffer_view_refresh_caret(view);
g_signal_emit_by_name(view, "caret-moved", view->caret_addr);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à manipuler. *
* *
* Description : Bascule et relance l'affichage du curseur. *
* *
* Retour : TRUE pour poursuivre les basculements automatiques. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_buffer_view_refresh_caret(GtkBufferView *view)
{
GtkWidget *widget; /* Autre version du composant */
GdkWindow *window; /* Fenêtre de support associée */
GdkRectangle area; /* Région adaptée à traiter */
cairo_t *cr; /* Contexte graphique */
GdkRGBA *color; /* Couleur du curseur */
widget = GTK_WIDGET(view);
window = gtk_widget_get_window(widget);
/**
* Si le composant n'est pas encore réalisé (ou caché, en cas de
* basculement entre les types de vues), gdk_cairo_create() ne va
* pas apprécier l'argument NULL. Donc on écourte l'opération.
*/
if (window == NULL)
{
view->show_caret = !view->show_caret;
return TRUE;
}
area = view->caret;
gtk_buffer_view_compute_relative_coords(view, &area.x, &area.y);
/* Réinitialisation de la surface */
if (view->show_caret)
{
view->show_caret = false;
gtk_widget_queue_draw_area(widget, area.x, area.y, area.width, area.height);
}
/* Dessin */
else
{
view->show_caret = true;
cr = gdk_cairo_create(gtk_widget_get_window(widget));
gtk_style_context_get(gtk_widget_get_style_context(widget),
gtk_widget_get_state_flags(widget),
GTK_STYLE_PROPERTY_COLOR, &color, NULL);
cairo_set_source_rgb(cr, color->red, color->green, color->blue);
cairo_rectangle(cr, area.x, area.y, area.width, area.height);
cairo_fill(cr);
cairo_destroy(cr);
}
return TRUE;
}