Compare commits

..

2 commits

Author SHA1 Message Date
grayTerminal-sh
0f67118f5b Ajouter la réinitialisation de la disposition du graphe 2026-07-22 07:23:31 +02:00
grayTerminal-sh
0465209d06 Ajouter la suppression globale des positions du graphe 2026-07-22 07:09:03 +02:00
9 changed files with 1842 additions and 5 deletions

View file

@ -1,3 +1,4 @@
CC = gcc
PKG_CONFIG = pkg-config
@ -27,6 +28,7 @@ EVIDENCE_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
EVIDENCE_TYPE_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
ENTITY_TYPE_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
EVIDENCE_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
GRAPH_NODE_POSITION_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
EVIDENCE_IMPORT_DIALOG_TEST_CFLAGS := \
-std=c17 \
-Wall \
@ -107,6 +109,7 @@ TEST_RELATION_SERVICE := tests/test_relation_service
TEST_INVESTIGATION_GRAPH_MODEL := tests/test_investigation_graph_model
TEST_INVESTIGATION_GRAPH_LOADER := tests/test_investigation_graph_loader
TEST_INVESTIGATION_GRAPH_LOAD_TASK := tests/test_investigation_graph_load_task
TEST_GRAPH_NODE_POSITION_DAO := tests/test_graph_node_position_dao
all: $(TARGET)
@ -510,6 +513,18 @@ $(TEST_INVESTIGATION_GRAPH_LOADER): \
$(CC) $(INVESTIGATION_GRAPH_LOADER_TEST_CFLAGS) $^ -o $@ \
$(TEST_LDFLAGS) -lsqlite3
$(TEST_GRAPH_NODE_POSITION_DAO): \
tests/test_graph_node_position_dao.c \
src/dao/graph_node_position_dao.c \
src/models/graph_node_position.c \
src/database/database.c \
src/database/schema.c \
src/database/statement.c \
src/database/transaction.c \
src/database/error.c
$(CC) $(GRAPH_NODE_POSITION_DAO_TEST_CFLAGS) $^ -o $@ \
$(TEST_LDFLAGS) -lsqlite3
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
tests/test_investigation_graph_load_task.c \
src/core/investigation_graph_load_task.c \
@ -576,7 +591,8 @@ test: \
$(TEST_RELATION_SERVICE) \
$(TEST_INVESTIGATION_GRAPH_MODEL) \
$(TEST_INVESTIGATION_GRAPH_LOADER) \
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK)
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
$(TEST_GRAPH_NODE_POSITION_DAO)
@echo "Exécution des tests..."
@./$(TEST_NODE)
@./$(TEST_TREE_MODEL)
@ -623,6 +639,7 @@ test: \
@$(TEST_INVESTIGATION_GRAPH_MODEL)
@$(TEST_INVESTIGATION_GRAPH_LOADER)
@$(TEST_INVESTIGATION_GRAPH_LOAD_TASK)
@$(TEST_GRAPH_NODE_POSITION_DAO)
@echo "Tous les tests sont valides."
%.o: %.c
@ -675,6 +692,7 @@ clean:
$(TEST_RELATION_SERVICE) \
$(TEST_INVESTIGATION_GRAPH_MODEL) \
$(TEST_INVESTIGATION_GRAPH_LOADER) \
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK)
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
$(TEST_GRAPH_NODE_POSITION_DAO)
.PHONY: clean run test

View file

@ -128,6 +128,21 @@ gboolean graph_node_position_dao_delete(
GError **error
);
/**
* @brief Supprime toutes les positions persistées du graphe.
*
* Une table déjà vide n'est pas une erreur.
*
* @param position_dao DAO valide.
* @param error Adresse recevant une éventuelle erreur.
*
* @return TRUE si la requête réussit, sinon FALSE.
*/
gboolean graph_node_position_dao_delete_all(
GraphNodePositionDao *position_dao,
GError **error
);
G_END_DECLS
#endif

View file

