diff --git a/include/views/main_window.h b/include/views/main_window.h index 6100184..e2863c9 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -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 ». diff --git a/include/views/manage_entity_evidence_dialog.h b/include/views/manage_entity_evidence_dialog.h new file mode 100644 index 0000000..7ad9eb4 --- /dev/null +++ b/include/views/manage_entity_evidence_dialog.h @@ -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 +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 été 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 diff --git a/include/widgets/entity_details_panel.h b/include/widgets/entity_details_panel.h index e7ba9ce..509fce9 100644 --- a/include/widgets/entity_details_panel.h +++ b/include/widgets/entity_details_panel.h @@ -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. diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index 78b1d29..7c54677 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -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. diff --git a/src/core/application.c b/src/core/application.c index dccf37b..276b68f 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -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 #include @@ -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, diff --git a/src/views/main_window.c b/src/views/main_window.c index 3a3b450..7dcd10c 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -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, diff --git a/src/views/manage_entity_evidence_dialog.c b/src/views/manage_entity_evidence_dialog.c new file mode 100644 index 0000000..2dc1ff0 --- /dev/null +++ b/src/views/manage_entity_evidence_dialog.c @@ -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 +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; +} diff --git a/src/widgets/entity_details_panel.c b/src/widgets/entity_details_panel.c index 0797b23..e3d3fdc 100644 --- a/src/widgets/entity_details_panel.c +++ b/src/widgets/entity_details_panel.c @@ -6,6 +6,7 @@ #include "widgets/entity_details_panel.h" #include "models/entity_record.h" +#include "models/evidence_record.h" #include @@ -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 diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index dfea72d..2f28f09 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -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