feat: integrate asynchronous graph loading
This commit is contained in:
parent
b0a2c18cbf
commit
fb44188e01
5 changed files with 1037 additions and 77 deletions
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,10 +261,21 @@ 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_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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue