feat(sidebar): add relation navigation
This commit is contained in:
parent
2dad97ddfb
commit
13315846e3
8 changed files with 604 additions and 9 deletions
|
|
@ -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 ;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue