Ajouter les relations déplaçables dans le graphe

Affiche les relations comme des nœuds indépendants et persistants.
  Généralise le stockage des positions aux entités et aux relations.
  Finalise la création de relation et le rafraîchissement du graphe.
This commit is contained in:
grayTerminal-sh 2026-07-22 10:15:05 +02:00
parent b1d0ec2a1e
commit bf56b09422
16 changed files with 1286 additions and 325 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Build
labfy-investigation
*.o
*.d
# Executables de tests
/tests/test_*

View file

@ -1,4 +1,3 @@
CC = gcc
PKG_CONFIG = pkg-config
@ -11,6 +10,8 @@ CFLAGS = -std=c17 \
-Werror \
-g \
-Iinclude \
-MMD \
-MP \
$(shell $(PKG_CONFIG) --cflags gtk4 sqlite3)
LDFLAGS = $(shell $(PKG_CONFIG) --libs gtk4 sqlite3)
@ -62,6 +63,8 @@ SRC := $(shell find src -name "*.c")
OBJ := $(SRC:.c=.o)
DEP := $(OBJ:.o=.d)
TARGET = labfy-investigation
TEST_NODE = tests/test_investigation_node
@ -649,7 +652,7 @@ run: $(TARGET)
./$(TARGET)
clean:
rm -f $(OBJ) $(TARGET) \
rm -f $(OBJ) $(DEP) $(TARGET) \
$(TEST_NODE) \
$(TEST_TREE_MODEL) \
$(TEST_TREE_BUILDER) \
@ -695,4 +698,6 @@ clean:
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
$(TEST_GRAPH_NODE_POSITION_DAO)
-include $(DEP)
.PHONY: clean run test

View file

@ -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 dinvestigation numérique et dOSINT, 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 nest pas encore prêt pour un usage opérationnel en production. Les formats internes, linterface et les mécanismes dintégration peuvent encore évoluer.
---
## Objectifs
Labfy Investigation doit permettre de :

View file

@ -57,3 +57,45 @@ CREATE TABLE IF NOT EXISTS graph_node_positions
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;

View file

@ -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
La base de données de Labfy Investigation constitue bien davantage qu'un simple

View file

