From fb44188e01306c6a8e44a6f7af436c1c1348edd7 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Tue, 21 Jul 2026 12:24:41 +0200 Subject: [PATCH] feat: integrate asynchronous graph loading --- include/views/main_window.h | 52 ++++ include/widgets/workspace.h | 127 ++++++---- src/core/application.c | 405 +++++++++++++++++++++++++++++++ src/views/main_window.c | 65 +++++ src/widgets/workspace.c | 465 +++++++++++++++++++++++++++++++++--- 5 files changed, 1037 insertions(+), 77 deletions(-) diff --git a/include/views/main_window.h b/include/views/main_window.h index ab229eb..ec77a42 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -15,6 +15,11 @@ #include +/** + * @brief Représentation opaque du graphe d'enquête. + */ +typedef struct InvestigationGraphModel InvestigationGraphModel; + /** * @brief Représentation opaque de la fenêtre principale. */ @@ -229,6 +234,53 @@ void main_window_set_selected_evidence( const EvidenceRecord *evidence_record ); + +/** + * @brief Affiche l'état de chargement du graphe. + * + * MainWindow transmet uniquement la demande au Workspace. + * + * @param main_window Fenêtre principale. + */ +void main_window_set_graph_loading( + MainWindow *main_window +); + +/** + * @brief Affiche le graphe chargé dans le Workspace. + * + * MainWindow et Workspace empruntent graph_model. + * + * @param main_window Fenêtre principale. + * @param graph_model Graphe emprunté, ou NULL. + */ +void main_window_set_graph( + MainWindow *main_window, + const InvestigationGraphModel *graph_model +); + +/** + * @brief Affiche une erreur de chargement du graphe. + * + * @param main_window Fenêtre principale. + * @param message Message d'erreur, ou NULL. + */ +void main_window_set_graph_error( + MainWindow *main_window, + const char *message +); + +/** + * @brief Détache le graphe du Workspace. + * + * Aucun graphe n'est libéré par MainWindow. + * + * @param main_window Fenêtre principale. + */ +void main_window_clear_graph( + MainWindow *main_window +); + /** * @brief Met à jour le texte de la barre d'état. * diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index f1ad576..17ddbd3 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -6,20 +6,32 @@ #ifndef LABFY_INVESTIGATION_WORKSPACE_H #define LABFY_INVESTIGATION_WORKSPACE_H - #include "core/investigation_node.h" #include "models/evidence_record.h" #include +/** + * @brief Représentation opaque du graphe d'enquête. + */ +typedef struct InvestigationGraphModel InvestigationGraphModel; + /** * @brief Représentation opaque de la zone de travail. - * - * La structure réelle est définie dans workspace.c. - * Les autres modules manipulent uniquement un pointeur vers Workspace. */ typedef struct Workspace Workspace; +/** + * @brief Callback appelé lors d'une demande de vérification d'une preuve. + * + * @param evidence_identifier UUID de la preuve affichée. + * @param user_data Données empruntées fournies par l'appelant. + */ +typedef void (*WorkspaceVerifyEvidenceCallback)( + const char *evidence_identifier, + gpointer user_data +); + /** * @brief Crée une nouvelle zone de travail. * @@ -27,44 +39,27 @@ typedef struct Workspace Workspace; */ Workspace *workspace_new(void); -/** - * @brief Callback appelé lorsque l'utilisateur demande la vérification - * de la preuve affichée. - * - * L'identifiant est emprunté et uniquement valide pendant l'appel. - * - * @param evidence_identifier UUID de la preuve. - * @param user_data Données privées du callback. - */ -typedef void (*WorkspaceVerifyEvidenceCallback)( - const char *evidence_identifier, - gpointer user_data -); - /** * @brief Retourne le widget GTK racine de la zone de travail. * - * Le widget retourné appartient au module Workspace et ne doit pas être - * détruit directement par le code appelant. + * Le widget retourné appartient au module Workspace. * * @param workspace Zone de travail à consulter. * - * @return Le widget racine, ou NULL si workspace est NULL. + * @return Widget racine, ou NULL. */ GtkWidget *workspace_get_widget( const Workspace *workspace ); /** - * @brief Affiche les informations du nœud sélectionné. + * @brief Affiche les informations d'un nœud sélectionné. * - * Le Workspace ne devient pas propriétaire du nœud. - * Le nœud doit rester valide pendant toute la durée de son affichage. + * Le Workspace emprunte le nœud. + * Passer NULL restaure l'état principal courant. * - * Passer NULL restaure la page d'accueil. - * - * @param workspace Zone de travail à mettre à jour. - * @param node Nœud sélectionné en lecture seule, ou NULL. + * @param workspace Zone de travail. + * @param node Nœud emprunté, ou NULL. */ void workspace_set_selected_node( Workspace *workspace, @@ -74,13 +69,13 @@ void workspace_set_selected_node( /** * @brief Affiche la fiche détaillée d'une preuve. * - * Les valeurs sont immédiatement copiées par les widgets GTK. - * Le Workspace ne conserve pas le pointeur EvidenceRecord. + * Les textes sont copiés dans les widgets. Le Workspace ne conserve pas le + * pointeur EvidenceRecord. * - * Passer NULL restaure la page d'accueil. + * Passer NULL restaure l'état principal courant. * - * @param workspace Zone de travail à mettre à jour. - * @param evidence_record Preuve sélectionnée, ou NULL. + * @param workspace Zone de travail. + * @param evidence_record Preuve empruntée, ou NULL. */ void workspace_set_selected_evidence( Workspace *workspace, @@ -88,13 +83,13 @@ void workspace_set_selected_evidence( ); /** - * @brief Définit le callback de vérification d'une preuve. + * @brief Définit le callback de vérification de la preuve affichée. * - * Le Workspace transmet uniquement l'identifiant de la preuve affichée. + * Le Workspace emprunte callback et user_data. * * @param workspace Zone de travail. - * @param callback Callback facultatif. - * @param user_data Données privées transmises au callback. + * @param callback Callback à utiliser, ou NULL. + * @param user_data Données empruntées du callback. */ void workspace_set_verify_evidence_callback( Workspace *workspace, @@ -103,12 +98,64 @@ void workspace_set_verify_evidence_callback( ); /** - * @brief Libère la structure d'encapsulation de la zone de travail. + * @brief Affiche l'état de chargement du graphe. * - * Cette fonction accepte NULL. + * L'ancien graphe emprunté est détaché. + * + * @param workspace Zone de travail. + */ +void workspace_set_graph_loading( + Workspace *workspace +); + +/** + * @brief Affiche le résumé du graphe chargé. + * + * Le Workspace emprunte graph_model et ne le libère jamais. + * Passer NULL équivaut à workspace_clear_graph(). + * + * @param workspace Zone de travail. + * @param graph_model Graphe emprunté. + */ +void workspace_set_graph( + Workspace *workspace, + const InvestigationGraphModel *graph_model +); + +/** + * @brief Affiche une erreur de chargement du graphe. + * + * Le texte du message est immédiatement copié par GTK. + * + * @param workspace Zone de travail. + * @param message Détail de l'erreur, ou NULL. + */ +void workspace_set_graph_error( + Workspace *workspace, + const char *message +); + +/** + * @brief Détache le graphe et restaure l'état sans enquête. + * + * Le graphe précédemment emprunté n'est jamais libéré par le Workspace. + * + * @param workspace Zone de travail. + */ +void workspace_clear_graph( + Workspace *workspace +); + +/** + * @brief Libère la structure Workspace. + * + * Cette fonction accepte NULL. Elle ne libère jamais le graphe emprunté. * * @param workspace Zone de travail à libérer. */ -void workspace_free(Workspace *workspace); +void workspace_free( + Workspace *workspace +); #endif + diff --git a/src/core/application.c b/src/core/application.c index 3d7cea9..293abcf 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -9,6 +9,8 @@ #include "core/investigation_project.h" #include "core/investigation_session.h" #include "core/investigation_tree_model.h" +#include "core/investigation_graph_load_task.h" +#include "models/investigation_graph_model.h" #include "models/investigation_record.h" #include "core/investigation_tree_builder.h" #include "views/create_investigation_dialog.h" @@ -91,6 +93,11 @@ struct Application ToolInitializer *tool_initializer; EvidenceListModel *evidence_list_model; EvidenceCategoryModel *evidence_category_model; + + InvestigationGraphLoadTask *graph_load_task; + InvestigationGraphModel *graph_model; + guint64 graph_load_generation; + char *selected_evidence_identifier; }; @@ -103,6 +110,15 @@ typedef struct char *investigation_root_path; } ApplicationEvidenceImportContext; +/** + * @brief Contexte conservé jusqu'à la fin d'un chargement de graphe. + */ +typedef struct +{ + Application *application; + guint64 generation; +} ApplicationGraphLoadContext; + /** * @brief Contexte conservé pendant le dialogue de métadonnées. * @@ -213,6 +229,374 @@ static void application_evidence_integrity_context_free( ); } +/** + * @brief Libère le contexte d'un chargement de graphe. + */ +static void application_graph_load_context_free( + gpointer user_data +) +{ + g_free( + user_data + ); +} + +/** + * @brief Annule et détache le chargement de graphe courant. + * + * Chaque appel invalide les résultats appartenant à la génération + * précédente, même lorsqu'aucune tâche n'est encore active. + */ +static void application_cancel_graph_loading( + Application *application +) +{ + InvestigationGraphLoadTask *load_task = + NULL; + + if (application == NULL) + { + return; + } + + application->graph_load_generation++; + + load_task = + application->graph_load_task; + + application->graph_load_task = + NULL; + + if (load_task == NULL) + { + return; + } + + investigation_graph_load_task_cancel( + load_task + ); + + investigation_graph_load_task_free( + load_task + ); +} + +/** + * @brief Détache puis libère le graphe possédé par Application. + */ +static void application_clear_graph( + Application *application +) +{ + InvestigationGraphModel *graph_model = + NULL; + + if (application == NULL) + { + return; + } + + if (application->main_window != NULL) + { + main_window_clear_graph( + application->main_window + ); + } + + graph_model = + application->graph_model; + + application->graph_model = + NULL; + + investigation_graph_model_free( + graph_model + ); +} + +/** + * @brief Traite le résultat d'un chargement asynchrone de graphe. + * + * Le callback est exécuté sur le contexte principal GLib. + */ +static void application_on_graph_loaded( + InvestigationGraphLoadTask *load_task, + InvestigationGraphModel *graph_model, + const GError *error, + gpointer user_data +) +{ + ApplicationGraphLoadContext *context = + user_data; + + Application *application = + NULL; + + char *status_message = + NULL; + + gboolean is_current_result = + FALSE; + + if (context != NULL) + { + application = + context->application; + } + + if (application != NULL) + { + is_current_result = + load_task == + application->graph_load_task && + context->generation == + application->graph_load_generation; + } + + /* + * Un résultat appartenant à une ancienne enquête ne doit jamais + * modifier l'interface actuelle. + */ + if (!is_current_result) + { + investigation_graph_model_free( + graph_model + ); + + investigation_graph_load_task_free( + load_task + ); + + return; + } + + application->graph_load_task = + NULL; + + if (error != NULL) + { + investigation_graph_model_free( + graph_model + ); + + main_window_set_graph_error( + application->main_window, + error->message + ); + + main_window_set_status( + application->main_window, + "Le graphe de l'enquête n'a pas pu être chargé." + ); + + investigation_graph_load_task_free( + load_task + ); + + return; + } + + if (graph_model == NULL) + { + main_window_set_graph_error( + application->main_window, + "Le chargement s'est terminé sans fournir de graphe." + ); + + main_window_set_status( + application->main_window, + "Le graphe de l'enquête est indisponible." + ); + + investigation_graph_load_task_free( + load_task + ); + + return; + } + + /* + * MainWindow et Workspace doivent être détachés de l'ancien modèle + * avant sa libération. + */ + application_clear_graph( + application + ); + + application->graph_model = + graph_model; + + main_window_set_graph( + application->main_window, + application->graph_model + ); + + status_message = + g_strdup_printf( + "Graphe chargé : %" G_GUINT64_FORMAT + " entité(s), %" G_GUINT64_FORMAT + " relation(s).", + investigation_graph_model_get_entity_count( + application->graph_model + ), + investigation_graph_model_get_relation_count( + application->graph_model + ) + ); + + main_window_set_status( + application->main_window, + status_message != NULL + ? status_message + : "Graphe chargé." + ); + + g_free( + status_message + ); + + investigation_graph_load_task_free( + load_task + ); +} + +/** + * @brief Démarre le chargement asynchrone du graphe d'une enquête. + */ +static void application_start_graph_loading( + Application *application, + const char *database_path +) +{ + InvestigationGraphLoadTask *load_task = + NULL; + + ApplicationGraphLoadContext *context = + NULL; + + GError *error = + NULL; + + if (application == NULL || + application->main_window == NULL) + { + return; + } + + /* + * Cette opération invalide l'ancienne génération et libère + * l'ancien chargement avant d'en créer un nouveau. + */ + application_cancel_graph_loading( + application + ); + + application_clear_graph( + application + ); + + if (database_path == NULL || + database_path[0] == '\0') + { + main_window_set_graph_error( + application->main_window, + "Le chemin de la base SQLite est invalide." + ); + + return; + } + + main_window_set_graph_loading( + application->main_window + ); + + load_task = + investigation_graph_load_task_new( + database_path, + &error + ); + + if (load_task == NULL) + { + main_window_set_graph_error( + application->main_window, + error != NULL + ? error->message + : "Impossible de préparer le chargement du graphe." + ); + + g_clear_error( + &error + ); + + return; + } + + context = + g_try_new0( + ApplicationGraphLoadContext, + 1 + ); + + if (context == NULL) + { + investigation_graph_load_task_free( + load_task + ); + + main_window_set_graph_error( + application->main_window, + "Impossible d'allouer le contexte de chargement." + ); + + return; + } + + context->application = + application; + + context->generation = + application->graph_load_generation; + + application->graph_load_task = + load_task; + + if (!investigation_graph_load_task_start( + load_task, + application_on_graph_loaded, + context, + application_graph_load_context_free, + &error + )) + { + /* + * En cas d'échec immédiat, la tâche n'a pas pris possession + * du contexte fourni. + */ + application->graph_load_task = + NULL; + + application_graph_load_context_free( + context + ); + + investigation_graph_load_task_free( + load_task + ); + + main_window_set_graph_error( + application->main_window, + error != NULL + ? error->message + : "Impossible de démarrer le chargement du graphe." + ); + + g_clear_error( + &error + ); + } +} + /** * @brief Vérifie que l’enquête active correspond à un chemin racine. */ @@ -972,6 +1356,7 @@ static gboolean application_install_session( const InvestigationRecord *record = NULL; const char *root_path = NULL; + const char *database_path = NULL; const char *investigation_name = NULL; GError *evidence_error = NULL; @@ -1002,12 +1387,19 @@ static gboolean application_install_session( project ); + database_path = + investigation_project_get_database_path( + project + ); + investigation_name = investigation_record_get_name( record ); if (root_path == NULL || root_path[0] == '\0' || + database_path == NULL || + database_path[0] == '\0' || investigation_name == NULL || investigation_name[0] == '\0') { @@ -1055,6 +1447,11 @@ static gboolean application_install_session( TRUE ); + application_start_graph_loading( + application, + database_path + ); + /* * L'ancienne liste ne doit jamais rester affichée après * un changement d'enquête. @@ -3930,6 +4327,14 @@ void application_free( application->task_manager ); + application_cancel_graph_loading( + application + ); + + application_clear_graph( + application + ); + investigation_tree_model_free( application->tree_model ); diff --git a/src/views/main_window.c b/src/views/main_window.c index e7f1970..f20c063 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -853,6 +853,67 @@ void main_window_set_investigation( g_free(window_title); } + +void main_window_set_graph_loading( + MainWindow *main_window +) +{ + if (main_window == NULL) + { + return; + } + + workspace_set_graph_loading( + main_window->workspace + ); +} + +void main_window_set_graph( + MainWindow *main_window, + const InvestigationGraphModel *graph_model +) +{ + if (main_window == NULL) + { + return; + } + + workspace_set_graph( + main_window->workspace, + graph_model + ); +} + +void main_window_set_graph_error( + MainWindow *main_window, + const char *message +) +{ + if (main_window == NULL) + { + return; + } + + workspace_set_graph_error( + main_window->workspace, + message + ); +} + +void main_window_clear_graph( + MainWindow *main_window +) +{ + if (main_window == NULL) + { + return; + } + + workspace_clear_graph( + main_window->workspace + ); +} + void main_window_set_status( MainWindow *main_window, const char *status_text @@ -1112,6 +1173,10 @@ void main_window_free( NULL, NULL ); + + main_window_clear_graph( + main_window + ); } workspace_free( diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index a4e042f..45582ce 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -5,12 +5,28 @@ #include "widgets/workspace.h" +#include "models/investigation_graph_model.h" + #include #define WORKSPACE_PAGE_WELCOME "welcome" #define WORKSPACE_PAGE_NODE_INFORMATION "node-information" #define WORKSPACE_PAGE_EVIDENCE_INFORMATION \ "evidence-information" +#define WORKSPACE_PAGE_GRAPH_STATE \ + "graph-state" + +/** + * @brief État métier représenté par la page du graphe. + */ +typedef enum +{ + WORKSPACE_GRAPH_STATE_EMPTY, + WORKSPACE_GRAPH_STATE_LOADING, + WORKSPACE_GRAPH_STATE_READY, + WORKSPACE_GRAPH_STATE_ERROR +} WorkspaceGraphState; + /** * @struct Workspace * @brief Représentation interne de la zone de travail. @@ -21,9 +37,11 @@ struct Workspace GtkWidget *stack; GtkWidget *welcome_page; - GtkWidget *welcome_title_label; - GtkWidget *welcome_status_label; - GtkWidget *welcome_instruction_label; + + GtkWidget *graph_state_page; + GtkWidget *graph_spinner; + GtkWidget *graph_title_label; + GtkWidget *graph_details_label; GtkWidget *node_page; @@ -56,6 +74,9 @@ struct Workspace GtkWidget *node_type_label; GtkWidget *node_parent_label; GtkWidget *node_children_label; + + const InvestigationGraphModel *graph_model; + WorkspaceGraphState graph_state; }; /** @@ -226,9 +247,12 @@ static const char *workspace_get_integrity_status_text( } /** - * @brief Affiche la page d'accueil. + * @brief Affiche l'état principal courant du Workspace. + * + * Sans enquête, la page d'accueil est utilisée. Lorsqu'un chargement, un + * graphe ou une erreur existe, la page d'état du graphe est affichée. */ -static void workspace_show_welcome( +static void workspace_show_default( Workspace *workspace ) { @@ -237,9 +261,20 @@ static void workspace_show_welcome( return; } + if (workspace->graph_state == + WORKSPACE_GRAPH_STATE_EMPTY) + { + gtk_stack_set_visible_child_name( + GTK_STACK(workspace->stack), + WORKSPACE_PAGE_WELCOME + ); + + return; + } + gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), - WORKSPACE_PAGE_WELCOME + WORKSPACE_PAGE_GRAPH_STATE ); } @@ -348,33 +383,6 @@ Workspace *workspace_new(void) GTK_ALIGN_CENTER ); - workspace->welcome_title_label = gtk_label_new( - "Labfy Investigation" - ); - - workspace->welcome_status_label = gtk_label_new( - "Aucune enquête ouverte" - ); - - workspace->welcome_instruction_label = gtk_label_new( - "Sélectionnez ou créez une enquête" - ); - - gtk_box_append( - GTK_BOX(workspace->welcome_page), - workspace->welcome_title_label - ); - - gtk_box_append( - GTK_BOX(workspace->welcome_page), - workspace->welcome_status_label - ); - - gtk_box_append( - GTK_BOX(workspace->welcome_page), - workspace->welcome_instruction_label - ); - gtk_stack_add_named( GTK_STACK(workspace->stack), workspace->welcome_page, @@ -767,7 +775,119 @@ Workspace *workspace_new(void) WORKSPACE_PAGE_EVIDENCE_INFORMATION ); - workspace_show_welcome(workspace); + /* + * Page temporaire de chargement et de résumé du graphe. + * Le futur canvas interactif remplacera cette représentation. + */ + workspace->graph_state_page = + gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 12 + ); + + if (workspace->graph_state_page == NULL) + { + workspace_free( + workspace + ); + + return NULL; + } + + gtk_widget_set_halign( + workspace->graph_state_page, + GTK_ALIGN_CENTER + ); + + gtk_widget_set_valign( + workspace->graph_state_page, + GTK_ALIGN_CENTER + ); + + workspace->graph_spinner = + gtk_spinner_new(); + + workspace->graph_title_label = + gtk_label_new( + NULL + ); + + workspace->graph_details_label = + gtk_label_new( + NULL + ); + + if (workspace->graph_spinner == NULL || + workspace->graph_title_label == NULL || + workspace->graph_details_label == NULL) + { + workspace_free( + workspace + ); + + return NULL; + } + + gtk_widget_add_css_class( + workspace->graph_title_label, + "title-2" + ); + + gtk_label_set_wrap( + GTK_LABEL( + workspace->graph_details_label + ), + TRUE + ); + + gtk_label_set_justify( + GTK_LABEL( + workspace->graph_details_label + ), + GTK_JUSTIFY_CENTER + ); + + gtk_widget_set_visible( + workspace->graph_spinner, + FALSE + ); + + gtk_box_append( + GTK_BOX( + workspace->graph_state_page + ), + workspace->graph_spinner + ); + + gtk_box_append( + GTK_BOX( + workspace->graph_state_page + ), + workspace->graph_title_label + ); + + gtk_box_append( + GTK_BOX( + workspace->graph_state_page + ), + workspace->graph_details_label + ); + + gtk_stack_add_named( + GTK_STACK(workspace->stack), + workspace->graph_state_page, + WORKSPACE_PAGE_GRAPH_STATE + ); + + workspace->graph_model = + NULL; + + workspace->graph_state = + WORKSPACE_GRAPH_STATE_EMPTY; + + workspace_show_default( + workspace + ); return workspace; } @@ -820,7 +940,7 @@ void workspace_set_selected_node( if (node == NULL) { - workspace_show_welcome(workspace); + workspace_show_default(workspace); return; } @@ -944,7 +1064,7 @@ void workspace_set_selected_evidence( if (evidence_record == NULL) { - workspace_show_welcome( + workspace_show_default( workspace ); @@ -960,7 +1080,7 @@ void workspace_set_selected_evidence( if (workspace->selected_evidence_identifier == NULL) { - workspace_show_welcome( + workspace_show_default( workspace ); @@ -1102,6 +1222,273 @@ void workspace_set_selected_evidence( ); } +void workspace_set_graph_loading( + Workspace *workspace +) +{ + if (workspace == NULL) + { + return; + } + + g_clear_pointer( + &workspace->selected_evidence_identifier, + g_free + ); + + if (workspace->verify_evidence_button != NULL) + { + gtk_widget_set_sensitive( + workspace->verify_evidence_button, + FALSE + ); + } + + workspace->graph_model = + NULL; + + workspace->graph_state = + WORKSPACE_GRAPH_STATE_LOADING; + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_title_label + ), + "Chargement de l’enquête…" + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_details_label + ), + "Lecture des entités et des relations" + ); + + gtk_widget_set_visible( + workspace->graph_spinner, + TRUE + ); + + gtk_spinner_start( + GTK_SPINNER( + workspace->graph_spinner + ) + ); + + workspace_show_default( + workspace + ); +} + +void workspace_set_graph( + Workspace *workspace, + const InvestigationGraphModel *graph_model +) +{ + char *summary_text = + NULL; + + if (workspace == NULL) + { + return; + } + + if (graph_model == NULL) + { + workspace_clear_graph( + workspace + ); + + return; + } + + g_clear_pointer( + &workspace->selected_evidence_identifier, + g_free + ); + + if (workspace->verify_evidence_button != NULL) + { + gtk_widget_set_sensitive( + workspace->verify_evidence_button, + FALSE + ); + } + + workspace->graph_model = + graph_model; + + workspace->graph_state = + WORKSPACE_GRAPH_STATE_READY; + + gtk_spinner_stop( + GTK_SPINNER( + workspace->graph_spinner + ) + ); + + gtk_widget_set_visible( + workspace->graph_spinner, + FALSE + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_title_label + ), + "Graphe chargé" + ); + + summary_text = + g_strdup_printf( + "%" G_GUINT64_FORMAT " entité(s)\n" + "%" G_GUINT64_FORMAT " relation(s)", + investigation_graph_model_get_entity_count( + graph_model + ), + investigation_graph_model_get_relation_count( + graph_model + ) + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_details_label + ), + summary_text != NULL + ? summary_text + : "Résumé indisponible" + ); + + g_free( + summary_text + ); + + workspace_show_default( + workspace + ); +} + +void workspace_set_graph_error( + Workspace *workspace, + const char *message +) +{ + if (workspace == NULL) + { + return; + } + + g_clear_pointer( + &workspace->selected_evidence_identifier, + g_free + ); + + if (workspace->verify_evidence_button != NULL) + { + gtk_widget_set_sensitive( + workspace->verify_evidence_button, + FALSE + ); + } + + workspace->graph_model = + NULL; + + workspace->graph_state = + WORKSPACE_GRAPH_STATE_ERROR; + + gtk_spinner_stop( + GTK_SPINNER( + workspace->graph_spinner + ) + ); + + gtk_widget_set_visible( + workspace->graph_spinner, + FALSE + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_title_label + ), + "Impossible de charger le graphe de l’enquête" + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_details_label + ), + message != NULL && + message[0] != '\0' + ? message + : "Aucun détail supplémentaire n’est disponible." + ); + + workspace_show_default( + workspace + ); +} + +void workspace_clear_graph( + Workspace *workspace +) +{ + if (workspace == NULL) + { + return; + } + + g_clear_pointer( + &workspace->selected_evidence_identifier, + g_free + ); + + if (workspace->verify_evidence_button != NULL) + { + gtk_widget_set_sensitive( + workspace->verify_evidence_button, + FALSE + ); + } + + workspace->graph_model = + NULL; + + workspace->graph_state = + WORKSPACE_GRAPH_STATE_EMPTY; + + gtk_spinner_stop( + GTK_SPINNER( + workspace->graph_spinner + ) + ); + + gtk_widget_set_visible( + workspace->graph_spinner, + FALSE + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_title_label + ), + "" + ); + + gtk_label_set_text( + GTK_LABEL( + workspace->graph_details_label + ), + "" + ); + + workspace_show_default( + workspace + ); +} + void workspace_set_verify_evidence_callback( Workspace *workspace, WorkspaceVerifyEvidenceCallback callback, @@ -1127,6 +1514,9 @@ void workspace_free(Workspace *workspace) return; } + workspace->graph_model = + NULL; + workspace->verify_evidence_callback = NULL; @@ -1140,3 +1530,4 @@ void workspace_free(Workspace *workspace) g_free(workspace); } +