feat(persons): manage attached evidence

This commit is contained in:
grayTerminal-sh 2026-07-22 23:13:56 +02:00
parent 67d8db15af
commit fb491639aa
9 changed files with 537 additions and 0 deletions

View file

@ -176,6 +176,12 @@ typedef void (*MainWindowOsintActionCallback)(
/** @brief Callback appelé pour modifier le nom affiché d'une personne. */
typedef void (*MainWindowPersonNameCallback)(const char *entity_identifier,
const char *display_name, gpointer user_data);
/** @brief Callback appelé pour gérer les preuves d'une personne. */
typedef void (*MainWindowPersonEvidenceCallback)(
const char *entity_identifier, gpointer user_data);
/** @brief Callback appelé quand une entité est sélectionnée. */
typedef void (*MainWindowEntitySelectedCallback)(
const char *entity_identifier, gpointer user_data);
/**
* @brief Définit le callback de vérification d'une preuve.
@ -299,6 +305,15 @@ void main_window_set_osint_action_callback(
/** @brief Définit le callback de modification du nom affiché. */
void main_window_set_person_name_callback(MainWindow *main_window,
MainWindowPersonNameCallback callback, gpointer user_data);
/** @brief Définit le callback de gestion des preuves d'une personne. */
void main_window_set_person_evidence_callback(MainWindow *main_window,
MainWindowPersonEvidenceCallback callback, gpointer user_data);
/** @brief Définit le callback de sélection d'une entité. */
void main_window_set_entity_selected_callback(MainWindow *main_window,
MainWindowEntitySelectedCallback callback, gpointer user_data);
/** @brief Affiche les preuves de la personne sélectionnée. */
void main_window_set_person_evidences(MainWindow *main_window,
const GPtrArray *evidence_records);
/**
* @brief Définit le callback du bouton « Nouvelle enquête ».

View file

@ -0,0 +1,29 @@
/******************************************************************************
* @file manage_entity_evidence_dialog.h
* @brief Dialogue de sélection des preuves associées à une entité.
******************************************************************************/
#ifndef LABFY_INVESTIGATION_MANAGE_ENTITY_EVIDENCE_DIALOG_H
#define LABFY_INVESTIGATION_MANAGE_ENTITY_EVIDENCE_DIALOG_H
#include "models/evidence_record.h"
#include <gtk/gtk.h>
G_BEGIN_DECLS
typedef void (*ManageEntityEvidenceDialogCallback)(
GPtrArray *selected_identifiers, gpointer user_data);
/**
* @brief Présente la sélection multiple de preuves avec aperçu.
* @param parent Fenêtre parente.
* @param evidence_records Preuves disponibles empruntées.
* @param selected_identifiers UUID déjà associés empruntés.
* @param investigation_root_path Racine de l'enquête.
* @param callback Callback recevant un tableau possédé, ou NULL à l'annulation.
* @param user_data Données empruntées du callback.
* @param error Destination facultative d'une erreur immédiate.
* @return TRUE si le dialogue a é présenté.
*/
gboolean manage_entity_evidence_dialog_present(GtkWindow *parent,
const GPtrArray *evidence_records, const GPtrArray *selected_identifiers,
const char *investigation_root_path,
ManageEntityEvidenceDialogCallback callback, gpointer user_data,
GError **error);
G_END_DECLS
#endif

View file

@ -66,6 +66,9 @@ typedef void (*EntityDetailsPanelPersonNameCallback)(
const char *display_name,
gpointer user_data
);
/** @brief Callback appelé pour gérer les preuves d'une personne. */
typedef void (*EntityDetailsPanelPersonEvidenceCallback)(
const char *entity_identifier, gpointer user_data);
/**
* @brief Crée un volet de détails fermé.
@ -168,6 +171,17 @@ void entity_details_panel_set_person_name_callback(
EntityDetailsPanelPersonNameCallback callback,
gpointer user_data
);
/** @brief Définit le callback de gestion des preuves d'une personne. */
void entity_details_panel_set_person_evidence_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelPersonEvidenceCallback callback, gpointer user_data);
/**
* @brief Affiche les preuves associées à la personne courante.
* @param details_panel Volet à mettre à jour.
* @param evidence_records Tableau emprunté de EvidenceRecord, ou NULL.
*/
void entity_details_panel_set_person_evidences(
EntityDetailsPanel *details_panel, const GPtrArray *evidence_records);
/**
* @brief Indique si le volet est actuellement ouvert.

View file

@ -120,6 +120,12 @@ typedef void (*WorkspaceOsintActionCallback)(
/** @brief Callback appelé pour modifier le nom affiché d'une personne. */
typedef void (*WorkspacePersonNameCallback)(const char *entity_identifier,
const char *display_name, gpointer user_data);
/** @brief Callback appelé pour gérer les preuves d'une personne. */
typedef void (*WorkspacePersonEvidenceCallback)(
const char *entity_identifier, gpointer user_data);
/** @brief Callback appelé quand une entité devient sélectionnée. */
typedef void (*WorkspaceEntitySelectedCallback)(
const char *entity_identifier, gpointer user_data);
/** @brief Callback appelé pour modifier les métadonnées d'une preuve. */
typedef void (*WorkspaceEditEvidenceCallback)(
@ -377,6 +383,15 @@ void workspace_set_graph_loading(
/** @brief Définit le callback de modification du nom affiché. */
void workspace_set_person_name_callback(Workspace *workspace,
WorkspacePersonNameCallback callback, gpointer user_data);
/** @brief Définit le callback de gestion des preuves d'une personne. */
void workspace_set_person_evidence_callback(Workspace *workspace,
WorkspacePersonEvidenceCallback callback, gpointer user_data);
/** @brief Définit le callback de sélection d'une entité. */
void workspace_set_entity_selected_callback(Workspace *workspace,
WorkspaceEntitySelectedCallback callback, gpointer user_data);
/** @brief Affiche les preuves de la personne sélectionnée. */
void workspace_set_person_evidences(Workspace *workspace,
const GPtrArray *evidence_records);
/**
* @brief Affiche le graphe chargé avec sa disposition persistée.

View file

@ -39,6 +39,7 @@
#include "views/osint_dns_review_dialog.h"
#include "views/osint_execution_history_dialog.h"
#include "dao/evidence_dao.h"
#include "dao/evidence_entity_dao.h"
#include "dao/entity_dao.h"
#include "dao/relation_dao.h"
#include "dao/relation_evidence_dao.h"
@ -58,6 +59,7 @@
#include "views/create_social_account_dialog.h"
#include "views/create_person_dialog.h"
#include "views/eml_analysis_dialog.h"
#include "views/manage_entity_evidence_dialog.h"
#include <gtk/gtk.h>
#include <errno.h>
@ -5176,6 +5178,144 @@ static void application_on_person_name_changed(const char *entity_identifier,
investigation_project_get_database_path(project));
}
/** @brief Charge et affiche les preuves liées à l'entité sélectionnée. */
static void application_on_entity_selected(const char *entity_identifier,
gpointer user_data)
{
Application *application = user_data; Database *database = NULL;
EvidenceEntityDao *link_dao = NULL; EvidenceDao *evidence_dao = NULL;
GPtrArray *identifiers = NULL; GPtrArray *records = NULL;
GError *error = NULL;
if (application == NULL || application->session == NULL ||
entity_identifier == NULL) return;
database = investigation_session_get_database(application->session);
link_dao = evidence_entity_dao_new(database, &error);
if (link_dao == NULL) goto cleanup;
evidence_dao = evidence_dao_new(database, &error);
if (evidence_dao == NULL) goto cleanup;
identifiers = evidence_entity_dao_list_evidence_identifiers(link_dao,
entity_identifier, &error);
if (identifiers == NULL) goto cleanup;
records = g_ptr_array_new_with_free_func((GDestroyNotify)
evidence_record_free);
for (guint index = 0; index < identifiers->len; index++)
{
EvidenceRecord *record = evidence_dao_find_by_identifier(evidence_dao,
g_ptr_array_index(identifiers, index), &error);
if (record == NULL) goto cleanup;
g_ptr_array_add(records, record);
}
main_window_set_person_evidences(application->main_window, records);
cleanup:
if (error != NULL)
main_window_set_person_evidences(application->main_window, NULL);
g_clear_error(&error); g_clear_pointer(&records, g_ptr_array_unref);
g_clear_pointer(&identifiers, g_ptr_array_unref);
evidence_dao_free(evidence_dao); evidence_entity_dao_free(link_dao);
}
/** @brief Contexte possédé par la gestion des preuves d'une personne. */
typedef struct { Application *application; char *entity_identifier; }
ApplicationPersonEvidenceContext;
/** @brief Libère le contexte de gestion des preuves. */
static void application_person_evidence_context_free(
ApplicationPersonEvidenceContext *context)
{
if (context == NULL) return;
g_free(context->entity_identifier); g_free(context);
}
/** @brief Synchronise atomiquement les preuves choisies. */
static void application_on_person_evidences_selected(GPtrArray *selected,
gpointer user_data)
{
ApplicationPersonEvidenceContext *context = user_data;
Application *application = context != NULL ? context->application : NULL;
Database *database = NULL; EvidenceEntityDao *dao = NULL;
GPtrArray *existing = NULL; GError *error = NULL;
gboolean active = FALSE;
if (selected == NULL || application == NULL || application->session == NULL)
goto cleanup;
database = investigation_session_get_database(application->session);
dao = evidence_entity_dao_new(database, &error);
if (dao == NULL || !database_transaction_begin(database)) goto failure;
active = TRUE;
existing = evidence_entity_dao_list_evidence_identifiers(dao,
context->entity_identifier, &error);
if (existing == NULL) goto failure;
for (guint index = 0; index < existing->len; index++)
{
const char *identifier = g_ptr_array_index(existing, index);
gboolean retained = FALSE;
for (guint wanted = 0; wanted < selected->len; wanted++)
if (g_strcmp0(identifier, g_ptr_array_index(selected, wanted)) == 0)
retained = TRUE;
if (!retained && !evidence_entity_dao_unlink(dao, identifier,
context->entity_identifier, &error)) goto failure;
}
for (guint index = 0; index < selected->len; index++)
{
const char *identifier = g_ptr_array_index(selected, index);
gboolean exists = FALSE;
if (!evidence_entity_dao_exists(dao, identifier,
context->entity_identifier, &exists, &error) ||
(!exists && !evidence_entity_dao_link(dao, identifier,
context->entity_identifier, &error))) goto failure;
}
if (!database_transaction_commit(database)) goto failure;
active = FALSE;
main_window_set_status(application->main_window,
"Pièces jointes de la personne enregistrées.");
application_on_entity_selected(context->entity_identifier, application);
goto cleanup;
failure:
if (active) database_transaction_rollback(database);
application_present_error(application, "Pièces jointes non enregistrées",
error != NULL ? error->message : "La sélection n'a pas pu être enregistrée.");
cleanup:
g_clear_error(&error); g_clear_pointer(&existing, g_ptr_array_unref);
g_clear_pointer(&selected, g_ptr_array_unref);
evidence_entity_dao_free(dao);
application_person_evidence_context_free(context);
}
/** @brief Ouvre le gestionnaire des preuves de la personne sélectionnée. */
static void application_on_person_evidence_requested(
const char *entity_identifier, gpointer user_data)
{
Application *application = user_data; Database *database = NULL;
EvidenceDao *evidence_dao = NULL; EvidenceEntityDao *link_dao = NULL;
GPtrArray *records = NULL; GPtrArray *selected = NULL;
ApplicationPersonEvidenceContext *context = NULL;
const InvestigationProject *project = NULL; GError *error = NULL;
if (application == NULL || application->session == NULL) return;
database = investigation_session_get_database(application->session);
project = investigation_session_get_project(application->session);
evidence_dao = evidence_dao_new(database, &error);
if (evidence_dao != NULL) records = evidence_dao_list_all(evidence_dao, &error);
link_dao = evidence_entity_dao_new(database, &error);
if (link_dao != NULL) selected =
evidence_entity_dao_list_evidence_identifiers(link_dao,
entity_identifier, &error);
if (records == NULL || selected == NULL || project == NULL) goto failure;
context = g_new0(ApplicationPersonEvidenceContext, 1);
context->application = application;
context->entity_identifier = g_strdup(entity_identifier);
if (context->entity_identifier == NULL ||
!manage_entity_evidence_dialog_present(main_window_get_window(
application->main_window), records, selected,
investigation_project_get_root_path(project),
application_on_person_evidences_selected, context, &error))
goto failure;
context = NULL; goto cleanup;
failure:
application_person_evidence_context_free(context); context = NULL;
application_present_error(application, "Pièces jointes indisponibles",
error != NULL ? error->message : "Le gestionnaire n'a pas pu être ouvert.");
cleanup:
g_clear_error(&error); g_clear_pointer(&records, g_ptr_array_unref);
g_clear_pointer(&selected, g_ptr_array_unref);
evidence_dao_free(evidence_dao); evidence_entity_dao_free(link_dao);
}
/** @brief Libère le contexte d'édition d'une relation. */
static void application_edit_relation_context_free(
ApplicationEditRelationContext *context)
@ -6852,6 +6992,10 @@ static void application_on_activate(
application_on_person_confidence_changed, application);
main_window_set_person_name_callback(application->main_window,
application_on_person_name_changed, application);
main_window_set_person_evidence_callback(application->main_window,
application_on_person_evidence_requested, application);
main_window_set_entity_selected_callback(application->main_window,
application_on_entity_selected, application);
main_window_set_osint_action_callback(
application->main_window,

View file

@ -139,6 +139,10 @@ struct MainWindow
gpointer person_confidence_user_data;
MainWindowPersonNameCallback person_name_callback;
gpointer person_name_user_data;
MainWindowPersonEvidenceCallback person_evidence_callback;
gpointer person_evidence_user_data;
MainWindowEntitySelectedCallback entity_selected_callback;
gpointer entity_selected_user_data;
MainWindowOsintActionCallback
osint_action_callback;
@ -574,6 +578,24 @@ static void main_window_on_person_name_changed(const char *entity_identifier,
main_window->person_name_callback(entity_identifier, display_name,
main_window->person_name_user_data);
}
/** @brief Relaie la gestion des preuves d'une personne. */
static void main_window_on_person_evidence_requested(const char *identifier,
gpointer user_data)
{
MainWindow *window = user_data;
if (window != NULL && window->person_evidence_callback != NULL)
window->person_evidence_callback(identifier,
window->person_evidence_user_data);
}
/** @brief Relaie la sélection d'une entité vers le contrôleur. */
static void main_window_on_entity_selected(const char *identifier,
gpointer user_data)
{
MainWindow *window = user_data;
if (window != NULL && window->entity_selected_callback != NULL)
window->entity_selected_callback(identifier,
window->entity_selected_user_data);
}
/**
* @brief Relaie la demande de vérification provenant du Workspace.
@ -923,6 +945,10 @@ MainWindow *main_window_new(
main_window_on_person_confidence_changed, main_window);
workspace_set_person_name_callback(main_window->workspace,
main_window_on_person_name_changed, main_window);
workspace_set_person_evidence_callback(main_window->workspace,
main_window_on_person_evidence_requested, main_window);
workspace_set_entity_selected_callback(main_window->workspace,
main_window_on_entity_selected, main_window);
workspace_set_osint_action_callback(
main_window->workspace,
@ -1761,6 +1787,26 @@ void main_window_set_person_name_callback(MainWindow *main_window,
main_window->person_name_callback = callback;
main_window->person_name_user_data = user_data;
}
void main_window_set_person_evidence_callback(MainWindow *main_window,
MainWindowPersonEvidenceCallback callback, gpointer user_data)
{
if (main_window == NULL) return;
main_window->person_evidence_callback = callback;
main_window->person_evidence_user_data = user_data;
}
void main_window_set_entity_selected_callback(MainWindow *main_window,
MainWindowEntitySelectedCallback callback, gpointer user_data)
{
if (main_window == NULL) return;
main_window->entity_selected_callback = callback;
main_window->entity_selected_user_data = user_data;
}
void main_window_set_person_evidences(MainWindow *main_window,
const GPtrArray *records)
{
if (main_window == NULL) return;
workspace_set_person_evidences(main_window->workspace, records);
}
void main_window_set_quit_callback(
MainWindow *main_window,

View file

@ -0,0 +1,170 @@
/******************************************************************************
* @file manage_entity_evidence_dialog.c
* @brief Dialogue de sélection des preuves associées à une entité.
******************************************************************************/
#include "views/manage_entity_evidence_dialog.h"
#include <gio/gio.h>
typedef struct
{
char *identifier;
char *path;
GtkCheckButton *check;
} EvidenceChoice;
typedef struct
{
GtkWindow *window;
GtkStack *preview;
GtkPicture *picture;
GtkVideo *video;
GPtrArray *choices;
ManageEntityEvidenceDialogCallback callback;
gpointer callback_data;
gboolean completed;
} ManageEvidenceState;
/** @brief Libère une option privée. */
static void evidence_choice_free(gpointer data)
{
EvidenceChoice *choice = data;
if (choice == NULL) return;
g_free(choice->identifier); g_free(choice->path); g_free(choice);
}
/** @brief Arrête et efface l'aperçu courant. */
static void manage_evidence_clear_preview(ManageEvidenceState *state)
{
GtkMediaStream *stream = NULL;
if (state == NULL || state->video == NULL) return;
stream = gtk_video_get_media_stream(state->video);
if (stream != NULL) gtk_media_stream_pause(stream);
gtk_video_set_media_stream(state->video, NULL);
gtk_picture_set_paintable(state->picture, NULL);
}
/** @brief Libère l'état du dialogue. */
static void manage_evidence_state_free(gpointer data)
{
ManageEvidenceState *state = data;
if (state == NULL) return;
manage_evidence_clear_preview(state);
g_ptr_array_unref(state->choices); g_free(state);
}
/** @brief Affiche l'aperçu de l'option manipulée. */
static void manage_evidence_on_toggled(GtkCheckButton *button, gpointer data)
{
ManageEvidenceState *state = data;
EvidenceChoice *choice = g_object_get_data(G_OBJECT(button), "choice");
GFile *file = NULL; GFileInfo *info = NULL; char *mime = NULL;
const char *content_type = NULL;
if (state == NULL || choice == NULL || choice->path == NULL) return;
manage_evidence_clear_preview(state);
file = g_file_new_for_path(choice->path);
info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
if (info != NULL) content_type = g_file_info_get_content_type(info);
if (content_type != NULL) mime = g_content_type_get_mime_type(content_type);
if (mime != NULL && g_str_has_prefix(mime, "image/"))
{
gtk_picture_set_filename(state->picture, choice->path);
gtk_stack_set_visible_child_name(state->preview, "image");
}
else if (mime != NULL && g_str_has_prefix(mime, "video/"))
{
GtkMediaStream *stream = gtk_media_file_new_for_filename(choice->path);
gtk_video_set_media_stream(state->video, stream); g_object_unref(stream);
gtk_stack_set_visible_child_name(state->preview, "video");
}
else gtk_stack_set_visible_child_name(state->preview, "unsupported");
g_free(mime); g_clear_object(&info); g_clear_object(&file);
}
/** @brief Termine le dialogue en renvoyant la sélection. */
static void manage_evidence_on_save(GtkButton *button, gpointer data)
{
ManageEvidenceState *state = data;
GPtrArray *selected = g_ptr_array_new_with_free_func(g_free);
(void) button;
for (guint index = 0; index < state->choices->len; index++)
{
EvidenceChoice *choice = g_ptr_array_index(state->choices, index);
if (gtk_check_button_get_active(choice->check))
g_ptr_array_add(selected, g_strdup(choice->identifier));
}
state->completed = TRUE; state->callback(selected, state->callback_data);
gtk_window_close(state->window);
}
/** @brief Termine le dialogue par une annulation. */
static gboolean manage_evidence_on_close(GtkWindow *window, gpointer data)
{
ManageEvidenceState *state = data; (void) window;
if (!state->completed) state->callback(NULL, state->callback_data);
return FALSE;
}
gboolean manage_entity_evidence_dialog_present(GtkWindow *parent,
const GPtrArray *records, const GPtrArray *selected_ids,
const char *root_path, ManageEntityEvidenceDialogCallback callback,
gpointer callback_data, GError **error)
{
ManageEvidenceState *state = NULL; GtkWidget *root = NULL;
GtkWidget *content = NULL; GtkWidget *list = NULL; GtkWidget *scroll = NULL;
GtkWidget *save = NULL;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (!GTK_IS_WINDOW(parent) || records == NULL || root_path == NULL ||
callback == NULL) return FALSE;
state = g_new0(ManageEvidenceState, 1);
state->choices = g_ptr_array_new_with_free_func(evidence_choice_free);
state->callback = callback; state->callback_data = callback_data;
state->window = GTK_WINDOW(gtk_window_new());
gtk_window_set_title(state->window, "Pièces jointes de la personne");
gtk_window_set_transient_for(state->window, parent);
gtk_window_set_modal(state->window, TRUE);
gtk_window_set_default_size(state->window, 900, 620);
root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
scroll = gtk_scrolled_window_new(); gtk_widget_set_size_request(scroll, 380, 480);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
for (guint index = 0; index < records->len; index++)
{
const EvidenceRecord *record = g_ptr_array_index(records, index);
EvidenceChoice *choice = g_new0(EvidenceChoice, 1); gboolean active = FALSE;
char *label = g_strdup_printf("%s — %s",
evidence_record_get_original_name(record),
evidence_record_get_type_identifier(record));
char *candidate = g_build_filename(root_path,
evidence_record_get_relative_path(record), NULL);
char *canonical_root = g_canonicalize_filename(root_path, NULL);
char *canonical_path = g_canonicalize_filename(candidate, NULL);
choice->identifier = g_strdup(evidence_record_get_identifier(record));
if (canonical_root != NULL && canonical_path != NULL &&
g_str_has_prefix(canonical_path, canonical_root) &&
(canonical_path[strlen(canonical_root)] == G_DIR_SEPARATOR ||
canonical_path[strlen(canonical_root)] == '\0'))
choice->path = g_strdup(canonical_path);
g_free(canonical_path); g_free(canonical_root); g_free(candidate);
choice->check = GTK_CHECK_BUTTON(gtk_check_button_new_with_label(label));
g_free(label);
for (guint selected = 0; selected_ids != NULL && selected < selected_ids->len; selected++)
if (g_strcmp0(choice->identifier, g_ptr_array_index(selected_ids, selected)) == 0) active = TRUE;
gtk_check_button_set_active(choice->check, active);
g_object_set_data(G_OBJECT(choice->check), "choice", choice);
g_signal_connect(choice->check, "toggled", G_CALLBACK(manage_evidence_on_toggled), state);
gtk_box_append(GTK_BOX(list), GTK_WIDGET(choice->check));
g_ptr_array_add(state->choices, choice);
}
state->preview = GTK_STACK(gtk_stack_new());
state->picture = GTK_PICTURE(gtk_picture_new());
gtk_picture_set_content_fit(state->picture, GTK_CONTENT_FIT_CONTAIN);
state->video = GTK_VIDEO(gtk_video_new());
gtk_stack_add_named(state->preview, gtk_label_new("Sélectionnez une preuve."), "unsupported");
gtk_stack_add_named(state->preview, GTK_WIDGET(state->picture), "image");
gtk_stack_add_named(state->preview, GTK_WIDGET(state->video), "video");
gtk_widget_set_hexpand(GTK_WIDGET(state->preview), TRUE);
gtk_box_append(GTK_BOX(content), scroll); gtk_box_append(GTK_BOX(content), GTK_WIDGET(state->preview));
save = gtk_button_new_with_label("Enregistrer les pièces jointes");
gtk_widget_add_css_class(save, "suggested-action"); gtk_widget_set_halign(save, GTK_ALIGN_END);
gtk_box_append(GTK_BOX(root), content); gtk_box_append(GTK_BOX(root), save);
gtk_window_set_child(state->window, root);
g_object_set_data_full(G_OBJECT(state->window), "state", state, manage_evidence_state_free);
g_signal_connect(save, "clicked", G_CALLBACK(manage_evidence_on_save), state);
g_signal_connect(state->window, "close-request", G_CALLBACK(manage_evidence_on_close), state);
gtk_window_present(state->window); return TRUE;
}

View file

@ -6,6 +6,7 @@
#include "widgets/entity_details_panel.h"
#include "models/entity_record.h"
#include "models/evidence_record.h"
#include <glib.h>
@ -35,6 +36,8 @@ struct EntityDetailsPanel
GtkWidget *person_confidence_button;
GtkEntry *person_name_entry;
GtkWidget *person_name_button;
GtkWidget *person_evidence_button;
GtkWidget *person_evidence_summary_label;
char *selected_entity_identifier;
@ -54,7 +57,19 @@ struct EntityDetailsPanel
gpointer person_confidence_user_data;
EntityDetailsPanelPersonNameCallback person_name_callback;
gpointer person_name_user_data;
EntityDetailsPanelPersonEvidenceCallback person_evidence_callback;
gpointer person_evidence_user_data;
};
/** @brief Relaie la demande de gestion des preuves d'une personne. */
static void entity_details_panel_on_person_evidence_clicked(GtkButton *button,
gpointer user_data)
{
EntityDetailsPanel *panel = user_data; (void) button;
if (panel != NULL && panel->person_evidence_callback != NULL &&
panel->selected_entity_identifier != NULL)
panel->person_evidence_callback(panel->selected_entity_identifier,
panel->person_evidence_user_data);
}
/** @brief Relaie le nouveau nom affiché explicitement enregistré. */
static void entity_details_panel_on_person_name_clicked(GtkButton *button,
@ -795,6 +810,22 @@ EntityDetailsPanel *entity_details_panel_new(void)
GTK_WIDGET(details_panel->person_name_entry));
gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_name_button);
details_panel->person_evidence_button = gtk_button_new_with_label(
"Gérer les pièces jointes");
details_panel->person_evidence_summary_label = gtk_label_new(
"Pièces jointes : chargement…");
gtk_label_set_xalign(GTK_LABEL(
details_panel->person_evidence_summary_label), 0.0F);
gtk_label_set_wrap(GTK_LABEL(
details_panel->person_evidence_summary_label), TRUE);
gtk_label_set_selectable(GTK_LABEL(
details_panel->person_evidence_summary_label), TRUE);
gtk_box_append(GTK_BOX(details_panel->person_role_box),
gtk_label_new("Preuves associées à cette personne"));
gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_evidence_summary_label);
gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_evidence_button);
gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box);
g_signal_connect(details_panel->person_role_dropdown, "notify::selected",
G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel);
@ -803,6 +834,8 @@ EntityDetailsPanel *entity_details_panel_new(void)
details_panel);
g_signal_connect(details_panel->person_name_button, "clicked",
G_CALLBACK(entity_details_panel_on_person_name_clicked), details_panel);
g_signal_connect(details_panel->person_evidence_button, "clicked",
G_CALLBACK(entity_details_panel_on_person_evidence_clicked), details_panel);
if (details_panel->entity_value_label == NULL ||
details_panel->entity_type_label == NULL ||
@ -1205,6 +1238,36 @@ void entity_details_panel_set_person_name_callback(
details_panel->person_name_callback = callback;
details_panel->person_name_user_data = user_data;
}
void entity_details_panel_set_person_evidence_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelPersonEvidenceCallback callback, gpointer user_data)
{
if (details_panel == NULL) return;
details_panel->person_evidence_callback = callback;
details_panel->person_evidence_user_data = user_data;
}
void entity_details_panel_set_person_evidences(EntityDetailsPanel *panel,
const GPtrArray *records)
{
GString *text = NULL;
if (panel == NULL || panel->person_evidence_summary_label == NULL) return;
text = g_string_new(NULL);
if (records == NULL || records->len == 0)
g_string_append(text, "Aucune pièce jointe");
else
for (guint index = 0; index < records->len; index++)
{
const EvidenceRecord *record = g_ptr_array_index(records, index);
if (record == NULL) continue;
if (text->len > 0) g_string_append_c(text, '\n');
g_string_append_printf(text, "• %s — %s",
evidence_record_get_original_name(record),
evidence_record_get_type_identifier(record));
}
gtk_label_set_text(GTK_LABEL(panel->person_evidence_summary_label),
text->str);
g_string_free(text, TRUE);
}
gboolean entity_details_panel_is_open(
const EntityDetailsPanel *details_panel

View file

@ -143,6 +143,10 @@ struct Workspace
gpointer person_confidence_user_data;
WorkspacePersonNameCallback person_name_callback;
gpointer person_name_user_data;
WorkspacePersonEvidenceCallback person_evidence_callback;
gpointer person_evidence_user_data;
WorkspaceEntitySelectedCallback entity_selected_callback;
gpointer entity_selected_user_data;
WorkspaceOsintActionCallback
osint_action_callback;
@ -741,6 +745,10 @@ static void workspace_on_graph_node_selected(
{
workspace->osint_selection_context =
osint_selection_context_new_entity(entity_record, NULL);
if (workspace->entity_selected_callback != NULL)
workspace->entity_selected_callback(
entity_record_get_identifier(entity_record),
workspace->entity_selected_user_data);
}
if (workspace->entity_details_panel != NULL)
@ -920,6 +928,15 @@ static void workspace_on_person_name_changed(const char *entity_identifier,
workspace->person_name_callback(entity_identifier, display_name,
workspace->person_name_user_data);
}
/** @brief Relaie la gestion des preuves d'une personne. */
static void workspace_on_person_evidence_requested(const char *identifier,
gpointer user_data)
{
Workspace *workspace = user_data;
if (workspace != NULL && workspace->person_evidence_callback != NULL)
workspace->person_evidence_callback(identifier,
workspace->person_evidence_user_data);
}
/**
* @brief Désélectionne le nœud lorsque le volet est fermé manuellement.
@ -1855,6 +1872,9 @@ Workspace *workspace_new(void)
entity_details_panel_set_person_name_callback(
workspace->entity_details_panel, workspace_on_person_name_changed,
workspace);
entity_details_panel_set_person_evidence_callback(
workspace->entity_details_panel,
workspace_on_person_evidence_requested, workspace);
gtk_overlay_set_child(
GTK_OVERLAY(
@ -3335,6 +3355,27 @@ void workspace_set_person_name_callback(Workspace *workspace,
workspace->person_name_callback = callback;
workspace->person_name_user_data = user_data;
}
void workspace_set_person_evidence_callback(Workspace *workspace,
WorkspacePersonEvidenceCallback callback, gpointer user_data)
{
if (workspace == NULL) return;
workspace->person_evidence_callback = callback;
workspace->person_evidence_user_data = user_data;
}
void workspace_set_entity_selected_callback(Workspace *workspace,
WorkspaceEntitySelectedCallback callback, gpointer user_data)
{
if (workspace == NULL) return;
workspace->entity_selected_callback = callback;
workspace->entity_selected_user_data = user_data;
}
void workspace_set_person_evidences(Workspace *workspace,
const GPtrArray *records)
{
if (workspace == NULL) return;
entity_details_panel_set_person_evidences(
workspace->entity_details_panel, records);
}
void workspace_reset_graph_layout(
Workspace *workspace