@ -91,6 +91,15 @@ typedef void (*MainWindowGraphNodeMovedCallback)(
gpointer user_data
);
/**
* @brief Callback appelé lors d'une demande de réinitialisation du graphe.
*
* @param user_data Données privées du callback.
*/
typedef void (*MainWindowResetGraphLayoutCallback)(
gpointer user_data
);
/**
* @brief Définit le callback de vérification d'une preuve.
*
@ -121,6 +130,21 @@ void main_window_set_graph_node_moved_callback(
gpointer user_data
);
/**
* @brief Définit le callback de demande de réinitialisation du graphe.
*
* MainWindow relaie uniquement la demande 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_reset_graph_layout_callback(
MainWindow *main_window,
MainWindowResetGraphLayoutCallback callback,
gpointer user_data
);
/**
* @brief Définit le callback du bouton « Nouvelle enquête ».
*
@ -320,6 +344,17 @@ void main_window_clear_graph(
MainWindow *main_window
);
/**
* @brief Reconstruit visuellement la disposition automatique du graphe.
*
* Cette fonction ne modifie pas SQLite.
*
* @param main_window Fenêtre principale.
*/
void main_window_reset_graph_layout(
MainWindow *main_window
);
/**
* @brief Met à jour le texte de la barre d'état.
*

View file

@ -54,6 +54,18 @@ typedef void (*WorkspaceGraphNodeMovedCallback)(
gpointer user_data
);
/**
* @brief Callback appelé lors d'une demande de réinitialisation du graphe.
*
* Le Workspace ne modifie ni SQLite ni le modèle de disposition. Il relaie
* uniquement la demande vers la couche applicative.
*
* @param user_data Données empruntées fournies par l'appelant.
*/
typedef void (*WorkspaceResetGraphLayoutCallback)(
gpointer user_data
);
/**
* @brief Crée une nouvelle zone de travail.
*
@ -134,6 +146,21 @@ void workspace_set_graph_node_moved_callback(
gpointer user_data
);
/**
* @brief Définit le callback de demande de réinitialisation du graphe.
*
* Le Workspace emprunte callback et user_data.
*
* @param workspace Zone de travail.
* @param callback Callback facultatif.
* @param user_data Données empruntées transmises au callback.
*/
void workspace_set_reset_graph_layout_callback(
Workspace *workspace,
WorkspaceResetGraphLayoutCallback callback,
gpointer user_data
);
/**
* @brief Affiche l'état de chargement du graphe.
*
@ -187,6 +214,21 @@ void workspace_clear_graph(
Workspace *workspace
);
/**
* @brief Reconstruit visuellement la disposition automatique du graphe.
*
* Cette fonction ne touche ni SQLite ni InvestigationGraphLayout. Elle doit
* être appelée par la couche applicative uniquement après la suppression
* réussie des positions persistées.
*
* Le zoom et le déplacement global du canvas sont conservés.
*
* @param workspace Zone de travail.
*/
void workspace_reset_graph_layout(
Workspace *workspace
);
/**
* @brief Libère la structure Workspace.
*

View file

@ -2825,7 +2825,7 @@ static void application_on_evidence_file_selected(
return;
}
Database *database = NULL;
EvidenceTypeDao *evidence_type_dao = NULL;
GPtrArray *evidence_types = NULL;
@ -4277,6 +4277,131 @@ static void application_on_graph_node_moved(
);
}
/**
* @brief Ferme proprement l'application.
*
* @param user_data Pointeur vers Application.
*/
static void application_on_reset_graph_layout_requested(
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_model == NULL ||
application->graph_layout == NULL)
{
return;
}
database =
investigation_session_get_database(
application->session
);
if (database == NULL)
{
application_present_error(
application,
"Réinitialisation 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"
);
application_present_error(
application,
"Réinitialisation impossible",
error != NULL
? error->message
: "Impossible d'accéder aux positions enregistrées."
);
g_clear_error(
&error
);
return;
}
if (!graph_node_position_dao_delete_all(
position_dao,
&error
))
{
g_warning(
"Impossible de supprimer les positions du graphe : %s",
error != NULL
? error->message
: "erreur inconnue"
);
application_present_error(
application,
"Réinitialisation impossible",
error != NULL
? error->message
: "Les positions enregistrées n'ont pas pu être supprimées."
);
g_clear_error(
&error
);
graph_node_position_dao_free(
position_dao
);
return;
}
graph_node_position_dao_free(
position_dao
);
investigation_graph_layout_clear(
application->graph_layout
);
main_window_reset_graph_layout(
application->main_window
);
main_window_set_status(
application->main_window,
"Disposition du graphe réinitialisée."
);
}
/**
* @brief Ferme proprement l'application.
*
@ -4373,6 +4498,12 @@ static void application_on_activate(
application
);
main_window_set_reset_graph_layout_callback(
application->main_window,
application_on_reset_graph_layout_requested,
application
);
main_window_set_new_investigation_callback(
application->main_window,
application_on_new_investigation_requested,
@ -4484,7 +4615,7 @@ Application *application_new(void)
tool_initializer_free(
application->tool_initializer
);
task_manager_free(
application->task_manager
);

View file

@ -65,6 +65,12 @@ static const char *const graph_node_position_dao_delete_sql =
"DELETE FROM graph_node_positions "
"WHERE entity_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;";
/**
* @brief Enregistre une erreur littérale.
*/
@ -791,3 +797,75 @@ cleanup:
return success;
}
gboolean graph_node_position_dao_delete_all(
GraphNodePositionDao *position_dao,
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)
{
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 FALSE;
}
statement =
database_statement_prepare(
position_dao->database,
graph_node_position_dao_delete_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 la suppression des positions"
);
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 les positions du graphe"
);
goto cleanup;
}
success =
TRUE;
cleanup:
database_statement_finalize(
statement
);
return success;
}

