feat: integrate asynchronous graph loading

This commit is contained in:
grayTerminal-sh 2026-07-21 12:24:41 +02:00
parent b0a2c18cbf
commit fb44188e01
5 changed files with 1037 additions and 77 deletions

View file

@ -15,6 +15,11 @@
#include <gtk/gtk.h>
/**
* @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.
*

View file

@ -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 <gtk/gtk.h>
/**
* @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

View file

@ -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 lenquê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
);

View file

@ -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(

View file

@ -5,12 +5,28 @@
#include "widgets/workspace.h"
#include "models/investigation_graph_model.h"
#include <glib.h>
#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 lenquê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 lenquête"
);
gtk_label_set_text(
GTK_LABEL(
workspace->graph_details_label
),
message != NULL &&
message[0] != '\0'
? message
: "Aucun détail supplémentaire nest 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);
}