@ -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.
*
* @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 y Coordonnée verticale logique.
* @param error Adresse recevant une éventuelle erreur.
@ -105,26 +105,26 @@ GPtrArray *graph_node_position_dao_list_all(
*/
gboolean graph_node_position_dao_upsert(
GraphNodePositionDao *position_dao,
const char *entity_identifier,
const char *node_identifier,
double x,
double y,
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.
*
* @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.
*
* @return TRUE si la requête réussit, sinon FALSE.
*/
gboolean graph_node_position_dao_delete(
GraphNodePositionDao *position_dao,
const char *entity_identifier,
const char *node_identifier,
GError **error
);

View file

@ -42,11 +42,11 @@ GQuark graph_node_position_error_quark(void);
*
* 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.
* 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 y Coordonnée verticale logique.
* @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.
*/
GraphNodePosition *graph_node_position_new(
const char *entity_identifier,
const char *node_identifier,
double x,
double y,
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.
*
@ -82,7 +82,7 @@ void graph_node_position_free(
*
* @return UUID emprunté, ou NULL.
*/
const char *graph_node_position_get_entity_identifier(
const char *graph_node_position_get_node_identifier(
const GraphNodePosition *position
);

View file

@ -47,16 +47,16 @@ typedef void (*InvestigationGraphViewSelectionCallback)(
* @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
* node_identifier est emprunté au modèle et reste valable uniquement
* 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 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,
const char *node_identifier,
double x,
double y,
gpointer user_data

View file

@ -35,6 +35,7 @@
#include "widgets/evidence_category_model.h"
#include "core/evidence_integrity_task.h"
#include "core/evidence_integrity_verifier.h"
#include "core/relation_service.h"
#include "database/database.h"
#include <gtk/gtk.h>
@ -3341,6 +3342,21 @@ static void application_on_create_relation_completed(
Application *application =
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 =
NULL;
@ -3350,9 +3366,21 @@ static void application_on_create_relation_completed(
const char *relation_type =
NULL;
char *status_message =
const char *database_path =
NULL;
char *relation_identifier =
NULL;
char *timestamp =
NULL;
GError *error =
NULL;
gboolean relation_created =
FALSE;
if (application == NULL ||
application->main_window == NULL)
{
@ -3373,6 +3401,17 @@ static void application_on_create_relation_completed(
return;
}
if (application->session == NULL)
{
application_present_error(
application,
"Ajout de relation impossible",
"Aucune enquête n'est actuellement ouverte."
);
goto cleanup;
}
source_identifier =
create_relation_dialog_result_get_source_identifier(
result
@ -3388,41 +3427,225 @@ static void application_on_create_relation_completed(
result
);
g_message(
"Relation préparée : %s -> %s (%s), confiance %d %%.",
source_identifier != NULL
? 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)"
database =
investigation_session_get_database(
application->session
);
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(
application->main_window,
status_message != NULL
? status_message
: "Relation préparée."
"Relation enregistrée. Actualisation du graphe…"
);
/*
* 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(
status_message
relation_identifier
);
create_relation_dialog_result_free(

View file

@ -946,7 +946,7 @@ static gboolean investigation_graph_load_task_worker(
if (!investigation_graph_layout_set_position(
graph_layout,
graph_node_position_get_entity_identifier(
graph_node_position_get_node_identifier(
position
),
graph_node_position_get_x(

View file

@ -27,20 +27,20 @@ struct GraphNodePositionDao
*/
static const char *const graph_node_position_dao_list_all_sql =
"SELECT "
" entity_id,"
" node_id,"
" x,"
" y,"
" updated_at "
"FROM graph_node_positions "
"ORDER BY entity_id ASC;";
"FROM graph_layout_positions "
"ORDER BY node_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"
"INSERT INTO graph_layout_positions"
"("
" entity_id,"
" node_id,"
" x,"
" y,"
" 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 "
" x = excluded.x,"
" 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.
*/
static const char *const graph_node_position_dao_delete_sql =
"DELETE FROM graph_node_positions "
"WHERE entity_id = ?;";
"DELETE FROM graph_layout_positions "
"WHERE node_id = ?;";
/**
* @brief Requête de suppression de toutes les positions.
*/
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.
@ -202,7 +202,7 @@ graph_node_position_dao_read_current_record(
GraphNodePosition *position =
NULL;
char *entity_identifier =
char *node_identifier =
NULL;
char *updated_at =
@ -233,7 +233,7 @@ graph_node_position_dao_read_current_record(
if (!database_statement_column_text(
statement,
0,
&entity_identifier
&node_identifier
) ||
!database_statement_column_double(
statement,
@ -262,7 +262,7 @@ graph_node_position_dao_read_current_record(
position =
graph_node_position_new(
entity_identifier,
node_identifier,
x,
y,
updated_at,
@ -302,7 +302,7 @@ cleanup:
);
g_free(
entity_identifier
node_identifier
);
return position;
@ -576,7 +576,7 @@ error:
gboolean graph_node_position_dao_upsert(
GraphNodePositionDao *position_dao,
const char *entity_identifier,
const char *node_identifier,
double x,
double y,
GError **error
@ -598,9 +598,9 @@ gboolean graph_node_position_dao_upsert(
if (position_dao == NULL ||
position_dao->database == NULL ||
entity_identifier == NULL ||
node_identifier == NULL ||
!g_uuid_string_is_valid(
entity_identifier
node_identifier
) ||
!isfinite(x) ||
!isfinite(y))
@ -649,7 +649,7 @@ gboolean graph_node_position_dao_upsert(
if (!database_statement_bind_text(
statement,
1,
entity_identifier
node_identifier
) ||
!database_statement_bind_double(
statement,
@ -707,7 +707,7 @@ cleanup:
gboolean graph_node_position_dao_delete(
GraphNodePositionDao *position_dao,
const char *entity_identifier,
const char *node_identifier,
GError **error
)
{
@ -724,15 +724,15 @@ gboolean graph_node_position_dao_delete(
if (position_dao == NULL ||
position_dao->database == NULL ||
entity_identifier == NULL ||
node_identifier == NULL ||
!g_uuid_string_is_valid(
entity_identifier
node_identifier
))
{
graph_node_position_dao_set_error_literal(
error,
GRAPH_NODE_POSITION_DAO_ERROR_INVALID_ARGUMENT,
"L'identifiant de l'entité est invalide."
"L'identifiant du nœud est invalide."
);
return FALSE;
@ -759,14 +759,14 @@ gboolean graph_node_position_dao_delete(
if (!database_statement_bind_text(
statement,
1,
entity_identifier
node_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é"
"Impossible de lier l'identifiant du nœud"
);
goto cleanup;

View file

@ -14,7 +14,7 @@
*/
struct GraphNodePosition
{
char *entity_identifier;
char *node_identifier;
double x;
double y;
@ -154,7 +154,7 @@ GQuark graph_node_position_error_quark(void)
}
GraphNodePosition *graph_node_position_new(
const char *entity_identifier,
const char *node_identifier,
double x,
double y,
const char *updated_at,
@ -164,7 +164,7 @@ GraphNodePosition *graph_node_position_new(
GraphNodePosition *position =
NULL;
char *entity_identifier_copy =
char *node_identifier_copy =
NULL;
char *updated_at_copy =
@ -175,22 +175,22 @@ GraphNodePosition *graph_node_position_new(
NULL
);
entity_identifier_copy =
node_identifier_copy =
graph_node_position_duplicate_trimmed(
entity_identifier
node_identifier
);
if (entity_identifier_copy == NULL ||
entity_identifier_copy[0] == '\0' ||
if (node_identifier_copy == NULL ||
node_identifier_copy[0] == '\0' ||
!g_uuid_string_is_valid(
entity_identifier_copy
node_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."
"L'identifiant du nœud n'est pas un UUID valide."
);
goto cleanup;
@ -246,8 +246,8 @@ GraphNodePosition *graph_node_position_new(
goto cleanup;
}
position->entity_identifier =
entity_identifier_copy;
position->node_identifier =
node_identifier_copy;
position->x =
x;
@ -258,7 +258,7 @@ GraphNodePosition *graph_node_position_new(
position->updated_at =
updated_at_copy;
entity_identifier_copy =
node_identifier_copy =
NULL;
updated_at_copy =
@ -271,7 +271,7 @@ cleanup:
);
g_free(
entity_identifier_copy
node_identifier_copy
);
return position;
@ -291,7 +291,7 @@ void graph_node_position_free(
);
g_free(
position->entity_identifier
position->node_identifier
);
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
)
{
return position != NULL
? position->entity_identifier
? position->node_identifier
: NULL;
}

View file

@ -8,7 +8,7 @@
#include <math.h>
/**
* @brief Coordonnées privées associées à une entité.
* @brief Coordonnées privées associées à un nœud.
*/
typedef struct
{
@ -22,7 +22,7 @@ typedef struct
*/
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(
const char *entity_identifier
const char *node_identifier
)
{
return entity_identifier != NULL &&
entity_identifier[0] != '\0' &&
return node_identifier != NULL &&
node_identifier[0] != '\0' &&
g_uuid_string_is_valid(
entity_identifier
node_identifier
);
}
@ -84,7 +84,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
return NULL;
}
layout->positions_by_entity_identifier =
layout->positions_by_node_identifier =
g_hash_table_new_full(
g_str_hash,
g_str_equal,
@ -92,7 +92,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
g_free
);
if (layout->positions_by_entity_identifier == NULL)
if (layout->positions_by_node_identifier == NULL)
{
investigation_graph_layout_free(
layout
@ -106,7 +106,7 @@ InvestigationGraphLayout *investigation_graph_layout_new(void)
gboolean investigation_graph_layout_set_position(
InvestigationGraphLayout *layout,
const char *entity_identifier,
const char *node_identifier,
double x,
double y,
GError **error
@ -115,7 +115,7 @@ gboolean investigation_graph_layout_set_position(
InvestigationGraphLayoutPosition *position =
NULL;
char *entity_identifier_copy =
char *node_identifier_copy =
NULL;
g_return_val_if_fail(
@ -124,7 +124,7 @@ gboolean investigation_graph_layout_set_position(
);
if (layout == NULL ||
layout->positions_by_entity_identifier == NULL)
layout->positions_by_node_identifier == NULL)
{
investigation_graph_layout_set_error_literal(
error,
@ -136,13 +136,13 @@ gboolean investigation_graph_layout_set_position(
}
if (!investigation_graph_layout_identifier_is_valid(
entity_identifier
node_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."
"L'identifiant du nœud n'est pas un UUID valide."
);
return FALSE;
@ -160,9 +160,9 @@ gboolean investigation_graph_layout_set_position(
return FALSE;
}
entity_identifier_copy =
node_identifier_copy =
g_strdup(
entity_identifier
node_identifier
);
position =
@ -171,7 +171,7 @@ gboolean investigation_graph_layout_set_position(
1
);
if (entity_identifier_copy == NULL ||
if (node_identifier_copy == NULL ||
position == NULL)
{
g_free(
@ -179,7 +179,7 @@ gboolean investigation_graph_layout_set_position(
);
g_free(
entity_identifier_copy
node_identifier_copy
);
investigation_graph_layout_set_error_literal(
@ -198,8 +198,8 @@ gboolean investigation_graph_layout_set_position(
y;
g_hash_table_replace(
layout->positions_by_entity_identifier,
entity_identifier_copy,
layout->positions_by_node_identifier,
node_identifier_copy,
position
);
@ -208,7 +208,7 @@ gboolean investigation_graph_layout_set_position(
gboolean investigation_graph_layout_get_position(
const InvestigationGraphLayout *layout,
const char *entity_identifier,
const char *node_identifier,
double *x,
double *y
)
@ -217,9 +217,9 @@ gboolean investigation_graph_layout_get_position(
NULL;
if (layout == NULL ||
layout->positions_by_entity_identifier == NULL ||
layout->positions_by_node_identifier == NULL ||
!investigation_graph_layout_identifier_is_valid(
entity_identifier
node_identifier
))
{
return FALSE;
@ -227,8 +227,8 @@ gboolean investigation_graph_layout_get_position(
position =
g_hash_table_lookup(
layout->positions_by_entity_identifier,
entity_identifier
layout->positions_by_node_identifier,
node_identifier
);
if (position == NULL)
@ -253,21 +253,21 @@ gboolean investigation_graph_layout_get_position(
gboolean investigation_graph_layout_remove_position(
InvestigationGraphLayout *layout,
const char *entity_identifier
const char *node_identifier
)
{
if (layout == NULL ||
layout->positions_by_entity_identifier == NULL ||
layout->positions_by_node_identifier == NULL ||
!investigation_graph_layout_identifier_is_valid(
entity_identifier
node_identifier
))
{
return FALSE;
}
return g_hash_table_remove(
layout->positions_by_entity_identifier,
entity_identifier
layout->positions_by_node_identifier,
node_identifier
);
}
@ -276,13 +276,13 @@ void investigation_graph_layout_clear(
)
{
if (layout == NULL ||
layout->positions_by_entity_identifier == NULL)
layout->positions_by_node_identifier == NULL)
{
return;
}
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 ||
layout->positions_by_entity_identifier == NULL)
layout->positions_by_node_identifier == NULL)
{
return 0U;
}
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(
&layout->positions_by_entity_identifier,
&layout->positions_by_node_identifier,
g_hash_table_unref
);

View file

@ -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 =
NULL;

File diff suppressed because it is too large Load diff

View file

@ -156,7 +156,7 @@ static void test_graph_node_position_dao_fixture_clear(
*/
static void test_graph_node_position_dao_insert_entity(
Database *database,
const char *entity_identifier,
const char *node_identifier,
const char *entity_value
)
{
@ -168,7 +168,7 @@ static void test_graph_node_position_dao_insert_entity(
);
g_assert_nonnull(
entity_identifier
node_identifier
);
g_assert_nonnull(
@ -208,7 +208,7 @@ static void test_graph_node_position_dao_insert_entity(
database_statement_bind_text(
statement,
1,
entity_identifier
node_identifier
)
);
@ -254,7 +254,7 @@ static int64_t test_graph_node_position_dao_count_rows(
database_statement_prepare(
database,
"SELECT COUNT(*) "
"FROM graph_node_positions;"
"FROM graph_layout_positions;"
);
g_assert_nonnull(
@ -454,19 +454,16 @@ static void test_graph_node_position_dao_upsert_insert(void)
GError *error =
NULL;
const char *entity_identifier =
const char *node_identifier =
"10000000-0000-4000-8000-000000000001";
test_graph_node_position_dao_insert_entity(
fixture.database,
entity_identifier,
"entite-position-1"
);
/* Un nœud générique peut être une relation et n'a pas à exister dans
* la table entites. */
g_assert_true(
graph_node_position_dao_upsert(
fixture.position_dao,
entity_identifier,
node_identifier,
120.5,
-42.25,
&error
@ -516,11 +513,11 @@ static void test_graph_node_position_dao_upsert_insert(void)
);
g_assert_cmpstr(
graph_node_position_get_entity_identifier(
graph_node_position_get_node_identifier(
position
),
==,
entity_identifier
node_identifier
);
g_assert_cmpfloat(
@ -578,19 +575,19 @@ static void test_graph_node_position_dao_upsert_update(void)
GError *error =
NULL;
const char *entity_identifier =
const char *node_identifier =
"20000000-0000-4000-8000-000000000002";
test_graph_node_position_dao_insert_entity(
fixture.database,
entity_identifier,
node_identifier,
"entite-position-2"
);
g_assert_true(
graph_node_position_dao_upsert(
fixture.position_dao,
entity_identifier,
node_identifier,
10.0,
20.0,
&error
@ -604,7 +601,7 @@ static void test_graph_node_position_dao_upsert_update(void)
g_assert_true(
graph_node_position_dao_upsert(
fixture.position_dao,
entity_identifier,
node_identifier,
-300.75,
450.5,
&error
@ -855,7 +852,7 @@ static void test_graph_node_position_dao_list_order(void)
);
g_assert_cmpstr(
graph_node_position_get_entity_identifier(
graph_node_position_get_node_identifier(
first_position
),
==,
@ -863,7 +860,7 @@ static void test_graph_node_position_dao_list_order(void)
);
g_assert_cmpstr(
graph_node_position_get_entity_identifier(
graph_node_position_get_node_identifier(
second_position
),
==,
@ -887,19 +884,19 @@ static void test_graph_node_position_dao_delete_existing(void)
GError *error =
NULL;
const char *entity_identifier =
const char *node_identifier =
"60000000-0000-4000-8000-000000000006";
test_graph_node_position_dao_insert_entity(
fixture.database,
entity_identifier,
node_identifier,
"entite-position-6"
);
g_assert_true(
graph_node_position_dao_upsert(
fixture.position_dao,
entity_identifier,
node_identifier,
60.0,
70.0,
&error
@ -913,7 +910,7 @@ static void test_graph_node_position_dao_delete_existing(void)
g_assert_true(
graph_node_position_dao_delete(
fixture.position_dao,
entity_identifier,
node_identifier,
&error
)
);