/* Chrysalide - Outil d'analyse de fichiers binaires
* edge.c - liens entre les noeuds d'un graphique
*
* Copyright (C) 2013-2019 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 "edge.h"
#include
#include
#include
#include
#include "../../core/params.h"
/* Lien graphique entre deux noeuds graphiques (instance) */
struct _GGraphEdge
{
GObject parent; /* A laisser en premier */
GCodeBlock *src; /* Bloc d'origine du lien */
GCodeBlock *dst; /* Bloc de destination du lien */
EdgeColor color; /* Couleur du rendu */
union
{
const GdkPoint **templates; /* Inspirations de coordonnées */
GdkPoint *points; /* Points de la ligne dessinée */
};
size_t count; /* Quantité de ces points */
};
/* Lien graphique entre deux noeuds graphiques (classe) */
struct _GGraphEdgeClass
{
GObjectClass parent; /* A laisser en premier */
};
/* Dimensions des flêches */
#define ARROW_LENGHT 10
#define ARROW_DEGREES 10
/* Initialise la classe des liens graphiques entre deux noeuds. */
static void g_graph_edge_class_init(GGraphEdgeClass *);
/* Initialise une encapsulation de bloc virtuel. */
static void g_graph_edge_init(GGraphEdge *);
/* Supprime toutes les références externes. */
static void g_graph_edge_dispose(GGraphEdge *);
/* Procède à la libération totale de la mémoire. */
static void g_graph_edge_finalize(GGraphEdge *);
/* Dessine une flèche au bout du lien représenté. */
static void draw_link_arrow(cairo_t *, gint, gint, gint, gint);
/* Indique le type défini par la GLib pour les liens graphiques entre noeuds. */
G_DEFINE_TYPE(GGraphEdge, g_graph_edge, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des liens graphiques entre deux noeuds. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_graph_edge_class_init(GGraphEdgeClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_graph_edge_dispose;
object->finalize = (GObjectFinalizeFunc)g_graph_edge_finalize;
}
/******************************************************************************
* *
* Paramètres : edge = instance à initialiser. *
* *
* Description : Initialise un lien graphique entre deux noeuds graphiques. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_graph_edge_init(GGraphEdge *edge)
{
}
/******************************************************************************
* *
* Paramètres : edge = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_graph_edge_dispose(GGraphEdge *edge)
{
g_clear_object(&edge->src);
g_clear_object(&edge->dst);
G_OBJECT_CLASS(g_graph_edge_parent_class)->dispose(G_OBJECT(edge));
}
/******************************************************************************
* *
* Paramètres : edge = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_graph_edge_finalize(GGraphEdge *edge)
{
free(edge->points);
G_OBJECT_CLASS(g_graph_edge_parent_class)->finalize(G_OBJECT(edge));
}
/******************************************************************************
* *
* Paramètres : templates = coordonnées des futurs points. *
* count = nombre de ces points fournis. *
* color = couleur de rendu à l'écran. *
* *
* Description : Etablit un lien graphique entre deux noeuds graphiques. *
* *
* Retour : Adresse de la structure mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GGraphEdge *_g_graph_edge_new(GCodeBlock *src, GCodeBlock *dst, const GdkPoint **templates, size_t count, EdgeColor color)
{
GGraphEdge *result; /* Structure à retourner */
result = g_object_new(G_TYPE_GRAPH_EDGE, NULL);
result->src = src;
result->dst = dst;
g_object_ref(G_OBJECT(src));
g_object_ref(G_OBJECT(dst));
result->color = color;
assert(count == 4 || count == 6);
result->templates = malloc(count * sizeof(GdkPoint *));
memcpy(result->templates, templates, count * sizeof(GdkPoint *));
result->count = count;
return result;
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à consulter. *
* src = bloc d'origine du lien. [OUT] *
* dst = bloc de destination du lien. [OUT] *
* *
* Description : Fournit les deux blocs aux extrémités d'un lien. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_graph_edge_get_boundaries(const GGraphEdge *edge, GCodeBlock **src, GCodeBlock **dst)
{
*src = edge->src;
*dst = edge->dst;
g_object_ref(G_OBJECT(*src));
g_object_ref(G_OBJECT(*dst));
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à consulter. *
* *
* Description : Fournit la couleur de rendu d'un lien graphique. *
* *
* Retour : Identifiant de couleur de rendu. *
* *
* Remarques : - *
* *
******************************************************************************/
EdgeColor g_graph_edge_get_color(const GGraphEdge *edge)
{
EdgeColor result; /* Couleur à retourner */
result = edge->color;
return result;
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à consulter. *
* x1 = abscisse du point de départ de la ligne. [OUT] *
* x2 = abscisse du point d'arrivée de la ligne. [OUT] *
* *
* Description : Fournit les abscisses des points extrèmes de la ligne. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_graph_edge_get_x_borders(const GGraphEdge *edge, gint *x1, gint *x2)
{
/**
* A l'appel de cette fonction, les informations des points n'ont
* pas encore été fixées ni recopiées.
*/
*x1 = edge->templates[0]->x;
*x2 = edge->templates[edge->count - 1]->x;
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à définir dans les détails. *
* *
* Description : Détermine les positions finales d'un lien graphique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_graph_edge_resolve(GGraphEdge *edge)
{
const GdkPoint **templates; /* Inspirations de coordonnées */
size_t i; /* Boucle de parcours */
templates = edge->templates;
edge->points = malloc(edge->count * sizeof(GdkPoint));
for (i = 0; i < edge->count; i++)
{
edge->points[i].x = templates[i]->x;
edge->points[i].y = templates[i]->y;
}
free(templates);
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à modifier dans ses positions. *
* dx = déplacement à effectuer sur l'axe des abscisses. *
* dy = déplacement à effectuer sur l'axe des ordonnées. *
* *
* Description : Opère un décalage du lien dans une direction donnée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_graph_edge_offset(GGraphEdge *edge, gint dx, gint dy)
{
size_t i; /* Boucle de parcours */
for (i = 0; i < edge->count; i++)
{
edge->points[i].x += dx;
edge->points[i].y += dy;
}
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu avec positions à consulter. *
* count = quantité de points représentés. [OUT] *
* *
* Description : Fournit l'ensemble des points constituant un lien graphique. *
* *
* Retour : Liste de points utilisés pour le dessin d'un lien. *
* *
* Remarques : - *
* *
******************************************************************************/
const GdkPoint *g_graph_edge_get_points(const GGraphEdge *edge, size_t *count)
{
const GdkPoint *result; /* Liste de points à renvoyer */
result = edge->points;
*count = edge->count;
return result;
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu avec positions à consulter. *
* x = emplacement de la souris sur l'axe des abscisses. *
* y = emplacement de la souris sur l'axe des ordonnées. *
* *
* Description : Opère un décalage du lien dans une direction donnée. *
* *
* Retour : true si un survol est en cours, false sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_graph_edge_detect_at(const GGraphEdge *edge, gint x, gint y)
{
bool result; /* Bilan à retourner */
size_t i; /* Boucle de parcours */
gint margin; /* Marge de précision */
gint pts[2]; /* Points d'analyse ordonnés */
gint inter[2]; /* Bilan d'intersection */
result = false;
for (i = 1; i < edge->count; i++)
{
/* Au niveau des abscisses */
if (edge->points[i - 1].x <= edge->points[i].x)
{
pts[0] = edge->points[i - 1].x;
pts[1] = edge->points[i].x;
margin = 1;
}
else
{
pts[0] = edge->points[i].x;
pts[1] = edge->points[i - 1].x;
margin = 1;
}
margin *= (LINK_MARGIN / 2);
inter[0] = MAX(pts[0] - margin, x);
inter[1] = MIN(pts[1] + margin, x);
if (inter[0] > inter[1])
continue;
/* Au niveau des ordonnées */
if (edge->points[i - 1].y <= edge->points[i].y)
{
pts[0] = edge->points[i - 1].y;
pts[1] = edge->points[i].y;
margin = 1;
}
else
{
pts[0] = edge->points[i].y;
pts[1] = edge->points[i - 1].y;
margin = 1;
}
margin *= (LINK_MARGIN / 2);
inter[0] = MAX(pts[0] - margin, y);
inter[1] = MIN(pts[1] + margin, y);
if (inter[0] > inter[1])
continue;
/* Détection ! */
result = true;
break;
}
return result;
}
/******************************************************************************
* *
* Paramètres : edge = ligne de rendu à manipuler. *
* cairo = assistant pour le rendu graphique. *
* arrow = indique le besoin en flèche à l'arrivée. *
* selected = s'agit-il d'un lien sélectionné ? *
* *
* Description : Dessine les liens graphiques enregistrés dans le moteur. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_graph_edge_draw(const GGraphEdge *edge, cairo_t *cairo, bool arrow, bool selected)
{
GGenConfig *config; /* Configuration globale */
GdkRGBA color; /* Couleur de lien définie */
#ifndef NDEBUG
bool status; /* Validité d'une couleur */
#endif
size_t i; /* Boucle de parcours */
if (selected)
cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0);
else
{
config = get_main_configuration();
switch (edge->color)
{
default:
case EGC_DEFAULT:
#ifndef NDEBUG
status = g_generic_config_get_value(config, MPK_LINK_DEFAULT, &color);
#else
g_generic_config_get_value(config, MPK_LINK_DEFAULT, &color);
#endif
break;
case EGC_GREEN:
#ifndef NDEBUG
status = g_generic_config_get_value(config, MPK_LINK_BRANCH_TRUE, &color);
#else
g_generic_config_get_value(config, MPK_LINK_BRANCH_TRUE, &color);
#endif
break;
case EGC_RED:
#ifndef NDEBUG
status = g_generic_config_get_value(config, MPK_LINK_BRANCH_FALSE, &color);
#else
g_generic_config_get_value(config, MPK_LINK_BRANCH_FALSE, &color);
#endif
break;
case EGC_BLUE:
#ifndef NDEBUG
status = g_generic_config_get_value(config, MPK_LINK_LOOP, &color);
#else
g_generic_config_get_value(config, MPK_LINK_LOOP, &color);
#endif
break;
case EGC_DASHED_GRAY:
cairo_set_source_rgb(cairo, 0.4, 0.4, 0.4);
break;
}
assert(status);
cairo_set_source_rgba(cairo, color.red, color.green, color.blue, color.alpha);
}
switch (edge->color)
{
default:
case EGC_DEFAULT:
case EGC_GREEN:
case EGC_RED:
case EGC_BLUE:
cairo_set_dash(cairo, (double []) { 6.0 }, 0, 0.0);
break;
case EGC_DASHED_GRAY:
cairo_set_dash(cairo, (double []) { 6.0 }, 1, 0.0);
break;
}
/**
* Si on ne veut pas de flèche, on doit se destiner à un aperçu...
* Dans ce cas, pour plus de lisibilité, on double la taille d'impression.
* Et pour faire ressortir les boucles, on double encore les liens associés.
*/
cairo_set_line_width(cairo, arrow ? 1 : (edge->color == EGC_BLUE ? 4 : 2));
cairo_move_to(cairo, edge->points[0].x + 0.5, edge->points[0].y);
for (i = 1; i < edge->count; i++)
cairo_line_to(cairo, edge->points[i].x + 0.5, edge->points[i].y);
cairo_stroke(cairo);
if (arrow)
draw_link_arrow(cairo,
edge->points[edge->count - 2].x,
edge->points[edge->count - 2].y,
edge->points[edge->count - 1].x,
edge->points[edge->count - 1].y);
}
/******************************************************************************
* *
* Paramètres : cairo = gestionnaire de rendu graphique. *
* start_x = abscisse du début du segment final. *
* start_y = ordonnée du début du segment final. *
* end_x = abscisse de fin de segment et pointe de flèche. *
* end_y = ordonnée de fin de segment et pointe de flèche. *
* *
* Description : Dessine une flèche au bout du lien représenté. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void draw_link_arrow(cairo_t *cairo, gint start_x, gint start_y, gint end_x, gint end_y)
{
double angle; /* Angle du triangle à remplir */
double factor_x; /* Direction de la flèche #1 */
double factor_y; /* Direction de la flèche #2 */
double x1; /* Abscisse du point n°1 */
double y1; /* Ordonnée du point n°1 */
double x2; /* Abscisse du point n°2 */
double y2; /* Ordonnée du point n°2 */
angle = atan2(end_y - start_y, end_x - start_x) + M_PI;
factor_x = -1;
factor_y = -1;
x1 = end_x + factor_x * ARROW_LENGHT * cos(angle - ARROW_DEGREES);
y1 = end_y + factor_y * ARROW_LENGHT * sin(angle - ARROW_DEGREES);
x2 = end_x + factor_x * ARROW_LENGHT * cos(angle + ARROW_DEGREES);
y2 = end_y + factor_y * ARROW_LENGHT * sin(angle + ARROW_DEGREES);
cairo_move_to(cairo, end_x, end_y);
cairo_line_to(cairo, x1, y1);
cairo_line_to(cairo, x2, y2);
cairo_move_to(cairo, end_x, end_y);
cairo_fill(cairo);
}