/****************************************************************************** * @file workspace.c * @brief Implémentation de la zone principale de travail. ******************************************************************************/ #include "widgets/workspace.h" #include "models/entity_record.h" #include "models/investigation_graph_model.h" #include "models/osint_selection_context.h" #include "models/osint_action_catalog.h" #include "models/relation_record.h" #include "widgets/entity_details_panel.h" #include "widgets/investigation_graph_view.h" #include "widgets/evidence_preview_widget.h" #include "widgets/ocr_provenance_overlay.h" #include "views/evidence_identity_ocr_dialog.h" #include #include #define WORKSPACE_PAGE_WELCOME "welcome" #define WORKSPACE_PAGE_NODE_INFORMATION "node-information" #define WORKSPACE_PAGE_EVIDENCE_INFORMATION \ "evidence-information" #define WORKSPACE_PAGE_GRAPH_STATE \ "graph-state" #define WORKSPACE_PAGE_GRAPH_VIEW \ "graph-view" /** * @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. */ struct Workspace { GtkWidget *root_widget; GtkWidget *stack; GtkWidget *welcome_page; GtkWidget *graph_state_page; GtkWidget *graph_spinner; GtkWidget *graph_title_label; GtkWidget *graph_details_label; GtkWidget *graph_view_page; GtkWidget *graph_toolbar; GtkWidget *reset_graph_layout_button; GtkWidget *osint_tools_button; GtkWidget *osint_tools_popover; GtkWidget *osint_tools_context_label; GtkWidget *osint_tools_status_label; GtkWidget *osint_tools_actions_box; GtkWidget *graph_help_button; GtkWidget *graph_help_popover; InvestigationGraphView *graph_view; EntityDetailsPanel *entity_details_panel; GtkWidget *relation_details_panel; GtkWidget *relation_details_title_label; GtkWidget *relation_details_summary_label; GtkWidget *relation_evidences_label; GtkWidget *edit_relation_button; GtkWidget *delete_relation_button; GtkWidget *close_relation_details_button; char *selected_relation_identifier; GtkWidget *node_page; GtkWidget *evidence_page; GtkWidget *evidence_name_label; GtkWidget *evidence_type_label; GtkWidget *evidence_integrity_label; GtkWidget *evidence_size_label; GtkWidget *evidence_imported_at_label; GtkWidget *evidence_collected_at_label; GtkWidget *evidence_source_label; GtkWidget *evidence_description_label; GtkWidget *evidence_relative_path_label; GtkWidget *evidence_internal_name_label; GtkWidget *evidence_identifier_label; GtkWidget *evidence_sha256_label; GtkWidget *evidence_observations_box; GtkWidget *verify_evidence_button; GtkWidget *edit_evidence_button; GtkWidget *analyze_eml_button; GtkWidget *analyze_rib_button; GtkWidget *extract_metadata_button; GtkWidget *recover_pdf_password_button; GtkWidget *analyze_identity_button; GtkWidget *identity_ocr_section; GtkDropDown *identity_ocr_selector; GtkStringList *identity_ocr_labels; GtkLabel *identity_ocr_summary; GtkTextBuffer *identity_ocr_raw; GtkTextBuffer *identity_ocr_corrected; GtkBox *identity_ocr_fields; GtkWidget *revise_identity_button; GtkWidget *rerun_identity_button; OcrProvenanceOverlay *identity_ocr_overlay; GPtrArray *identity_ocr_records; WorkspaceIdentityOcrCallback identity_ocr_callback; gpointer identity_ocr_user_data; EvidencePreviewWidget *evidence_preview; char *selected_evidence_identifier; WorkspaceVerifyEvidenceCallback verify_evidence_callback; gpointer verify_evidence_user_data; WorkspaceEditEvidenceCallback edit_evidence_callback; gpointer edit_evidence_user_data; WorkspaceAnalyzeEmlCallback analyze_eml_callback; gpointer analyze_eml_user_data; WorkspaceAnalyzeRibCallback analyze_rib_callback; gpointer analyze_rib_user_data; WorkspaceExtractMetadataCallback extract_metadata_callback; gpointer extract_metadata_user_data; WorkspaceRecoverPdfPasswordCallback recover_pdf_password_callback; gpointer recover_pdf_password_user_data; WorkspaceObservationRemoveCallback observation_remove_callback; gpointer observation_remove_user_data; WorkspaceGraphNodeMovedCallback graph_node_moved_callback; gpointer graph_node_moved_user_data; WorkspaceGraphTransformChangedCallback graph_transform_changed_callback; gpointer graph_transform_changed_user_data; WorkspaceExtractionDropCallback extraction_drop_callback; gpointer extraction_drop_user_data; WorkspaceResetGraphLayoutCallback reset_graph_layout_callback; gpointer reset_graph_layout_user_data; WorkspaceAddRelationCallback add_relation_callback; gpointer add_relation_user_data; WorkspaceEditRelationCallback edit_relation_callback; WorkspaceDeleteRelationCallback delete_relation_callback; gpointer delete_relation_user_data; WorkspaceRelationSelectedCallback relation_selected_callback; gpointer relation_selected_user_data; gpointer edit_relation_user_data; WorkspacePersonRoleCallback person_role_callback; gpointer person_role_user_data; WorkspacePersonConfidenceCallback person_confidence_callback; gpointer person_confidence_user_data; WorkspacePersonNameCallback person_name_callback; gpointer person_name_user_data; WorkspacePersonEvidenceCallback person_evidence_callback; gpointer person_evidence_user_data; WorkspaceEvidenceActivatedCallback evidence_activated_callback; gpointer evidence_activated_user_data; WorkspaceEntitySelectedCallback entity_selected_callback; gpointer entity_selected_user_data; WorkspaceOsintActionCallback osint_action_callback; gpointer osint_action_user_data; GtkWidget *node_name_label; GtkWidget *node_path_label; GtkWidget *node_type_label; GtkWidget *node_parent_label; GtkWidget *node_children_label; const InvestigationGraphModel *graph_model; const InvestigationGraphLayout *graph_layout; OsintSelectionContext *osint_selection_context; OsintActionCatalog *osint_action_catalog; WorkspaceGraphState graph_state; gboolean saved_graph_transform_valid; double saved_graph_zoom; double saved_graph_offset_x; double saved_graph_offset_y; }; /** * @brief Traite localement l'aperçu ou relaie une action OSINT exécutable. */ static void workspace_on_osint_action_clicked( GtkButton *button, gpointer user_data ) { Workspace *workspace = user_data; const char *action_identifier = g_object_get_data( G_OBJECT(button), "osint-action-identifier" ); const char *target_value = NULL; const char *target_identifier = NULL; if (workspace == NULL || workspace->osint_tools_status_label == NULL) { return; } if (g_strcmp0(action_identifier, "selection-preview") == 0) { gtk_label_set_text( GTK_LABEL(workspace->osint_tools_status_label), "Aperçu local : aucun outil externe exécuté." ); return; } target_value = osint_selection_context_get_value( workspace->osint_selection_context ); target_identifier = osint_selection_context_get_identifier( workspace->osint_selection_context ); if (workspace->osint_action_callback == NULL || action_identifier == NULL || target_identifier == NULL || target_value == NULL) { gtk_label_set_text( GTK_LABEL(workspace->osint_tools_status_label), "Impossible de transmettre cette action OSINT." ); return; } gtk_label_set_text( GTK_LABEL(workspace->osint_tools_status_label), g_str_has_prefix(action_identifier, "history-") ? "Ouverture de l'historique enregistré." : "Exécution lancée ; suivez sa progression dans les tâches." ); gtk_popover_popdown( GTK_POPOVER(workspace->osint_tools_popover) ); workspace->osint_action_callback( action_identifier, target_identifier, target_value, workspace->osint_action_user_data ); } /** * @brief Actualise le menu OSINT selon la sélection du graphe. * * Aucun outil n'est encore exposé par ce ticket. Le menu prépare le point * d'entrée contextuel et explique explicitement cet état à l'utilisateur. */ static void workspace_update_osint_tools_menu( Workspace *workspace, const OsintSelectionContext *context ) { const char *selection_kind = NULL; const char *selection_identifier = NULL; char *context_text = NULL; GPtrArray *compatible_actions = NULL; GtkWidget *child = NULL; if (workspace == NULL || workspace->osint_tools_button == NULL || workspace->osint_tools_context_label == NULL || workspace->osint_tools_status_label == NULL || workspace->osint_tools_actions_box == NULL) { return; } while ((child = gtk_widget_get_first_child( workspace->osint_tools_actions_box )) != NULL) { gtk_box_remove(GTK_BOX(workspace->osint_tools_actions_box), child); } if (osint_selection_context_get_kind(context) == OSINT_SELECTION_CONTEXT_KIND_ENTITY) { selection_kind = "Entité"; selection_identifier = osint_selection_context_get_identifier(context); } else if (osint_selection_context_get_kind(context) == OSINT_SELECTION_CONTEXT_KIND_RELATION) { selection_kind = "Relation"; selection_identifier = osint_selection_context_get_identifier(context); } gtk_widget_set_sensitive( workspace->osint_tools_button, selection_identifier != NULL && selection_identifier[0] != '\0' ); if (selection_identifier == NULL || selection_identifier[0] == '\0') { gtk_label_set_text( GTK_LABEL( workspace->osint_tools_context_label ), "Sélectionnez un nœud du graphe." ); gtk_label_set_text( GTK_LABEL( workspace->osint_tools_status_label ), "Les outils compatibles apparaîtront ici." ); return; } compatible_actions = osint_action_catalog_list_compatible( workspace->osint_action_catalog, context ); { GtkWidget *history_button = gtk_button_new_with_label( "Historique OSINT" ); const char *history_action = osint_selection_context_get_kind(context) == OSINT_SELECTION_CONTEXT_KIND_RELATION ? "history-relation" : "history-entity"; gtk_widget_set_tooltip_text( history_button, "Consulter les exécutions OSINT enregistrées pour cette sélection" ); g_object_set_data_full( G_OBJECT(history_button), "osint-action-identifier", g_strdup(history_action), g_free ); g_signal_connect( history_button, "clicked", G_CALLBACK(workspace_on_osint_action_clicked), workspace ); gtk_box_append( GTK_BOX(workspace->osint_tools_actions_box), history_button ); } for (guint action_index = 0; compatible_actions != NULL && action_index < compatible_actions->len; action_index++) { const OsintAction *action = g_ptr_array_index( compatible_actions, action_index ); const char *tool_version = osint_action_get_tool_version(action); char *button_label = tool_version != NULL && tool_version[0] != '\0' ? g_strdup_printf("%s — %s", osint_action_get_label(action), tool_version) : g_strdup(osint_action_get_label(action)); GtkWidget *action_button = gtk_button_new_with_label(button_label); g_free(button_label); if (action_button == NULL) { continue; } gtk_widget_set_sensitive( action_button, osint_action_is_available(action) ); gtk_widget_set_tooltip_text( action_button, osint_action_is_available(action) ? osint_action_get_description(action) : osint_action_get_unavailable_reason(action) ); g_object_set_data_full( G_OBJECT(action_button), "osint-action-identifier", g_strdup(osint_action_get_identifier(action)), g_free ); g_signal_connect( action_button, "clicked", G_CALLBACK(workspace_on_osint_action_clicked), workspace ); gtk_box_append( GTK_BOX(workspace->osint_tools_actions_box), action_button ); } context_text = g_strdup_printf( "%s sélectionnée\n%s", selection_kind, selection_identifier ); gtk_label_set_text( GTK_LABEL( workspace->osint_tools_context_label ), context_text != NULL ? context_text : selection_kind ); gtk_label_set_text( GTK_LABEL( workspace->osint_tools_status_label ), compatible_actions != NULL && compatible_actions->len > 0U ? "Actions compatibles avec la sélection." : "Aucune action OSINT compatible." ); g_free( context_text ); g_clear_pointer(&compatible_actions, g_ptr_array_unref); } /** @brief Ouvre le menu OSINT depuis le bouton de la fiche entité. */ static void workspace_on_entity_osint_requested(const char *entity_identifier, gpointer user_data) { Workspace *workspace = user_data; (void) entity_identifier; if (workspace == NULL || workspace->osint_tools_popover == NULL) return; workspace_update_osint_tools_menu(workspace, workspace->osint_selection_context); gtk_popover_popup(GTK_POPOVER(workspace->osint_tools_popover)); } /** * @brief Ajoute une ligne de métadonnée dans la grille. * * @return Label GTK destiné à recevoir la valeur. */ static GtkWidget *workspace_add_evidence_field( GtkGrid *grid, gint row, const char *title ) { GtkWidget *title_label = NULL; GtkWidget *value_label = NULL; if (grid == NULL || title == NULL || title[0] == '\0') { return NULL; } title_label = gtk_label_new( title ); value_label = gtk_label_new( NULL ); if (title_label == NULL || value_label == NULL) { g_clear_object( &value_label ); g_clear_object( &title_label ); return NULL; } gtk_label_set_xalign( GTK_LABEL(title_label), 0.0F ); gtk_widget_set_valign( title_label, GTK_ALIGN_START ); gtk_widget_add_css_class( title_label, "dim-label" ); gtk_label_set_xalign( GTK_LABEL(value_label), 0.0F ); gtk_label_set_wrap( GTK_LABEL(value_label), TRUE ); gtk_label_set_wrap_mode( GTK_LABEL(value_label), PANGO_WRAP_WORD_CHAR ); gtk_label_set_selectable( GTK_LABEL(value_label), TRUE ); gtk_widget_set_hexpand( value_label, TRUE ); gtk_widget_set_halign( value_label, GTK_ALIGN_FILL ); gtk_widget_set_valign( value_label, GTK_ALIGN_START ); gtk_grid_attach( grid, title_label, 0, row, 1, 1 ); gtk_grid_attach( grid, value_label, 1, row, 1, 1 ); return value_label; } /** * @brief Définit une valeur avec un texte de remplacement. */ static void workspace_set_field_text( GtkWidget *label, const char *value, const char *fallback ) { if (label == NULL) { return; } gtk_label_set_text( GTK_LABEL(label), value != NULL && value[0] != '\0' ? value : fallback ); } /** * @brief Convertit un statut d'intégrité en texte utilisateur. */ static const char *workspace_get_integrity_status_text( EvidenceIntegrityStatus integrity_status ) { switch (integrity_status) { case EVIDENCE_INTEGRITY_STATUS_UNKNOWN: return "Non vérifiée"; case EVIDENCE_INTEGRITY_STATUS_VALID: return "Valide"; case EVIDENCE_INTEGRITY_STATUS_MISSING: return "Fichier absent"; case EVIDENCE_INTEGRITY_STATUS_MODIFIED: return "Fichier modifié"; case EVIDENCE_INTEGRITY_STATUS_ERROR: return "Erreur de vérification"; default: return "Statut inconnu"; } } /** @brief Arrête et détache le média vidéo actuellement affiché. */ static void workspace_clear_evidence_preview(Workspace *workspace) { if (workspace == NULL) return; evidence_preview_widget_clear(workspace->evidence_preview); } /** @brief Convertit le statut d'une relation en texte utilisateur. */ static const char *workspace_get_relation_status_text(RelationStatus status) { switch (status) { case RELATION_STATUS_ACTIVE: return "Active"; case RELATION_STATUS_ARCHIVED: return "Archivée"; case RELATION_STATUS_DELETED: return "Supprimée"; case RELATION_STATUS_DISPUTED: return "Contestée"; case RELATION_STATUS_UNKNOWN: default: return "Inconnu"; } } /** * @brief Transmet la sélection du graphe au volet de détails. */ static void workspace_on_graph_node_moved( const char *entity_identifier, double x, double y, gpointer user_data ) { Workspace *workspace = user_data; if (workspace == NULL || workspace->graph_node_moved_callback == NULL || entity_identifier == NULL || entity_identifier[0] == '\0') { return; } workspace->graph_node_moved_callback( entity_identifier, x, y, workspace->graph_node_moved_user_data ); } static void workspace_on_graph_transform_changed(gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->graph_transform_changed_callback != NULL) workspace->graph_transform_changed_callback( workspace->graph_transform_changed_user_data); } /** * @brief Transmet la sélection du graphe au volet de détails. */ static void workspace_on_graph_node_selected( const EntityRecord *entity_record, const RelationRecord *relation_record, gpointer user_data ) { Workspace *workspace = user_data; if (workspace == NULL) { return; } g_clear_pointer( &workspace->osint_selection_context, osint_selection_context_free ); g_clear_pointer(&workspace->selected_relation_identifier, g_free); if (entity_record != NULL) { 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) { entity_details_panel_set_entity( workspace->entity_details_panel, entity_record ); } if (workspace->relation_details_panel != NULL) { gtk_widget_set_can_target(workspace->relation_details_panel, relation_record != NULL); gtk_widget_set_visible( workspace->relation_details_panel, relation_record != NULL ); } if (relation_record != NULL && workspace->relation_details_title_label != NULL && workspace->relation_details_summary_label != NULL) { workspace->selected_relation_identifier = g_strdup( relation_record_get_identifier(relation_record)); const EntityRecord *source_entity = investigation_graph_model_find_entity( workspace->graph_model, relation_record_get_source_entity_identifier(relation_record) ); const EntityRecord *target_entity = investigation_graph_model_find_entity( workspace->graph_model, relation_record_get_target_entity_identifier(relation_record) ); workspace->osint_selection_context = osint_selection_context_new_relation( relation_record, source_entity, target_entity, NULL ); const char *label = relation_record_get_label(relation_record); char *summary = g_strdup_printf( "%s → %s\n\nType : %s\nConfiance : %d %%\nStatut : %s\n" "Justification : %s\nCréée le : %s\nModifiée le : %s\nUUID : %s", source_entity != NULL ? entity_record_get_value(source_entity) : "Source inconnue", target_entity != NULL ? entity_record_get_value(target_entity) : "Cible inconnue", relation_record_get_relation_type_label(relation_record), relation_record_get_confidence(relation_record), workspace_get_relation_status_text( relation_record_get_status(relation_record)), relation_record_get_justification(relation_record) != NULL ? relation_record_get_justification(relation_record) : "Non renseignée", relation_record_get_created_at(relation_record), relation_record_get_updated_at(relation_record), relation_record_get_identifier(relation_record) ); gtk_label_set_text( GTK_LABEL(workspace->relation_details_title_label), label != NULL && label[0] != '\0' ? label : "Relation" ); gtk_label_set_text( GTK_LABEL(workspace->relation_details_summary_label), summary != NULL ? summary : "Informations indisponibles" ); g_free(summary); if (workspace->relation_selected_callback != NULL) workspace->relation_selected_callback( workspace->selected_relation_identifier, workspace->relation_selected_user_data); } workspace_update_osint_tools_menu( workspace, workspace->osint_selection_context ); } /** @brief Relaie la demande de modification de la relation affichée. */ static void workspace_on_edit_relation_clicked(GtkButton *button, gpointer user_data) { Workspace *workspace = user_data; (void) button; if (workspace == NULL || workspace->edit_relation_callback == NULL || workspace->selected_relation_identifier == NULL) { return; } workspace->edit_relation_callback(workspace->selected_relation_identifier, workspace->edit_relation_user_data); } /** @brief Relaie la demande de suppression de la relation affichée. */ static void workspace_on_delete_relation_clicked(GtkButton *button, gpointer user_data) { Workspace *workspace = user_data; (void) button; if (workspace == NULL || workspace->delete_relation_callback == NULL || workspace->selected_relation_identifier == NULL) return; workspace->delete_relation_callback(workspace->selected_relation_identifier, workspace->delete_relation_user_data); } /** @brief Ferme le volet de relation et désélectionne le graphe. */ static void workspace_on_close_relation_details_clicked(GtkButton *button, gpointer user_data) { Workspace *workspace = user_data; (void) button; if (workspace != NULL) investigation_graph_view_clear_selection(workspace->graph_view); } /** * @brief Relaie la demande d'ajout d'une relation. */ static void workspace_on_add_relation_requested( const char *source_entity_identifier, gpointer user_data ) { Workspace *workspace = user_data; if (workspace == NULL || workspace->add_relation_callback == NULL || source_entity_identifier == NULL || !g_uuid_string_is_valid( source_entity_identifier )) { return; } workspace->add_relation_callback( source_entity_identifier, workspace->add_relation_user_data ); } /** @brief Relaie le changement de catégorie d'une personne. */ static void workspace_on_person_role_changed(const char *entity_identifier, PersonRole role, gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->person_role_callback != NULL) workspace->person_role_callback(entity_identifier, role, workspace->person_role_user_data); } /** @brief Relaie la modification de confiance d'une personne. */ static void workspace_on_person_confidence_changed( const char *entity_identifier, gint confidence, gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->person_confidence_callback != NULL) workspace->person_confidence_callback(entity_identifier, confidence, workspace->person_confidence_user_data); } /** @brief Relaie la modification du nom affiché d'une personne. */ static void workspace_on_person_name_changed(const char *entity_identifier, const char *display_name, gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->person_name_callback != NULL) 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); } static void workspace_on_evidence_activated(const char *identifier, gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->evidence_activated_callback != NULL) workspace->evidence_activated_callback(identifier, workspace->evidence_activated_user_data); } static void workspace_on_extraction_dropped(const char *file_path, const char *target_entity_identifier, gpointer user_data) { Workspace *workspace = user_data; if (workspace != NULL && workspace->extraction_drop_callback != NULL) workspace->extraction_drop_callback(file_path, target_entity_identifier, workspace->extraction_drop_user_data); } /** * @brief Désélectionne le nœud lorsque le volet est fermé manuellement. */ static void workspace_on_entity_details_closed( gpointer user_data ) { Workspace *workspace = user_data; if (workspace == NULL || workspace->graph_view == NULL) { return; } investigation_graph_view_clear_selection( workspace->graph_view ); } /** * @brief Affiche l'état principal courant du Workspace. * * Sans enquête, la page d'accueil est utilisée. Le chargement et les erreurs * utilisent la page d'état. Un graphe prêt utilise la vue graphique. */ static void workspace_show_default( Workspace *workspace ) { if (workspace == NULL) { return; } switch (workspace->graph_state) { case WORKSPACE_GRAPH_STATE_EMPTY: gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), WORKSPACE_PAGE_WELCOME ); return; case WORKSPACE_GRAPH_STATE_READY: gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), WORKSPACE_PAGE_GRAPH_VIEW ); return; case WORKSPACE_GRAPH_STATE_LOADING: case WORKSPACE_GRAPH_STATE_ERROR: default: gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), WORKSPACE_PAGE_GRAPH_STATE ); return; } } /** * @brief Transmet la demande de vérification de la preuve affichée. */ static void workspace_on_reset_graph_layout_clicked( GtkButton *button, gpointer user_data ) { Workspace *workspace = user_data; (void) button; if (workspace == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY || workspace->graph_model == NULL || workspace->reset_graph_layout_callback == NULL) { return; } workspace->reset_graph_layout_callback( workspace->reset_graph_layout_user_data ); } /** * @brief Transmet la demande de vérification de la preuve affichée. */ static void workspace_on_verify_evidence_clicked( GtkButton *button, gpointer user_data ) { Workspace *workspace = user_data; (void) button; if (workspace == NULL || workspace->verify_evidence_callback == NULL || workspace->selected_evidence_identifier == NULL || workspace->selected_evidence_identifier[0] == '\0') { return; } workspace->verify_evidence_callback( workspace->selected_evidence_identifier, workspace->verify_evidence_user_data ); } /** @brief Transmet la demande de modification de la preuve affichée. */ static void workspace_on_edit_evidence_clicked( GtkButton *button, gpointer user_data ) { Workspace *workspace = user_data; (void) button; if (workspace == NULL || workspace->edit_evidence_callback == NULL || workspace->selected_evidence_identifier == NULL) return; workspace->edit_evidence_callback( workspace->selected_evidence_identifier, workspace->edit_evidence_user_data); } /** @brief Transmet la demande d'analyse de la preuve EML affichée. */ static void workspace_on_analyze_eml_clicked(GtkButton *button, gpointer data) { Workspace *workspace = data; (void) button; if (workspace != NULL && workspace->analyze_eml_callback != NULL && workspace->selected_evidence_identifier != NULL) workspace->analyze_eml_callback(workspace->selected_evidence_identifier, workspace->analyze_eml_user_data); } /** @brief Transmet la demande d'analyse OCR du RIB affiché. */ static void workspace_on_analyze_rib_clicked(GtkButton *button, gpointer data) { Workspace *workspace = data; (void) button; if (workspace != NULL && workspace->analyze_rib_callback != NULL && workspace->selected_evidence_identifier != NULL) workspace->analyze_rib_callback(workspace->selected_evidence_identifier, workspace->analyze_rib_user_data); } /** @brief Transmet la demande d'extraction des métadonnées. */ static void workspace_on_extract_metadata_clicked(GtkButton *button, gpointer data) { Workspace *workspace = data; (void) button; if (workspace != NULL && workspace->extract_metadata_callback != NULL && workspace->selected_evidence_identifier != NULL) workspace->extract_metadata_callback( workspace->selected_evidence_identifier, workspace->extract_metadata_user_data); } /** @brief Transmet la demande de récupération du mot de passe PDF. */ static void workspace_on_recover_pdf_password_clicked(GtkButton *button, gpointer data) { Workspace *workspace = data; (void) button; if (workspace != NULL && workspace->recover_pdf_password_callback != NULL && workspace->selected_evidence_identifier != NULL) workspace->recover_pdf_password_callback( workspace->selected_evidence_identifier, workspace->recover_pdf_password_user_data); } WorkspaceIdentityOcrRecord *workspace_identity_ocr_record_new( IdentityOcrRun *owned_run, const char *person_identifier, const char *executed_at, const char *text_relative_path, const char *text_sha256, const char *tsv_relative_path, const char *tsv_sha256) { if (owned_run == NULL) return NULL; WorkspaceIdentityOcrRecord *record = g_new0(WorkspaceIdentityOcrRecord, 1); record->run = owned_run; record->person_identifier = g_strdup(person_identifier); record->executed_at = g_strdup(executed_at); record->text_relative_path = g_strdup(text_relative_path); record->text_sha256 = g_strdup(text_sha256); record->tsv_relative_path = g_strdup(tsv_relative_path); record->tsv_sha256 = g_strdup(tsv_sha256); return record; } void workspace_identity_ocr_record_free(WorkspaceIdentityOcrRecord *record) { if (record == NULL) return; identity_ocr_run_free(record->run); g_free(record->person_identifier); g_free(record->executed_at); g_free(record->text_relative_path); g_free(record->text_sha256); g_free(record->tsv_relative_path); g_free(record->tsv_sha256); g_free(record); } static void workspace_clear_box(GtkBox *box) { GtkWidget *child = gtk_widget_get_first_child(GTK_WIDGET(box)); while (child != NULL) { GtkWidget *next = gtk_widget_get_next_sibling(child); gtk_box_remove(box, child); child = next; } } static void workspace_render_identity_ocr(Workspace *workspace) { guint selected = gtk_drop_down_get_selected( workspace->identity_ocr_selector); workspace_clear_box(workspace->identity_ocr_fields); gtk_text_buffer_set_text(workspace->identity_ocr_raw, "", -1); gtk_text_buffer_set_text(workspace->identity_ocr_corrected, "", -1); ocr_provenance_overlay_clear(workspace->identity_ocr_overlay, selected + 1U); if (workspace->identity_ocr_records == NULL || selected >= workspace->identity_ocr_records->len) { gtk_label_set_text(workspace->identity_ocr_summary, "Aucune analyse OCR sélectionnée."); gtk_widget_set_sensitive(workspace->revise_identity_button, FALSE); return; } WorkspaceIdentityOcrRecord *record = g_ptr_array_index( workspace->identity_ocr_records, selected); IdentityOcrRun *run = record->run; char *summary = g_strdup_printf( "Run actif : %s\nUTC : %s\nPersonne liée : %s\n" "Document : %s — %s — page %u\nLangues : %s\n" "Notes : %s\nArtefact texte : %s\nSHA-256 texte : %s\n" "Artefact TSV : %s\nSHA-256 TSV : %s\nSHA-256 preuve : %s", identity_ocr_run_get_identifier(run), record->executed_at != NULL ? record->executed_at : "inconnue", record->person_identifier != NULL ? record->person_identifier : "indisponible", identity_ocr_run_get_document_type(run), identity_ocr_run_get_document_side(run), identity_ocr_run_get_page(run), identity_ocr_run_get_languages(run), identity_ocr_run_get_factual_notes(run) != NULL ? identity_ocr_run_get_factual_notes(run) : "Aucune note", record->text_relative_path, record->text_sha256, record->tsv_relative_path, record->tsv_sha256, identity_ocr_run_get_expected_sha256(run)); gtk_label_set_text(workspace->identity_ocr_summary, summary); g_free(summary); gtk_text_buffer_set_text(workspace->identity_ocr_raw, identity_ocr_run_get_raw_text(run), -1); gtk_text_buffer_set_text(workspace->identity_ocr_corrected, identity_ocr_run_get_corrected_transcription(run) != NULL ? identity_ocr_run_get_corrected_transcription(run) : "Aucune transcription corrigée", -1); const GPtrArray *fields = identity_ocr_run_get_fields(run); for (guint index = 0; fields != NULL && index < fields->len; index++) { IdentityFieldObservation *field = g_ptr_array_index( (GPtrArray *) fields, index); char *field_text = g_strdup_printf( "%s — brut : %s — corrigé : %s — origine : %s — statut : %d", identity_field_observation_get_code(field), identity_field_observation_get_raw_value(field) != NULL ? identity_field_observation_get_raw_value(field) : "absent", identity_field_observation_get_corrected_value(field) != NULL ? identity_field_observation_get_corrected_value(field) : "absente", identity_field_observation_get_origin(field), identity_field_observation_get_status(field)); GtkWidget *label = gtk_label_new(field_text); gtk_label_set_wrap(GTK_LABEL(label), TRUE); gtk_label_set_xalign(GTK_LABEL(label), 0.0f); gtk_box_append(workspace->identity_ocr_fields, label); g_free(field_text); if (!ocr_provenance_overlay_has_region( workspace->identity_ocr_overlay)) ocr_provenance_overlay_set_field( workspace->identity_ocr_overlay, field, selected + 1U); } gtk_widget_set_sensitive(workspace->revise_identity_button, TRUE); } static void workspace_on_identity_ocr_selected( GObject *object, GParamSpec *pspec, gpointer data) { (void) object; (void) pspec; workspace_render_identity_ocr(data); } static void workspace_on_identity_ocr_action( GtkButton *button, gpointer data) { Workspace *workspace = data; gboolean revise = button == GTK_BUTTON(workspace->revise_identity_button); const char *run_identifier = NULL; if (revise && workspace->identity_ocr_records != NULL) { guint selected = gtk_drop_down_get_selected( workspace->identity_ocr_selector); if (selected < workspace->identity_ocr_records->len) { WorkspaceIdentityOcrRecord *record = g_ptr_array_index( workspace->identity_ocr_records, selected); run_identifier = identity_ocr_run_get_identifier(record->run); } } if (workspace->identity_ocr_callback != NULL && (!revise || run_identifier != NULL)) workspace->identity_ocr_callback( workspace->selected_evidence_identifier, revise, run_identifier, workspace->identity_ocr_user_data); } Workspace *workspace_new(TaskManager *task_manager) { GtkWidget *evidence_content = NULL; GtkWidget *evidence_separator = NULL; GtkWidget *evidence_grid = NULL; Workspace *workspace = NULL; if (task_manager == NULL) return NULL; workspace = g_new0(Workspace, 1); workspace->root_widget = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); if (workspace->root_widget == NULL) { workspace_free(workspace); return NULL; } gtk_widget_set_hexpand( workspace->root_widget, TRUE ); gtk_widget_set_vexpand( workspace->root_widget, TRUE ); workspace->stack = gtk_stack_new(); if (workspace->stack == NULL) { workspace_free(workspace); return NULL; } gtk_widget_set_hexpand( workspace->stack, TRUE ); gtk_widget_set_vexpand( workspace->stack, TRUE ); gtk_box_append( GTK_BOX(workspace->root_widget), workspace->stack ); /* * Page d'accueil. */ workspace->welcome_page = gtk_box_new( GTK_ORIENTATION_VERTICAL, 12 ); if (workspace->welcome_page == NULL) { workspace_free(workspace); return NULL; } gtk_widget_set_halign( workspace->welcome_page, GTK_ALIGN_CENTER ); gtk_widget_set_valign( workspace->welcome_page, GTK_ALIGN_CENTER ); gtk_stack_add_named( GTK_STACK(workspace->stack), workspace->welcome_page, WORKSPACE_PAGE_WELCOME ); /* * Page d'informations sur le nœud. */ workspace->node_page = gtk_box_new( GTK_ORIENTATION_VERTICAL, 12 ); if (workspace->node_page == NULL) { workspace_free(workspace); return NULL; } gtk_widget_set_halign( workspace->node_page, GTK_ALIGN_CENTER ); gtk_widget_set_valign( workspace->node_page, GTK_ALIGN_CENTER ); workspace->node_name_label = gtk_label_new(NULL); workspace->node_path_label = gtk_label_new(NULL); workspace->node_type_label = gtk_label_new(NULL); workspace->node_parent_label = gtk_label_new(NULL); workspace->node_children_label = gtk_label_new(NULL); gtk_box_append( GTK_BOX(workspace->node_page), workspace->node_name_label ); gtk_box_append( GTK_BOX(workspace->node_page), workspace->node_path_label ); gtk_label_set_wrap( GTK_LABEL(workspace->node_path_label), TRUE ); gtk_label_set_selectable( GTK_LABEL(workspace->node_path_label), TRUE ); gtk_widget_set_halign( workspace->node_path_label, GTK_ALIGN_CENTER ); gtk_box_append( GTK_BOX(workspace->node_page), workspace->node_type_label ); gtk_box_append( GTK_BOX(workspace->node_page), workspace->node_parent_label ); gtk_box_append( GTK_BOX(workspace->node_page), workspace->node_children_label ); gtk_stack_add_named( GTK_STACK(workspace->stack), workspace->node_page, WORKSPACE_PAGE_NODE_INFORMATION ); /* * Page détaillée d'une preuve. */ workspace->evidence_page = gtk_scrolled_window_new(); if (workspace->evidence_page == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_hexpand( workspace->evidence_page, TRUE ); gtk_widget_set_vexpand( workspace->evidence_page, TRUE ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( workspace->evidence_page ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); evidence_content = gtk_box_new( GTK_ORIENTATION_VERTICAL, 8 ); if (evidence_content == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_margin_start( evidence_content, 12 ); gtk_widget_set_margin_end( evidence_content, 12 ); gtk_widget_set_margin_top( evidence_content, 12 ); gtk_widget_set_margin_bottom( evidence_content, 12 ); workspace->evidence_name_label = gtk_label_new( NULL ); if (workspace->evidence_name_label == NULL) { workspace_free( workspace ); return NULL; } gtk_label_set_xalign( GTK_LABEL( workspace->evidence_name_label ), 0.0F ); gtk_label_set_wrap( GTK_LABEL( workspace->evidence_name_label ), TRUE ); gtk_label_set_selectable( GTK_LABEL( workspace->evidence_name_label ), TRUE ); gtk_widget_add_css_class( workspace->evidence_name_label, "title-2" ); gtk_box_append( GTK_BOX(evidence_content), workspace->evidence_name_label ); workspace->evidence_preview = evidence_preview_widget_new( task_manager, NULL, NULL); if (workspace->evidence_preview == NULL) { workspace_free(workspace); return NULL; } gtk_box_append(GTK_BOX(evidence_content), evidence_preview_widget_get_widget(workspace->evidence_preview)); workspace->verify_evidence_button = gtk_button_new_with_label( "Vérifier l'intégrité" ); if (workspace->verify_evidence_button == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_halign( workspace->verify_evidence_button, GTK_ALIGN_START ); gtk_widget_set_tooltip_text( workspace->verify_evidence_button, "Recalculer le SHA-256 et le comparer à l'empreinte enregistrée" ); /* * Aucun bouton actif tant qu'aucune preuve n'est affichée. */ gtk_widget_set_sensitive( workspace->verify_evidence_button, FALSE ); g_signal_connect( workspace->verify_evidence_button, "clicked", G_CALLBACK( workspace_on_verify_evidence_clicked ), workspace ); gtk_box_append( GTK_BOX(evidence_content), workspace->verify_evidence_button ); workspace->edit_evidence_button = gtk_button_new_with_label( "Modifier les métadonnées"); gtk_widget_set_halign(workspace->edit_evidence_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE); gtk_widget_set_tooltip_text(workspace->edit_evidence_button, "Corriger le classement sans modifier le fichier original"); g_signal_connect(workspace->edit_evidence_button, "clicked", G_CALLBACK(workspace_on_edit_evidence_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->edit_evidence_button); workspace->analyze_eml_button = gtk_button_new_with_label( "Analyser l’e-mail"); gtk_widget_set_halign(workspace->analyze_eml_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->analyze_eml_button, FALSE); gtk_widget_set_tooltip_text(workspace->analyze_eml_button, "Vérifier la preuve puis lancer l’analyse complète en arrière-plan"); g_signal_connect(workspace->analyze_eml_button, "clicked", G_CALLBACK(workspace_on_analyze_eml_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_eml_button); workspace->analyze_rib_button = gtk_button_new_with_label( "Analyser le RIB par OCR"); gtk_widget_set_halign(workspace->analyze_rib_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE); g_signal_connect(workspace->analyze_rib_button, "clicked", G_CALLBACK(workspace_on_analyze_rib_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_rib_button); workspace->extract_metadata_button = gtk_button_new_with_label( "Extraire les métadonnées"); gtk_widget_set_halign(workspace->extract_metadata_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->extract_metadata_button, FALSE); gtk_widget_set_tooltip_text(workspace->extract_metadata_button, "Analyser localement une copie avec ExifTool"); g_signal_connect(workspace->extract_metadata_button, "clicked", G_CALLBACK(workspace_on_extract_metadata_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->extract_metadata_button); workspace->recover_pdf_password_button = gtk_button_new_with_label( "Récupérer le mot de passe PDF"); gtk_widget_set_halign(workspace->recover_pdf_password_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->recover_pdf_password_button, FALSE); g_signal_connect(workspace->recover_pdf_password_button, "clicked", G_CALLBACK(workspace_on_recover_pdf_password_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->recover_pdf_password_button); workspace->analyze_identity_button = gtk_button_new_with_label( "Analyser comme document d’identité"); gtk_widget_set_name(workspace->analyze_identity_button, "workspace-analyze-identity"); gtk_widget_set_halign(workspace->analyze_identity_button, GTK_ALIGN_START); gtk_widget_set_sensitive(workspace->analyze_identity_button, FALSE); g_signal_connect(workspace->analyze_identity_button, "clicked", G_CALLBACK(workspace_on_identity_ocr_action), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_identity_button); workspace->identity_ocr_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); gtk_widget_set_name(workspace->identity_ocr_section, "workspace-identity-ocr-section"); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), gtk_label_new("Analyse OCR d’identité")); workspace->identity_ocr_labels = gtk_string_list_new(NULL); workspace->identity_ocr_selector = GTK_DROP_DOWN( gtk_drop_down_new(G_LIST_MODEL(g_object_ref( workspace->identity_ocr_labels)), NULL)); gtk_widget_set_name(GTK_WIDGET(workspace->identity_ocr_selector), "workspace-identity-ocr-selector"); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), GTK_WIDGET(workspace->identity_ocr_selector)); workspace->identity_ocr_summary = GTK_LABEL(gtk_label_new(NULL)); gtk_widget_set_name(GTK_WIDGET(workspace->identity_ocr_summary), "workspace-identity-ocr-active-run"); gtk_label_set_wrap(workspace->identity_ocr_summary, TRUE); gtk_label_set_xalign(workspace->identity_ocr_summary, 0.0f); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), GTK_WIDGET(workspace->identity_ocr_summary)); GtkWidget *raw_view = gtk_text_view_new(); gtk_widget_set_name(raw_view, "workspace-identity-ocr-raw"); gtk_text_view_set_editable(GTK_TEXT_VIEW(raw_view), FALSE); workspace->identity_ocr_raw = gtk_text_view_get_buffer(GTK_TEXT_VIEW(raw_view)); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), gtk_label_new("Texte OCR brut")); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), raw_view); GtkWidget *corrected_view = gtk_text_view_new(); gtk_widget_set_name(corrected_view, "workspace-identity-ocr-corrected"); gtk_text_view_set_editable(GTK_TEXT_VIEW(corrected_view), FALSE); workspace->identity_ocr_corrected = gtk_text_view_get_buffer(GTK_TEXT_VIEW(corrected_view)); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), gtk_label_new("Transcription corrigée")); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), corrected_view); workspace->identity_ocr_fields = GTK_BOX( gtk_box_new(GTK_ORIENTATION_VERTICAL, 6)); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), GTK_WIDGET(workspace->identity_ocr_fields)); workspace->identity_ocr_overlay = ocr_provenance_overlay_new(); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), ocr_provenance_overlay_get_widget( workspace->identity_ocr_overlay)); GtkWidget *identity_actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); workspace->revise_identity_button = gtk_button_new_with_label("Réviser l’analyse OCR"); workspace->rerun_identity_button = gtk_button_new_with_label("Relancer une nouvelle analyse OCR"); gtk_widget_set_name(workspace->revise_identity_button, "workspace-revise-identity-ocr"); gtk_widget_set_name(workspace->rerun_identity_button, "workspace-rerun-identity-ocr"); g_signal_connect(workspace->revise_identity_button, "clicked", G_CALLBACK(workspace_on_identity_ocr_action), workspace); g_signal_connect(workspace->rerun_identity_button, "clicked", G_CALLBACK(workspace_on_identity_ocr_action), workspace); gtk_box_append(GTK_BOX(identity_actions), workspace->revise_identity_button); gtk_box_append(GTK_BOX(identity_actions), workspace->rerun_identity_button); gtk_box_append(GTK_BOX(workspace->identity_ocr_section), identity_actions); gtk_widget_set_visible(workspace->identity_ocr_section, FALSE); gtk_box_append(GTK_BOX(evidence_content), workspace->identity_ocr_section); g_signal_connect(workspace->identity_ocr_selector, "notify::selected", G_CALLBACK(workspace_on_identity_ocr_selected), workspace); evidence_separator = gtk_separator_new( GTK_ORIENTATION_HORIZONTAL ); gtk_box_append( GTK_BOX(evidence_content), evidence_separator ); evidence_grid = gtk_grid_new(); if (evidence_grid == NULL) { workspace_free( workspace ); return NULL; } gtk_grid_set_column_spacing( GTK_GRID(evidence_grid), 24 ); gtk_grid_set_row_spacing( GTK_GRID(evidence_grid), 12 ); gtk_widget_set_hexpand( evidence_grid, TRUE ); workspace->evidence_type_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 0, "Type" ); workspace->evidence_integrity_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 1, "Intégrité" ); workspace->evidence_size_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 2, "Taille" ); workspace->evidence_imported_at_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 3, "Importée le" ); workspace->evidence_collected_at_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 4, "Collectée le" ); workspace->evidence_source_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 5, "Source" ); workspace->evidence_description_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 6, "Description" ); workspace->evidence_relative_path_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 7, "Chemin relatif" ); workspace->evidence_internal_name_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 8, "Nom interne" ); workspace->evidence_identifier_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 9, "Identifiant" ); workspace->evidence_sha256_label = workspace_add_evidence_field( GTK_GRID(evidence_grid), 10, "SHA-256" ); GtkWidget *observations_title = gtk_label_new( "Observations extraites"); gtk_label_set_xalign(GTK_LABEL(observations_title), 0.0f); gtk_widget_add_css_class(observations_title, "heading"); workspace->evidence_observations_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); gtk_grid_attach(GTK_GRID(evidence_grid), observations_title, 0, 11, 1, 1); gtk_grid_attach(GTK_GRID(evidence_grid), workspace->evidence_observations_box, 1, 11, 1, 1); if (workspace->evidence_type_label == NULL || workspace->evidence_integrity_label == NULL || workspace->evidence_size_label == NULL || workspace->evidence_imported_at_label == NULL || workspace->evidence_collected_at_label == NULL || workspace->evidence_source_label == NULL || workspace->evidence_description_label == NULL || workspace->evidence_relative_path_label == NULL || workspace->evidence_internal_name_label == NULL || workspace->evidence_identifier_label == NULL || workspace->evidence_sha256_label == NULL || workspace->evidence_observations_box == NULL) { workspace_free( workspace ); return NULL; } gtk_box_append( GTK_BOX(evidence_content), evidence_grid ); gtk_scrolled_window_set_child( GTK_SCROLLED_WINDOW( workspace->evidence_page ), evidence_content ); gtk_stack_add_named( GTK_STACK(workspace->stack), workspace->evidence_page, WORKSPACE_PAGE_EVIDENCE_INFORMATION ); /* * Page d'état utilisée pendant le chargement ou en cas d'erreur. */ 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 ); /* * Page graphique : canvas principal et volet de détails superposé. */ workspace->graph_view_page = gtk_overlay_new(); if (workspace->graph_view_page == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_hexpand( workspace->graph_view_page, TRUE ); gtk_widget_set_vexpand( workspace->graph_view_page, TRUE ); workspace->graph_view = investigation_graph_view_new(); workspace->entity_details_panel = entity_details_panel_new(); workspace->relation_details_panel = gtk_box_new( GTK_ORIENTATION_VERTICAL, 12 ); workspace->relation_details_title_label = gtk_label_new(NULL); workspace->relation_details_summary_label = gtk_label_new(NULL); workspace->relation_evidences_label = gtk_label_new( "Pièces jointes : chargement…"); workspace->edit_relation_button = gtk_button_new_with_label("Modifier"); workspace->delete_relation_button = gtk_button_new_with_label( "Supprimer la relation"); workspace->close_relation_details_button = gtk_button_new_from_icon_name("window-close-symbolic"); if (workspace->graph_view == NULL || workspace->entity_details_panel == NULL || workspace->relation_details_panel == NULL || workspace->relation_details_title_label == NULL || workspace->relation_details_summary_label == NULL || workspace->relation_evidences_label == NULL || workspace->edit_relation_button == NULL || workspace->delete_relation_button == NULL || workspace->close_relation_details_button == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_size_request(workspace->relation_details_panel, 300, -1); gtk_widget_set_halign(workspace->relation_details_panel, GTK_ALIGN_END); gtk_widget_set_valign(workspace->relation_details_panel, GTK_ALIGN_START); gtk_widget_set_margin_end(workspace->relation_details_panel, 12); gtk_widget_set_margin_top(workspace->relation_details_panel, 56); gtk_widget_set_margin_start(workspace->relation_details_panel, 12); gtk_widget_set_margin_bottom(workspace->relation_details_panel, 12); /* `view` fournit le fond opaque du thème, comme le volet d'entité. */ gtk_widget_add_css_class(workspace->relation_details_panel, "view"); gtk_widget_add_css_class(workspace->relation_details_panel, "card"); gtk_widget_set_margin_start(workspace->relation_details_title_label, 16); gtk_widget_set_margin_end(workspace->relation_details_title_label, 16); gtk_widget_set_margin_top(workspace->relation_details_title_label, 16); gtk_label_set_xalign( GTK_LABEL(workspace->relation_details_title_label), 0.0F ); gtk_widget_add_css_class( workspace->relation_details_title_label, "title-3" ); gtk_widget_set_margin_start(workspace->relation_details_summary_label, 16); gtk_widget_set_margin_end(workspace->relation_details_summary_label, 16); gtk_widget_set_margin_bottom(workspace->relation_details_summary_label, 16); gtk_label_set_xalign( GTK_LABEL(workspace->relation_details_summary_label), 0.0F ); gtk_label_set_wrap( GTK_LABEL(workspace->relation_details_summary_label), TRUE ); gtk_label_set_selectable( GTK_LABEL(workspace->relation_details_summary_label), TRUE ); gtk_widget_set_margin_start(workspace->relation_evidences_label, 16); gtk_widget_set_margin_end(workspace->relation_evidences_label, 16); gtk_widget_set_margin_bottom(workspace->relation_evidences_label, 12); gtk_label_set_xalign(GTK_LABEL(workspace->relation_evidences_label), 0.0F); gtk_label_set_wrap(GTK_LABEL(workspace->relation_evidences_label), TRUE); gtk_label_set_selectable(GTK_LABEL(workspace->relation_evidences_label), TRUE); gtk_box_append( GTK_BOX(workspace->relation_details_panel), workspace->relation_details_title_label ); gtk_widget_set_tooltip_text(workspace->close_relation_details_button, "Fermer le volet"); gtk_widget_add_css_class(workspace->close_relation_details_button, "flat"); gtk_box_append(GTK_BOX(workspace->relation_details_panel), workspace->close_relation_details_button); gtk_box_append( GTK_BOX(workspace->relation_details_panel), workspace->relation_details_summary_label ); gtk_box_append(GTK_BOX(workspace->relation_details_panel), workspace->relation_evidences_label); gtk_box_append(GTK_BOX(workspace->relation_details_panel), workspace->edit_relation_button); gtk_widget_add_css_class(workspace->delete_relation_button, "destructive-action"); gtk_box_append(GTK_BOX(workspace->relation_details_panel), workspace->delete_relation_button); g_signal_connect(workspace->edit_relation_button, "clicked", G_CALLBACK(workspace_on_edit_relation_clicked), workspace); g_signal_connect(workspace->delete_relation_button, "clicked", G_CALLBACK(workspace_on_delete_relation_clicked), workspace); g_signal_connect(workspace->close_relation_details_button, "clicked", G_CALLBACK(workspace_on_close_relation_details_clicked), workspace); gtk_widget_set_visible(workspace->relation_details_panel, FALSE); gtk_widget_set_can_target(workspace->relation_details_panel, FALSE); investigation_graph_view_set_selection_callback( workspace->graph_view, workspace_on_graph_node_selected, workspace ); investigation_graph_view_set_node_moved_callback( workspace->graph_view, workspace_on_graph_node_moved, workspace ); investigation_graph_view_set_transform_changed_callback( workspace->graph_view, workspace_on_graph_transform_changed, workspace); investigation_graph_view_set_extraction_drop_callback( workspace->graph_view, workspace_on_extraction_dropped, workspace); entity_details_panel_set_close_callback( workspace->entity_details_panel, workspace_on_entity_details_closed, workspace ); entity_details_panel_set_add_relation_callback( workspace->entity_details_panel, workspace_on_add_relation_requested, workspace ); entity_details_panel_set_person_role_callback( workspace->entity_details_panel, workspace_on_person_role_changed, workspace); entity_details_panel_set_person_confidence_callback( workspace->entity_details_panel, workspace_on_person_confidence_changed, workspace); 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); entity_details_panel_set_evidence_activated_callback( workspace->entity_details_panel, workspace_on_evidence_activated, workspace); entity_details_panel_set_osint_callback(workspace->entity_details_panel, workspace_on_entity_osint_requested, workspace); gtk_overlay_set_child( GTK_OVERLAY( workspace->graph_view_page ), investigation_graph_view_get_widget( workspace->graph_view ) ); workspace->graph_toolbar = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 4 ); workspace->reset_graph_layout_button = gtk_button_new_from_icon_name( "view-refresh-symbolic" ); workspace->osint_tools_button = gtk_menu_button_new(); workspace->osint_tools_popover = gtk_popover_new(); GtkWidget *osint_tools_content = gtk_box_new( GTK_ORIENTATION_VERTICAL, 8 ); GtkWidget *osint_tools_title = gtk_label_new( "Outils OSINT" ); workspace->osint_tools_context_label = gtk_label_new( "Sélectionnez un nœud du graphe." ); workspace->osint_tools_status_label = gtk_label_new( "Les outils compatibles apparaîtront ici." ); workspace->osint_tools_actions_box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 6 ); workspace->osint_action_catalog = osint_action_catalog_new_defaults(); if (workspace->graph_toolbar == NULL || workspace->reset_graph_layout_button == NULL || workspace->osint_tools_button == NULL || workspace->osint_tools_popover == NULL || osint_tools_content == NULL || osint_tools_title == NULL || workspace->osint_tools_context_label == NULL || workspace->osint_tools_status_label == NULL || workspace->osint_tools_actions_box == NULL || workspace->osint_action_catalog == NULL) { workspace_free( workspace ); return NULL; } gtk_widget_set_halign( workspace->graph_toolbar, GTK_ALIGN_START ); gtk_widget_set_valign( workspace->graph_toolbar, GTK_ALIGN_START ); gtk_widget_set_margin_start( workspace->graph_toolbar, 12 ); gtk_widget_set_margin_top( workspace->graph_toolbar, 12 ); gtk_widget_add_css_class( workspace->graph_toolbar, "toolbar" ); gtk_widget_add_css_class( workspace->reset_graph_layout_button, "flat" ); gtk_widget_set_tooltip_text( workspace->reset_graph_layout_button, "Réinitialiser la disposition du graphe" ); gtk_widget_set_sensitive( workspace->reset_graph_layout_button, FALSE ); gtk_menu_button_set_label( GTK_MENU_BUTTON( workspace->osint_tools_button ), "Outils OSINT" ); gtk_widget_add_css_class( workspace->osint_tools_button, "flat" ); gtk_widget_set_tooltip_text( workspace->osint_tools_button, "Afficher les outils compatibles avec la sélection" ); gtk_widget_set_sensitive( workspace->osint_tools_button, FALSE ); gtk_widget_set_margin_start( osint_tools_content, 16 ); gtk_widget_set_margin_end( osint_tools_content, 16 ); gtk_widget_set_margin_top( osint_tools_content, 14 ); gtk_widget_set_margin_bottom( osint_tools_content, 14 ); gtk_label_set_xalign( GTK_LABEL( osint_tools_title ), 0.0F ); gtk_widget_add_css_class( osint_tools_title, "heading" ); gtk_label_set_xalign( GTK_LABEL( workspace->osint_tools_context_label ), 0.0F ); gtk_label_set_selectable( GTK_LABEL( workspace->osint_tools_context_label ), TRUE ); gtk_label_set_xalign( GTK_LABEL( workspace->osint_tools_status_label ), 0.0F ); gtk_widget_add_css_class( workspace->osint_tools_status_label, "dim-label" ); gtk_box_append( GTK_BOX( osint_tools_content ), osint_tools_title ); gtk_box_append( GTK_BOX( osint_tools_content ), workspace->osint_tools_context_label ); gtk_box_append( GTK_BOX(osint_tools_content), workspace->osint_tools_actions_box ); gtk_box_append( GTK_BOX( osint_tools_content ), workspace->osint_tools_status_label ); gtk_popover_set_child( GTK_POPOVER( workspace->osint_tools_popover ), osint_tools_content ); gtk_menu_button_set_popover( GTK_MENU_BUTTON( workspace->osint_tools_button ), workspace->osint_tools_popover ); g_signal_connect( workspace->reset_graph_layout_button, "clicked", G_CALLBACK( workspace_on_reset_graph_layout_clicked ), workspace ); gtk_box_append( GTK_BOX( workspace->graph_toolbar ), workspace->reset_graph_layout_button ); gtk_box_append( GTK_BOX( workspace->graph_toolbar ), workspace->osint_tools_button ); gtk_overlay_add_overlay( GTK_OVERLAY( workspace->graph_view_page ), workspace->graph_toolbar ); gtk_overlay_set_clip_overlay( GTK_OVERLAY( workspace->graph_view_page ), workspace->graph_toolbar, TRUE ); workspace->graph_help_button = gtk_menu_button_new(); workspace->graph_help_popover = gtk_popover_new(); GtkWidget *graph_help_content = gtk_box_new( GTK_ORIENTATION_VERTICAL, 8 ); GtkWidget *graph_help_title = gtk_label_new( "Commandes du graphe" ); GtkWidget *graph_help_text = gtk_label_new( "• Clic : sélectionner un nœud\n" "• Glisser un nœud : le déplacer\n" "• Maj + glisser : déplacer la vue\n" "• Ctrl + molette : zoomer\n" "• Pincement : zoomer" ); if (workspace->graph_help_button == NULL || workspace->graph_help_popover == NULL || graph_help_content == NULL || graph_help_title == NULL || graph_help_text == NULL) { workspace_free( workspace ); return NULL; } gtk_menu_button_set_label( GTK_MENU_BUTTON( workspace->graph_help_button ), "?" ); gtk_widget_add_css_class( workspace->graph_help_button, "flat" ); gtk_widget_set_tooltip_text( workspace->graph_help_button, "Afficher les commandes du graphe" ); gtk_widget_set_can_target( workspace->graph_help_button, TRUE ); gtk_widget_set_halign( workspace->graph_help_button, GTK_ALIGN_END ); gtk_widget_set_valign( workspace->graph_help_button, GTK_ALIGN_START ); gtk_widget_set_margin_end( workspace->graph_help_button, 12 ); gtk_widget_set_margin_top( workspace->graph_help_button, 12 ); gtk_widget_set_margin_start( graph_help_content, 16 ); gtk_widget_set_margin_end( graph_help_content, 16 ); gtk_widget_set_margin_top( graph_help_content, 14 ); gtk_widget_set_margin_bottom( graph_help_content, 14 ); gtk_label_set_xalign( GTK_LABEL( graph_help_title ), 0.0F ); gtk_widget_add_css_class( graph_help_title, "heading" ); gtk_label_set_xalign( GTK_LABEL( graph_help_text ), 0.0F ); gtk_label_set_wrap( GTK_LABEL( graph_help_text ), TRUE ); gtk_label_set_wrap_mode( GTK_LABEL( graph_help_text ), PANGO_WRAP_WORD_CHAR ); gtk_label_set_max_width_chars( GTK_LABEL( graph_help_text ), 44 ); gtk_box_append( GTK_BOX( graph_help_content ), graph_help_title ); gtk_box_append( GTK_BOX( graph_help_content ), graph_help_text ); gtk_popover_set_child( GTK_POPOVER( workspace->graph_help_popover ), graph_help_content ); gtk_menu_button_set_popover( GTK_MENU_BUTTON( workspace->graph_help_button ), workspace->graph_help_popover ); gtk_overlay_add_overlay( GTK_OVERLAY( workspace->graph_view_page ), entity_details_panel_get_widget( workspace->entity_details_panel ) ); gtk_overlay_add_overlay( GTK_OVERLAY(workspace->graph_view_page), workspace->relation_details_panel ); gtk_overlay_set_clip_overlay( GTK_OVERLAY(workspace->graph_view_page), workspace->relation_details_panel, TRUE ); gtk_overlay_set_clip_overlay( GTK_OVERLAY( workspace->graph_view_page ), entity_details_panel_get_widget( workspace->entity_details_panel ), TRUE ); gtk_overlay_add_overlay( GTK_OVERLAY( workspace->graph_view_page ), workspace->graph_help_button ); gtk_overlay_set_clip_overlay( GTK_OVERLAY( workspace->graph_view_page ), workspace->graph_help_button, TRUE ); gtk_stack_add_named( GTK_STACK(workspace->stack), workspace->graph_view_page, WORKSPACE_PAGE_GRAPH_VIEW ); workspace->graph_model = NULL; workspace->graph_layout = NULL; workspace->graph_state = WORKSPACE_GRAPH_STATE_EMPTY; workspace_show_default( workspace ); return workspace; } static void workspace_on_remove_observation_clicked(GtkButton *button, gpointer user_data) { Workspace *workspace = user_data; const char *identifier = g_object_get_data(G_OBJECT(button), "observation-identifier"); if (workspace != NULL && workspace->observation_remove_callback != NULL) workspace->observation_remove_callback(identifier, workspace->observation_remove_user_data); } void workspace_set_evidence_observations(Workspace *workspace, const GPtrArray *observations) { if (workspace == NULL || workspace->evidence_observations_box == NULL) return; GtkWidget *child = gtk_widget_get_first_child( workspace->evidence_observations_box); while (child != NULL) { GtkWidget *next = gtk_widget_get_next_sibling(child); gtk_box_remove(GTK_BOX(workspace->evidence_observations_box), child); child = next; } if (observations == NULL || observations->len == 0) { gtk_box_append(GTK_BOX(workspace->evidence_observations_box), gtk_label_new("Aucune observation confirmée pour cette preuve.")); return; } for (guint index = 0; index < observations->len; index++) { const EvidenceObservation *item = g_ptr_array_index( (GPtrArray *) observations, index); GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); char *text = g_strdup_printf( "%s\nType : %s — rôle : %s\nSource : %s #%u — provenance : %s\n" "Validation : %s — intégrée : %s\nGraphe : %s", item->value, item->type_identifier, item->role, item->source_header, item->occurrence, item->provenance_kind, item->verification_status, item->integrated_at, item->entity_identifier == NULL ? "Non ajoutée" : (g_strcmp0(item->promotion_kind, "created") == 0 ? "Entité créée" : g_strcmp0(item->promotion_kind, "reused") == 0 ? "Entité existante réutilisée" : "Promotion historique")); GtkWidget *label = gtk_label_new(text); gtk_label_set_xalign(GTK_LABEL(label), 0.0f); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_widget_set_hexpand(label, TRUE); gtk_box_append(GTK_BOX(row), label); if (item->entity_identifier != NULL) { GtkWidget *remove = gtk_button_new_with_label("Retirer du graphe"); gtk_widget_add_css_class(remove, "flat"); gtk_widget_set_tooltip_text(remove, "Détache cette observation sans supprimer son contenu."); g_object_set_data_full(G_OBJECT(remove), "observation-identifier", g_strdup(item->identifier), g_free); g_signal_connect(remove, "clicked", G_CALLBACK(workspace_on_remove_observation_clicked), workspace); gtk_box_append(GTK_BOX(row), remove); } gtk_box_append(GTK_BOX(workspace->evidence_observations_box), row); g_free(text); } } void workspace_set_observation_remove_callback(Workspace *workspace, WorkspaceObservationRemoveCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->observation_remove_callback = callback; workspace->observation_remove_user_data = user_data; } GtkWidget *workspace_get_widget( const Workspace *workspace ) { if (workspace == NULL) { return NULL; } return workspace->root_widget; } void workspace_set_selected_node( Workspace *workspace, const InvestigationNode *node ) { const char *node_name = NULL; const InvestigationNode *parent_node = NULL; const char *parent_name = NULL; const char *node_path = NULL; InvestigationNodeType node_type; size_t children_count = 0; const char *type_text = NULL; char *parent_text = NULL; char *children_text = NULL; 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 ); } if (workspace->edit_evidence_button != NULL) gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE); if (workspace->analyze_eml_button != NULL) gtk_widget_set_sensitive(workspace->analyze_eml_button, FALSE); if (workspace->analyze_rib_button != NULL) gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE); if (workspace->extract_metadata_button != NULL) gtk_widget_set_sensitive(workspace->extract_metadata_button, FALSE); if (workspace->recover_pdf_password_button != NULL) gtk_widget_set_sensitive(workspace->recover_pdf_password_button, FALSE); if (node == NULL) { workspace_show_default(workspace); return; } node_name = investigation_node_get_name(node); node_path = investigation_node_get_path(node); node_type = investigation_node_get_type(node); parent_node = investigation_node_get_parent(node); gtk_label_set_text( GTK_LABEL(workspace->node_name_label), node_name != NULL ? node_name : "(sans nom)" ); gtk_label_set_text( GTK_LABEL(workspace->node_path_label), node_path != NULL ? node_path : "(chemin indisponible)" ); if (node_type == INVESTIGATION_NODE_DIRECTORY) { type_text = "Type : dossier"; } else { type_text = "Type : fichier"; } gtk_label_set_text( GTK_LABEL(workspace->node_type_label), type_text ); if (parent_node != NULL) { parent_name = investigation_node_get_name(parent_node); parent_text = g_strdup_printf( "Parent : %s", parent_name != NULL ? parent_name : "(sans nom)" ); } else { parent_text = g_strdup("Parent : aucun"); } gtk_label_set_text( GTK_LABEL(workspace->node_parent_label), parent_text ); if (node_type == INVESTIGATION_NODE_DIRECTORY) { children_count = investigation_node_get_children_count(node); children_text = g_strdup_printf( "Enfants : %zu", children_count ); gtk_label_set_text( GTK_LABEL(workspace->node_children_label), children_text ); gtk_widget_set_visible( workspace->node_children_label, TRUE ); } else { gtk_label_set_text( GTK_LABEL(workspace->node_children_label), "" ); gtk_widget_set_visible( workspace->node_children_label, FALSE ); } gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), node_type == INVESTIGATION_NODE_FILE ? WORKSPACE_PAGE_EVIDENCE_INFORMATION : WORKSPACE_PAGE_NODE_INFORMATION ); if (node_type == INVESTIGATION_NODE_FILE) { gtk_label_set_text(GTK_LABEL(workspace->evidence_name_label), node_name != NULL ? node_name : "Fichier"); workspace_set_evidence_preview(workspace, node_path, node_name); } g_free(children_text); g_free(parent_text); } void workspace_set_selected_evidence( Workspace *workspace, const EvidenceRecord *evidence_record ) { char *human_size = NULL; char *size_text = NULL; guint64 size_bytes = 0; if (workspace == NULL) { return; } workspace_clear_evidence_preview(workspace); workspace_set_identity_ocr_runs(workspace, NULL); g_clear_pointer( &workspace->selected_evidence_identifier, g_free ); if (workspace->verify_evidence_button != NULL) { gtk_widget_set_sensitive( workspace->verify_evidence_button, FALSE ); } if (evidence_record == NULL) { workspace_show_default( workspace ); return; } workspace->selected_evidence_identifier = g_strdup( evidence_record_get_identifier( evidence_record ) ); if (workspace->selected_evidence_identifier == NULL) { workspace_show_default( workspace ); return; } gtk_widget_set_sensitive( workspace->verify_evidence_button, TRUE ); gtk_widget_set_sensitive(workspace->edit_evidence_button, TRUE); { const char *name = evidence_record_get_original_name(evidence_record); char *lower = name != NULL ? g_ascii_strdown(name, -1) : NULL; gtk_widget_set_sensitive(workspace->analyze_eml_button, lower != NULL && g_str_has_suffix(lower, ".eml")); gtk_widget_set_sensitive(workspace->analyze_rib_button, lower != NULL && (g_str_has_suffix(lower, ".jpg") || g_str_has_suffix(lower, ".jpeg") || g_str_has_suffix(lower, ".png"))); gtk_widget_set_sensitive(workspace->extract_metadata_button, lower != NULL && (g_str_has_suffix(lower, ".jpg") || g_str_has_suffix(lower, ".jpeg") || g_str_has_suffix(lower, ".png") || g_str_has_suffix(lower, ".mov") || g_str_has_suffix(lower, ".mp4") || g_str_has_suffix(lower, ".pdf"))); gtk_widget_set_sensitive(workspace->recover_pdf_password_button, lower != NULL && g_str_has_suffix(lower, ".pdf")); gtk_widget_set_sensitive(workspace->analyze_identity_button, evidence_identity_ocr_dialog_file_is_compatible(name) || evidence_identity_ocr_dialog_file_is_compatible( evidence_record_get_internal_name(evidence_record))); g_free(lower); } size_bytes = evidence_record_get_size_bytes( evidence_record ); human_size = g_format_size_full( size_bytes, G_FORMAT_SIZE_IEC_UNITS ); size_text = g_strdup_printf( "%s (%" G_GUINT64_FORMAT " octets)", human_size != NULL ? human_size : "taille inconnue", size_bytes ); workspace_set_field_text( workspace->evidence_name_label, evidence_record_get_original_name( evidence_record ), "(sans nom)" ); workspace_set_field_text( workspace->evidence_type_label, evidence_record_get_type_identifier( evidence_record ), "(type inconnu)" ); workspace_set_field_text( workspace->evidence_integrity_label, workspace_get_integrity_status_text( evidence_record_get_integrity_status( evidence_record ) ), "Statut inconnu" ); workspace_set_field_text( workspace->evidence_size_label, size_text, "Taille inconnue" ); workspace_set_field_text( workspace->evidence_imported_at_label, evidence_record_get_imported_at( evidence_record ), "(date inconnue)" ); workspace_set_field_text( workspace->evidence_collected_at_label, evidence_record_get_collected_at( evidence_record ), "Non renseignée" ); workspace_set_field_text( workspace->evidence_source_label, evidence_record_get_source( evidence_record ), "Non renseignée" ); workspace_set_field_text( workspace->evidence_description_label, evidence_record_get_description( evidence_record ), "Non renseignée" ); workspace_set_field_text( workspace->evidence_relative_path_label, evidence_record_get_relative_path( evidence_record ), "(chemin inconnu)" ); workspace_set_field_text( workspace->evidence_internal_name_label, evidence_record_get_internal_name( evidence_record ), "(nom interne inconnu)" ); workspace_set_field_text( workspace->evidence_identifier_label, evidence_record_get_identifier( evidence_record ), "(identifiant inconnu)" ); workspace_set_field_text( workspace->evidence_sha256_label, evidence_record_get_sha256( evidence_record ), "(empreinte inconnue)" ); gtk_stack_set_visible_child_name( GTK_STACK(workspace->stack), WORKSPACE_PAGE_EVIDENCE_INFORMATION ); g_free( size_text ); g_free( human_size ); } void workspace_set_eml_analysis_available( Workspace *workspace, gboolean available) { if (workspace != NULL && workspace->analyze_eml_button != NULL) gtk_widget_set_sensitive( workspace->analyze_eml_button, available); } void workspace_set_identity_ocr_runs( Workspace *workspace, GPtrArray *owned_records) { if (workspace == NULL) { g_clear_pointer(&owned_records, g_ptr_array_unref); return; } g_clear_pointer(&workspace->identity_ocr_records, g_ptr_array_unref); workspace->identity_ocr_records = owned_records; guint old_count = g_list_model_get_n_items( G_LIST_MODEL(workspace->identity_ocr_labels)); gtk_string_list_splice(workspace->identity_ocr_labels, 0, old_count, NULL); for (guint index = 0; owned_records != NULL && index < owned_records->len; index++) { WorkspaceIdentityOcrRecord *record = g_ptr_array_index(owned_records, index); char *short_id = g_strndup( identity_ocr_run_get_identifier(record->run), 8); char *label = g_strdup_printf( "%s — %s/%s — page %u — %s", record->executed_at != NULL ? record->executed_at : "UTC inconnue", identity_ocr_run_get_document_type(record->run), identity_ocr_run_get_document_side(record->run), identity_ocr_run_get_page(record->run), short_id); gtk_string_list_append(workspace->identity_ocr_labels, label); g_free(label); g_free(short_id); } gboolean available = owned_records != NULL && owned_records->len > 0; gtk_widget_set_visible(workspace->identity_ocr_section, available); gtk_widget_set_sensitive(workspace->revise_identity_button, available); gtk_widget_set_sensitive(workspace->rerun_identity_button, available); if (available) gtk_drop_down_set_selected(workspace->identity_ocr_selector, owned_records->len - 1); else workspace_render_identity_ocr(workspace); } void workspace_set_identity_ocr_callback( Workspace *workspace, WorkspaceIdentityOcrCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->identity_ocr_callback = callback; workspace->identity_ocr_user_data = user_data; } void workspace_set_evidence_preview(Workspace *workspace, const char *file_path, const char *display_name) { char *contents = NULL; gsize length = 0; char *sha256 = NULL; char *root = NULL; char *name = NULL; EvidencePreviewRequest *request = NULL; if (workspace == NULL) return; workspace_clear_evidence_preview(workspace); if (file_path == NULL || !g_file_get_contents( file_path, &contents, &length, NULL)) return; sha256 = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (const guchar *) contents, length); root = g_path_get_dirname(file_path); name = g_path_get_basename(file_path); request = evidence_preview_request_new(root, display_name != NULL ? display_name : name, name, sha256, NULL, 0); evidence_preview_widget_show(workspace->evidence_preview, request, display_name); evidence_preview_request_free(request); g_free(name); g_free(root); g_free(sha256); g_free(contents); } void workspace_show_evidence_preview(Workspace *workspace, const char *investigation_root, const EvidenceRecord *record) { EvidencePreviewRequest *request; char *size; char *metadata; if (workspace == NULL) return; if (investigation_root == NULL || record == NULL) { workspace_clear_evidence_preview(workspace); return; } size = g_format_size(evidence_record_get_size_bytes(record)); metadata = g_strdup_printf( "Nom original : %s\nUUID : %s\nType métier : %s\n" "MIME enregistré : %s\nTaille : %s\nSHA-256 : %s\n" "Intégrité enregistrée : %s", evidence_record_get_original_name(record), evidence_record_get_identifier(record), evidence_record_get_type_identifier(record), evidence_record_get_mime_type(record) != NULL ? evidence_record_get_mime_type(record) : "Non renseigné", size, evidence_record_get_sha256(record), workspace_get_integrity_status_text( evidence_record_get_integrity_status(record))); request = evidence_preview_request_new(investigation_root, evidence_record_get_identifier(record), evidence_record_get_relative_path(record), evidence_record_get_sha256(record), evidence_record_get_mime_type(record), 0); evidence_preview_widget_show(workspace->evidence_preview, request, metadata); evidence_preview_request_free(request); g_free(metadata); g_free(size); } gboolean workspace_select_graph_entity( Workspace *workspace, const char *entity_identifier ) { if (workspace == NULL || workspace->graph_view == NULL) { return FALSE; } return investigation_graph_view_select_entity( workspace->graph_view, entity_identifier ); } gboolean workspace_select_graph_relation( Workspace *workspace, const char *relation_identifier ) { if (workspace == NULL || workspace->graph_view == NULL) { return FALSE; } return investigation_graph_view_select_relation( workspace->graph_view, relation_identifier ); } void workspace_set_osint_tool_state( Workspace *workspace, const char *tool_identifier, OsintActionToolState state, const char *version ) { if (workspace == NULL || workspace->osint_action_catalog == NULL) { return; } osint_action_catalog_update_tool_state( workspace->osint_action_catalog, tool_identifier, state, version ); workspace_update_osint_tools_menu( workspace, workspace->osint_selection_context ); } void workspace_set_osint_action_callback( Workspace *workspace, WorkspaceOsintActionCallback callback, gpointer user_data ) { if (workspace == NULL) { return; } workspace->osint_action_callback = callback; workspace->osint_action_user_data = user_data; } 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 ); } entity_details_panel_clear( workspace->entity_details_panel ); workspace->saved_graph_transform_valid = investigation_graph_view_get_view_transform( workspace->graph_view, &workspace->saved_graph_zoom, &workspace->saved_graph_offset_x, &workspace->saved_graph_offset_y); investigation_graph_view_clear( workspace->graph_view ); workspace->graph_model = NULL; workspace->graph_layout = NULL; workspace->graph_state = WORKSPACE_GRAPH_STATE_LOADING; gtk_widget_set_sensitive( workspace->reset_graph_layout_button, FALSE ); 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, const InvestigationGraphLayout *graph_layout ) { 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 ); } entity_details_panel_clear( workspace->entity_details_panel ); investigation_graph_view_clear( workspace->graph_view ); workspace->graph_model = graph_model; workspace->graph_layout = graph_layout; investigation_graph_view_set_layout( workspace->graph_view, graph_layout ); investigation_graph_view_set_graph( workspace->graph_view, graph_model ); if (workspace->saved_graph_transform_valid) { investigation_graph_view_set_view_transform( workspace->graph_view, workspace->saved_graph_zoom, workspace->saved_graph_offset_x, workspace->saved_graph_offset_y); workspace->saved_graph_transform_valid = FALSE; } workspace->graph_state = WORKSPACE_GRAPH_STATE_READY; gtk_widget_set_sensitive( workspace->reset_graph_layout_button, TRUE ); 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 ); } gboolean workspace_get_graph_view_transform( Workspace *workspace, double *zoom, double *offset_x, double *offset_y) { if (workspace == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY) return FALSE; return investigation_graph_view_get_view_transform( workspace->graph_view, zoom, offset_x, offset_y); } void workspace_set_graph_view_transform( Workspace *workspace, double zoom, double offset_x, double offset_y) { if (workspace == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY) return; investigation_graph_view_set_view_transform( workspace->graph_view, zoom, offset_x, offset_y); } 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 ); } entity_details_panel_clear( workspace->entity_details_panel ); investigation_graph_view_clear( workspace->graph_view ); workspace->graph_model = NULL; workspace->graph_layout = NULL; workspace->graph_state = WORKSPACE_GRAPH_STATE_ERROR; gtk_widget_set_sensitive( workspace->reset_graph_layout_button, FALSE ); 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 ); } entity_details_panel_clear( workspace->entity_details_panel ); investigation_graph_view_clear( workspace->graph_view ); workspace->graph_model = NULL; workspace->graph_layout = NULL; workspace->graph_state = WORKSPACE_GRAPH_STATE_EMPTY; gtk_widget_set_sensitive( workspace->reset_graph_layout_button, FALSE ); 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, gpointer user_data ) { if (workspace == NULL) { return; } workspace->verify_evidence_callback = callback; workspace->verify_evidence_user_data = user_data; } void workspace_set_edit_evidence_callback( Workspace *workspace, WorkspaceEditEvidenceCallback callback, gpointer user_data ) { if (workspace == NULL) return; workspace->edit_evidence_callback = callback; workspace->edit_evidence_user_data = user_data; } void workspace_set_analyze_eml_callback(Workspace *workspace, WorkspaceAnalyzeEmlCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->analyze_eml_callback = callback; workspace->analyze_eml_user_data = user_data; } void workspace_set_analyze_rib_callback(Workspace *workspace, WorkspaceAnalyzeRibCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->analyze_rib_callback = callback; workspace->analyze_rib_user_data = user_data; } void workspace_set_extract_metadata_callback(Workspace *workspace, WorkspaceExtractMetadataCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->extract_metadata_callback = callback; workspace->extract_metadata_user_data = user_data; } void workspace_set_recover_pdf_password_callback(Workspace *workspace, WorkspaceRecoverPdfPasswordCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->recover_pdf_password_callback = callback; workspace->recover_pdf_password_user_data = user_data; } void workspace_set_graph_node_moved_callback( Workspace *workspace, WorkspaceGraphNodeMovedCallback callback, gpointer user_data ) { if (workspace == NULL) { return; } workspace->graph_node_moved_callback = callback; workspace->graph_node_moved_user_data = user_data; } void workspace_set_graph_transform_changed_callback( Workspace *workspace, WorkspaceGraphTransformChangedCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->graph_transform_changed_callback = callback; workspace->graph_transform_changed_user_data = user_data; } void workspace_set_extraction_drop_callback( Workspace *workspace, WorkspaceExtractionDropCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->extraction_drop_callback = callback; workspace->extraction_drop_user_data = user_data; } void workspace_set_reset_graph_layout_callback( Workspace *workspace, WorkspaceResetGraphLayoutCallback callback, gpointer user_data ) { if (workspace == NULL) { return; } workspace->reset_graph_layout_callback = callback; workspace->reset_graph_layout_user_data = user_data; } void workspace_set_add_relation_callback( Workspace *workspace, WorkspaceAddRelationCallback callback, gpointer user_data ) { if (workspace == NULL) { return; } workspace->add_relation_callback = callback; workspace->add_relation_user_data = user_data; } void workspace_set_edit_relation_callback(Workspace *workspace, WorkspaceEditRelationCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->edit_relation_callback = callback; workspace->edit_relation_user_data = user_data; } void workspace_set_delete_relation_callback(Workspace *workspace, WorkspaceDeleteRelationCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->delete_relation_callback = callback; workspace->delete_relation_user_data = user_data; } void workspace_set_relation_selected_callback(Workspace *workspace, WorkspaceRelationSelectedCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->relation_selected_callback = callback; workspace->relation_selected_user_data = user_data; } void workspace_set_relation_evidences(Workspace *workspace, const GPtrArray *evidence_records) { GString *text = NULL; if (workspace == NULL || workspace->relation_evidences_label == NULL) return; text = g_string_new("Pièces jointes"); if (evidence_records == NULL || evidence_records->len == 0) g_string_append(text, " : aucune"); else { g_string_append_printf(text, " (%u) :", evidence_records->len); for (guint index = 0; index < evidence_records->len; index++) { const EvidenceRecord *record = g_ptr_array_index(evidence_records, index); if (record == NULL) continue; g_string_append_printf(text, "\n• %s — %s", evidence_record_get_original_name(record), evidence_record_get_type_identifier(record)); } } gtk_label_set_text(GTK_LABEL(workspace->relation_evidences_label), text->str); g_string_free(text, TRUE); } void workspace_set_person_role_callback(Workspace *workspace, WorkspacePersonRoleCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->person_role_callback = callback; workspace->person_role_user_data = user_data; } void workspace_set_person_confidence_callback(Workspace *workspace, WorkspacePersonConfidenceCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->person_confidence_callback = callback; workspace->person_confidence_user_data = user_data; } void workspace_set_person_name_callback(Workspace *workspace, WorkspacePersonNameCallback callback, gpointer user_data) { if (workspace == NULL) return; 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_evidence_activated_callback( Workspace *workspace, WorkspaceEvidenceActivatedCallback callback, gpointer user_data) { if (workspace == NULL) return; workspace->evidence_activated_callback = callback; workspace->evidence_activated_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 ) { if (workspace == NULL || workspace->graph_view == NULL || workspace->graph_state != WORKSPACE_GRAPH_STATE_READY || workspace->graph_model == NULL) { return; } entity_details_panel_clear( workspace->entity_details_panel ); investigation_graph_view_clear_selection( workspace->graph_view ); investigation_graph_view_reset_layout( workspace->graph_view ); } void workspace_free(Workspace *workspace) { if (workspace == NULL) { return; } workspace_clear_evidence_preview(workspace); g_clear_pointer(&workspace->evidence_preview, evidence_preview_widget_free); g_clear_pointer( &workspace->osint_selection_context, osint_selection_context_free ); g_clear_pointer(&workspace->selected_relation_identifier, g_free); g_clear_pointer( &workspace->osint_action_catalog, osint_action_catalog_free ); investigation_graph_view_set_selection_callback( workspace->graph_view, NULL, NULL ); investigation_graph_view_set_node_moved_callback( workspace->graph_view, NULL, NULL ); investigation_graph_view_set_transform_changed_callback( workspace->graph_view, NULL, NULL); entity_details_panel_set_close_callback( workspace->entity_details_panel, NULL, NULL ); entity_details_panel_set_add_relation_callback( workspace->entity_details_panel, NULL, NULL ); entity_details_panel_set_person_role_callback( workspace->entity_details_panel, NULL, NULL); entity_details_panel_clear( workspace->entity_details_panel ); investigation_graph_view_clear( workspace->graph_view ); investigation_graph_view_free( workspace->graph_view ); entity_details_panel_free( workspace->entity_details_panel ); workspace->graph_view = NULL; workspace->entity_details_panel = NULL; workspace->graph_model = NULL; workspace->graph_layout = NULL; workspace->verify_evidence_callback = NULL; workspace->verify_evidence_user_data = NULL; workspace->graph_node_moved_callback = NULL; workspace->graph_node_moved_user_data = NULL; workspace->reset_graph_layout_callback = NULL; workspace->reset_graph_layout_user_data = NULL; workspace->add_relation_callback = NULL; workspace->add_relation_user_data = NULL; workspace->graph_toolbar = NULL; workspace->reset_graph_layout_button = NULL; workspace->graph_help_button = NULL; workspace->graph_help_popover = NULL; g_clear_pointer( &workspace->selected_evidence_identifier, g_free ); g_clear_pointer(&workspace->identity_ocr_records, g_ptr_array_unref); g_clear_object(&workspace->identity_ocr_labels); g_clear_pointer(&workspace->identity_ocr_overlay, ocr_provenance_overlay_free); g_free(workspace); }