Persister la position des nœuds du graphe
This commit is contained in:
parent
995939bef8
commit
c46039a2aa
22 changed files with 3223 additions and 17 deletions
3
Makefile
3
Makefile
|
|
@ -516,6 +516,9 @@ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
||||||
src/core/investigation_graph_loader.c \
|
src/core/investigation_graph_loader.c \
|
||||||
src/core/background_task.c \
|
src/core/background_task.c \
|
||||||
src/dao/entity_dao.c \
|
src/dao/entity_dao.c \
|
||||||
|
src/models/investigation_graph_layout.c \
|
||||||
|
src/models/graph_node_position.c \
|
||||||
|
src/dao/graph_node_position_dao.c \
|
||||||
src/dao/relation_dao.c \
|
src/dao/relation_dao.c \
|
||||||
src/models/investigation_graph_model.c \
|
src/models/investigation_graph_model.c \
|
||||||
src/models/entity_record.c \
|
src/models/entity_record.c \
|
||||||
|
|
|
||||||
59
database/schema_current.sql
Normal file
59
database/schema_current.sql
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Labfy Investigation
|
||||||
|
*
|
||||||
|
* Extensions idempotentes du schéma SQLite courant V2
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les coordonnées du graphe sont un état de présentation.
|
||||||
|
*
|
||||||
|
* Elles restent séparées des données métier de la table entites.
|
||||||
|
*
|
||||||
|
* Cette table peut être créée à chaque ouverture grâce à IF NOT EXISTS.
|
||||||
|
*/
|
||||||
|
CREATE TABLE IF NOT EXISTS graph_node_positions
|
||||||
|
(
|
||||||
|
entity_id TEXT PRIMARY KEY,
|
||||||
|
|
||||||
|
x REAL NOT NULL,
|
||||||
|
y REAL NOT NULL,
|
||||||
|
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (entity_id)
|
||||||
|
REFERENCES entites(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
length(trim(entity_id)) > 0
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
typeof(x) IN (
|
||||||
|
'integer',
|
||||||
|
'real'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
typeof(y) IN (
|
||||||
|
'integer',
|
||||||
|
'real'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
x = x
|
||||||
|
AND abs(x) <= 1.7976931348623157e308
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
y = y
|
||||||
|
AND abs(y) <= 1.7976931348623157e308
|
||||||
|
),
|
||||||
|
|
||||||
|
CHECK (
|
||||||
|
length(trim(updated_at)) > 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#ifndef LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LOAD_TASK_H
|
#ifndef LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LOAD_TASK_H
|
||||||
#define LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LOAD_TASK_H
|
#define LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LOAD_TASK_H
|
||||||
|
|
||||||
|
#include "models/investigation_graph_layout.h"
|
||||||
#include "models/investigation_graph_model.h"
|
#include "models/investigation_graph_model.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
@ -74,6 +75,10 @@ typedef enum
|
||||||
*
|
*
|
||||||
* En cas de succès, graph_model appartient au destinataire du callback.
|
* En cas de succès, graph_model appartient au destinataire du callback.
|
||||||
*
|
*
|
||||||
|
* La disposition associée peut être récupérée pendant le callback avec
|
||||||
|
* investigation_graph_load_task_take_layout(). Elle devient alors la
|
||||||
|
* propriété du code appelant.
|
||||||
|
*
|
||||||
* En cas d'échec, error est empruntée et valable uniquement pendant le
|
* En cas d'échec, error est empruntée et valable uniquement pendant le
|
||||||
* callback.
|
* callback.
|
||||||
*
|
*
|
||||||
|
|
@ -171,6 +176,23 @@ gboolean investigation_graph_load_task_is_running(
|
||||||
const InvestigationGraphLoadTask *load_task
|
const InvestigationGraphLoadTask *load_task
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transfère la disposition chargée au code appelant.
|
||||||
|
*
|
||||||
|
* Cette fonction doit être appelée après une fin de chargement réussie,
|
||||||
|
* normalement depuis InvestigationGraphLoadTaskCallback.
|
||||||
|
*
|
||||||
|
* La tâche cesse de posséder la disposition retournée.
|
||||||
|
* Un second appel retourne NULL.
|
||||||
|
*
|
||||||
|
* @param load_task Tâche terminée.
|
||||||
|
*
|
||||||
|
* @return Disposition transférée, ou NULL.
|
||||||
|
*/
|
||||||
|
InvestigationGraphLayout *investigation_graph_load_task_take_layout(
|
||||||
|
InvestigationGraphLoadTask *load_task
|
||||||
|
);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
133
include/dao/graph_node_position_dao.h
Normal file
133
include/dao/graph_node_position_dao.h
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file graph_node_position_dao.h
|
||||||
|
* @brief Persistance des positions des nœuds du graphe dans SQLite.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_GRAPH_NODE_POSITION_DAO_H
|
||||||
|
#define LABFY_INVESTIGATION_GRAPH_NODE_POSITION_DAO_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/graph_node_position.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Catégories d'erreurs du DAO des positions de nœuds.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MEMORY,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_CONSTRAINT,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_READ,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MODEL
|
||||||
|
} GraphNodePositionDaoError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreurs du DAO des positions de nœuds.
|
||||||
|
*/
|
||||||
|
#define GRAPH_NODE_POSITION_DAO_ERROR \
|
||||||
|
graph_node_position_dao_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreurs du DAO.
|
||||||
|
*/
|
||||||
|
GQuark graph_node_position_dao_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DAO opaque donnant accès aux positions persistées.
|
||||||
|
*
|
||||||
|
* L'objet emprunte la connexion Database reçue lors de sa création.
|
||||||
|
*/
|
||||||
|
typedef struct GraphNodePositionDao GraphNodePositionDao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée un DAO utilisant une connexion Database existante.
|
||||||
|
*
|
||||||
|
* La connexion est empruntée et doit rester valide pendant toute la durée
|
||||||
|
* de vie du DAO.
|
||||||
|
*
|
||||||
|
* @param database Connexion ouverte.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau DAO, ou NULL en cas d'échec.
|
||||||
|
*/
|
||||||
|
GraphNodePositionDao *graph_node_position_dao_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère le DAO.
|
||||||
|
*
|
||||||
|
* La connexion Database empruntée n'est pas fermée.
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param position_dao DAO à libérer.
|
||||||
|
*/
|
||||||
|
void graph_node_position_dao_free(
|
||||||
|
GraphNodePositionDao *position_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Charge toutes les positions dans un ordre déterministe.
|
||||||
|
*
|
||||||
|
* Le tableau retourné utilise graph_node_position_free() comme fonction
|
||||||
|
* de destruction.
|
||||||
|
*
|
||||||
|
* @param position_dao DAO valide.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau GPtrArray possédé par l'appelant, ou NULL.
|
||||||
|
*/
|
||||||
|
GPtrArray *graph_node_position_dao_list_all(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insère ou met à jour la position d'une entité.
|
||||||
|
*
|
||||||
|
* La date updated_at est générée en UTC par le DAO.
|
||||||
|
*
|
||||||
|
* @param position_dao DAO valide.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
* @param x Coordonnée horizontale logique.
|
||||||
|
* @param y Coordonnée verticale logique.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si l'enregistrement réussit, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean graph_node_position_dao_upsert(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Supprime la position persistée d'une entité.
|
||||||
|
*
|
||||||
|
* L'absence de position n'est pas une erreur.
|
||||||
|
*
|
||||||
|
* @param position_dao DAO valide.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si la requête réussit, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean graph_node_position_dao_delete(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
const char *entity_identifier,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -54,4 +54,23 @@ bool schema_install_v2(
|
||||||
Database *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Garantit la présence des extensions du schéma courant V2.
|
||||||
|
*
|
||||||
|
* La connexion doit être valide et une transaction doit déjà être active.
|
||||||
|
*
|
||||||
|
* Cette fonction applique uniquement des opérations idempotentes destinées
|
||||||
|
* aux nouvelles bases comme aux bases V2 déjà existantes.
|
||||||
|
*
|
||||||
|
* Elle ne modifie pas la version enregistrée dans metadata.
|
||||||
|
* Elle ne réalise ni COMMIT ni ROLLBACK.
|
||||||
|
*
|
||||||
|
* @param database Connexion Database ouverte.
|
||||||
|
*
|
||||||
|
* @return true si les extensions sont présentes, sinon false.
|
||||||
|
*/
|
||||||
|
bool schema_ensure_current(
|
||||||
|
Database *database
|
||||||
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,26 @@ bool database_statement_column_int64(
|
||||||
int64_t *value
|
int64_t *value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lit un nombre à virgule flottante depuis une colonne.
|
||||||
|
*
|
||||||
|
* Les indices de colonnes SQLite commencent à 0.
|
||||||
|
*
|
||||||
|
* La colonne doit être de type SQLITE_FLOAT ou SQLITE_INTEGER.
|
||||||
|
* Une colonne entière est convertie en double par SQLite.
|
||||||
|
*
|
||||||
|
* @param statement Requête positionnée sur une ligne.
|
||||||
|
* @param column_index Indice de la colonne.
|
||||||
|
* @param value Destination de la valeur.
|
||||||
|
*
|
||||||
|
* @return true si le nombre a pu être lu, sinon false.
|
||||||
|
*/
|
||||||
|
bool database_statement_column_double(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int column_index,
|
||||||
|
double *value
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copie le contenu texte d'une colonne.
|
* @brief Copie le contenu texte d'une colonne.
|
||||||
*
|
*
|
||||||
|
|
@ -173,6 +193,21 @@ bool database_statement_bind_int64(
|
||||||
int64_t value
|
int64_t value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lie un nombre à virgule flottante à un paramètre SQL.
|
||||||
|
*
|
||||||
|
* @param statement Requête préparée.
|
||||||
|
* @param index Indice du paramètre.
|
||||||
|
* @param value Valeur à lier.
|
||||||
|
*
|
||||||
|
* @return true en cas de succès, sinon false.
|
||||||
|
*/
|
||||||
|
bool database_statement_bind_double(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int index,
|
||||||
|
double value
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Lie la valeur SQL NULL à un paramètre.
|
* @brief Lie la valeur SQL NULL à un paramètre.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
126
include/models/graph_node_position.h
Normal file
126
include/models/graph_node_position.h
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file graph_node_position.h
|
||||||
|
* @brief Modèle représentant la position persistée d'un nœud du graphe.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_GRAPH_NODE_POSITION_H
|
||||||
|
#define LABFY_INVESTIGATION_GRAPH_NODE_POSITION_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modèle opaque représentant une position de nœud.
|
||||||
|
*/
|
||||||
|
typedef struct GraphNodePosition GraphNodePosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Codes d'erreur produits par GraphNodePosition.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_ARGUMENT,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_IDENTIFIER,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_COORDINATE,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_DATE
|
||||||
|
} GraphNodePositionError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreur du modèle GraphNodePosition.
|
||||||
|
*/
|
||||||
|
#define GRAPH_NODE_POSITION_ERROR \
|
||||||
|
graph_node_position_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur du modèle.
|
||||||
|
*/
|
||||||
|
GQuark graph_node_position_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée une position persistée de nœud.
|
||||||
|
*
|
||||||
|
* Toutes les chaînes sont copiées.
|
||||||
|
*
|
||||||
|
* entity_identifier doit être un UUID valide.
|
||||||
|
* x et y doivent être des nombres finis.
|
||||||
|
* updated_at doit respecter le format UTC YYYY-MM-DDTHH:MM:SSZ.
|
||||||
|
*
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
* @param x Coordonnée horizontale logique.
|
||||||
|
* @param y Coordonnée verticale logique.
|
||||||
|
* @param updated_at Date UTC de dernière modification.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouvelle position, ou NULL lorsque les données sont invalides.
|
||||||
|
*/
|
||||||
|
GraphNodePosition *graph_node_position_new(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
const char *updated_at,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère une position.
|
||||||
|
*
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param position Position à libérer.
|
||||||
|
*/
|
||||||
|
void graph_node_position_free(
|
||||||
|
GraphNodePosition *position
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne l'UUID de l'entité.
|
||||||
|
*
|
||||||
|
* La chaîne retournée appartient au modèle.
|
||||||
|
*
|
||||||
|
* @param position Position à consulter.
|
||||||
|
*
|
||||||
|
* @return UUID emprunté, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *graph_node_position_get_entity_identifier(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la coordonnée horizontale.
|
||||||
|
*
|
||||||
|
* @param position Position à consulter.
|
||||||
|
*
|
||||||
|
* @return Coordonnée horizontale, ou 0.0 si position est NULL.
|
||||||
|
*/
|
||||||
|
double graph_node_position_get_x(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la coordonnée verticale.
|
||||||
|
*
|
||||||
|
* @param position Position à consulter.
|
||||||
|
*
|
||||||
|
* @return Coordonnée verticale, ou 0.0 si position est NULL.
|
||||||
|
*/
|
||||||
|
double graph_node_position_get_y(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la date UTC de dernière modification.
|
||||||
|
*
|
||||||
|
* La chaîne retournée appartient au modèle.
|
||||||
|
*
|
||||||
|
* @param position Position à consulter.
|
||||||
|
*
|
||||||
|
* @return Date empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *graph_node_position_get_updated_at(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
133
include/models/investigation_graph_layout.h
Normal file
133
include/models/investigation_graph_layout.h
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file investigation_graph_layout.h
|
||||||
|
* @brief Modèle en mémoire de la disposition du graphe d'enquête.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LAYOUT_H
|
||||||
|
#define LABFY_INVESTIGATION_INVESTIGATION_GRAPH_LAYOUT_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modèle opaque représentant la disposition du graphe.
|
||||||
|
*/
|
||||||
|
typedef struct InvestigationGraphLayout InvestigationGraphLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Codes d'erreur du modèle de disposition.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_ARGUMENT,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_IDENTIFIER,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_COORDINATE,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_MEMORY
|
||||||
|
} InvestigationGraphLayoutError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreur du modèle de disposition.
|
||||||
|
*/
|
||||||
|
#define INVESTIGATION_GRAPH_LAYOUT_ERROR \
|
||||||
|
investigation_graph_layout_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur du modèle.
|
||||||
|
*/
|
||||||
|
GQuark investigation_graph_layout_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée une disposition vide.
|
||||||
|
*
|
||||||
|
* @return Nouvelle disposition, ou NULL en cas d'échec.
|
||||||
|
*/
|
||||||
|
InvestigationGraphLayout *investigation_graph_layout_new(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enregistre ou remplace la position d'une entité.
|
||||||
|
*
|
||||||
|
* L'identifiant est copié.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à modifier.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
* @param x Coordonnée horizontale logique.
|
||||||
|
* @param y Coordonnée verticale logique.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si la position a été enregistrée, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean investigation_graph_layout_set_position(
|
||||||
|
InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lit la position d'une entité.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à consulter.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
* @param x Destination facultative de la coordonnée horizontale.
|
||||||
|
* @param y Destination facultative de la coordonnée verticale.
|
||||||
|
*
|
||||||
|
* @return TRUE si une position existe, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean investigation_graph_layout_get_position(
|
||||||
|
const InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double *x,
|
||||||
|
double *y
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Supprime la position d'une entité.
|
||||||
|
*
|
||||||
|
* L'absence de position n'est pas une erreur.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à modifier.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
*
|
||||||
|
* @return TRUE si une position a été supprimée, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean investigation_graph_layout_remove_position(
|
||||||
|
InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Supprime toutes les positions.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à vider.
|
||||||
|
*/
|
||||||
|
void investigation_graph_layout_clear(
|
||||||
|
InvestigationGraphLayout *layout
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le nombre de positions conservées.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à consulter.
|
||||||
|
*
|
||||||
|
* @return Nombre de positions.
|
||||||
|
*/
|
||||||
|
guint investigation_graph_layout_get_count(
|
||||||
|
const InvestigationGraphLayout *layout
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère la disposition.
|
||||||
|
*
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param layout Disposition à libérer.
|
||||||
|
*/
|
||||||
|
void investigation_graph_layout_free(
|
||||||
|
InvestigationGraphLayout *layout
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -20,6 +20,11 @@
|
||||||
*/
|
*/
|
||||||
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Représentation opaque de la disposition du graphe.
|
||||||
|
*/
|
||||||
|
typedef struct InvestigationGraphLayout InvestigationGraphLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Représentation opaque de la fenêtre principale.
|
* @brief Représentation opaque de la fenêtre principale.
|
||||||
*/
|
*/
|
||||||
|
|
@ -71,6 +76,21 @@ typedef void (*MainWindowVerifyEvidenceCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||||
|
*
|
||||||
|
* @param entity_identifier UUID de l'entité déplacée.
|
||||||
|
* @param x Coordonnée horizontale logique.
|
||||||
|
* @param y Coordonnée verticale logique.
|
||||||
|
* @param user_data Données privées du callback.
|
||||||
|
*/
|
||||||
|
typedef void (*MainWindowGraphNodeMovedCallback)(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit le callback de vérification d'une preuve.
|
* @brief Définit le callback de vérification d'une preuve.
|
||||||
*
|
*
|
||||||
|
|
@ -86,6 +106,21 @@ void main_window_set_verify_evidence_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||||
|
*
|
||||||
|
* MainWindow relaie uniquement l'événement provenant du Workspace.
|
||||||
|
*
|
||||||
|
* @param main_window Fenêtre principale.
|
||||||
|
* @param callback Callback facultatif.
|
||||||
|
* @param user_data Données privées transmises au callback.
|
||||||
|
*/
|
||||||
|
void main_window_set_graph_node_moved_callback(
|
||||||
|
MainWindow *main_window,
|
||||||
|
MainWindowGraphNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit le callback du bouton « Nouvelle enquête ».
|
* @brief Définit le callback du bouton « Nouvelle enquête ».
|
||||||
*
|
*
|
||||||
|
|
@ -247,16 +282,20 @@ void main_window_set_graph_loading(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche le graphe chargé dans le Workspace.
|
* @brief Affiche le graphe chargé et sa disposition dans le Workspace.
|
||||||
*
|
*
|
||||||
* MainWindow et Workspace empruntent graph_model.
|
* MainWindow et Workspace empruntent graph_model et graph_layout.
|
||||||
|
*
|
||||||
|
* Passer NULL pour graph_layout conserve le placement automatique.
|
||||||
*
|
*
|
||||||
* @param main_window Fenêtre principale.
|
* @param main_window Fenêtre principale.
|
||||||
* @param graph_model Graphe emprunté, ou NULL.
|
* @param graph_model Graphe emprunté, ou NULL.
|
||||||
|
* @param graph_layout Disposition empruntée, ou NULL.
|
||||||
*/
|
*/
|
||||||
void main_window_set_graph(
|
void main_window_set_graph(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@ typedef struct EntityRecord EntityRecord;
|
||||||
*/
|
*/
|
||||||
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Représentation opaque de la disposition persistée du graphe.
|
||||||
|
*/
|
||||||
|
typedef struct InvestigationGraphLayout InvestigationGraphLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Représentation opaque de la vue graphique.
|
* @brief Représentation opaque de la vue graphique.
|
||||||
*/
|
*/
|
||||||
|
|
@ -38,6 +43,25 @@ typedef void (*InvestigationGraphViewSelectionCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||||
|
*
|
||||||
|
* Les coordonnées sont exprimées dans l'espace logique du graphe.
|
||||||
|
* entity_identifier est emprunté au modèle et reste valable uniquement
|
||||||
|
* pendant l'appel.
|
||||||
|
*
|
||||||
|
* @param entity_identifier UUID de l'entité déplacée.
|
||||||
|
* @param x Coordonnée horizontale logique du coin supérieur gauche.
|
||||||
|
* @param y Coordonnée verticale logique du coin supérieur gauche.
|
||||||
|
* @param user_data Données empruntées fournies par l'appelant.
|
||||||
|
*/
|
||||||
|
typedef void (*InvestigationGraphViewNodeMovedCallback)(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crée une nouvelle vue graphique vide.
|
* @brief Crée une nouvelle vue graphique vide.
|
||||||
*
|
*
|
||||||
|
|
@ -74,6 +98,25 @@ void investigation_graph_view_set_graph(
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Associe une disposition persistée à la vue.
|
||||||
|
*
|
||||||
|
* La disposition est empruntée et doit rester valide pendant son utilisation.
|
||||||
|
*
|
||||||
|
* Lorsqu'un graphe est déjà affiché, la disposition privée de ses nœuds est
|
||||||
|
* reconstruite. Une position persistée remplace la position automatique
|
||||||
|
* uniquement lorsqu'elle existe pour l'entité concernée.
|
||||||
|
*
|
||||||
|
* Passer NULL rétablit le placement automatique.
|
||||||
|
*
|
||||||
|
* @param graph_view Vue graphique à mettre à jour.
|
||||||
|
* @param graph_layout Disposition persistée empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
void investigation_graph_view_set_layout(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Détache le graphe actuellement affiché.
|
* @brief Détache le graphe actuellement affiché.
|
||||||
*
|
*
|
||||||
|
|
@ -100,6 +143,24 @@ void investigation_graph_view_set_selection_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||||
|
*
|
||||||
|
* Le callback et user_data sont empruntés par la vue.
|
||||||
|
*
|
||||||
|
* Le callback n'est appelé ni pendant le déplacement du canvas, ni pour
|
||||||
|
* un simple clic, ni pendant une reconstruction de la disposition.
|
||||||
|
*
|
||||||
|
* @param graph_view Vue graphique.
|
||||||
|
* @param callback Callback à appeler, ou NULL.
|
||||||
|
* @param user_data Données empruntées du callback.
|
||||||
|
*/
|
||||||
|
void investigation_graph_view_set_node_moved_callback(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
InvestigationGraphViewNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retourne l'entité actuellement sélectionnée.
|
* @brief Retourne l'entité actuellement sélectionnée.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@
|
||||||
*/
|
*/
|
||||||
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
typedef struct InvestigationGraphModel InvestigationGraphModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Représentation opaque de la disposition du graphe.
|
||||||
|
*/
|
||||||
|
typedef struct InvestigationGraphLayout InvestigationGraphLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Représentation opaque de la zone de travail.
|
* @brief Représentation opaque de la zone de travail.
|
||||||
*/
|
*/
|
||||||
|
|
@ -32,6 +37,23 @@ typedef void (*WorkspaceVerifyEvidenceCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||||
|
*
|
||||||
|
* Les coordonnées sont exprimées dans l'espace logique du graphe.
|
||||||
|
*
|
||||||
|
* @param entity_identifier UUID de l'entité déplacée.
|
||||||
|
* @param x Coordonnée horizontale logique.
|
||||||
|
* @param y Coordonnée verticale logique.
|
||||||
|
* @param user_data Données empruntées fournies par l'appelant.
|
||||||
|
*/
|
||||||
|
typedef void (*WorkspaceGraphNodeMovedCallback)(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crée une nouvelle zone de travail.
|
* @brief Crée une nouvelle zone de travail.
|
||||||
*
|
*
|
||||||
|
|
@ -97,6 +119,21 @@ void workspace_set_verify_evidence_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||||
|
*
|
||||||
|
* Le Workspace relaie uniquement l'événement provenant de la vue graphique.
|
||||||
|
*
|
||||||
|
* @param workspace Zone de travail.
|
||||||
|
* @param callback Callback facultatif.
|
||||||
|
* @param user_data Données empruntées transmises au callback.
|
||||||
|
*/
|
||||||
|
void workspace_set_graph_node_moved_callback(
|
||||||
|
Workspace *workspace,
|
||||||
|
WorkspaceGraphNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche l'état de chargement du graphe.
|
* @brief Affiche l'état de chargement du graphe.
|
||||||
*
|
*
|
||||||
|
|
@ -109,17 +146,21 @@ void workspace_set_graph_loading(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche le résumé du graphe chargé.
|
* @brief Affiche le graphe chargé avec sa disposition persistée.
|
||||||
*
|
*
|
||||||
* Le Workspace emprunte graph_model et ne le libère jamais.
|
* Le Workspace emprunte graph_model et graph_layout et ne les libère jamais.
|
||||||
* Passer NULL équivaut à workspace_clear_graph().
|
*
|
||||||
|
* Passer NULL pour graph_model équivaut à workspace_clear_graph().
|
||||||
|
* Passer NULL pour graph_layout conserve le placement automatique.
|
||||||
*
|
*
|
||||||
* @param workspace Zone de travail.
|
* @param workspace Zone de travail.
|
||||||
* @param graph_model Graphe emprunté.
|
* @param graph_model Graphe emprunté.
|
||||||
|
* @param graph_layout Disposition empruntée, ou NULL.
|
||||||
*/
|
*/
|
||||||
void workspace_set_graph(
|
void workspace_set_graph(
|
||||||
Workspace *workspace,
|
Workspace *workspace,
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "core/investigation_session.h"
|
#include "core/investigation_session.h"
|
||||||
#include "core/investigation_tree_model.h"
|
#include "core/investigation_tree_model.h"
|
||||||
#include "core/investigation_graph_load_task.h"
|
#include "core/investigation_graph_load_task.h"
|
||||||
|
#include "models/investigation_graph_layout.h"
|
||||||
#include "models/investigation_graph_model.h"
|
#include "models/investigation_graph_model.h"
|
||||||
#include "models/investigation_record.h"
|
#include "models/investigation_record.h"
|
||||||
#include "core/investigation_tree_builder.h"
|
#include "core/investigation_tree_builder.h"
|
||||||
|
|
@ -24,6 +25,7 @@
|
||||||
#include "core/evidence_import_task.h"
|
#include "core/evidence_import_task.h"
|
||||||
#include "models/evidence_record.h"
|
#include "models/evidence_record.h"
|
||||||
#include "dao/evidence_type_dao.h"
|
#include "dao/evidence_type_dao.h"
|
||||||
|
#include "dao/graph_node_position_dao.h"
|
||||||
#include "views/evidence_import_dialog.h"
|
#include "views/evidence_import_dialog.h"
|
||||||
#include "dao/evidence_dao.h"
|
#include "dao/evidence_dao.h"
|
||||||
#include "models/evidence_type.h"
|
#include "models/evidence_type.h"
|
||||||
|
|
@ -96,6 +98,7 @@ struct Application
|
||||||
|
|
||||||
InvestigationGraphLoadTask *graph_load_task;
|
InvestigationGraphLoadTask *graph_load_task;
|
||||||
InvestigationGraphModel *graph_model;
|
InvestigationGraphModel *graph_model;
|
||||||
|
InvestigationGraphLayout *graph_layout;
|
||||||
guint64 graph_load_generation;
|
guint64 graph_load_generation;
|
||||||
|
|
||||||
char *selected_evidence_identifier;
|
char *selected_evidence_identifier;
|
||||||
|
|
@ -291,6 +294,9 @@ static void application_clear_graph(
|
||||||
InvestigationGraphModel *graph_model =
|
InvestigationGraphModel *graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
InvestigationGraphLayout *graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
if (application == NULL)
|
if (application == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -306,12 +312,22 @@ static void application_clear_graph(
|
||||||
graph_model =
|
graph_model =
|
||||||
application->graph_model;
|
application->graph_model;
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
application->graph_layout;
|
||||||
|
|
||||||
application->graph_model =
|
application->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
application->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
investigation_graph_model_free(
|
investigation_graph_model_free(
|
||||||
graph_model
|
graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -332,6 +348,9 @@ static void application_on_graph_loaded(
|
||||||
Application *application =
|
Application *application =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
InvestigationGraphLayout *graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
char *status_message =
|
char *status_message =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
|
@ -344,6 +363,11 @@ static void application_on_graph_loaded(
|
||||||
context->application;
|
context->application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
investigation_graph_load_task_take_layout(
|
||||||
|
load_task
|
||||||
|
);
|
||||||
|
|
||||||
if (application != NULL)
|
if (application != NULL)
|
||||||
{
|
{
|
||||||
is_current_result =
|
is_current_result =
|
||||||
|
|
@ -363,6 +387,10 @@ static void application_on_graph_loaded(
|
||||||
graph_model
|
graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
investigation_graph_load_task_free(
|
investigation_graph_load_task_free(
|
||||||
load_task
|
load_task
|
||||||
);
|
);
|
||||||
|
|
@ -379,6 +407,10 @@ static void application_on_graph_loaded(
|
||||||
graph_model
|
graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
main_window_set_graph_error(
|
main_window_set_graph_error(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
error->message
|
error->message
|
||||||
|
|
@ -396,11 +428,21 @@ static void application_on_graph_loaded(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph_model == NULL)
|
if (graph_model == NULL ||
|
||||||
|
graph_layout == NULL)
|
||||||
{
|
{
|
||||||
|
investigation_graph_model_free(
|
||||||
|
graph_model
|
||||||
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
main_window_set_graph_error(
|
main_window_set_graph_error(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
"Le chargement s'est terminé sans fournir de graphe."
|
"Le chargement s'est terminé sans fournir "
|
||||||
|
"un graphe et une disposition valides."
|
||||||
);
|
);
|
||||||
|
|
||||||
main_window_set_status(
|
main_window_set_status(
|
||||||
|
|
@ -426,21 +468,28 @@ static void application_on_graph_loaded(
|
||||||
application->graph_model =
|
application->graph_model =
|
||||||
graph_model;
|
graph_model;
|
||||||
|
|
||||||
|
application->graph_layout =
|
||||||
|
graph_layout;
|
||||||
|
|
||||||
main_window_set_graph(
|
main_window_set_graph(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
application->graph_model
|
application->graph_model,
|
||||||
|
application->graph_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
status_message =
|
status_message =
|
||||||
g_strdup_printf(
|
g_strdup_printf(
|
||||||
"Graphe chargé : %" G_GUINT64_FORMAT
|
"Graphe chargé : %" G_GUINT64_FORMAT
|
||||||
" entité(s), %" G_GUINT64_FORMAT
|
" entité(s), %" G_GUINT64_FORMAT
|
||||||
" relation(s).",
|
" relation(s), %u position(s) restaurée(s).",
|
||||||
investigation_graph_model_get_entity_count(
|
investigation_graph_model_get_entity_count(
|
||||||
application->graph_model
|
application->graph_model
|
||||||
),
|
),
|
||||||
investigation_graph_model_get_relation_count(
|
investigation_graph_model_get_relation_count(
|
||||||
application->graph_model
|
application->graph_model
|
||||||
|
),
|
||||||
|
investigation_graph_layout_get_count(
|
||||||
|
application->graph_layout
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -4048,6 +4097,186 @@ static void application_on_verify_evidence_requested(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ferme proprement l'application.
|
||||||
|
*
|
||||||
|
* @param user_data Pointeur vers Application.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Enregistre la nouvelle position logique d'un nœud.
|
||||||
|
*
|
||||||
|
* Le modèle en mémoire est mis à jour avant l'UPSERT SQLite. En cas d'échec
|
||||||
|
* SQLite, le déplacement reste visible pendant la session, mais un message
|
||||||
|
* explicite indique qu'il ne survivra pas à la réouverture de l'enquête.
|
||||||
|
*/
|
||||||
|
static void application_on_graph_node_moved(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Application *application =
|
||||||
|
user_data;
|
||||||
|
|
||||||
|
Database *database =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GraphNodePositionDao *position_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (application == NULL ||
|
||||||
|
application->main_window == NULL ||
|
||||||
|
application->session == NULL ||
|
||||||
|
application->graph_layout == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!investigation_graph_layout_set_position(
|
||||||
|
application->graph_layout,
|
||||||
|
entity_identifier,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible de mettre à jour la position du nœud '%s' : %s",
|
||||||
|
entity_identifier,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Position invalide",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "La position du nœud ne peut pas être conservée."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
database =
|
||||||
|
investigation_session_get_database(
|
||||||
|
application->session
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
main_window_set_status(
|
||||||
|
application->main_window,
|
||||||
|
"Nœud déplacé, mais position non enregistrée."
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enregistrement impossible",
|
||||||
|
"La session ne fournit aucune connexion SQLite."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
position_dao =
|
||||||
|
graph_node_position_dao_new(
|
||||||
|
database,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL)
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible de créer le DAO des positions : %s",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
main_window_set_status(
|
||||||
|
application->main_window,
|
||||||
|
"Nœud déplacé, mais position non enregistrée."
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enregistrement impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Impossible d'accéder aux positions du graphe."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graph_node_position_dao_upsert(
|
||||||
|
position_dao,
|
||||||
|
entity_identifier,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d'enregistrer la position du nœud '%s' : %s",
|
||||||
|
entity_identifier,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
main_window_set_status(
|
||||||
|
application->main_window,
|
||||||
|
"Nœud déplacé, mais position non enregistrée."
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enregistrement impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "La position du nœud n'a pas pu être enregistrée."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
graph_node_position_dao_free(
|
||||||
|
position_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_node_position_dao_free(
|
||||||
|
position_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
main_window_set_status(
|
||||||
|
application->main_window,
|
||||||
|
"Position du nœud enregistrée."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Ferme proprement l'application.
|
* @brief Ferme proprement l'application.
|
||||||
*
|
*
|
||||||
|
|
@ -4138,6 +4367,12 @@ static void application_on_activate(
|
||||||
application
|
application
|
||||||
);
|
);
|
||||||
|
|
||||||
|
main_window_set_graph_node_moved_callback(
|
||||||
|
application->main_window,
|
||||||
|
application_on_graph_node_moved,
|
||||||
|
application
|
||||||
|
);
|
||||||
|
|
||||||
main_window_set_new_investigation_callback(
|
main_window_set_new_investigation_callback(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
application_on_new_investigation_requested,
|
application_on_new_investigation_requested,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
#include "core/background_task.h"
|
#include "core/background_task.h"
|
||||||
#include "core/investigation_graph_loader.h"
|
#include "core/investigation_graph_loader.h"
|
||||||
|
|
||||||
|
#include "dao/graph_node_position_dao.h"
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
#include "models/graph_node_position.h"
|
||||||
|
#include "models/investigation_graph_layout.h"
|
||||||
|
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
@ -35,6 +38,7 @@ typedef struct
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
InvestigationGraphModel *graph_model;
|
InvestigationGraphModel *graph_model;
|
||||||
|
InvestigationGraphLayout *graph_layout;
|
||||||
} InvestigationGraphLoadResult;
|
} InvestigationGraphLoadResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,6 +65,8 @@ struct InvestigationGraphLoadTask
|
||||||
gpointer user_data;
|
gpointer user_data;
|
||||||
GDestroyNotify user_data_destroy;
|
GDestroyNotify user_data_destroy;
|
||||||
|
|
||||||
|
InvestigationGraphLayout *completed_layout;
|
||||||
|
|
||||||
gboolean running;
|
gboolean running;
|
||||||
gboolean disposed;
|
gboolean disposed;
|
||||||
};
|
};
|
||||||
|
|
@ -484,9 +490,16 @@ static void investigation_graph_load_result_free(
|
||||||
load_result->graph_model
|
load_result->graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
load_result->graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
load_result->graph_model =
|
load_result->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
load_result->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
load_result
|
load_result
|
||||||
);
|
);
|
||||||
|
|
@ -573,6 +586,13 @@ static void investigation_graph_load_task_destroy(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
load_task->completed_layout
|
||||||
|
);
|
||||||
|
|
||||||
|
load_task->completed_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
load_task->database_path
|
load_task->database_path
|
||||||
);
|
);
|
||||||
|
|
@ -685,15 +705,27 @@ static gboolean investigation_graph_load_task_worker(
|
||||||
InvestigationGraphLoader *graph_loader =
|
InvestigationGraphLoader *graph_loader =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
GraphNodePositionDao *position_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GPtrArray *positions =
|
||||||
|
NULL;
|
||||||
|
|
||||||
InvestigationGraphModel *graph_model =
|
InvestigationGraphModel *graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
InvestigationGraphLayout *graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
InvestigationGraphLoadResult *load_result =
|
InvestigationGraphLoadResult *load_result =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
GError *component_error =
|
GError *component_error =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
guint position_index =
|
||||||
|
0;
|
||||||
|
|
||||||
if (background_task == NULL ||
|
if (background_task == NULL ||
|
||||||
cancellable == NULL ||
|
cancellable == NULL ||
|
||||||
worker_data == NULL ||
|
worker_data == NULL ||
|
||||||
|
|
@ -833,6 +865,128 @@ static gboolean investigation_graph_load_task_worker(
|
||||||
goto failure;
|
goto failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
background_task_report_progress(
|
||||||
|
background_task,
|
||||||
|
0.65,
|
||||||
|
"Chargement des positions du graphe"
|
||||||
|
);
|
||||||
|
|
||||||
|
position_dao =
|
||||||
|
graph_node_position_dao_new(
|
||||||
|
database,
|
||||||
|
&component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_load_task_set_nested_error(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
|
||||||
|
"Impossible de préparer le chargement des positions",
|
||||||
|
component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
positions =
|
||||||
|
graph_node_position_dao_list_all(
|
||||||
|
position_dao,
|
||||||
|
&component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (positions == NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_load_task_set_nested_error(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
|
||||||
|
"Impossible de charger les positions du graphe",
|
||||||
|
component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
investigation_graph_layout_new();
|
||||||
|
|
||||||
|
if (graph_layout == NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_load_task_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
|
||||||
|
"Impossible d'allouer la disposition du graphe."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (position_index = 0;
|
||||||
|
position_index < positions->len;
|
||||||
|
position_index++)
|
||||||
|
{
|
||||||
|
const GraphNodePosition *position =
|
||||||
|
g_ptr_array_index(
|
||||||
|
positions,
|
||||||
|
position_index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!investigation_graph_layout_set_position(
|
||||||
|
graph_layout,
|
||||||
|
graph_node_position_get_entity_identifier(
|
||||||
|
position
|
||||||
|
),
|
||||||
|
graph_node_position_get_x(
|
||||||
|
position
|
||||||
|
),
|
||||||
|
graph_node_position_get_y(
|
||||||
|
position
|
||||||
|
),
|
||||||
|
&component_error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
investigation_graph_load_task_set_nested_error(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
|
||||||
|
"Impossible de construire la disposition du graphe",
|
||||||
|
component_error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&component_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ptr_array_unref(
|
||||||
|
positions
|
||||||
|
);
|
||||||
|
|
||||||
|
positions =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
graph_node_position_dao_free(
|
||||||
|
position_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
position_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
investigation_graph_loader_free(
|
investigation_graph_loader_free(
|
||||||
graph_loader
|
graph_loader
|
||||||
);
|
);
|
||||||
|
|
@ -875,9 +1029,15 @@ static gboolean investigation_graph_load_task_worker(
|
||||||
load_result->graph_model =
|
load_result->graph_model =
|
||||||
graph_model;
|
graph_model;
|
||||||
|
|
||||||
|
load_result->graph_layout =
|
||||||
|
graph_layout;
|
||||||
|
|
||||||
graph_model =
|
graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
if (investigation_graph_load_task_check_cancelled(
|
if (investigation_graph_load_task_check_cancelled(
|
||||||
cancellable,
|
cancellable,
|
||||||
error
|
error
|
||||||
|
|
@ -911,6 +1071,21 @@ failure:
|
||||||
graph_model
|
graph_model
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
|
if (positions != NULL)
|
||||||
|
{
|
||||||
|
g_ptr_array_unref(
|
||||||
|
positions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_node_position_dao_free(
|
||||||
|
position_dao
|
||||||
|
);
|
||||||
|
|
||||||
investigation_graph_loader_free(
|
investigation_graph_loader_free(
|
||||||
graph_loader
|
graph_loader
|
||||||
);
|
);
|
||||||
|
|
@ -1015,6 +1190,9 @@ static void investigation_graph_load_task_on_completed(
|
||||||
InvestigationGraphModel *graph_model =
|
InvestigationGraphModel *graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
InvestigationGraphLayout *graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
GError *callback_error =
|
GError *callback_error =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
|
@ -1085,13 +1263,20 @@ static void investigation_graph_load_task_on_completed(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (load_result != NULL &&
|
if (load_result != NULL &&
|
||||||
load_result->graph_model != NULL)
|
load_result->graph_model != NULL &&
|
||||||
|
load_result->graph_layout != NULL)
|
||||||
{
|
{
|
||||||
graph_model =
|
graph_model =
|
||||||
load_result->graph_model;
|
load_result->graph_model;
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
load_result->graph_layout;
|
||||||
|
|
||||||
load_result->graph_model =
|
load_result->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
load_result->graph_layout =
|
||||||
|
NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1111,6 +1296,27 @@ static void investigation_graph_load_task_on_completed(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (graph_layout != NULL)
|
||||||
|
{
|
||||||
|
g_mutex_lock(
|
||||||
|
&load_task->mutex
|
||||||
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
load_task->completed_layout
|
||||||
|
);
|
||||||
|
|
||||||
|
load_task->completed_layout =
|
||||||
|
graph_layout;
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_mutex_unlock(
|
||||||
|
&load_task->mutex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
callback(
|
callback(
|
||||||
load_task,
|
load_task,
|
||||||
graph_model,
|
graph_model,
|
||||||
|
|
@ -1448,6 +1654,13 @@ gboolean investigation_graph_load_task_start(
|
||||||
goto start_failure_locked;
|
goto start_failure_locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
load_task->completed_layout
|
||||||
|
);
|
||||||
|
|
||||||
|
load_task->completed_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
load_task->background_task =
|
load_task->background_task =
|
||||||
background_task;
|
background_task;
|
||||||
|
|
||||||
|
|
@ -1610,3 +1823,32 @@ gboolean investigation_graph_load_task_is_running(
|
||||||
|
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InvestigationGraphLayout *investigation_graph_load_task_take_layout(
|
||||||
|
InvestigationGraphLoadTask *load_task
|
||||||
|
)
|
||||||
|
{
|
||||||
|
InvestigationGraphLayout *graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (load_task == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_mutex_lock(
|
||||||
|
&load_task->mutex
|
||||||
|
);
|
||||||
|
|
||||||
|
graph_layout =
|
||||||
|
load_task->completed_layout;
|
||||||
|
|
||||||
|
load_task->completed_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_mutex_unlock(
|
||||||
|
&load_task->mutex
|
||||||
|
);
|
||||||
|
|
||||||
|
return graph_layout;
|
||||||
|
}
|
||||||
|
|
|
||||||
793
src/dao/graph_node_position_dao.c
Normal file
793
src/dao/graph_node_position_dao.c
Normal file
|
|
@ -0,0 +1,793 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file graph_node_position_dao.c
|
||||||
|
* @brief Persistance des positions des nœuds du graphe dans SQLite.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "dao/graph_node_position_dao.h"
|
||||||
|
|
||||||
|
#include "database/error.h"
|
||||||
|
#include "database/statement.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct GraphNodePositionDao
|
||||||
|
* @brief Représentation privée du DAO.
|
||||||
|
*
|
||||||
|
* La connexion Database est empruntée.
|
||||||
|
*/
|
||||||
|
struct GraphNodePositionDao
|
||||||
|
{
|
||||||
|
Database *database;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requête de chargement de toutes les positions.
|
||||||
|
*/
|
||||||
|
static const char *const graph_node_position_dao_list_all_sql =
|
||||||
|
"SELECT "
|
||||||
|
" entity_id,"
|
||||||
|
" x,"
|
||||||
|
" y,"
|
||||||
|
" updated_at "
|
||||||
|
"FROM graph_node_positions "
|
||||||
|
"ORDER BY entity_id ASC;";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requête d'insertion ou de mise à jour d'une position.
|
||||||
|
*/
|
||||||
|
static const char *const graph_node_position_dao_upsert_sql =
|
||||||
|
"INSERT INTO graph_node_positions"
|
||||||
|
"("
|
||||||
|
" entity_id,"
|
||||||
|
" x,"
|
||||||
|
" y,"
|
||||||
|
" updated_at"
|
||||||
|
")"
|
||||||
|
"VALUES"
|
||||||
|
"("
|
||||||
|
" ?,"
|
||||||
|
" ?,"
|
||||||
|
" ?,"
|
||||||
|
" ?"
|
||||||
|
")"
|
||||||
|
"ON CONFLICT(entity_id)"
|
||||||
|
"DO UPDATE SET "
|
||||||
|
" x = excluded.x,"
|
||||||
|
" y = excluded.y,"
|
||||||
|
" updated_at = excluded.updated_at;";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requête de suppression d'une position.
|
||||||
|
*/
|
||||||
|
static const char *const graph_node_position_dao_delete_sql =
|
||||||
|
"DELETE FROM graph_node_positions "
|
||||||
|
"WHERE entity_id = ?;";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enregistre une erreur littérale.
|
||||||
|
*/
|
||||||
|
static void graph_node_position_dao_set_error_literal(
|
||||||
|
GError **error,
|
||||||
|
GraphNodePositionDaoError error_code,
|
||||||
|
const char *error_message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR,
|
||||||
|
error_code,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transforme la dernière erreur Database en erreur du DAO.
|
||||||
|
*/
|
||||||
|
static void graph_node_position_dao_set_database_error(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
GError **error,
|
||||||
|
GraphNodePositionDaoError error_code,
|
||||||
|
const char *context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *database_error_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (error == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position_dao != NULL &&
|
||||||
|
position_dao->database != NULL)
|
||||||
|
{
|
||||||
|
database_error_message =
|
||||||
|
database_error_get_message(
|
||||||
|
position_dao->database
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_error_message != NULL &&
|
||||||
|
database_error_message[0] != '\0')
|
||||||
|
{
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR,
|
||||||
|
error_code,
|
||||||
|
"%s : %s",
|
||||||
|
context,
|
||||||
|
database_error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR,
|
||||||
|
error_code,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Génère une date UTC au format attendu par le modèle.
|
||||||
|
*
|
||||||
|
* @return Nouvelle chaîne à libérer avec g_free(), ou NULL.
|
||||||
|
*/
|
||||||
|
static char *graph_node_position_dao_create_utc_timestamp(void)
|
||||||
|
{
|
||||||
|
GDateTime *date_time =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *timestamp =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
date_time =
|
||||||
|
g_date_time_new_now_utc();
|
||||||
|
|
||||||
|
if (date_time == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp =
|
||||||
|
g_date_time_format(
|
||||||
|
date_time,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_date_time_unref(
|
||||||
|
date_time
|
||||||
|
);
|
||||||
|
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère une position contenue dans un GPtrArray.
|
||||||
|
*/
|
||||||
|
static void graph_node_position_dao_record_destroy(
|
||||||
|
gpointer data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
graph_node_position_free(
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construit une position depuis la ligne SQLite courante.
|
||||||
|
*/
|
||||||
|
static GraphNodePosition *
|
||||||
|
graph_node_position_dao_read_current_record(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GraphNodePosition *position =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *entity_identifier =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *updated_at =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
double x =
|
||||||
|
0.0;
|
||||||
|
|
||||||
|
double y =
|
||||||
|
0.0;
|
||||||
|
|
||||||
|
GError *model_error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (position_dao == NULL ||
|
||||||
|
position_dao->database == NULL ||
|
||||||
|
statement == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Impossible de lire une position avec des paramètres invalides."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_statement_column_text(
|
||||||
|
statement,
|
||||||
|
0,
|
||||||
|
&entity_identifier
|
||||||
|
) ||
|
||||||
|
!database_statement_column_double(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
&x
|
||||||
|
) ||
|
||||||
|
!database_statement_column_double(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
&y
|
||||||
|
) ||
|
||||||
|
!database_statement_column_text(
|
||||||
|
statement,
|
||||||
|
3,
|
||||||
|
&updated_at
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_READ,
|
||||||
|
"Impossible de lire les colonnes de la position du nœud."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
position =
|
||||||
|
graph_node_position_new(
|
||||||
|
entity_identifier,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
updated_at,
|
||||||
|
&model_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
if (model_error != NULL)
|
||||||
|
{
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MODEL,
|
||||||
|
"La position persistée est invalide : %s",
|
||||||
|
model_error->message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MODEL,
|
||||||
|
"La position persistée ne peut pas être reconstruite."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&model_error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convertit une erreur SQLite d'UPSERT en erreur du DAO.
|
||||||
|
*/
|
||||||
|
static void graph_node_position_dao_set_upsert_error(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *database_error_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (position_dao != NULL &&
|
||||||
|
position_dao->database != NULL)
|
||||||
|
{
|
||||||
|
database_error_message =
|
||||||
|
database_error_get_message(
|
||||||
|
position_dao->database
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_error_message != NULL &&
|
||||||
|
(
|
||||||
|
strstr(
|
||||||
|
database_error_message,
|
||||||
|
"constraint"
|
||||||
|
) != NULL ||
|
||||||
|
strstr(
|
||||||
|
database_error_message,
|
||||||
|
"CONSTRAINT"
|
||||||
|
) != NULL ||
|
||||||
|
strstr(
|
||||||
|
database_error_message,
|
||||||
|
"FOREIGN KEY"
|
||||||
|
) != NULL
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_CONSTRAINT,
|
||||||
|
"La position viole une contrainte SQLite"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
|
||||||
|
"Impossible d'enregistrer la position du nœud"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark graph_node_position_dao_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"graph-node-position-dao-error-quark"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphNodePositionDao *graph_node_position_dao_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GraphNodePositionDao *position_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La connexion Database est absente."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
position_dao =
|
||||||
|
g_try_new0(
|
||||||
|
GraphNodePositionDao,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MEMORY,
|
||||||
|
"Impossible d'allouer le DAO des positions de nœuds."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
position_dao->database =
|
||||||
|
database;
|
||||||
|
|
||||||
|
return position_dao;
|
||||||
|
}
|
||||||
|
|
||||||
|
void graph_node_position_dao_free(
|
||||||
|
GraphNodePositionDao *position_dao
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (position_dao == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
position_dao->database =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
position_dao
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPtrArray *graph_node_position_dao_list_all(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GPtrArray *positions =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GraphNodePosition *position =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
DatabaseStatementStepResult step_result =
|
||||||
|
DATABASE_STATEMENT_STEP_ERROR;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL ||
|
||||||
|
position_dao->database == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Le DAO des positions de nœuds est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
positions =
|
||||||
|
g_ptr_array_new_with_free_func(
|
||||||
|
graph_node_position_dao_record_destroy
|
||||||
|
);
|
||||||
|
|
||||||
|
if (positions == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MEMORY,
|
||||||
|
"Impossible d'allouer la liste des positions de nœuds."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
statement =
|
||||||
|
database_statement_prepare(
|
||||||
|
position_dao->database,
|
||||||
|
graph_node_position_dao_list_all_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
|
||||||
|
"Impossible de préparer le chargement des positions"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
step_result =
|
||||||
|
database_statement_step(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
if (step_result ==
|
||||||
|
DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step_result ==
|
||||||
|
DATABASE_STATEMENT_STEP_ERROR)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
|
||||||
|
"Impossible d'exécuter le chargement des positions"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
position =
|
||||||
|
graph_node_position_dao_read_current_record(
|
||||||
|
position_dao,
|
||||||
|
statement,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ptr_array_add(
|
||||||
|
positions,
|
||||||
|
position
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Le tableau devient propriétaire du modèle.
|
||||||
|
*/
|
||||||
|
position =
|
||||||
|
NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
|
||||||
|
error:
|
||||||
|
|
||||||
|
graph_node_position_free(
|
||||||
|
position
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
g_ptr_array_unref(
|
||||||
|
positions
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean graph_node_position_dao_upsert(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *updated_at =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean success =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL ||
|
||||||
|
position_dao->database == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
) ||
|
||||||
|
!isfinite(x) ||
|
||||||
|
!isfinite(y))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Les paramètres de la position du nœud sont invalides."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated_at =
|
||||||
|
graph_node_position_dao_create_utc_timestamp();
|
||||||
|
|
||||||
|
if (updated_at == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_MEMORY,
|
||||||
|
"Impossible de générer la date de modification de la position."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
statement =
|
||||||
|
database_statement_prepare(
|
||||||
|
position_dao->database,
|
||||||
|
graph_node_position_dao_upsert_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
|
||||||
|
"Impossible de préparer l'enregistrement de la position"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
entity_identifier
|
||||||
|
) ||
|
||||||
|
!database_statement_bind_double(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
x
|
||||||
|
) ||
|
||||||
|
!database_statement_bind_double(
|
||||||
|
statement,
|
||||||
|
3,
|
||||||
|
y
|
||||||
|
) ||
|
||||||
|
!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
4,
|
||||||
|
updated_at
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
||||||
|
"Impossible de lier les données de la position"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_statement_step(
|
||||||
|
statement
|
||||||
|
) != DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_upsert_error(
|
||||||
|
position_dao,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
success =
|
||||||
|
TRUE;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean graph_node_position_dao_delete(
|
||||||
|
GraphNodePositionDao *position_dao,
|
||||||
|
const char *entity_identifier,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean success =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position_dao == NULL ||
|
||||||
|
position_dao->database == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"L'identifiant de l'entité est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
statement =
|
||||||
|
database_statement_prepare(
|
||||||
|
position_dao->database,
|
||||||
|
graph_node_position_dao_delete_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_PREPARE,
|
||||||
|
"Impossible de préparer la suppression de la position"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
||||||
|
"Impossible de lier l'identifiant de l'entité"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_statement_step(
|
||||||
|
statement
|
||||||
|
) != DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
{
|
||||||
|
graph_node_position_dao_set_database_error(
|
||||||
|
position_dao,
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_DAO_ERROR_EXECUTE,
|
||||||
|
"Impossible de supprimer la position du nœud"
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
success =
|
||||||
|
TRUE;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
@ -668,6 +668,65 @@ rollback:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Garantit atomiquement la présence des extensions du schéma courant.
|
||||||
|
*/
|
||||||
|
static bool database_ensure_current_schema(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
bool transaction_started = false;
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_begin(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started = true;
|
||||||
|
|
||||||
|
if (!schema_ensure_current(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_commit(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
|
||||||
|
if (transaction_started)
|
||||||
|
{
|
||||||
|
if (!database_transaction_rollback(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d’annuler l’installation des extensions "
|
||||||
|
"du schéma SQLite courant."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Database *database_open(
|
Database *database_open(
|
||||||
const char *database_path
|
const char *database_path
|
||||||
)
|
)
|
||||||
|
|
@ -807,6 +866,13 @@ bool database_migrate_to_latest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!database_ensure_current_schema(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
database->schema_version =
|
database->schema_version =
|
||||||
schema_version;
|
schema_version;
|
||||||
|
|
||||||
|
|
@ -897,6 +963,13 @@ bool database_initialize(
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!schema_ensure_current(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
if (!database_insert_all_metadata(
|
if (!database_insert_all_metadata(
|
||||||
database,
|
database,
|
||||||
created_at,
|
created_at,
|
||||||
|
|
|
||||||
|
|
@ -192,3 +192,14 @@ bool schema_install_v2(
|
||||||
"la migration SQLite V2"
|
"la migration SQLite V2"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool schema_ensure_current(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return schema_execute_file(
|
||||||
|
database,
|
||||||
|
"database/schema_current.sql",
|
||||||
|
"les extensions du schéma SQLite courant"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,79 @@ bool database_statement_bind_int64(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool database_statement_bind_double(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int index,
|
||||||
|
double value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
sqlite3 *database_handle =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
const char *error_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
int result =
|
||||||
|
SQLITE_ERROR;
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->handle == NULL ||
|
||||||
|
index <= 0)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Paramètres invalides pour la liaison d'un nombre réel."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result =
|
||||||
|
sqlite3_bind_double(
|
||||||
|
statement->handle,
|
||||||
|
index,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != SQLITE_OK)
|
||||||
|
{
|
||||||
|
database_handle =
|
||||||
|
database_get_handle(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
|
error_message =
|
||||||
|
database_handle != NULL
|
||||||
|
? sqlite3_errmsg(database_handle)
|
||||||
|
: sqlite3_errstr(result);
|
||||||
|
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_warning(
|
||||||
|
"Impossible de lier le paramètre réel %d : %s",
|
||||||
|
index,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool database_statement_bind_null(
|
bool database_statement_bind_null(
|
||||||
DatabaseStatement *statement,
|
DatabaseStatement *statement,
|
||||||
int index
|
int index
|
||||||
|
|
@ -477,6 +550,57 @@ bool database_statement_column_is_null(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool database_statement_column_double(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int column_index,
|
||||||
|
double *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int column_count =
|
||||||
|
0;
|
||||||
|
|
||||||
|
int column_type =
|
||||||
|
SQLITE_NULL;
|
||||||
|
|
||||||
|
if (statement == NULL ||
|
||||||
|
statement->handle == NULL ||
|
||||||
|
value == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
column_count =
|
||||||
|
sqlite3_column_count(
|
||||||
|
statement->handle
|
||||||
|
);
|
||||||
|
|
||||||
|
if (column_index < 0 ||
|
||||||
|
column_index >= column_count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
column_type =
|
||||||
|
sqlite3_column_type(
|
||||||
|
statement->handle,
|
||||||
|
column_index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (column_type != SQLITE_FLOAT &&
|
||||||
|
column_type != SQLITE_INTEGER)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*value =
|
||||||
|
sqlite3_column_double(
|
||||||
|
statement->handle,
|
||||||
|
column_index
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool database_statement_column_text(
|
bool database_statement_column_text(
|
||||||
DatabaseStatement *statement,
|
DatabaseStatement *statement,
|
||||||
int column_index,
|
int column_index,
|
||||||
|
|
|
||||||
336
src/models/graph_node_position.c
Normal file
336
src/models/graph_node_position.c
Normal file
|
|
@ -0,0 +1,336 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file graph_node_position.c
|
||||||
|
* @brief Implémentation du modèle de position persistée d'un nœud.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/graph_node_position.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct GraphNodePosition
|
||||||
|
* @brief Représentation privée d'une position de nœud.
|
||||||
|
*/
|
||||||
|
struct GraphNodePosition
|
||||||
|
{
|
||||||
|
char *entity_identifier;
|
||||||
|
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
|
||||||
|
char *updated_at;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copie et nettoie une chaîne.
|
||||||
|
*/
|
||||||
|
static char *graph_node_position_duplicate_trimmed(
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (value == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy =
|
||||||
|
g_strdup(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (copy == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strstrip(
|
||||||
|
copy
|
||||||
|
);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie une date UTC stricte YYYY-MM-DDTHH:MM:SSZ.
|
||||||
|
*/
|
||||||
|
static gboolean graph_node_position_timestamp_is_valid(
|
||||||
|
const char *timestamp
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GDateTime *date_time =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *normalized_timestamp =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean valid =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
gsize character_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (timestamp == NULL ||
|
||||||
|
strlen(timestamp) != 20U)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp[4] != '-' ||
|
||||||
|
timestamp[7] != '-' ||
|
||||||
|
timestamp[10] != 'T' ||
|
||||||
|
timestamp[13] != ':' ||
|
||||||
|
timestamp[16] != ':' ||
|
||||||
|
timestamp[19] != 'Z')
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (character_index = 0;
|
||||||
|
character_index < 20U;
|
||||||
|
character_index++)
|
||||||
|
{
|
||||||
|
if (character_index == 4U ||
|
||||||
|
character_index == 7U ||
|
||||||
|
character_index == 10U ||
|
||||||
|
character_index == 13U ||
|
||||||
|
character_index == 16U ||
|
||||||
|
character_index == 19U)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_ascii_isdigit(
|
||||||
|
timestamp[character_index]
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
date_time =
|
||||||
|
g_date_time_new_from_iso8601(
|
||||||
|
timestamp,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (date_time == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized_timestamp =
|
||||||
|
g_date_time_format(
|
||||||
|
date_time,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
valid =
|
||||||
|
normalized_timestamp != NULL &&
|
||||||
|
g_strcmp0(
|
||||||
|
normalized_timestamp,
|
||||||
|
timestamp
|
||||||
|
) == 0;
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
normalized_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
g_date_time_unref(
|
||||||
|
date_time
|
||||||
|
);
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark graph_node_position_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"graph-node-position-error-quark"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphNodePosition *graph_node_position_new(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
const char *updated_at,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GraphNodePosition *position =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *entity_identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *updated_at_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
entity_identifier_copy =
|
||||||
|
graph_node_position_duplicate_trimmed(
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entity_identifier_copy == NULL ||
|
||||||
|
entity_identifier_copy[0] == '\0' ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
entity_identifier_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_ERROR,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_IDENTIFIER,
|
||||||
|
"L'identifiant de l'entité n'est pas un UUID valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isfinite(x) ||
|
||||||
|
!isfinite(y))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_ERROR,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_COORDINATE,
|
||||||
|
"Les coordonnées du nœud doivent être des nombres finis."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated_at_copy =
|
||||||
|
graph_node_position_duplicate_trimmed(
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!graph_node_position_timestamp_is_valid(
|
||||||
|
updated_at_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_ERROR,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_DATE,
|
||||||
|
"La date de modification de la position est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
position =
|
||||||
|
g_try_new0(
|
||||||
|
GraphNodePosition,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
GRAPH_NODE_POSITION_ERROR,
|
||||||
|
GRAPH_NODE_POSITION_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Impossible d'allouer la position du nœud."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
position->entity_identifier =
|
||||||
|
entity_identifier_copy;
|
||||||
|
|
||||||
|
position->x =
|
||||||
|
x;
|
||||||
|
|
||||||
|
position->y =
|
||||||
|
y;
|
||||||
|
|
||||||
|
position->updated_at =
|
||||||
|
updated_at_copy;
|
||||||
|
|
||||||
|
entity_identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
updated_at_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
updated_at_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
entity_identifier_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void graph_node_position_free(
|
||||||
|
GraphNodePosition *position
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
position->updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
position->entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *graph_node_position_get_entity_identifier(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return position != NULL
|
||||||
|
? position->entity_identifier
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
double graph_node_position_get_x(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return position != NULL
|
||||||
|
? position->x
|
||||||
|
: 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double graph_node_position_get_y(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return position != NULL
|
||||||
|
? position->y
|
||||||
|
: 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *graph_node_position_get_updated_at(
|
||||||
|
const GraphNodePosition *position
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return position != NULL
|
||||||
|
? position->updated_at
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
321
src/models/investigation_graph_layout.c
Normal file
321
src/models/investigation_graph_layout.c
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file investigation_graph_layout.c
|
||||||
|
* @brief Implémentation du modèle en mémoire de disposition du graphe.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/investigation_graph_layout.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Coordonnées privées associées à une entité.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
} InvestigationGraphLayoutPosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct InvestigationGraphLayout
|
||||||
|
* @brief Représentation privée de la disposition.
|
||||||
|
*/
|
||||||
|
struct InvestigationGraphLayout
|
||||||
|
{
|
||||||
|
GHashTable *positions_by_entity_identifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enregistre une erreur littérale.
|
||||||
|
*/
|
||||||
|
static void investigation_graph_layout_set_error_literal(
|
||||||
|
GError **error,
|
||||||
|
InvestigationGraphLayoutError error_code,
|
||||||
|
const char *error_message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR,
|
||||||
|
error_code,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie un identifiant d'entité.
|
||||||
|
*/
|
||||||
|
static gboolean investigation_graph_layout_identifier_is_valid(
|
||||||
|
const char *entity_identifier
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return entity_identifier != NULL &&
|
||||||
|
entity_identifier[0] != '\0' &&
|
||||||
|
g_uuid_string_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark investigation_graph_layout_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"investigation-graph-layout-error-quark"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InvestigationGraphLayout *investigation_graph_layout_new(void)
|
||||||
|
{
|
||||||
|
InvestigationGraphLayout *layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
layout =
|
||||||
|
g_try_new0(
|
||||||
|
InvestigationGraphLayout,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (layout == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout->positions_by_entity_identifier =
|
||||||
|
g_hash_table_new_full(
|
||||||
|
g_str_hash,
|
||||||
|
g_str_equal,
|
||||||
|
g_free,
|
||||||
|
g_free
|
||||||
|
);
|
||||||
|
|
||||||
|
if (layout->positions_by_entity_identifier == NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_layout_free(
|
||||||
|
layout
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean investigation_graph_layout_set_position(
|
||||||
|
InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
InvestigationGraphLayoutPosition *position =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *entity_identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (layout == NULL ||
|
||||||
|
layout->positions_by_entity_identifier == NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_layout_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La disposition du graphe est absente."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!investigation_graph_layout_identifier_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
investigation_graph_layout_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_IDENTIFIER,
|
||||||
|
"L'identifiant de l'entité n'est pas un UUID valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isfinite(x) ||
|
||||||
|
!isfinite(y))
|
||||||
|
{
|
||||||
|
investigation_graph_layout_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_COORDINATE,
|
||||||
|
"Les coordonnées du nœud doivent être des nombres finis."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_identifier_copy =
|
||||||
|
g_strdup(
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
position =
|
||||||
|
g_try_new0(
|
||||||
|
InvestigationGraphLayoutPosition,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entity_identifier_copy == NULL ||
|
||||||
|
position == NULL)
|
||||||
|
{
|
||||||
|
g_free(
|
||||||
|
position
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
entity_identifier_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
investigation_graph_layout_set_error_literal(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_MEMORY,
|
||||||
|
"Impossible d'allouer la position du nœud."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
position->x =
|
||||||
|
x;
|
||||||
|
|
||||||
|
position->y =
|
||||||
|
y;
|
||||||
|
|
||||||
|
g_hash_table_replace(
|
||||||
|
layout->positions_by_entity_identifier,
|
||||||
|
entity_identifier_copy,
|
||||||
|
position
|
||||||
|
);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean investigation_graph_layout_get_position(
|
||||||
|
const InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier,
|
||||||
|
double *x,
|
||||||
|
double *y
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const InvestigationGraphLayoutPosition *position =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (layout == NULL ||
|
||||||
|
layout->positions_by_entity_identifier == NULL ||
|
||||||
|
!investigation_graph_layout_identifier_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
position =
|
||||||
|
g_hash_table_lookup(
|
||||||
|
layout->positions_by_entity_identifier,
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (position == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x != NULL)
|
||||||
|
{
|
||||||
|
*x =
|
||||||
|
position->x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y != NULL)
|
||||||
|
{
|
||||||
|
*y =
|
||||||
|
position->y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean investigation_graph_layout_remove_position(
|
||||||
|
InvestigationGraphLayout *layout,
|
||||||
|
const char *entity_identifier
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (layout == NULL ||
|
||||||
|
layout->positions_by_entity_identifier == NULL ||
|
||||||
|
!investigation_graph_layout_identifier_is_valid(
|
||||||
|
entity_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_hash_table_remove(
|
||||||
|
layout->positions_by_entity_identifier,
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void investigation_graph_layout_clear(
|
||||||
|
InvestigationGraphLayout *layout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (layout == NULL ||
|
||||||
|
layout->positions_by_entity_identifier == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_hash_table_remove_all(
|
||||||
|
layout->positions_by_entity_identifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
guint investigation_graph_layout_get_count(
|
||||||
|
const InvestigationGraphLayout *layout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (layout == NULL ||
|
||||||
|
layout->positions_by_entity_identifier == NULL)
|
||||||
|
{
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_hash_table_size(
|
||||||
|
layout->positions_by_entity_identifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void investigation_graph_layout_free(
|
||||||
|
InvestigationGraphLayout *layout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (layout == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_pointer(
|
||||||
|
&layout->positions_by_entity_identifier,
|
||||||
|
g_hash_table_unref
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
layout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -92,6 +92,12 @@ struct MainWindow
|
||||||
gpointer
|
gpointer
|
||||||
verify_evidence_user_data;
|
verify_evidence_user_data;
|
||||||
|
|
||||||
|
MainWindowGraphNodeMovedCallback
|
||||||
|
graph_node_moved_callback;
|
||||||
|
|
||||||
|
gpointer
|
||||||
|
graph_node_moved_user_data;
|
||||||
|
|
||||||
MainWindowQuitCallback
|
MainWindowQuitCallback
|
||||||
quit_callback;
|
quit_callback;
|
||||||
|
|
||||||
|
|
@ -252,6 +258,35 @@ static GtkWidget *main_window_create_action_button(
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Relaie la demande de vérification provenant du Workspace.
|
||||||
|
*/
|
||||||
|
static void main_window_on_graph_node_moved(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
MainWindow *main_window =
|
||||||
|
user_data;
|
||||||
|
|
||||||
|
if (main_window == NULL ||
|
||||||
|
main_window->graph_node_moved_callback == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
entity_identifier[0] == '\0')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
main_window->graph_node_moved_callback(
|
||||||
|
entity_identifier,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
main_window->graph_node_moved_user_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Relaie la demande de vérification provenant du Workspace.
|
* @brief Relaie la demande de vérification provenant du Workspace.
|
||||||
*/
|
*/
|
||||||
|
|
@ -515,6 +550,12 @@ MainWindow *main_window_new(
|
||||||
main_window
|
main_window
|
||||||
);
|
);
|
||||||
|
|
||||||
|
workspace_set_graph_node_moved_callback(
|
||||||
|
main_window->workspace,
|
||||||
|
main_window_on_graph_node_moved,
|
||||||
|
main_window
|
||||||
|
);
|
||||||
|
|
||||||
workspace_widget = workspace_get_widget(
|
workspace_widget = workspace_get_widget(
|
||||||
main_window->workspace
|
main_window->workspace
|
||||||
);
|
);
|
||||||
|
|
@ -870,7 +911,8 @@ void main_window_set_graph_loading(
|
||||||
|
|
||||||
void main_window_set_graph(
|
void main_window_set_graph(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (main_window == NULL)
|
if (main_window == NULL)
|
||||||
|
|
@ -880,7 +922,8 @@ void main_window_set_graph(
|
||||||
|
|
||||||
workspace_set_graph(
|
workspace_set_graph(
|
||||||
main_window->workspace,
|
main_window->workspace,
|
||||||
graph_model
|
graph_model,
|
||||||
|
graph_layout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1058,6 +1101,24 @@ void main_window_set_verify_evidence_callback(
|
||||||
user_data;
|
user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void main_window_set_graph_node_moved_callback(
|
||||||
|
MainWindow *main_window,
|
||||||
|
MainWindowGraphNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (main_window == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
main_window->graph_node_moved_callback =
|
||||||
|
callback;
|
||||||
|
|
||||||
|
main_window->graph_node_moved_user_data =
|
||||||
|
user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void main_window_set_quit_callback(
|
void main_window_set_quit_callback(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
MainWindowQuitCallback callback,
|
MainWindowQuitCallback callback,
|
||||||
|
|
@ -1174,6 +1235,18 @@ void main_window_free(
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
workspace_set_graph_node_moved_callback(
|
||||||
|
main_window->workspace,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
main_window->graph_node_moved_callback =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
main_window->graph_node_moved_user_data =
|
||||||
|
NULL;
|
||||||
|
|
||||||
main_window_clear_graph(
|
main_window_clear_graph(
|
||||||
main_window
|
main_window
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "widgets/investigation_graph_view.h"
|
#include "widgets/investigation_graph_view.h"
|
||||||
|
|
||||||
#include "models/entity_record.h"
|
#include "models/entity_record.h"
|
||||||
|
#include "models/investigation_graph_layout.h"
|
||||||
#include "models/investigation_graph_model.h"
|
#include "models/investigation_graph_model.h"
|
||||||
#include "models/relation_record.h"
|
#include "models/relation_record.h"
|
||||||
|
|
||||||
|
|
@ -57,6 +58,7 @@ struct InvestigationGraphView
|
||||||
GtkEventController *motion_controller;
|
GtkEventController *motion_controller;
|
||||||
|
|
||||||
const InvestigationGraphModel *graph_model;
|
const InvestigationGraphModel *graph_model;
|
||||||
|
const InvestigationGraphLayout *graph_layout;
|
||||||
|
|
||||||
GPtrArray *node_layouts;
|
GPtrArray *node_layouts;
|
||||||
GHashTable *node_layouts_by_identifier;
|
GHashTable *node_layouts_by_identifier;
|
||||||
|
|
@ -92,6 +94,9 @@ struct InvestigationGraphView
|
||||||
InvestigationGraphViewSelectionCallback selection_callback;
|
InvestigationGraphViewSelectionCallback selection_callback;
|
||||||
gpointer selection_user_data;
|
gpointer selection_user_data;
|
||||||
|
|
||||||
|
InvestigationGraphViewNodeMovedCallback node_moved_callback;
|
||||||
|
gpointer node_moved_user_data;
|
||||||
|
|
||||||
double node_drag_pointer_offset_x;
|
double node_drag_pointer_offset_x;
|
||||||
double node_drag_pointer_offset_y;
|
double node_drag_pointer_offset_y;
|
||||||
|
|
||||||
|
|
@ -1042,6 +1047,44 @@ static void investigation_graph_view_on_drag_update(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prévient l'appelant qu'un nœud a été déplacé.
|
||||||
|
*/
|
||||||
|
static void investigation_graph_view_notify_node_moved(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
const InvestigationGraphNodeLayout *node_layout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *entity_identifier =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (graph_view == NULL ||
|
||||||
|
node_layout == NULL ||
|
||||||
|
node_layout->entity_record == NULL ||
|
||||||
|
graph_view->node_moved_callback == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_identifier =
|
||||||
|
entity_record_get_identifier(
|
||||||
|
node_layout->entity_record
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entity_identifier == NULL ||
|
||||||
|
entity_identifier[0] == '\0')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_view->node_moved_callback(
|
||||||
|
entity_identifier,
|
||||||
|
node_layout->x,
|
||||||
|
node_layout->y,
|
||||||
|
graph_view->node_moved_user_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Termine le déplacement actif.
|
* @brief Termine le déplacement actif.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1067,6 +1110,12 @@ static void investigation_graph_view_on_drag_end(
|
||||||
double logical_y =
|
double logical_y =
|
||||||
0.0;
|
0.0;
|
||||||
|
|
||||||
|
InvestigationGraphNodeLayout *moved_node =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean node_was_moved =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
(void) gesture;
|
(void) gesture;
|
||||||
|
|
||||||
if (graph_view == NULL ||
|
if (graph_view == NULL ||
|
||||||
|
|
@ -1088,6 +1137,19 @@ static void investigation_graph_view_on_drag_end(
|
||||||
else if (graph_view->node_dragging &&
|
else if (graph_view->node_dragging &&
|
||||||
graph_view->dragged_node != NULL)
|
graph_view->dragged_node != NULL)
|
||||||
{
|
{
|
||||||
|
moved_node =
|
||||||
|
graph_view->dragged_node;
|
||||||
|
|
||||||
|
node_was_moved =
|
||||||
|
(
|
||||||
|
(offset_x * offset_x) +
|
||||||
|
(offset_y * offset_y)
|
||||||
|
) >
|
||||||
|
(
|
||||||
|
INVESTIGATION_GRAPH_VIEW_CLICK_TOLERANCE *
|
||||||
|
INVESTIGATION_GRAPH_VIEW_CLICK_TOLERANCE
|
||||||
|
);
|
||||||
|
|
||||||
pointer_x =
|
pointer_x =
|
||||||
graph_view->drag_start_x +
|
graph_view->drag_start_x +
|
||||||
offset_x;
|
offset_x;
|
||||||
|
|
@ -1118,6 +1180,15 @@ static void investigation_graph_view_on_drag_end(
|
||||||
graph_view
|
graph_view
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (node_was_moved &&
|
||||||
|
moved_node != NULL)
|
||||||
|
{
|
||||||
|
investigation_graph_view_notify_node_moved(
|
||||||
|
graph_view,
|
||||||
|
moved_node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
gtk_widget_queue_draw(
|
gtk_widget_queue_draw(
|
||||||
graph_view->drawing_area
|
graph_view->drawing_area
|
||||||
);
|
);
|
||||||
|
|
@ -1276,7 +1347,9 @@ static GPtrArray *investigation_graph_view_list_active_entities(
|
||||||
*/
|
*/
|
||||||
static GPtrArray *investigation_graph_view_create_layout(
|
static GPtrArray *investigation_graph_view_create_layout(
|
||||||
const GPtrArray *active_entities,
|
const GPtrArray *active_entities,
|
||||||
int width
|
const InvestigationGraphLayout *graph_layout,
|
||||||
|
int width,
|
||||||
|
gboolean apply_persisted_positions
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
GPtrArray *node_layouts =
|
GPtrArray *node_layouts =
|
||||||
|
|
@ -1361,6 +1434,15 @@ static GPtrArray *investigation_graph_view_create_layout(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *entity_identifier =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
double persisted_x =
|
||||||
|
0.0;
|
||||||
|
|
||||||
|
double persisted_y =
|
||||||
|
0.0;
|
||||||
|
|
||||||
node_layout->entity_record =
|
node_layout->entity_record =
|
||||||
g_ptr_array_index(
|
g_ptr_array_index(
|
||||||
active_entities,
|
active_entities,
|
||||||
|
|
@ -1377,6 +1459,29 @@ static GPtrArray *investigation_graph_view_create_layout(
|
||||||
(INVESTIGATION_GRAPH_VIEW_NODE_HEIGHT +
|
(INVESTIGATION_GRAPH_VIEW_NODE_HEIGHT +
|
||||||
INVESTIGATION_GRAPH_VIEW_VERTICAL_GAP));
|
INVESTIGATION_GRAPH_VIEW_VERTICAL_GAP));
|
||||||
|
|
||||||
|
entity_identifier =
|
||||||
|
entity_record_get_identifier(
|
||||||
|
node_layout->entity_record
|
||||||
|
);
|
||||||
|
|
||||||
|
if (apply_persisted_positions &&
|
||||||
|
graph_layout != NULL &&
|
||||||
|
entity_identifier != NULL &&
|
||||||
|
entity_identifier[0] != '\0' &&
|
||||||
|
investigation_graph_layout_get_position(
|
||||||
|
graph_layout,
|
||||||
|
entity_identifier,
|
||||||
|
&persisted_x,
|
||||||
|
&persisted_y
|
||||||
|
))
|
||||||
|
{
|
||||||
|
node_layout->x =
|
||||||
|
persisted_x;
|
||||||
|
|
||||||
|
node_layout->y =
|
||||||
|
persisted_y;
|
||||||
|
}
|
||||||
|
|
||||||
g_ptr_array_add(
|
g_ptr_array_add(
|
||||||
node_layouts,
|
node_layouts,
|
||||||
node_layout
|
node_layout
|
||||||
|
|
@ -1540,6 +1645,7 @@ static void investigation_graph_view_set_layout_error(
|
||||||
static gboolean investigation_graph_view_build_layout(
|
static gboolean investigation_graph_view_build_layout(
|
||||||
InvestigationGraphView *graph_view,
|
InvestigationGraphView *graph_view,
|
||||||
int width,
|
int width,
|
||||||
|
gboolean apply_persisted_positions,
|
||||||
GError **error
|
GError **error
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -1597,7 +1703,9 @@ static gboolean investigation_graph_view_build_layout(
|
||||||
node_layouts =
|
node_layouts =
|
||||||
investigation_graph_view_create_layout(
|
investigation_graph_view_create_layout(
|
||||||
active_entities,
|
active_entities,
|
||||||
width
|
graph_view->graph_layout,
|
||||||
|
width,
|
||||||
|
apply_persisted_positions
|
||||||
);
|
);
|
||||||
|
|
||||||
if (node_layouts == NULL)
|
if (node_layouts == NULL)
|
||||||
|
|
@ -3146,6 +3254,73 @@ GtkWidget *investigation_graph_view_get_widget(
|
||||||
return graph_view->drawing_area;
|
return graph_view->drawing_area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reconstruit la disposition privée du graphe affiché.
|
||||||
|
*
|
||||||
|
* Le zoom et le déplacement global du canvas sont conservés.
|
||||||
|
*/
|
||||||
|
static void investigation_graph_view_rebuild_current_layout(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
gboolean apply_persisted_positions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
int width =
|
||||||
|
640;
|
||||||
|
|
||||||
|
if (graph_view == NULL ||
|
||||||
|
graph_view->graph_model == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
investigation_graph_view_clear_layout(
|
||||||
|
graph_view
|
||||||
|
);
|
||||||
|
|
||||||
|
if (graph_view->drawing_area != NULL)
|
||||||
|
{
|
||||||
|
width =
|
||||||
|
gtk_widget_get_width(
|
||||||
|
graph_view->drawing_area
|
||||||
|
);
|
||||||
|
|
||||||
|
if (width <= 0)
|
||||||
|
{
|
||||||
|
width =
|
||||||
|
640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!investigation_graph_view_build_layout(
|
||||||
|
graph_view,
|
||||||
|
width,
|
||||||
|
apply_persisted_positions,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
graph_view->layout_error_message =
|
||||||
|
g_strdup(
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Impossible de reconstruire la disposition du graphe."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (graph_view->drawing_area != NULL)
|
||||||
|
{
|
||||||
|
gtk_widget_queue_draw(
|
||||||
|
graph_view->drawing_area
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void investigation_graph_view_set_graph(
|
void investigation_graph_view_set_graph(
|
||||||
InvestigationGraphView *graph_view,
|
InvestigationGraphView *graph_view,
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model
|
||||||
|
|
@ -3195,6 +3370,7 @@ void investigation_graph_view_set_graph(
|
||||||
if (!investigation_graph_view_build_layout(
|
if (!investigation_graph_view_build_layout(
|
||||||
graph_view,
|
graph_view,
|
||||||
width,
|
width,
|
||||||
|
TRUE,
|
||||||
&error
|
&error
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
|
|
@ -3215,6 +3391,25 @@ void investigation_graph_view_set_graph(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void investigation_graph_view_set_layout(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (graph_view == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_view->graph_layout =
|
||||||
|
graph_layout;
|
||||||
|
|
||||||
|
investigation_graph_view_rebuild_current_layout(
|
||||||
|
graph_view,
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void investigation_graph_view_clear(
|
void investigation_graph_view_clear(
|
||||||
InvestigationGraphView *graph_view
|
InvestigationGraphView *graph_view
|
||||||
)
|
)
|
||||||
|
|
@ -3231,6 +3426,9 @@ void investigation_graph_view_clear(
|
||||||
graph_view->graph_model =
|
graph_view->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
graph_view->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
investigation_graph_view_reset_view(
|
investigation_graph_view_reset_view(
|
||||||
graph_view
|
graph_view
|
||||||
);
|
);
|
||||||
|
|
@ -3254,6 +3452,24 @@ void investigation_graph_view_set_selection_callback(
|
||||||
user_data;
|
user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void investigation_graph_view_set_node_moved_callback(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
InvestigationGraphViewNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (graph_view == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_view->node_moved_callback =
|
||||||
|
callback;
|
||||||
|
|
||||||
|
graph_view->node_moved_user_data =
|
||||||
|
user_data;
|
||||||
|
}
|
||||||
|
|
||||||
const EntityRecord *investigation_graph_view_get_selected_entity(
|
const EntityRecord *investigation_graph_view_get_selected_entity(
|
||||||
const InvestigationGraphView *graph_view
|
const InvestigationGraphView *graph_view
|
||||||
)
|
)
|
||||||
|
|
@ -3377,6 +3593,7 @@ void investigation_graph_view_reset_layout(
|
||||||
if (!investigation_graph_view_build_layout(
|
if (!investigation_graph_view_build_layout(
|
||||||
graph_view,
|
graph_view,
|
||||||
width,
|
width,
|
||||||
|
FALSE,
|
||||||
&error
|
&error
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
|
|
@ -3416,6 +3633,12 @@ void investigation_graph_view_free(
|
||||||
graph_view->selection_user_data =
|
graph_view->selection_user_data =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
graph_view->node_moved_callback =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
graph_view->node_moved_user_data =
|
||||||
|
NULL;
|
||||||
|
|
||||||
investigation_graph_view_clear_layout(
|
investigation_graph_view_clear_layout(
|
||||||
graph_view
|
graph_view
|
||||||
);
|
);
|
||||||
|
|
@ -3423,6 +3646,9 @@ void investigation_graph_view_free(
|
||||||
graph_view->graph_model =
|
graph_view->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
graph_view->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
if (graph_view->drawing_area != NULL)
|
if (graph_view->drawing_area != NULL)
|
||||||
{
|
{
|
||||||
gtk_drawing_area_set_draw_func(
|
gtk_drawing_area_set_draw_func(
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,12 @@ struct Workspace
|
||||||
gpointer
|
gpointer
|
||||||
verify_evidence_user_data;
|
verify_evidence_user_data;
|
||||||
|
|
||||||
|
WorkspaceGraphNodeMovedCallback
|
||||||
|
graph_node_moved_callback;
|
||||||
|
|
||||||
|
gpointer
|
||||||
|
graph_node_moved_user_data;
|
||||||
|
|
||||||
GtkWidget *node_name_label;
|
GtkWidget *node_name_label;
|
||||||
GtkWidget *node_path_label;
|
GtkWidget *node_path_label;
|
||||||
GtkWidget *node_type_label;
|
GtkWidget *node_type_label;
|
||||||
|
|
@ -84,6 +90,8 @@ struct Workspace
|
||||||
GtkWidget *node_children_label;
|
GtkWidget *node_children_label;
|
||||||
|
|
||||||
const InvestigationGraphModel *graph_model;
|
const InvestigationGraphModel *graph_model;
|
||||||
|
const InvestigationGraphLayout *graph_layout;
|
||||||
|
|
||||||
WorkspaceGraphState graph_state;
|
WorkspaceGraphState graph_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -254,6 +262,35 @@ static const char *workspace_get_integrity_status_text(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transmet la sélection du graphe au volet de détails.
|
||||||
|
*/
|
||||||
|
static void workspace_on_graph_node_moved(
|
||||||
|
const char *entity_identifier,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Workspace *workspace =
|
||||||
|
user_data;
|
||||||
|
|
||||||
|
if (workspace == NULL ||
|
||||||
|
workspace->graph_node_moved_callback == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
entity_identifier[0] == '\0')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace->graph_node_moved_callback(
|
||||||
|
entity_identifier,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
workspace->graph_node_moved_user_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Transmet la sélection du graphe au volet de détails.
|
* @brief Transmet la sélection du graphe au volet de détails.
|
||||||
*/
|
*/
|
||||||
|
|
@ -990,6 +1027,12 @@ Workspace *workspace_new(void)
|
||||||
workspace
|
workspace
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_view_set_node_moved_callback(
|
||||||
|
workspace->graph_view,
|
||||||
|
workspace_on_graph_node_moved,
|
||||||
|
workspace
|
||||||
|
);
|
||||||
|
|
||||||
entity_details_panel_set_close_callback(
|
entity_details_panel_set_close_callback(
|
||||||
workspace->entity_details_panel,
|
workspace->entity_details_panel,
|
||||||
workspace_on_entity_details_closed,
|
workspace_on_entity_details_closed,
|
||||||
|
|
@ -1033,6 +1076,9 @@ Workspace *workspace_new(void)
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
workspace->graph_state =
|
workspace->graph_state =
|
||||||
WORKSPACE_GRAPH_STATE_EMPTY;
|
WORKSPACE_GRAPH_STATE_EMPTY;
|
||||||
|
|
||||||
|
|
@ -1406,6 +1452,9 @@ void workspace_set_graph_loading(
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
workspace->graph_state =
|
workspace->graph_state =
|
||||||
WORKSPACE_GRAPH_STATE_LOADING;
|
WORKSPACE_GRAPH_STATE_LOADING;
|
||||||
|
|
||||||
|
|
@ -1441,7 +1490,8 @@ void workspace_set_graph_loading(
|
||||||
|
|
||||||
void workspace_set_graph(
|
void workspace_set_graph(
|
||||||
Workspace *workspace,
|
Workspace *workspace,
|
||||||
const InvestigationGraphModel *graph_model
|
const InvestigationGraphModel *graph_model,
|
||||||
|
const InvestigationGraphLayout *graph_layout
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (workspace == NULL)
|
if (workspace == NULL)
|
||||||
|
|
@ -1475,9 +1525,21 @@ void workspace_set_graph(
|
||||||
workspace->entity_details_panel
|
workspace->entity_details_panel
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_view_clear(
|
||||||
|
workspace->graph_view
|
||||||
|
);
|
||||||
|
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
graph_model;
|
graph_model;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
graph_layout;
|
||||||
|
|
||||||
|
investigation_graph_view_set_layout(
|
||||||
|
workspace->graph_view,
|
||||||
|
graph_layout
|
||||||
|
);
|
||||||
|
|
||||||
investigation_graph_view_set_graph(
|
investigation_graph_view_set_graph(
|
||||||
workspace->graph_view,
|
workspace->graph_view,
|
||||||
graph_model
|
graph_model
|
||||||
|
|
@ -1550,6 +1612,9 @@ void workspace_set_graph_error(
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
workspace->graph_state =
|
workspace->graph_state =
|
||||||
WORKSPACE_GRAPH_STATE_ERROR;
|
WORKSPACE_GRAPH_STATE_ERROR;
|
||||||
|
|
||||||
|
|
@ -1619,6 +1684,9 @@ void workspace_clear_graph(
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
workspace->graph_state =
|
workspace->graph_state =
|
||||||
WORKSPACE_GRAPH_STATE_EMPTY;
|
WORKSPACE_GRAPH_STATE_EMPTY;
|
||||||
|
|
||||||
|
|
@ -1670,6 +1738,24 @@ void workspace_set_verify_evidence_callback(
|
||||||
user_data;
|
user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void workspace_set_graph_node_moved_callback(
|
||||||
|
Workspace *workspace,
|
||||||
|
WorkspaceGraphNodeMovedCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (workspace == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace->graph_node_moved_callback =
|
||||||
|
callback;
|
||||||
|
|
||||||
|
workspace->graph_node_moved_user_data =
|
||||||
|
user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void workspace_free(Workspace *workspace)
|
void workspace_free(Workspace *workspace)
|
||||||
{
|
{
|
||||||
if (workspace == NULL)
|
if (workspace == NULL)
|
||||||
|
|
@ -1683,6 +1769,12 @@ void workspace_free(Workspace *workspace)
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
investigation_graph_view_set_node_moved_callback(
|
||||||
|
workspace->graph_view,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
entity_details_panel_set_close_callback(
|
entity_details_panel_set_close_callback(
|
||||||
workspace->entity_details_panel,
|
workspace->entity_details_panel,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -1714,12 +1806,21 @@ void workspace_free(Workspace *workspace)
|
||||||
workspace->graph_model =
|
workspace->graph_model =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_layout =
|
||||||
|
NULL;
|
||||||
|
|
||||||
workspace->verify_evidence_callback =
|
workspace->verify_evidence_callback =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
workspace->verify_evidence_user_data =
|
workspace->verify_evidence_user_data =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_node_moved_callback =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
workspace->graph_node_moved_user_data =
|
||||||
|
NULL;
|
||||||
|
|
||||||
g_clear_pointer(
|
g_clear_pointer(
|
||||||
&workspace->selected_evidence_identifier,
|
&workspace->selected_evidence_identifier,
|
||||||
g_free
|
g_free
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue