diff --git a/README.md b/README.md index 84c00a1..d65d5aa 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,8 @@ Le socle actuel comprend notamment : - exécution sécurisée par `GSubprocess` ; - exécution d’outils en tâche de fond ; - catalogue initial d’outils ; -- détection de présence et de version. +- détection de présence et de version ; +- menu OSINT contextuel préparé pour les entités et relations du graphe. Les outils actuellement présents dans le catalogue initial sont : diff --git a/include/widgets/investigation_graph_view.h b/include/widgets/investigation_graph_view.h index 931f6fc..0028dc4 100644 --- a/include/widgets/investigation_graph_view.h +++ b/include/widgets/investigation_graph_view.h @@ -15,6 +15,11 @@ G_BEGIN_DECLS */ typedef struct EntityRecord EntityRecord; +/** + * @brief Représentation opaque d'une relation métier. + */ +typedef struct RelationRecord RelationRecord; + /** * @brief Représentation opaque du graphe métier d'une enquête. */ @@ -33,13 +38,17 @@ typedef struct InvestigationGraphView InvestigationGraphView; /** * @brief Callback appelé lors d'un changement de sélection. * - * entity_record est emprunté au graphe. NULL représente une désélection. + * Les enregistrements sont empruntés au graphe et restent valides uniquement + * pendant l'appel. Un seul des deux pointeurs est non NULL. Deux pointeurs + * NULL représentent une désélection. * * @param entity_record Entité sélectionnée empruntée, ou NULL. + * @param relation_record Relation sélectionnée empruntée, ou NULL. * @param user_data Données empruntées fournies par l'appelant. */ typedef void (*InvestigationGraphViewSelectionCallback)( const EntityRecord *entity_record, + const RelationRecord *relation_record, gpointer user_data ); diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index 4a04cda..d3d44cb 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -42,13 +42,13 @@ typedef void (*WorkspaceVerifyEvidenceCallback)( * * Les coordonnées sont exprimées dans l'espace logique du graphe. * - * @param entity_identifier UUID de l'entité déplacée. + * @param node_identifier UUID du nœud déplacé. * @param x Coordonnée horizontale logique. * @param y Coordonnée verticale logique. * @param user_data Données empruntées fournies par l'appelant. */ typedef void (*WorkspaceGraphNodeMovedCallback)( - const char *entity_identifier, + const char *node_identifier, double x, double y, gpointer user_data diff --git a/src/widgets/investigation_graph_view.c b/src/widgets/investigation_graph_view.c index 087d3da..297310b 100644 --- a/src/widgets/investigation_graph_view.c +++ b/src/widgets/investigation_graph_view.c @@ -623,6 +623,9 @@ static void investigation_graph_view_set_selected_node( const EntityRecord *entity_record = NULL; + const RelationRecord *relation_record = + NULL; + if (graph_view == NULL || graph_view->selected_node == node_layout) { @@ -639,6 +642,13 @@ static void investigation_graph_view_set_selected_node( entity_record = node_layout->entity_record; } + else if (node_layout != NULL && + node_layout->kind == + INVESTIGATION_GRAPH_NODE_KIND_RELATION) + { + relation_record = + node_layout->relation_record; + } if (graph_view->drawing_area != NULL) { @@ -651,6 +661,7 @@ static void investigation_graph_view_set_selected_node( { graph_view->selection_callback( entity_record, + relation_record, graph_view->selection_user_data ); } diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index 5f87493..fafdb61 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -5,6 +5,8 @@ #include "widgets/workspace.h" +#include "models/entity_record.h" +#include "models/relation_record.h" #include "widgets/entity_details_panel.h" #include "widgets/investigation_graph_view.h" @@ -49,6 +51,10 @@ struct Workspace 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 *graph_help_button; GtkWidget *graph_help_popover; @@ -111,6 +117,110 @@ struct Workspace WorkspaceGraphState graph_state; }; +/** + * @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 EntityRecord *entity_record, + const RelationRecord *relation_record +) +{ + const char *selection_kind = + NULL; + + const char *selection_identifier = + NULL; + + char *context_text = + NULL; + + if (workspace == NULL || + workspace->osint_tools_button == NULL || + workspace->osint_tools_context_label == NULL || + workspace->osint_tools_status_label == NULL) + { + return; + } + + if (entity_record != NULL) + { + selection_kind = + "Entité"; + + selection_identifier = + entity_record_get_identifier( + entity_record + ); + } + else if (relation_record != NULL) + { + selection_kind = + "Relation"; + + selection_identifier = + relation_record_get_identifier( + relation_record + ); + } + + 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; + } + + 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 + ), + "Aucun outil OSINT intégré pour le moment." + ); + + g_free( + context_text + ); +} + /** * @brief Ajoute une ligne de métadonnée dans la grille. * @@ -310,23 +420,32 @@ static void workspace_on_graph_node_moved( /** * @brief Transmet la sélection du graphe au volet de détails. */ -static void workspace_on_graph_entity_selected( +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 || - workspace->entity_details_panel == NULL) + if (workspace == NULL) { return; } - entity_details_panel_set_entity( - workspace->entity_details_panel, - entity_record + if (workspace->entity_details_panel != NULL) + { + entity_details_panel_set_entity( + workspace->entity_details_panel, + entity_record + ); + } + + workspace_update_osint_tools_menu( + workspace, + entity_record, + relation_record ); } @@ -1092,7 +1211,7 @@ Workspace *workspace_new(void) investigation_graph_view_set_selection_callback( workspace->graph_view, - workspace_on_graph_entity_selected, + workspace_on_graph_node_selected, workspace ); @@ -1134,8 +1253,41 @@ Workspace *workspace_new(void) "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." + ); + if (workspace->graph_toolbar == NULL || - workspace->reset_graph_layout_button == 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_free( workspace @@ -1184,6 +1336,121 @@ Workspace *workspace_new(void) 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_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", @@ -1200,6 +1467,13 @@ Workspace *workspace_new(void) 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