feat(sidebar): add relation navigation

This commit is contained in:
grayTerminal-sh 2026-07-22 11:12:16 +02:00
parent 2dad97ddfb
commit 13315846e3
8 changed files with 604 additions and 9 deletions

View file

@ -131,6 +131,7 @@ Le socle actuel comprend notamment :
- fenêtre principale GTK4 ; - fenêtre principale GTK4 ;
- barre latérale et espace de travail ; - barre latérale et espace de travail ;
- navigation et recherche locale des entités dans la barre latérale ; - navigation et recherche locale des entités dans la barre latérale ;
- navigation et recherche locale des relations dans la barre latérale ;
- affichage graphique des erreurs ; - affichage graphique des erreurs ;
- tâches asynchrones annulables ; - tâches asynchrones annulables ;
- gestionnaire de tâches ; - gestionnaire de tâches ;

View file

@ -198,6 +198,19 @@ gboolean investigation_graph_view_select_entity(
const char *entity_identifier const char *entity_identifier
); );
/**
* @brief Sélectionne une relation du graphe par son UUID.
*
* @param graph_view Vue graphique à modifier.
* @param relation_identifier UUID de la relation.
*
* @return TRUE lorsque la relation est trouvée et sélectionnée.
*/
gboolean investigation_graph_view_select_relation(
InvestigationGraphView *graph_view,
const char *relation_identifier
);
/** /**
* @brief Supprime la sélection courante. * @brief Supprime la sélection courante.
* *

View file

@ -49,6 +49,17 @@ typedef void (*SidebarEntitySelectionCallback)(
gpointer user_data gpointer user_data
); );
/**
* @brief Callback appelé lorsqu'une relation est sélectionnée.
*
* @param relation_identifier UUID emprunté de la relation, ou NULL.
* @param user_data Données privées fournies par l'appelant.
*/
typedef void (*SidebarRelationSelectionCallback)(
const char *relation_identifier,
gpointer user_data
);
/** /**
* @brief Crée un nouveau panneau de navigation latéral. * @brief Crée un nouveau panneau de navigation latéral.
* *
@ -129,14 +140,14 @@ void sidebar_set_evidence_selection_callback(
); );
/** /**
* @brief Installe les entités du graphe dans la barre latérale. * @brief Installe les entités et relations du graphe dans la barre latérale.
* *
* La Sidebar emprunte graph_model. Passer NULL vide la liste. * La Sidebar emprunte graph_model. Passer NULL vide la liste.
* *
* @param sidebar Barre latérale à actualiser. * @param sidebar Barre latérale à actualiser.
* @param graph_model Graphe emprunté, ou NULL. * @param graph_model Graphe emprunté, ou NULL.
*/ */
void sidebar_set_entity_model( void sidebar_set_graph_model(
Sidebar *sidebar, Sidebar *sidebar,
const InvestigationGraphModel *graph_model const InvestigationGraphModel *graph_model
); );
@ -154,6 +165,19 @@ void sidebar_set_entity_selection_callback(
gpointer user_data gpointer user_data
); );
/**
* @brief Définit le callback de sélection d'une relation.
*
* @param sidebar Barre latérale à configurer.
* @param callback Callback facultatif.
* @param user_data Données privées transmises au callback.
*/
void sidebar_set_relation_selection_callback(
Sidebar *sidebar,
SidebarRelationSelectionCallback callback,
gpointer user_data
);
/** /**
* @brief Libère la structure d'encapsulation du panneau latéral. * @brief Libère la structure d'encapsulation du panneau latéral.
* *

View file

@ -142,6 +142,19 @@ gboolean workspace_select_graph_entity(
const char *entity_identifier const char *entity_identifier
); );
/**
* @brief Sélectionne une relation dans le graphe affiché.
*
* @param workspace Zone de travail.
* @param relation_identifier UUID de la relation à sélectionner.
*
* @return TRUE lorsque la relation est trouvée et sélectionnée.
*/
gboolean workspace_select_graph_relation(
Workspace *workspace,
const char *relation_identifier
);
/** /**
* @brief Définit le callback de vérification de la preuve affichée. * @brief Définit le callback de vérification de la preuve affichée.
* *

View file

@ -148,6 +148,29 @@ static void main_window_on_sidebar_entity_selected(
); );
} }
/**
* @brief Ouvre dans le workspace la relation choisie dans la sidebar.
*/
static void main_window_on_sidebar_relation_selected(
const char *relation_identifier,
gpointer user_data
)
{
MainWindow *main_window = user_data;
if (main_window == NULL ||
relation_identifier == NULL ||
relation_identifier[0] == '\0')
{
return;
}
workspace_select_graph_relation(
main_window->workspace,
relation_identifier
);
}
/** /**
* @brief Transmet la demande de création d'une enquête au contrôleur. * @brief Transmet la demande de création d'une enquête au contrôleur.
* *
@ -692,6 +715,12 @@ MainWindow *main_window_new(
main_window main_window
); );
sidebar_set_relation_selection_callback(
main_window->sidebar,
main_window_on_sidebar_relation_selected,
main_window
);
workspace_set_verify_evidence_callback( workspace_set_verify_evidence_callback(
main_window->workspace, main_window->workspace,
main_window_on_verify_evidence_requested, main_window_on_verify_evidence_requested,
@ -1069,7 +1098,7 @@ void main_window_set_graph_loading(
FALSE FALSE
); );
sidebar_set_entity_model( sidebar_set_graph_model(
main_window->sidebar, main_window->sidebar,
NULL NULL
); );
@ -1096,7 +1125,7 @@ void main_window_set_graph(
graph_layout graph_layout
); );
sidebar_set_entity_model( sidebar_set_graph_model(
main_window->sidebar, main_window->sidebar,
graph_model graph_model
); );
@ -1122,7 +1151,7 @@ void main_window_set_graph_error(
FALSE FALSE
); );
sidebar_set_entity_model( sidebar_set_graph_model(
main_window->sidebar, main_window->sidebar,
NULL NULL
); );
@ -1147,7 +1176,7 @@ void main_window_clear_graph(
FALSE FALSE
); );
sidebar_set_entity_model( sidebar_set_graph_model(
main_window->sidebar, main_window->sidebar,
NULL NULL
); );
@ -1492,7 +1521,13 @@ void main_window_free(
NULL NULL
); );
sidebar_set_entity_model( sidebar_set_relation_selection_callback(
main_window->sidebar,
NULL,
NULL
);
sidebar_set_graph_model(
main_window->sidebar, main_window->sidebar,
NULL NULL
); );

View file

@ -4291,6 +4291,35 @@ gboolean investigation_graph_view_select_entity(
return TRUE; return TRUE;
} }
gboolean investigation_graph_view_select_relation(
InvestigationGraphView *graph_view,
const char *relation_identifier
)
{
InvestigationGraphNodeLayout *node_layout = NULL;
if (graph_view == NULL ||
graph_view->node_layouts_by_identifier == NULL ||
relation_identifier == NULL ||
!g_uuid_string_is_valid(relation_identifier))
{
return FALSE;
}
node_layout = g_hash_table_lookup(
graph_view->node_layouts_by_identifier,
relation_identifier
);
if (node_layout == NULL ||
node_layout->kind != INVESTIGATION_GRAPH_NODE_KIND_RELATION)
{
return FALSE;
}
investigation_graph_view_set_selected_node(graph_view, node_layout);
return TRUE;
}
void investigation_graph_view_clear_selection( void investigation_graph_view_clear_selection(
InvestigationGraphView *graph_view InvestigationGraphView *graph_view
) )

View file

@ -34,6 +34,12 @@
#define SIDEBAR_PAGE_ENTITY \ #define SIDEBAR_PAGE_ENTITY \
"entity" "entity"
/**
* @brief Identifiant de la page contenant les relations.
*/
#define SIDEBAR_PAGE_RELATION \
"relation"
/** /**
* @brief État vide de l'onglet Preuves. * @brief État vide de l'onglet Preuves.
*/ */
@ -77,6 +83,14 @@ struct Sidebar
GtkWidget *entity_empty_label; GtkWidget *entity_empty_label;
GtkWidget *entity_no_result_label; GtkWidget *entity_no_result_label;
GtkWidget *relation_page;
GtkWidget *relation_search_entry;
GtkWidget *relation_count_label;
GtkWidget *relation_scrolled_window;
GtkWidget *relation_list_box;
GtkWidget *relation_empty_label;
GtkWidget *relation_no_result_label;
InvestigationTreeView *tree_view; InvestigationTreeView *tree_view;
EvidenceTreeView *evidence_tree_view; EvidenceTreeView *evidence_tree_view;
@ -89,8 +103,140 @@ struct Sidebar
entity_selection_callback; entity_selection_callback;
gpointer entity_selection_user_data; gpointer entity_selection_user_data;
SidebarRelationSelectionCallback
relation_selection_callback;
gpointer relation_selection_user_data;
const InvestigationGraphModel *graph_model;
}; };
/**
* @brief Vérifie si un texte contient une recherche sans distinction de casse.
*/
static gboolean sidebar_text_contains_search(
const char *text,
const char *search_text
)
{
char *normalized_text = NULL;
char *normalized_search = NULL;
gboolean matches = FALSE;
if (text == NULL || search_text == NULL)
{
return FALSE;
}
normalized_text = g_utf8_casefold(text, -1);
normalized_search = g_utf8_casefold(search_text, -1);
matches = normalized_text != NULL && normalized_search != NULL &&
strstr(normalized_text, normalized_search) != NULL;
g_free(normalized_search);
g_free(normalized_text);
return matches;
}
/**
* @brief Filtre une ligne de relation avec le texte courant.
*/
static gboolean sidebar_filter_relation_row(
GtkListBoxRow *row,
gpointer user_data
)
{
Sidebar *sidebar = user_data;
const RelationRecord *relation_record = NULL;
const EntityRecord *source_entity = NULL;
const EntityRecord *target_entity = NULL;
const char *search_text = NULL;
if (sidebar == NULL || row == NULL)
{
return FALSE;
}
search_text = gtk_editable_get_text(
GTK_EDITABLE(sidebar->relation_search_entry)
);
if (search_text == NULL || search_text[0] == '\0')
{
return TRUE;
}
relation_record = g_object_get_data(G_OBJECT(row), "relation-record");
if (relation_record == NULL || sidebar->graph_model == NULL)
{
return FALSE;
}
source_entity = investigation_graph_model_find_entity(
sidebar->graph_model,
relation_record_get_source_entity_identifier(relation_record)
);
target_entity = investigation_graph_model_find_entity(
sidebar->graph_model,
relation_record_get_target_entity_identifier(relation_record)
);
return sidebar_text_contains_search(
relation_record_get_label(relation_record), search_text) ||
sidebar_text_contains_search(
relation_record_get_relation_type(relation_record), search_text) ||
sidebar_text_contains_search(
entity_record_get_value(source_entity), search_text) ||
sidebar_text_contains_search(
entity_record_get_value(target_entity), search_text);
}
/**
* @brief Réapplique le filtre local des relations.
*/
static void sidebar_on_relation_search_changed(
GtkSearchEntry *search_entry,
gpointer user_data
)
{
Sidebar *sidebar = user_data;
(void) search_entry;
if (sidebar != NULL && sidebar->relation_list_box != NULL)
{
gtk_list_box_invalidate_filter(GTK_LIST_BOX(sidebar->relation_list_box));
}
}
/**
* @brief Relaie la sélection provenant de la liste des relations.
*/
static void sidebar_on_relation_row_selected(
GtkListBox *list_box,
GtkListBoxRow *row,
gpointer user_data
)
{
Sidebar *sidebar = user_data;
const RelationRecord *relation_record = NULL;
(void) list_box;
if (sidebar == NULL || sidebar->relation_selection_callback == NULL)
{
return;
}
if (row != NULL)
{
relation_record = g_object_get_data(G_OBJECT(row), "relation-record");
}
sidebar->relation_selection_callback(
relation_record_get_identifier(relation_record),
sidebar->relation_selection_user_data
);
}
/** /**
* @brief Filtre une ligne d'entité avec le texte de recherche courant. * @brief Filtre une ligne d'entité avec le texte de recherche courant.
*/ */
@ -900,6 +1046,92 @@ Sidebar *sidebar_new(void)
"Entités" "Entités"
); );
/*
* Page Relations : recherche locale et liste verticale défilable.
*/
sidebar->relation_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
sidebar->relation_search_entry = gtk_search_entry_new();
sidebar->relation_count_label = gtk_label_new("0 relation");
sidebar->relation_scrolled_window = gtk_scrolled_window_new();
sidebar->relation_list_box = gtk_list_box_new();
sidebar->relation_empty_label = gtk_label_new(
"Aucune relation active dans cette enquête."
);
sidebar->relation_no_result_label = gtk_label_new(
"Aucune relation ne correspond à la recherche."
);
if (sidebar->relation_page == NULL ||
sidebar->relation_search_entry == NULL ||
sidebar->relation_count_label == NULL ||
sidebar->relation_scrolled_window == NULL ||
sidebar->relation_list_box == NULL ||
sidebar->relation_empty_label == NULL ||
sidebar->relation_no_result_label == NULL)
{
sidebar_free(sidebar);
return NULL;
}
gtk_widget_set_margin_start(sidebar->relation_search_entry, 8);
gtk_widget_set_margin_end(sidebar->relation_search_entry, 8);
gtk_search_entry_set_placeholder_text(
GTK_SEARCH_ENTRY(sidebar->relation_search_entry),
"Rechercher une relation"
);
gtk_widget_set_halign(sidebar->relation_count_label, GTK_ALIGN_START);
gtk_widget_set_margin_start(sidebar->relation_count_label, 12);
gtk_widget_add_css_class(sidebar->relation_count_label, "dim-label");
gtk_widget_set_hexpand(sidebar->relation_scrolled_window, TRUE);
gtk_widget_set_vexpand(sidebar->relation_scrolled_window, TRUE);
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(sidebar->relation_scrolled_window),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC
);
gtk_scrolled_window_set_child(
GTK_SCROLLED_WINDOW(sidebar->relation_scrolled_window),
sidebar->relation_list_box
);
gtk_list_box_set_filter_func(
GTK_LIST_BOX(sidebar->relation_list_box),
sidebar_filter_relation_row,
sidebar,
NULL
);
gtk_label_set_wrap(GTK_LABEL(sidebar->relation_no_result_label), TRUE);
gtk_widget_set_margin_top(sidebar->relation_no_result_label, 20);
gtk_list_box_set_placeholder(
GTK_LIST_BOX(sidebar->relation_list_box),
sidebar->relation_no_result_label
);
gtk_label_set_wrap(GTK_LABEL(sidebar->relation_empty_label), TRUE);
gtk_widget_set_halign(sidebar->relation_empty_label, GTK_ALIGN_CENTER);
gtk_widget_set_valign(sidebar->relation_empty_label, GTK_ALIGN_CENTER);
gtk_widget_set_vexpand(sidebar->relation_empty_label, TRUE);
g_signal_connect(
sidebar->relation_search_entry,
"search-changed",
G_CALLBACK(sidebar_on_relation_search_changed),
sidebar
);
g_signal_connect(
sidebar->relation_list_box,
"row-selected",
G_CALLBACK(sidebar_on_relation_row_selected),
sidebar
);
gtk_box_append(GTK_BOX(sidebar->relation_page), sidebar->relation_search_entry);
gtk_box_append(GTK_BOX(sidebar->relation_page), sidebar->relation_count_label);
gtk_box_append(GTK_BOX(sidebar->relation_page), sidebar->relation_scrolled_window);
gtk_box_append(GTK_BOX(sidebar->relation_page), sidebar->relation_empty_label);
gtk_stack_add_titled(
GTK_STACK(sidebar->stack),
sidebar->relation_page,
SIDEBAR_PAGE_RELATION,
"Relations"
);
/* /*
* L'onglet Dossier reste sélectionné au démarrage. * L'onglet Dossier reste sélectionné au démarrage.
*/ */
@ -1066,14 +1298,16 @@ void sidebar_set_evidence_selection_callback(
user_data; user_data;
} }
void sidebar_set_entity_model( void sidebar_set_graph_model(
Sidebar *sidebar, Sidebar *sidebar,
const InvestigationGraphModel *graph_model const InvestigationGraphModel *graph_model
) )
{ {
GPtrArray *entity_records = NULL; GPtrArray *entity_records = NULL;
GPtrArray *relation_records = NULL;
GtkWidget *child = NULL; GtkWidget *child = NULL;
guint entity_index = 0; guint entity_index = 0;
guint relation_index = 0;
char *count_text = NULL; char *count_text = NULL;
if (sidebar == NULL || sidebar->entity_list_box == NULL) if (sidebar == NULL || sidebar->entity_list_box == NULL)
@ -1081,6 +1315,8 @@ void sidebar_set_entity_model(
return; return;
} }
sidebar->graph_model = graph_model;
while ((child = gtk_widget_get_first_child( while ((child = gtk_widget_get_first_child(
sidebar->entity_list_box sidebar->entity_list_box
)) != NULL) )) != NULL)
@ -1168,7 +1404,105 @@ void sidebar_set_entity_model(
); );
g_free(count_text); g_free(count_text);
count_text = NULL;
g_clear_pointer(&entity_records, g_ptr_array_unref); g_clear_pointer(&entity_records, g_ptr_array_unref);
while ((child = gtk_widget_get_first_child(
sidebar->relation_list_box
)) != NULL)
{
gtk_list_box_remove(GTK_LIST_BOX(sidebar->relation_list_box), child);
}
if (graph_model != NULL)
{
relation_records = investigation_graph_model_list_relations(
graph_model,
NULL
);
}
for (relation_index = 0;
relation_records != NULL && relation_index < relation_records->len;
relation_index++)
{
const RelationRecord *relation_record = g_ptr_array_index(
relation_records,
relation_index
);
const EntityRecord *source_entity = investigation_graph_model_find_entity(
graph_model,
relation_record_get_source_entity_identifier(relation_record)
);
const EntityRecord *target_entity = investigation_graph_model_find_entity(
graph_model,
relation_record_get_target_entity_identifier(relation_record)
);
const char *label = relation_record_get_label(relation_record);
char *details = g_strdup_printf(
"%s → %s · %d %%",
entity_record_get_value(source_entity),
entity_record_get_value(target_entity),
relation_record_get_confidence(relation_record)
);
GtkWidget *row = gtk_list_box_row_new();
GtkWidget *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
GtkWidget *title_label = gtk_label_new(
label != NULL && label[0] != '\0'
? label
: relation_record_get_relation_type(relation_record)
);
GtkWidget *details_label = gtk_label_new(details);
g_free(details);
if (row == NULL || content == NULL ||
title_label == NULL || details_label == NULL)
{
continue;
}
gtk_widget_set_margin_start(content, 12);
gtk_widget_set_margin_end(content, 12);
gtk_widget_set_margin_top(content, 8);
gtk_widget_set_margin_bottom(content, 8);
gtk_label_set_xalign(GTK_LABEL(title_label), 0.0F);
gtk_label_set_ellipsize(GTK_LABEL(title_label), PANGO_ELLIPSIZE_END);
gtk_label_set_xalign(GTK_LABEL(details_label), 0.0F);
gtk_label_set_ellipsize(GTK_LABEL(details_label), PANGO_ELLIPSIZE_END);
gtk_widget_add_css_class(details_label, "dim-label");
gtk_box_append(GTK_BOX(content), title_label);
gtk_box_append(GTK_BOX(content), details_label);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), content);
g_object_set_data(
G_OBJECT(row),
"relation-record",
(gpointer) relation_record
);
gtk_list_box_append(GTK_LIST_BOX(sidebar->relation_list_box), row);
}
count_text = g_strdup_printf(
"%u %s",
relation_records != NULL ? relation_records->len : 0U,
relation_records != NULL && relation_records->len > 1U
? "relations"
: "relation"
);
gtk_label_set_text(
GTK_LABEL(sidebar->relation_count_label),
count_text != NULL ? count_text : "0 relation"
);
gtk_widget_set_visible(
sidebar->relation_empty_label,
relation_records == NULL || relation_records->len == 0U
);
gtk_widget_set_visible(
sidebar->relation_scrolled_window,
relation_records != NULL && relation_records->len > 0U
);
g_free(count_text);
g_clear_pointer(&relation_records, g_ptr_array_unref);
} }
void sidebar_set_entity_selection_callback( void sidebar_set_entity_selection_callback(
@ -1186,6 +1520,21 @@ void sidebar_set_entity_selection_callback(
sidebar->entity_selection_user_data = user_data; sidebar->entity_selection_user_data = user_data;
} }
void sidebar_set_relation_selection_callback(
Sidebar *sidebar,
SidebarRelationSelectionCallback callback,
gpointer user_data
)
{
if (sidebar == NULL)
{
return;
}
sidebar->relation_selection_callback = callback;
sidebar->relation_selection_user_data = user_data;
}
void sidebar_free(Sidebar *sidebar) void sidebar_free(Sidebar *sidebar)
{ {
if (sidebar == NULL) if (sidebar == NULL)
@ -1210,6 +1559,9 @@ void sidebar_free(Sidebar *sidebar)
sidebar->entity_selection_callback = NULL; sidebar->entity_selection_callback = NULL;
sidebar->entity_selection_user_data = NULL; sidebar->entity_selection_user_data = NULL;
sidebar->relation_selection_callback = NULL;
sidebar->relation_selection_user_data = NULL;
sidebar->graph_model = NULL;
/* /*
* Les widgets GTK sont intégrés dans l'arbre de widgets de la fenêtre. * Les widgets GTK sont intégrés dans l'arbre de widgets de la fenêtre.

View file

@ -6,6 +6,7 @@
#include "widgets/workspace.h" #include "widgets/workspace.h"
#include "models/entity_record.h" #include "models/entity_record.h"
#include "models/investigation_graph_model.h"
#include "models/relation_record.h" #include "models/relation_record.h"
#include "widgets/entity_details_panel.h" #include "widgets/entity_details_panel.h"
#include "widgets/investigation_graph_view.h" #include "widgets/investigation_graph_view.h"
@ -60,6 +61,9 @@ struct Workspace
InvestigationGraphView *graph_view; InvestigationGraphView *graph_view;
EntityDetailsPanel *entity_details_panel; EntityDetailsPanel *entity_details_panel;
GtkWidget *relation_details_panel;
GtkWidget *relation_details_title_label;
GtkWidget *relation_details_summary_label;
GtkWidget *node_page; GtkWidget *node_page;
@ -442,6 +446,49 @@ static void workspace_on_graph_node_selected(
); );
} }
if (workspace->relation_details_panel != 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)
{
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)
);
const char *label = relation_record_get_label(relation_record);
char *summary = g_strdup_printf(
"%s → %s\n\nType : %s\nConfiance : %d %%\nUUID : %s",
entity_record_get_value(source_entity),
entity_record_get_value(target_entity),
relation_record_get_relation_type(relation_record),
relation_record_get_confidence(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);
}
workspace_update_osint_tools_menu( workspace_update_osint_tools_menu(
workspace, workspace,
entity_record, entity_record,
@ -1199,8 +1246,18 @@ Workspace *workspace_new(void)
workspace->entity_details_panel = workspace->entity_details_panel =
entity_details_panel_new(); 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);
if (workspace->graph_view == NULL || if (workspace->graph_view == NULL ||
workspace->entity_details_panel == NULL) workspace->entity_details_panel == NULL ||
workspace->relation_details_panel == NULL ||
workspace->relation_details_title_label == NULL ||
workspace->relation_details_summary_label == NULL)
{ {
workspace_free( workspace_free(
workspace workspace
@ -1209,6 +1266,50 @@ Workspace *workspace_new(void)
return NULL; 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);
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_box_append(
GTK_BOX(workspace->relation_details_panel),
workspace->relation_details_title_label
);
gtk_box_append(
GTK_BOX(workspace->relation_details_panel),
workspace->relation_details_summary_label
);
gtk_widget_set_visible(workspace->relation_details_panel, FALSE);
investigation_graph_view_set_selection_callback( investigation_graph_view_set_selection_callback(
workspace->graph_view, workspace->graph_view,
workspace_on_graph_node_selected, workspace_on_graph_node_selected,
@ -1667,6 +1768,17 @@ Workspace *workspace_new(void)
) )
); );
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_set_clip_overlay(
GTK_OVERLAY( GTK_OVERLAY(
workspace->graph_view_page workspace->graph_view_page
@ -2061,6 +2173,22 @@ gboolean workspace_select_graph_entity(
); );
} }
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_graph_loading( void workspace_set_graph_loading(
Workspace *workspace Workspace *workspace
) )