View file

@ -98,6 +98,12 @@ struct MainWindow
gpointer
graph_node_moved_user_data;
MainWindowResetGraphLayoutCallback
reset_graph_layout_callback;
gpointer
reset_graph_layout_user_data;
MainWindowQuitCallback
quit_callback;
@ -287,6 +293,27 @@ static void main_window_on_graph_node_moved(
);
}
/**
* @brief Relaie la demande de vérification provenant du Workspace.
*/
static void main_window_on_reset_graph_layout_requested(
gpointer user_data
)
{
MainWindow *main_window =
user_data;
if (main_window == NULL ||
main_window->reset_graph_layout_callback == NULL)
{
return;
}
main_window->reset_graph_layout_callback(
main_window->reset_graph_layout_user_data
);
}
/**
* @brief Relaie la demande de vérification provenant du Workspace.
*/
@ -556,6 +583,12 @@ MainWindow *main_window_new(
main_window
);
workspace_set_reset_graph_layout_callback(
main_window->workspace,
main_window_on_reset_graph_layout_requested,
main_window
);
workspace_widget = workspace_get_widget(
main_window->workspace
);
@ -565,7 +598,7 @@ MainWindow *main_window_new(
main_window_free(main_window);
return NULL;
}
main_window->task_panel = task_panel_new(
task_manager
);
@ -957,6 +990,20 @@ void main_window_clear_graph(
);
}
void main_window_reset_graph_layout(
MainWindow *main_window
)
{
if (main_window == NULL)
{
return;
}
workspace_reset_graph_layout(
main_window->workspace
);
}
void main_window_set_status(
MainWindow *main_window,
const char *status_text
@ -1119,6 +1166,24 @@ void main_window_set_graph_node_moved_callback(
user_data;
}
void main_window_set_reset_graph_layout_callback(
MainWindow *main_window,
MainWindowResetGraphLayoutCallback callback,
gpointer user_data
)
{
if (main_window == NULL)
{
return;
}
main_window->reset_graph_layout_callback =
callback;
main_window->reset_graph_layout_user_data =
user_data;
}
void main_window_set_quit_callback(
MainWindow *main_window,
MainWindowQuitCallback callback,
@ -1241,12 +1306,24 @@ void main_window_free(
NULL
);
workspace_set_reset_graph_layout_callback(
main_window->workspace,
NULL,
NULL
);
main_window->graph_node_moved_callback =
NULL;
main_window->graph_node_moved_user_data =
NULL;
main_window->reset_graph_layout_callback =
NULL;
main_window->reset_graph_layout_user_data =
NULL;
main_window_clear_graph(
main_window
);

View file

@ -47,6 +47,8 @@ struct Workspace
GtkWidget *graph_details_label;
GtkWidget *graph_view_page;
GtkWidget *graph_toolbar;
GtkWidget *reset_graph_layout_button;
InvestigationGraphView *graph_view;
EntityDetailsPanel *entity_details_panel;
@ -83,6 +85,12 @@ struct Workspace
gpointer
graph_node_moved_user_data;
WorkspaceResetGraphLayoutCallback
reset_graph_layout_callback;
gpointer
reset_graph_layout_user_data;
GtkWidget *node_name_label;
GtkWidget *node_path_label;
GtkWidget *node_type_label;
@ -380,6 +388,32 @@ static void workspace_show_default(
}
}
/**
* @brief Transmet la demande de vérification de la preuve affichée.
*/
static void workspace_on_reset_graph_layout_clicked(
GtkButton *button,
gpointer user_data
)
{
Workspace *workspace =
user_data;
(void) button;
if (workspace == NULL ||
workspace->graph_state != WORKSPACE_GRAPH_STATE_READY ||
workspace->graph_model == NULL ||
workspace->reset_graph_layout_callback == NULL)
{
return;
}
workspace->reset_graph_layout_callback(
workspace->reset_graph_layout_user_data
);
}
/**
* @brief Transmet la demande de vérification de la preuve affichée.
*/
@ -1048,6 +1082,98 @@ Workspace *workspace_new(void)
)
);
workspace->graph_toolbar =
gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
4
);
workspace->reset_graph_layout_button =
gtk_button_new_from_icon_name(
"view-refresh-symbolic"
);
if (workspace->graph_toolbar == NULL ||
workspace->reset_graph_layout_button == NULL)
{
workspace_free(
workspace
);
return NULL;
}
gtk_widget_set_halign(
workspace->graph_toolbar,
GTK_ALIGN_START
);
gtk_widget_set_valign(
workspace->graph_toolbar,
GTK_ALIGN_START
);
gtk_widget_set_margin_start(
workspace->graph_toolbar,
12
);
gtk_widget_set_margin_top(
workspace->graph_toolbar,
12
);
gtk_widget_add_css_class(
workspace->graph_toolbar,
"toolbar"
);
gtk_widget_add_css_class(
workspace->reset_graph_layout_button,
"flat"
);
gtk_widget_set_tooltip_text(
workspace->reset_graph_layout_button,
"Réinitialiser la disposition du graphe"
);
gtk_widget_set_sensitive(
workspace->reset_graph_layout_button,
FALSE
);
g_signal_connect(
workspace->reset_graph_layout_button,
"clicked",
G_CALLBACK(
workspace_on_reset_graph_layout_clicked
),
workspace
);
gtk_box_append(
GTK_BOX(
workspace->graph_toolbar
),
workspace->reset_graph_layout_button
);
gtk_overlay_add_overlay(
GTK_OVERLAY(
workspace->graph_view_page
),
workspace->graph_toolbar
);
gtk_overlay_set_clip_overlay(
GTK_OVERLAY(
workspace->graph_view_page
),
workspace->graph_toolbar,
TRUE
);
gtk_overlay_add_overlay(
GTK_OVERLAY(
workspace->graph_view_page
@ -1458,6 +1584,11 @@ void workspace_set_graph_loading(
workspace->graph_state =
WORKSPACE_GRAPH_STATE_LOADING;
gtk_widget_set_sensitive(
workspace->reset_graph_layout_button,
FALSE
);
gtk_label_set_text(
GTK_LABEL(
workspace->graph_title_label
@ -1548,6 +1679,11 @@ void workspace_set_graph(
workspace->graph_state =
WORKSPACE_GRAPH_STATE_READY;
gtk_widget_set_sensitive(
workspace->reset_graph_layout_button,
TRUE
);
gtk_spinner_stop(
GTK_SPINNER(
workspace->graph_spinner
@ -1618,6 +1754,11 @@ void workspace_set_graph_error(
workspace->graph_state =
WORKSPACE_GRAPH_STATE_ERROR;
gtk_widget_set_sensitive(
workspace->reset_graph_layout_button,
FALSE
);
gtk_spinner_stop(
GTK_SPINNER(
workspace->graph_spinner
@ -1690,6 +1831,11 @@ void workspace_clear_graph(
workspace->graph_state =
WORKSPACE_GRAPH_STATE_EMPTY;
gtk_widget_set_sensitive(
workspace->reset_graph_layout_button,
FALSE
);
gtk_spinner_stop(
GTK_SPINNER(
workspace->graph_spinner
@ -1756,6 +1902,49 @@ void workspace_set_graph_node_moved_callback(
user_data;
}
void workspace_set_reset_graph_layout_callback(
Workspace *workspace,
WorkspaceResetGraphLayoutCallback callback,
gpointer user_data
)
{
if (workspace == NULL)
{
return;
}
workspace->reset_graph_layout_callback =
callback;
workspace->reset_graph_layout_user_data =
user_data;
}
void workspace_reset_graph_layout(
Workspace *workspace
)
{
if (workspace == NULL ||
workspace->graph_view == NULL ||
workspace->graph_state != WORKSPACE_GRAPH_STATE_READY ||
workspace->graph_model == NULL)
{
return;
}
entity_details_panel_clear(
workspace->entity_details_panel
);
investigation_graph_view_clear_selection(
workspace->graph_view
);
investigation_graph_view_reset_layout(
workspace->graph_view
);
}
void workspace_free(Workspace *workspace)
{
if (workspace == NULL)
@ -1821,6 +2010,18 @@ void workspace_free(Workspace *workspace)
workspace->graph_node_moved_user_data =
NULL;
workspace->reset_graph_layout_callback =
NULL;
workspace->reset_graph_layout_user_data =
NULL;
workspace->graph_toolbar =
NULL;
workspace->reset_graph_layout_button =
NULL;
g_clear_pointer(
&workspace->selected_evidence_identifier,
g_free

File diff suppressed because it is too large Load diff