feat(graph): add movable relation nodes
This commit is contained in:
parent
b1d0ec2a1e
commit
be138946be
16 changed files with 1370 additions and 325 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
# Build
|
# Build
|
||||||
labfy-investigation
|
labfy-investigation
|
||||||
*.o
|
*.o
|
||||||
|
*.d
|
||||||
|
|
||||||
# Executables de tests
|
# Executables de tests
|
||||||
/tests/test_*
|
/tests/test_*
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
CC = gcc
|
CC = gcc
|
||||||
|
|
||||||
PKG_CONFIG = pkg-config
|
PKG_CONFIG = pkg-config
|
||||||
|
|
@ -11,6 +10,8 @@ CFLAGS = -std=c17 \
|
||||||
-Werror \
|
-Werror \
|
||||||
-g \
|
-g \
|
||||||
-Iinclude \
|
-Iinclude \
|
||||||
|
-MMD \
|
||||||
|
-MP \
|
||||||
$(shell $(PKG_CONFIG) --cflags gtk4 sqlite3)
|
$(shell $(PKG_CONFIG) --cflags gtk4 sqlite3)
|
||||||
|
|
||||||
LDFLAGS = $(shell $(PKG_CONFIG) --libs gtk4 sqlite3)
|
LDFLAGS = $(shell $(PKG_CONFIG) --libs gtk4 sqlite3)
|
||||||
|
|
@ -62,6 +63,8 @@ SRC := $(shell find src -name "*.c")
|
||||||
|
|
||||||
OBJ := $(SRC:.c=.o)
|
OBJ := $(SRC:.c=.o)
|
||||||
|
|
||||||
|
DEP := $(OBJ:.o=.d)
|
||||||
|
|
||||||
TARGET = labfy-investigation
|
TARGET = labfy-investigation
|
||||||
|
|
||||||
TEST_NODE = tests/test_investigation_node
|
TEST_NODE = tests/test_investigation_node
|
||||||
|
|
@ -649,7 +652,7 @@ run: $(TARGET)
|
||||||
./$(TARGET)
|
./$(TARGET)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJ) $(TARGET) \
|
rm -f $(OBJ) $(DEP) $(TARGET) \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
$(TEST_TREE_BUILDER) \
|
$(TEST_TREE_BUILDER) \
|
||||||
|
|
@ -695,4 +698,6 @@ clean:
|
||||||
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
|
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
|
||||||
$(TEST_GRAPH_NODE_POSITION_DAO)
|
$(TEST_GRAPH_NODE_POSITION_DAO)
|
||||||
|
|
||||||
|
-include $(DEP)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.PHONY: clean run test
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -23,16 +23,6 @@ Le projet vise à fournir un environnement local, modulaire et traçable pour or
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Labfy Investigation est un poste de travail libre d’investigation numérique et d’OSINT, développé en **C17** avec **GTK4**.
|
|
||||||
|
|
||||||
Le projet vise à fournir un environnement local, modulaire et traçable pour organiser une enquête, préserver les preuves originales, analyser des données, corréler des entités et produire des rapports exploitables.
|
|
||||||
|
|
||||||
> **État du projet : développement actif**
|
|
||||||
>
|
|
||||||
> Le logiciel n’est pas encore prêt pour un usage opérationnel en production. Les formats internes, l’interface et les mécanismes d’intégration peuvent encore évoluer.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Objectifs
|
## Objectifs
|
||||||
|
|
||||||
Labfy Investigation doit permettre de :
|
Labfy Investigation doit permettre de :
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,45 @@ CREATE TABLE IF NOT EXISTS graph_node_positions
|
||||||
length(trim(updated_at)) > 0
|
length(trim(updated_at)) > 0
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disposition générique du graphe.
|
||||||
|
*
|
||||||
|
* Contrairement à graph_node_positions, cette table accepte aussi les UUID
|
||||||
|
* des relations. L'ancienne table reste présente pour assurer la compatibilité
|
||||||
|
* avec les enquêtes créées avant l'introduction des nœuds de relation.
|
||||||
|
*/
|
||||||
|
CREATE TABLE IF NOT EXISTS graph_layout_positions
|
||||||
|
(
|
||||||
|
node_id TEXT PRIMARY KEY,
|
||||||
|
x REAL NOT NULL,
|
||||||
|
y REAL NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
|
||||||
|
CHECK (length(trim(node_id)) > 0),
|
||||||
|
CHECK (length(updated_at) = 20)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Migration idempotente des positions d'entités déjà enregistrées. */
|
||||||
|
INSERT OR IGNORE INTO graph_layout_positions(node_id, x, y, updated_at)
|
||||||
|
SELECT entity_id, x, y, updated_at
|
||||||
|
FROM graph_node_positions;
|
||||||
|
|
||||||
|
/* Évite qu'une réinitialisation future ne réimporte des coordonnées obsolètes. */
|
||||||
|
DELETE FROM graph_node_positions;
|
||||||
|
|
||||||
|
/* Une clé étrangère polymorphe n'existe pas dans SQLite : ces triggers
|
||||||
|
* suppriment donc les positions devenues orphelines. */
|
||||||
|
CREATE TRIGGER IF NOT EXISTS graph_layout_positions_delete_entity
|
||||||
|
AFTER DELETE ON entites
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM graph_layout_positions WHERE node_id = OLD.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS graph_layout_positions_delete_relation
|
||||||
|
AFTER DELETE ON relations
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM graph_layout_positions WHERE node_id = OLD.id;
|
||||||
|
END;
|
||||||
|
|
|
||||||
|
|
@ -2181,6 +2181,30 @@ des fichiers ou des notes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 11.5 Disposition générique du graphe
|
||||||
|
|
||||||
|
La disposition visuelle du graphe est un état de présentation et non une
|
||||||
|
donnée métier. Elle est stockée dans `graph_layout_positions`, séparément des
|
||||||
|
tables `entites` et `relations`.
|
||||||
|
|
||||||
|
Chaque ligne associe un UUID de nœud à des coordonnées logiques et à une date
|
||||||
|
UTC de mise à jour. Un nœud peut représenter une entité ou une relation. SQLite
|
||||||
|
ne proposant pas de clé étrangère polymorphe, l'intégrité de ce stockage est
|
||||||
|
assurée par deux triggers qui suppriment la position correspondante lors de la
|
||||||
|
suppression physique d'une entité ou d'une relation.
|
||||||
|
|
||||||
|
La table historique `graph_node_positions` ne référençait que `entites(id)`.
|
||||||
|
À l'ouverture d'une enquête, ses lignes sont copiées de manière idempotente
|
||||||
|
vers `graph_layout_positions`, puis retirées de la table historique afin
|
||||||
|
qu'une réinitialisation volontaire de la disposition ne puisse pas restaurer
|
||||||
|
des coordonnées obsolètes.
|
||||||
|
|
||||||
|
Cette stratégie préserve les positions existantes tout en permettant aux
|
||||||
|
nœuds de relation de suivre exactement le même cycle de chargement,
|
||||||
|
d'enregistrement et de réinitialisation que les nœuds d'entité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# 12. Conclusion
|
# 12. Conclusion
|
||||||
|
|
||||||
La base de données de Labfy Investigation constitue bien davantage qu'un simple
|
La base de données de Labfy Investigation constitue bien davantage qu'un simple
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,12 @@ GPtrArray *graph_node_position_dao_list_all(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Insère ou met à jour la position d'une entité.
|
* @brief Insère ou met à jour la position d'un nœud.
|
||||||
*
|
*
|
||||||
* La date updated_at est générée en UTC par le DAO.
|
* La date updated_at est générée en UTC par le DAO.
|
||||||
*
|
*
|
||||||
* @param position_dao DAO valide.
|
* @param position_dao DAO valide.
|
||||||
* @param entity_identifier UUID de l'entité.
|
* @param node_identifier UUID du nœud (entité ou relation).
|
||||||
* @param x Coordonnée horizontale logique.
|
* @param x Coordonnée horizontale logique.
|
||||||
* @param y Coordonnée verticale logique.
|
* @param y Coordonnée verticale logique.
|
||||||
* @param error Adresse recevant une éventuelle erreur.
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
|
@ -105,26 +105,26 @@ GPtrArray *graph_node_position_dao_list_all(
|
||||||
*/
|
*/
|
||||||
gboolean graph_node_position_dao_upsert(
|
gboolean graph_node_position_dao_upsert(
|
||||||
GraphNodePositionDao *position_dao,
|
GraphNodePositionDao *position_dao,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
GError **error
|
GError **error
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Supprime la position persistée d'une entité.
|
* @brief Supprime la position persistée d'un nœud.
|
||||||
*
|
*
|
||||||
* L'absence de position n'est pas une erreur.
|
* L'absence de position n'est pas une erreur.
|
||||||
*
|
*
|
||||||
* @param position_dao DAO valide.
|
* @param position_dao DAO valide.
|
||||||
* @param entity_identifier UUID de l'entité.
|
* @param node_identifier UUID du nœud (entité ou relation).
|
||||||
* @param error Adresse recevant une éventuelle erreur.
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
*
|
*
|
||||||
* @return TRUE si la requête réussit, sinon FALSE.
|
* @return TRUE si la requête réussit, sinon FALSE.
|
||||||
*/
|
*/
|
||||||
gboolean graph_node_position_dao_delete(
|
gboolean graph_node_position_dao_delete(
|
||||||
GraphNodePositionDao *position_dao,
|
GraphNodePositionDao *position_dao,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
GError **error
|
GError **error
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ GQuark graph_node_position_error_quark(void);
|
||||||
*
|
*
|
||||||
* Toutes les chaînes sont copiées.
|
* Toutes les chaînes sont copiées.
|
||||||
*
|
*
|
||||||
* entity_identifier doit être un UUID valide.
|
* node_identifier doit être un UUID valide.
|
||||||
* x et y doivent être des nombres finis.
|
* x et y doivent être des nombres finis.
|
||||||
* updated_at doit respecter le format UTC YYYY-MM-DDTHH:MM:SSZ.
|
* updated_at doit respecter le format UTC YYYY-MM-DDTHH:MM:SSZ.
|
||||||
*
|
*
|
||||||
* @param entity_identifier UUID de l'entité.
|
* @param node_identifier UUID du nœud (entité ou relation).
|
||||||
* @param x Coordonnée horizontale logique.
|
* @param x Coordonnée horizontale logique.
|
||||||
* @param y Coordonnée verticale logique.
|
* @param y Coordonnée verticale logique.
|
||||||
* @param updated_at Date UTC de dernière modification.
|
* @param updated_at Date UTC de dernière modification.
|
||||||
|
|
@ -55,7 +55,7 @@ GQuark graph_node_position_error_quark(void);
|
||||||
* @return Nouvelle position, ou NULL lorsque les données sont invalides.
|
* @return Nouvelle position, ou NULL lorsque les données sont invalides.
|
||||||
*/
|
*/
|
||||||
GraphNodePosition *graph_node_position_new(
|
GraphNodePosition *graph_node_position_new(
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
const char *updated_at,
|
const char *updated_at,
|
||||||
|
|
@ -74,7 +74,7 @@ void graph_node_position_free(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retourne l'UUID de l'entité.
|
* @brief Retourne l'UUID du nœud (entité ou relation).
|
||||||
*
|
*
|
||||||
* La chaîne retournée appartient au modèle.
|
* La chaîne retournée appartient au modèle.
|
||||||
*
|
*
|
||||||
|
|
@ -82,7 +82,7 @@ void graph_node_position_free(
|
||||||
*
|
*
|
||||||
* @return UUID emprunté, ou NULL.
|
* @return UUID emprunté, ou NULL.
|
||||||
*/
|
*/
|
||||||
const char *graph_node_position_get_entity_identifier(
|
const char *graph_node_position_get_node_identifier(
|
||||||
const GraphNodePosition *position
|
const GraphNodePosition *position
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,16 @@ typedef void (*InvestigationGraphViewSelectionCallback)(
|
||||||
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
* @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.
|
* Les coordonnées sont exprimées dans l'espace logique du graphe.
|
||||||
* entity_identifier est emprunté au modèle et reste valable uniquement
|
* node_identifier est emprunté au modèle et reste valable uniquement
|
||||||
* pendant l'appel.
|
* pendant l'appel.
|
||||||
*
|
*
|
||||||
* @param entity_identifier UUID de l'entité déplacée.
|
* @param node_identifier UUID du nœud déplacé (entité ou relation).
|
||||||
* @param x Coordonnée horizontale logique du coin supérieur gauche.
|
* @param x Coordonnée horizontale logique du coin supérieur gauche.
|
||||||
* @param y Coordonnée verticale 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.
|
* @param user_data Données empruntées fournies par l'appelant.
|
||||||
*/
|
*/
|
||||||
typedef void (*InvestigationGraphViewNodeMovedCallback)(
|
typedef void (*InvestigationGraphViewNodeMovedCallback)(
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
#include "widgets/evidence_category_model.h"
|
#include "widgets/evidence_category_model.h"
|
||||||
#include "core/evidence_integrity_task.h"
|
#include "core/evidence_integrity_task.h"
|
||||||
#include "core/evidence_integrity_verifier.h"
|
#include "core/evidence_integrity_verifier.h"
|
||||||
|
#include "core/relation_service.h"
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
@ -3341,6 +3342,21 @@ static void application_on_create_relation_completed(
|
||||||
Application *application =
|
Application *application =
|
||||||
user_data;
|
user_data;
|
||||||
|
|
||||||
|
const InvestigationProject *project =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
Database *database =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
RelationService *relation_service =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GDateTime *current_date_time =
|
||||||
|
NULL;
|
||||||
|
|
||||||
const char *source_identifier =
|
const char *source_identifier =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
|
@ -3350,9 +3366,21 @@ static void application_on_create_relation_completed(
|
||||||
const char *relation_type =
|
const char *relation_type =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *status_message =
|
const char *database_path =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
char *relation_identifier =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *timestamp =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean relation_created =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
if (application == NULL ||
|
if (application == NULL ||
|
||||||
application->main_window == NULL)
|
application->main_window == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -3373,6 +3401,17 @@ static void application_on_create_relation_completed(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (application->session == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ajout de relation impossible",
|
||||||
|
"Aucune enquête n'est actuellement ouverte."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
source_identifier =
|
source_identifier =
|
||||||
create_relation_dialog_result_get_source_identifier(
|
create_relation_dialog_result_get_source_identifier(
|
||||||
result
|
result
|
||||||
|
|
@ -3388,41 +3427,225 @@ static void application_on_create_relation_completed(
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
|
|
||||||
g_message(
|
database =
|
||||||
"Relation préparée : %s -> %s (%s), confiance %d %%.",
|
investigation_session_get_database(
|
||||||
source_identifier != NULL
|
application->session
|
||||||
? source_identifier
|
|
||||||
: "(source absente)",
|
|
||||||
target_identifier != NULL
|
|
||||||
? target_identifier
|
|
||||||
: "(cible absente)",
|
|
||||||
relation_type != NULL
|
|
||||||
? relation_type
|
|
||||||
: "(type absent)",
|
|
||||||
create_relation_dialog_result_get_confidence(
|
|
||||||
result
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
status_message =
|
|
||||||
g_strdup_printf(
|
|
||||||
"Relation préparée : %s. "
|
|
||||||
"L'enregistrement SQLite sera branché à l'étape suivante.",
|
|
||||||
relation_type != NULL &&
|
|
||||||
relation_type[0] != '\0'
|
|
||||||
? relation_type
|
|
||||||
: "(type inconnu)"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
project =
|
||||||
|
investigation_session_get_project(
|
||||||
|
application->session
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database == NULL ||
|
||||||
|
project == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ajout de relation impossible",
|
||||||
|
"La session d'enquête est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_path =
|
||||||
|
investigation_project_get_database_path(
|
||||||
|
project
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database_path == NULL ||
|
||||||
|
database_path[0] == '\0')
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ajout de relation impossible",
|
||||||
|
"Le chemin de la base SQLite est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_identifier =
|
||||||
|
g_uuid_string_random();
|
||||||
|
|
||||||
|
current_date_time =
|
||||||
|
g_date_time_new_now_utc();
|
||||||
|
|
||||||
|
if (current_date_time != NULL)
|
||||||
|
{
|
||||||
|
timestamp =
|
||||||
|
g_date_time_format(
|
||||||
|
current_date_time,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relation_identifier == NULL ||
|
||||||
|
timestamp == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ajout de relation impossible",
|
||||||
|
"Impossible de préparer l'identifiant ou la date "
|
||||||
|
"de la nouvelle relation."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
relation_record_new(
|
||||||
|
relation_identifier,
|
||||||
|
source_identifier,
|
||||||
|
target_identifier,
|
||||||
|
relation_type,
|
||||||
|
create_relation_dialog_result_get_label(
|
||||||
|
result
|
||||||
|
),
|
||||||
|
create_relation_dialog_result_get_justification(
|
||||||
|
result
|
||||||
|
),
|
||||||
|
create_relation_dialog_result_get_confidence(
|
||||||
|
result
|
||||||
|
),
|
||||||
|
timestamp,
|
||||||
|
timestamp,
|
||||||
|
RELATION_STATUS_ACTIVE,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_record == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Relation invalide",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Les informations de la relation sont invalides."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_service =
|
||||||
|
relation_service_new(
|
||||||
|
database,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_service == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ajout de relation impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Impossible de préparer le service des relations."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Aucune preuve n'est associée pendant ce premier parcours.
|
||||||
|
* Le service accepte donc un tableau NULL.
|
||||||
|
*/
|
||||||
|
if (!relation_service_create(
|
||||||
|
relation_service,
|
||||||
|
relation_record,
|
||||||
|
NULL,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d'enregistrer la relation '%s' : %s",
|
||||||
|
relation_identifier,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enregistrement de la relation impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "La relation n'a pas pu être enregistrée."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_created =
|
||||||
|
TRUE;
|
||||||
|
|
||||||
|
g_message(
|
||||||
|
"Relation enregistrée : %s -> %s (%s), identifiant %s.",
|
||||||
|
source_identifier,
|
||||||
|
target_identifier,
|
||||||
|
relation_type,
|
||||||
|
relation_identifier
|
||||||
|
);
|
||||||
|
|
||||||
main_window_set_status(
|
main_window_set_status(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
status_message != NULL
|
"Relation enregistrée. Actualisation du graphe…"
|
||||||
? status_message
|
);
|
||||||
: "Relation préparée."
|
|
||||||
|
/*
|
||||||
|
* Le graphe n'est rechargé qu'après le COMMIT réussi.
|
||||||
|
* En cas d'échec SQLite, l'affichage courant reste intact.
|
||||||
|
*/
|
||||||
|
application_start_graph_loading(
|
||||||
|
application,
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
if (!relation_created &&
|
||||||
|
application->main_window != NULL)
|
||||||
|
{
|
||||||
|
main_window_set_status(
|
||||||
|
application->main_window,
|
||||||
|
"La relation n'a pas été enregistrée."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_service_free(
|
||||||
|
relation_service
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
if (current_date_time != NULL)
|
||||||
|
{
|
||||||
|
g_date_time_unref(
|
||||||
|
current_date_time
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
status_message
|
relation_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
create_relation_dialog_result_free(
|
create_relation_dialog_result_free(
|
||||||
|
|
|
||||||
|
|
@ -946,7 +946,7 @@ static gboolean investigation_graph_load_task_worker(
|
||||||
|
|
||||||
if (!investigation_graph_layout_set_position(
|
if (!investigation_graph_layout_set_position(
|
||||||
graph_layout,
|
graph_layout,
|
||||||
graph_node_position_get_entity_identifier(
|
graph_node_position_get_node_identifier(
|
||||||
position
|
position
|
||||||
),
|
),
|
||||||
graph_node_position_get_x(
|
graph_node_position_get_x(
|
||||||
|
|
|
||||||
|
|
@ -27,20 +27,20 @@ struct GraphNodePositionDao
|
||||||
*/
|
*/
|
||||||
static const char *const graph_node_position_dao_list_all_sql =
|
static const char *const graph_node_position_dao_list_all_sql =
|
||||||
"SELECT "
|
"SELECT "
|
||||||
" entity_id,"
|
" node_id,"
|
||||||
" x,"
|
" x,"
|
||||||
" y,"
|
" y,"
|
||||||
" updated_at "
|
" updated_at "
|
||||||
"FROM graph_node_positions "
|
"FROM graph_layout_positions "
|
||||||
"ORDER BY entity_id ASC;";
|
"ORDER BY node_id ASC;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Requête d'insertion ou de mise à jour d'une position.
|
* @brief Requête d'insertion ou de mise à jour d'une position.
|
||||||
*/
|
*/
|
||||||
static const char *const graph_node_position_dao_upsert_sql =
|
static const char *const graph_node_position_dao_upsert_sql =
|
||||||
"INSERT INTO graph_node_positions"
|
"INSERT INTO graph_layout_positions"
|
||||||
"("
|
"("
|
||||||
" entity_id,"
|
" node_id,"
|
||||||
" x,"
|
" x,"
|
||||||
" y,"
|
" y,"
|
||||||
" updated_at"
|
" updated_at"
|
||||||
|
|
@ -52,7 +52,7 @@ static const char *const graph_node_position_dao_upsert_sql =
|
||||||
" ?,"
|
" ?,"
|
||||||
" ?"
|
" ?"
|
||||||
")"
|
")"
|
||||||
"ON CONFLICT(entity_id)"
|
"ON CONFLICT(node_id)"
|
||||||
"DO UPDATE SET "
|
"DO UPDATE SET "
|
||||||
" x = excluded.x,"
|
" x = excluded.x,"
|
||||||
" y = excluded.y,"
|
" y = excluded.y,"
|
||||||
|
|
@ -62,14 +62,14 @@ static const char *const graph_node_position_dao_upsert_sql =
|
||||||
* @brief Requête de suppression d'une position.
|
* @brief Requête de suppression d'une position.
|
||||||
*/
|
*/
|
||||||
static const char *const graph_node_position_dao_delete_sql =
|
static const char *const graph_node_position_dao_delete_sql =
|
||||||
"DELETE FROM graph_node_positions "
|
"DELETE FROM graph_layout_positions "
|
||||||
"WHERE entity_id = ?;";
|
"WHERE node_id = ?;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Requête de suppression de toutes les positions.
|
* @brief Requête de suppression de toutes les positions.
|
||||||
*/
|
*/
|
||||||
static const char *const graph_node_position_dao_delete_all_sql =
|
static const char *const graph_node_position_dao_delete_all_sql =
|
||||||
"DELETE FROM graph_node_positions;";
|
"DELETE FROM graph_layout_positions;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enregistre une erreur littérale.
|
* @brief Enregistre une erreur littérale.
|
||||||
|
|
@ -202,7 +202,7 @@ graph_node_position_dao_read_current_record(
|
||||||
GraphNodePosition *position =
|
GraphNodePosition *position =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *entity_identifier =
|
char *node_identifier =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *updated_at =
|
char *updated_at =
|
||||||
|
|
@ -233,7 +233,7 @@ graph_node_position_dao_read_current_record(
|
||||||
if (!database_statement_column_text(
|
if (!database_statement_column_text(
|
||||||
statement,
|
statement,
|
||||||
0,
|
0,
|
||||||
&entity_identifier
|
&node_identifier
|
||||||
) ||
|
) ||
|
||||||
!database_statement_column_double(
|
!database_statement_column_double(
|
||||||
statement,
|
statement,
|
||||||
|
|
@ -262,7 +262,7 @@ graph_node_position_dao_read_current_record(
|
||||||
|
|
||||||
position =
|
position =
|
||||||
graph_node_position_new(
|
graph_node_position_new(
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
updated_at,
|
updated_at,
|
||||||
|
|
@ -302,7 +302,7 @@ cleanup:
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
|
|
@ -576,7 +576,7 @@ error:
|
||||||
|
|
||||||
gboolean graph_node_position_dao_upsert(
|
gboolean graph_node_position_dao_upsert(
|
||||||
GraphNodePositionDao *position_dao,
|
GraphNodePositionDao *position_dao,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
GError **error
|
GError **error
|
||||||
|
|
@ -598,9 +598,9 @@ gboolean graph_node_position_dao_upsert(
|
||||||
|
|
||||||
if (position_dao == NULL ||
|
if (position_dao == NULL ||
|
||||||
position_dao->database == NULL ||
|
position_dao->database == NULL ||
|
||||||
entity_identifier == NULL ||
|
node_identifier == NULL ||
|
||||||
!g_uuid_string_is_valid(
|
!g_uuid_string_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
) ||
|
) ||
|
||||||
!isfinite(x) ||
|
!isfinite(x) ||
|
||||||
!isfinite(y))
|
!isfinite(y))
|
||||||
|
|
@ -649,7 +649,7 @@ gboolean graph_node_position_dao_upsert(
|
||||||
if (!database_statement_bind_text(
|
if (!database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
1,
|
1,
|
||||||
entity_identifier
|
node_identifier
|
||||||
) ||
|
) ||
|
||||||
!database_statement_bind_double(
|
!database_statement_bind_double(
|
||||||
statement,
|
statement,
|
||||||
|
|
@ -707,7 +707,7 @@ cleanup:
|
||||||
|
|
||||||
gboolean graph_node_position_dao_delete(
|
gboolean graph_node_position_dao_delete(
|
||||||
GraphNodePositionDao *position_dao,
|
GraphNodePositionDao *position_dao,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
GError **error
|
GError **error
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -724,15 +724,15 @@ gboolean graph_node_position_dao_delete(
|
||||||
|
|
||||||
if (position_dao == NULL ||
|
if (position_dao == NULL ||
|
||||||
position_dao->database == NULL ||
|
position_dao->database == NULL ||
|
||||||
entity_identifier == NULL ||
|
node_identifier == NULL ||
|
||||||
!g_uuid_string_is_valid(
|
!g_uuid_string_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
graph_node_position_dao_set_error_literal(
|
graph_node_position_dao_set_error_literal(
|
||||||
error,
|
error,
|
||||||
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
"L'identifiant de l'entité est invalide."
|
"L'identifiant du nœud est invalide."
|
||||||
);
|
);
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
@ -759,14 +759,14 @@ gboolean graph_node_position_dao_delete(
|
||||||
if (!database_statement_bind_text(
|
if (!database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
1,
|
1,
|
||||||
entity_identifier
|
node_identifier
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
graph_node_position_dao_set_database_error(
|
graph_node_position_dao_set_database_error(
|
||||||
position_dao,
|
position_dao,
|
||||||
error,
|
error,
|
||||||
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
GRAPH_NODE_POSITION_DAO_ERROR_BIND,
|
||||||
"Impossible de lier l'identifiant de l'entité"
|
"Impossible de lier l'identifiant du nœud"
|
||||||
);
|
);
|
||||||
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
struct GraphNodePosition
|
struct GraphNodePosition
|
||||||
{
|
{
|
||||||
char *entity_identifier;
|
char *node_identifier;
|
||||||
|
|
||||||
double x;
|
double x;
|
||||||
double y;
|
double y;
|
||||||
|
|
@ -154,7 +154,7 @@ GQuark graph_node_position_error_quark(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphNodePosition *graph_node_position_new(
|
GraphNodePosition *graph_node_position_new(
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
const char *updated_at,
|
const char *updated_at,
|
||||||
|
|
@ -164,7 +164,7 @@ GraphNodePosition *graph_node_position_new(
|
||||||
GraphNodePosition *position =
|
GraphNodePosition *position =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *entity_identifier_copy =
|
char *node_identifier_copy =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *updated_at_copy =
|
char *updated_at_copy =
|
||||||
|
|
@ -175,22 +175,22 @@ GraphNodePosition *graph_node_position_new(
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_identifier_copy =
|
node_identifier_copy =
|
||||||
graph_node_position_duplicate_trimmed(
|
graph_node_position_duplicate_trimmed(
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entity_identifier_copy == NULL ||
|
if (node_identifier_copy == NULL ||
|
||||||
entity_identifier_copy[0] == '\0' ||
|
node_identifier_copy[0] == '\0' ||
|
||||||
!g_uuid_string_is_valid(
|
!g_uuid_string_is_valid(
|
||||||
entity_identifier_copy
|
node_identifier_copy
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
g_set_error_literal(
|
g_set_error_literal(
|
||||||
error,
|
error,
|
||||||
GRAPH_NODE_POSITION_ERROR,
|
GRAPH_NODE_POSITION_ERROR,
|
||||||
GRAPH_NODE_POSITION_ERROR_INVALID_IDENTIFIER,
|
GRAPH_NODE_POSITION_ERROR_INVALID_IDENTIFIER,
|
||||||
"L'identifiant de l'entité n'est pas un UUID valide."
|
"L'identifiant du nœud n'est pas un UUID valide."
|
||||||
);
|
);
|
||||||
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
@ -246,8 +246,8 @@ GraphNodePosition *graph_node_position_new(
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
position->entity_identifier =
|
position->node_identifier =
|
||||||
entity_identifier_copy;
|
node_identifier_copy;
|
||||||
|
|
||||||
position->x =
|
position->x =
|
||||||
x;
|
x;
|
||||||
|
|
@ -258,7 +258,7 @@ GraphNodePosition *graph_node_position_new(
|
||||||
position->updated_at =
|
position->updated_at =
|
||||||
updated_at_copy;
|
updated_at_copy;
|
||||||
|
|
||||||
entity_identifier_copy =
|
node_identifier_copy =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
updated_at_copy =
|
updated_at_copy =
|
||||||
|
|
@ -271,7 +271,7 @@ cleanup:
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
entity_identifier_copy
|
node_identifier_copy
|
||||||
);
|
);
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
|
|
@ -291,7 +291,7 @@ void graph_node_position_free(
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
position->entity_identifier
|
position->node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
|
|
@ -299,12 +299,12 @@ void graph_node_position_free(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *graph_node_position_get_entity_identifier(
|
const char *graph_node_position_get_node_identifier(
|
||||||
const GraphNodePosition *position
|
const GraphNodePosition *position
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return position != NULL
|
return position != NULL
|
||||||
? position->entity_identifier
|
? position->node_identifier
|
||||||
: NULL;
|
: NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Coordonnées privées associées à une entité.
|
* @brief Coordonnées privées associées à un nœud.
|
||||||
*/
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +22,7 @@ typedef struct
|
||||||
*/
|
*/
|
||||||
struct InvestigationGraphLayout
|
struct InvestigationGraphLayout
|
||||||
{
|
{
|
||||||
GHashTable *positions_by_entity_identifier;
|
GHashTable *positions_by_node_identifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,16 +48,16 @@ static void investigation_graph_layout_set_error_literal(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vérifie un identifiant d'entité.
|
* @brief Vérifie un identifiant de nœud.
|
||||||
*/
|
*/
|
||||||
static gboolean investigation_graph_layout_identifier_is_valid(
|
static gboolean investigation_graph_layout_identifier_is_valid(
|
||||||
const char *entity_identifier
|
const char *node_identifier
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return entity_identifier != NULL &&
|
return node_identifier != NULL &&
|
||||||
entity_identifier[0] != '\0' &&
|
node_identifier[0] != '\0' &&
|
||||||
g_uuid_string_is_valid(
|
g_uuid_string_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout->positions_by_entity_identifier =
|
layout->positions_by_node_identifier =
|
||||||
g_hash_table_new_full(
|
g_hash_table_new_full(
|
||||||
g_str_hash,
|
g_str_hash,
|
||||||
g_str_equal,
|
g_str_equal,
|
||||||
|
|
@ -92,7 +92,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
||||||
g_free
|
g_free
|
||||||
);
|
);
|
||||||
|
|
||||||
if (layout->positions_by_entity_identifier == NULL)
|
if (layout->positions_by_node_identifier == NULL)
|
||||||
{
|
{
|
||||||
investigation_graph_layout_free(
|
investigation_graph_layout_free(
|
||||||
layout
|
layout
|
||||||
|
|
@ -106,7 +106,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
|
||||||
|
|
||||||
gboolean investigation_graph_layout_set_position(
|
gboolean investigation_graph_layout_set_position(
|
||||||
InvestigationGraphLayout *layout,
|
InvestigationGraphLayout *layout,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
GError **error
|
GError **error
|
||||||
|
|
@ -115,7 +115,7 @@ gboolean investigation_graph_layout_set_position(
|
||||||
InvestigationGraphLayoutPosition *position =
|
InvestigationGraphLayoutPosition *position =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
char *entity_identifier_copy =
|
char *node_identifier_copy =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
g_return_val_if_fail(
|
g_return_val_if_fail(
|
||||||
|
|
@ -124,7 +124,7 @@ gboolean investigation_graph_layout_set_position(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (layout == NULL ||
|
if (layout == NULL ||
|
||||||
layout->positions_by_entity_identifier == NULL)
|
layout->positions_by_node_identifier == NULL)
|
||||||
{
|
{
|
||||||
investigation_graph_layout_set_error_literal(
|
investigation_graph_layout_set_error_literal(
|
||||||
error,
|
error,
|
||||||
|
|
@ -136,13 +136,13 @@ gboolean investigation_graph_layout_set_position(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!investigation_graph_layout_identifier_is_valid(
|
if (!investigation_graph_layout_identifier_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
investigation_graph_layout_set_error_literal(
|
investigation_graph_layout_set_error_literal(
|
||||||
error,
|
error,
|
||||||
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_IDENTIFIER,
|
INVESTIGATION_GRAPH_LAYOUT_ERROR_INVALID_IDENTIFIER,
|
||||||
"L'identifiant de l'entité n'est pas un UUID valide."
|
"L'identifiant du nœud n'est pas un UUID valide."
|
||||||
);
|
);
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
@ -160,9 +160,9 @@ gboolean investigation_graph_layout_set_position(
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
entity_identifier_copy =
|
node_identifier_copy =
|
||||||
g_strdup(
|
g_strdup(
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
position =
|
position =
|
||||||
|
|
@ -171,7 +171,7 @@ gboolean investigation_graph_layout_set_position(
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entity_identifier_copy == NULL ||
|
if (node_identifier_copy == NULL ||
|
||||||
position == NULL)
|
position == NULL)
|
||||||
{
|
{
|
||||||
g_free(
|
g_free(
|
||||||
|
|
@ -179,7 +179,7 @@ gboolean investigation_graph_layout_set_position(
|
||||||
);
|
);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
entity_identifier_copy
|
node_identifier_copy
|
||||||
);
|
);
|
||||||
|
|
||||||
investigation_graph_layout_set_error_literal(
|
investigation_graph_layout_set_error_literal(
|
||||||
|
|
@ -198,8 +198,8 @@ gboolean investigation_graph_layout_set_position(
|
||||||
y;
|
y;
|
||||||
|
|
||||||
g_hash_table_replace(
|
g_hash_table_replace(
|
||||||
layout->positions_by_entity_identifier,
|
layout->positions_by_node_identifier,
|
||||||
entity_identifier_copy,
|
node_identifier_copy,
|
||||||
position
|
position
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -208,7 +208,7 @@ gboolean investigation_graph_layout_set_position(
|
||||||
|
|
||||||
gboolean investigation_graph_layout_get_position(
|
gboolean investigation_graph_layout_get_position(
|
||||||
const InvestigationGraphLayout *layout,
|
const InvestigationGraphLayout *layout,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
double *x,
|
double *x,
|
||||||
double *y
|
double *y
|
||||||
)
|
)
|
||||||
|
|
@ -217,9 +217,9 @@ gboolean investigation_graph_layout_get_position(
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
if (layout == NULL ||
|
if (layout == NULL ||
|
||||||
layout->positions_by_entity_identifier == NULL ||
|
layout->positions_by_node_identifier == NULL ||
|
||||||
!investigation_graph_layout_identifier_is_valid(
|
!investigation_graph_layout_identifier_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
@ -227,8 +227,8 @@ gboolean investigation_graph_layout_get_position(
|
||||||
|
|
||||||
position =
|
position =
|
||||||
g_hash_table_lookup(
|
g_hash_table_lookup(
|
||||||
layout->positions_by_entity_identifier,
|
layout->positions_by_node_identifier,
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
if (position == NULL)
|
if (position == NULL)
|
||||||
|
|
@ -253,21 +253,21 @@ gboolean investigation_graph_layout_get_position(
|
||||||
|
|
||||||
gboolean investigation_graph_layout_remove_position(
|
gboolean investigation_graph_layout_remove_position(
|
||||||
InvestigationGraphLayout *layout,
|
InvestigationGraphLayout *layout,
|
||||||
const char *entity_identifier
|
const char *node_identifier
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (layout == NULL ||
|
if (layout == NULL ||
|
||||||
layout->positions_by_entity_identifier == NULL ||
|
layout->positions_by_node_identifier == NULL ||
|
||||||
!investigation_graph_layout_identifier_is_valid(
|
!investigation_graph_layout_identifier_is_valid(
|
||||||
entity_identifier
|
node_identifier
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_hash_table_remove(
|
return g_hash_table_remove(
|
||||||
layout->positions_by_entity_identifier,
|
layout->positions_by_node_identifier,
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,13 +276,13 @@ void investigation_graph_layout_clear(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (layout == NULL ||
|
if (layout == NULL ||
|
||||||
layout->positions_by_entity_identifier == NULL)
|
layout->positions_by_node_identifier == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_remove_all(
|
g_hash_table_remove_all(
|
||||||
layout->positions_by_entity_identifier
|
layout->positions_by_node_identifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,13 +291,13 @@ guint investigation_graph_layout_get_count(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (layout == NULL ||
|
if (layout == NULL ||
|
||||||
layout->positions_by_entity_identifier == NULL)
|
layout->positions_by_node_identifier == NULL)
|
||||||
{
|
{
|
||||||
return 0U;
|
return 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_hash_table_size(
|
return g_hash_table_size(
|
||||||
layout->positions_by_entity_identifier
|
layout->positions_by_node_identifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +311,7 @@ void investigation_graph_layout_free(
|
||||||
}
|
}
|
||||||
|
|
||||||
g_clear_pointer(
|
g_clear_pointer(
|
||||||
&layout->positions_by_entity_identifier,
|
&layout->positions_by_node_identifier,
|
||||||
g_hash_table_unref
|
g_hash_table_unref
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1170,10 +1170,10 @@ gboolean create_relation_dialog_present(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
g_object_unref(
|
/*
|
||||||
target_labels
|
* Le constructeur prend possession de la référence du modèle.
|
||||||
);
|
* Le dialogue ne doit donc pas appeler g_object_unref() ici.
|
||||||
|
*/
|
||||||
target_labels =
|
target_labels =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -156,7 +156,7 @@ static void test_graph_node_position_dao_fixture_clear(
|
||||||
*/
|
*/
|
||||||
static void test_graph_node_position_dao_insert_entity(
|
static void test_graph_node_position_dao_insert_entity(
|
||||||
Database *database,
|
Database *database,
|
||||||
const char *entity_identifier,
|
const char *node_identifier,
|
||||||
const char *entity_value
|
const char *entity_value
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -168,7 +168,7 @@ static void test_graph_node_position_dao_insert_entity(
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_nonnull(
|
g_assert_nonnull(
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_nonnull(
|
g_assert_nonnull(
|
||||||
|
|
@ -208,7 +208,7 @@ static void test_graph_node_position_dao_insert_entity(
|
||||||
database_statement_bind_text(
|
database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
1,
|
1,
|
||||||
entity_identifier
|
node_identifier
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -254,7 +254,7 @@ static int64_t test_graph_node_position_dao_count_rows(
|
||||||
database_statement_prepare(
|
database_statement_prepare(
|
||||||
database,
|
database,
|
||||||
"SELECT COUNT(*) "
|
"SELECT COUNT(*) "
|
||||||
"FROM graph_node_positions;"
|
"FROM graph_layout_positions;"
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_nonnull(
|
g_assert_nonnull(
|
||||||
|
|
@ -454,19 +454,16 @@ static void test_graph_node_position_dao_upsert_insert(void)
|
||||||
GError *error =
|
GError *error =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
const char *entity_identifier =
|
const char *node_identifier =
|
||||||
"10000000-0000-4000-8000-000000000001";
|
"10000000-0000-4000-8000-000000000001";
|
||||||
|
|
||||||
test_graph_node_position_dao_insert_entity(
|
/* Un nœud générique peut être une relation et n'a pas à exister dans
|
||||||
fixture.database,
|
* la table entites. */
|
||||||
entity_identifier,
|
|
||||||
"entite-position-1"
|
|
||||||
);
|
|
||||||
|
|
||||||
g_assert_true(
|
g_assert_true(
|
||||||
graph_node_position_dao_upsert(
|
graph_node_position_dao_upsert(
|
||||||
fixture.position_dao,
|
fixture.position_dao,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
120.5,
|
120.5,
|
||||||
-42.25,
|
-42.25,
|
||||||
&error
|
&error
|
||||||
|
|
@ -516,11 +513,11 @@ static void test_graph_node_position_dao_upsert_insert(void)
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpstr(
|
g_assert_cmpstr(
|
||||||
graph_node_position_get_entity_identifier(
|
graph_node_position_get_node_identifier(
|
||||||
position
|
position
|
||||||
),
|
),
|
||||||
==,
|
==,
|
||||||
entity_identifier
|
node_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpfloat(
|
g_assert_cmpfloat(
|
||||||
|
|
@ -578,19 +575,19 @@ static void test_graph_node_position_dao_upsert_update(void)
|
||||||
GError *error =
|
GError *error =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
const char *entity_identifier =
|
const char *node_identifier =
|
||||||
"20000000-0000-4000-8000-000000000002";
|
"20000000-0000-4000-8000-000000000002";
|
||||||
|
|
||||||
test_graph_node_position_dao_insert_entity(
|
test_graph_node_position_dao_insert_entity(
|
||||||
fixture.database,
|
fixture.database,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
"entite-position-2"
|
"entite-position-2"
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_true(
|
g_assert_true(
|
||||||
graph_node_position_dao_upsert(
|
graph_node_position_dao_upsert(
|
||||||
fixture.position_dao,
|
fixture.position_dao,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
10.0,
|
10.0,
|
||||||
20.0,
|
20.0,
|
||||||
&error
|
&error
|
||||||
|
|
@ -604,7 +601,7 @@ static void test_graph_node_position_dao_upsert_update(void)
|
||||||
g_assert_true(
|
g_assert_true(
|
||||||
graph_node_position_dao_upsert(
|
graph_node_position_dao_upsert(
|
||||||
fixture.position_dao,
|
fixture.position_dao,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
-300.75,
|
-300.75,
|
||||||
450.5,
|
450.5,
|
||||||
&error
|
&error
|
||||||
|
|
@ -855,7 +852,7 @@ static void test_graph_node_position_dao_list_order(void)
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpstr(
|
g_assert_cmpstr(
|
||||||
graph_node_position_get_entity_identifier(
|
graph_node_position_get_node_identifier(
|
||||||
first_position
|
first_position
|
||||||
),
|
),
|
||||||
==,
|
==,
|
||||||
|
|
@ -863,7 +860,7 @@ static void test_graph_node_position_dao_list_order(void)
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpstr(
|
g_assert_cmpstr(
|
||||||
graph_node_position_get_entity_identifier(
|
graph_node_position_get_node_identifier(
|
||||||
second_position
|
second_position
|
||||||
),
|
),
|
||||||
==,
|
==,
|
||||||
|
|
@ -887,19 +884,19 @@ static void test_graph_node_position_dao_delete_existing(void)
|
||||||
GError *error =
|
GError *error =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
const char *entity_identifier =
|
const char *node_identifier =
|
||||||
"60000000-0000-4000-8000-000000000006";
|
"60000000-0000-4000-8000-000000000006";
|
||||||
|
|
||||||
test_graph_node_position_dao_insert_entity(
|
test_graph_node_position_dao_insert_entity(
|
||||||
fixture.database,
|
fixture.database,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
"entite-position-6"
|
"entite-position-6"
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_true(
|
g_assert_true(
|
||||||
graph_node_position_dao_upsert(
|
graph_node_position_dao_upsert(
|
||||||
fixture.position_dao,
|
fixture.position_dao,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
60.0,
|
60.0,
|
||||||
70.0,
|
70.0,
|
||||||
&error
|
&error
|
||||||
|
|
@ -913,7 +910,7 @@ static void test_graph_node_position_dao_delete_existing(void)
|
||||||
g_assert_true(
|
g_assert_true(
|
||||||
graph_node_position_dao_delete(
|
graph_node_position_dao_delete(
|
||||||
fixture.position_dao,
|
fixture.position_dao,
|
||||||
entity_identifier,
|
node_identifier,
|
||||||
&error
|
&error
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue