feat(sidebar): add entity navigation
This commit is contained in:
parent
1fb3c71c3f
commit
ab6621e95f
8 changed files with 567 additions and 1 deletions
|
|
@ -130,6 +130,7 @@ Le socle actuel comprend notamment :
|
||||||
- arborescence des fichiers ;
|
- arborescence des fichiers ;
|
||||||
- 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 ;
|
||||||
- affichage graphique des erreurs ;
|
- affichage graphique des erreurs ;
|
||||||
- tâches asynchrones annulables ;
|
- tâches asynchrones annulables ;
|
||||||
- gestionnaire de tâches ;
|
- gestionnaire de tâches ;
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,21 @@ const EntityRecord *investigation_graph_view_get_selected_entity(
|
||||||
const InvestigationGraphView *graph_view
|
const InvestigationGraphView *graph_view
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sélectionne une entité du graphe par son UUID.
|
||||||
|
*
|
||||||
|
* La vue reste inchangée si l'identifiant est absent du graphe.
|
||||||
|
*
|
||||||
|
* @param graph_view Vue graphique à modifier.
|
||||||
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
*
|
||||||
|
* @return TRUE lorsque l'entité est trouvée et sélectionnée.
|
||||||
|
*/
|
||||||
|
gboolean investigation_graph_view_select_entity(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
const char *entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Supprime la sélection courante.
|
* @brief Supprime la sélection courante.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "core/investigation_tree_model.h"
|
#include "core/investigation_tree_model.h"
|
||||||
#include "widgets/investigation_tree_view.h"
|
#include "widgets/investigation_tree_view.h"
|
||||||
#include "widgets/evidence_category_model.h"
|
#include "widgets/evidence_category_model.h"
|
||||||
|
#include "models/investigation_graph_model.h"
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
|
@ -34,6 +35,20 @@ typedef void (*SidebarEvidenceSelectionCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé lorsqu'une entité est sélectionnée.
|
||||||
|
*
|
||||||
|
* L'identifiant est emprunté et valide uniquement pendant l'appel.
|
||||||
|
* Une sélection vide transmet NULL.
|
||||||
|
*
|
||||||
|
* @param entity_identifier UUID de l'entité, ou NULL.
|
||||||
|
* @param user_data Données privées fournies par l'appelant.
|
||||||
|
*/
|
||||||
|
typedef void (*SidebarEntitySelectionCallback)(
|
||||||
|
const char *entity_identifier,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crée un nouveau panneau de navigation latéral.
|
* @brief Crée un nouveau panneau de navigation latéral.
|
||||||
*
|
*
|
||||||
|
|
@ -113,6 +128,32 @@ void sidebar_set_evidence_selection_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Installe les entités du graphe dans la barre latérale.
|
||||||
|
*
|
||||||
|
* La Sidebar emprunte graph_model. Passer NULL vide la liste.
|
||||||
|
*
|
||||||
|
* @param sidebar Barre latérale à actualiser.
|
||||||
|
* @param graph_model Graphe emprunté, ou NULL.
|
||||||
|
*/
|
||||||
|
void sidebar_set_entity_model(
|
||||||
|
Sidebar *sidebar,
|
||||||
|
const InvestigationGraphModel *graph_model
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Définit le callback de sélection d'une entité.
|
||||||
|
*
|
||||||
|
* @param sidebar Barre latérale à configurer.
|
||||||
|
* @param callback Callback facultatif.
|
||||||
|
* @param user_data Données privées transmises au callback.
|
||||||
|
*/
|
||||||
|
void sidebar_set_entity_selection_callback(
|
||||||
|
Sidebar *sidebar,
|
||||||
|
SidebarEntitySelectionCallback 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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,21 @@ void workspace_set_selected_evidence(
|
||||||
const EvidenceRecord *evidence_record
|
const EvidenceRecord *evidence_record
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sélectionne une entité dans le graphe affiché.
|
||||||
|
*
|
||||||
|
* La sélection actualise également le volet de détails et le contexte OSINT.
|
||||||
|
*
|
||||||
|
* @param workspace Zone de travail.
|
||||||
|
* @param entity_identifier UUID de l'entité à sélectionner.
|
||||||
|
*
|
||||||
|
* @return TRUE lorsque l'entité est trouvée et sélectionnée.
|
||||||
|
*/
|
||||||
|
gboolean workspace_select_graph_entity(
|
||||||
|
Workspace *workspace,
|
||||||
|
const char *entity_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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,29 @@ struct MainWindow
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ouvre dans le workspace l'entité choisie dans la sidebar.
|
||||||
|
*/
|
||||||
|
static void main_window_on_sidebar_entity_selected(
|
||||||
|
const char *entity_identifier,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
MainWindow *main_window = user_data;
|
||||||
|
|
||||||
|
if (main_window == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
entity_identifier[0] == '\0')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace_select_graph_entity(
|
||||||
|
main_window->workspace,
|
||||||
|
entity_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.
|
||||||
*
|
*
|
||||||
|
|
@ -663,6 +686,12 @@ MainWindow *main_window_new(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebar_set_entity_selection_callback(
|
||||||
|
main_window->sidebar,
|
||||||
|
main_window_on_sidebar_entity_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,
|
||||||
|
|
@ -1040,6 +1069,11 @@ void main_window_set_graph_loading(
|
||||||
FALSE
|
FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_model(
|
||||||
|
main_window->sidebar,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
workspace_set_graph_loading(
|
workspace_set_graph_loading(
|
||||||
main_window->workspace
|
main_window->workspace
|
||||||
);
|
);
|
||||||
|
|
@ -1062,6 +1096,11 @@ void main_window_set_graph(
|
||||||
graph_layout
|
graph_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_model(
|
||||||
|
main_window->sidebar,
|
||||||
|
graph_model
|
||||||
|
);
|
||||||
|
|
||||||
gtk_widget_set_sensitive(
|
gtk_widget_set_sensitive(
|
||||||
main_window->show_graph_button,
|
main_window->show_graph_button,
|
||||||
graph_model != NULL
|
graph_model != NULL
|
||||||
|
|
@ -1083,6 +1122,11 @@ void main_window_set_graph_error(
|
||||||
FALSE
|
FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_model(
|
||||||
|
main_window->sidebar,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
workspace_set_graph_error(
|
workspace_set_graph_error(
|
||||||
main_window->workspace,
|
main_window->workspace,
|
||||||
message
|
message
|
||||||
|
|
@ -1103,6 +1147,11 @@ void main_window_clear_graph(
|
||||||
FALSE
|
FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_model(
|
||||||
|
main_window->sidebar,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
workspace_clear_graph(
|
workspace_clear_graph(
|
||||||
main_window->workspace
|
main_window->workspace
|
||||||
);
|
);
|
||||||
|
|
@ -1436,6 +1485,17 @@ void main_window_free(
|
||||||
main_window->sidebar,
|
main_window->sidebar,
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_selection_callback(
|
||||||
|
main_window->sidebar,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
sidebar_set_entity_model(
|
||||||
|
main_window->sidebar,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1526,4 +1586,3 @@ void main_window_free(
|
||||||
main_window
|
main_window
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4257,6 +4257,40 @@ const EntityRecord *investigation_graph_view_get_selected_entity(
|
||||||
return graph_view->selected_node->entity_record;
|
return graph_view->selected_node->entity_record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean investigation_graph_view_select_entity(
|
||||||
|
InvestigationGraphView *graph_view,
|
||||||
|
const char *entity_identifier
|
||||||
|
)
|
||||||
|
{
|
||||||
|
InvestigationGraphNodeLayout *node_layout = NULL;
|
||||||
|
|
||||||
|
if (graph_view == NULL ||
|
||||||
|
graph_view->node_layouts_by_identifier == NULL ||
|
||||||
|
entity_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(entity_identifier))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_layout = g_hash_table_lookup(
|
||||||
|
graph_view->node_layouts_by_identifier,
|
||||||
|
entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (node_layout == NULL ||
|
||||||
|
node_layout->kind != INVESTIGATION_GRAPH_NODE_KIND_ENTITY)
|
||||||
|
{
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "widgets/evidence_tree_view.h"
|
#include "widgets/evidence_tree_view.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Largeur initiale du panneau latéral.
|
* @brief Largeur initiale du panneau latéral.
|
||||||
|
|
@ -27,6 +28,12 @@
|
||||||
#define SIDEBAR_PAGE_EVIDENCE \
|
#define SIDEBAR_PAGE_EVIDENCE \
|
||||||
"evidence"
|
"evidence"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Identifiant de la page contenant les entités.
|
||||||
|
*/
|
||||||
|
#define SIDEBAR_PAGE_ENTITY \
|
||||||
|
"entity"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief État vide de l'onglet Preuves.
|
* @brief État vide de l'onglet Preuves.
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,6 +68,14 @@ struct Sidebar
|
||||||
GtkWidget *evidence_empty_page;
|
GtkWidget *evidence_empty_page;
|
||||||
GtkWidget *evidence_empty_label;
|
GtkWidget *evidence_empty_label;
|
||||||
|
|
||||||
|
GtkWidget *entity_page;
|
||||||
|
GtkWidget *entity_search_entry;
|
||||||
|
GtkWidget *entity_count_label;
|
||||||
|
GtkWidget *entity_scrolled_window;
|
||||||
|
GtkWidget *entity_list_box;
|
||||||
|
GtkWidget *entity_empty_label;
|
||||||
|
GtkWidget *entity_no_result_label;
|
||||||
|
|
||||||
InvestigationTreeView *tree_view;
|
InvestigationTreeView *tree_view;
|
||||||
EvidenceTreeView *evidence_tree_view;
|
EvidenceTreeView *evidence_tree_view;
|
||||||
|
|
||||||
|
|
@ -68,8 +83,132 @@ struct Sidebar
|
||||||
evidence_selection_callback;
|
evidence_selection_callback;
|
||||||
|
|
||||||
gpointer evidence_selection_user_data;
|
gpointer evidence_selection_user_data;
|
||||||
|
|
||||||
|
SidebarEntitySelectionCallback
|
||||||
|
entity_selection_callback;
|
||||||
|
|
||||||
|
gpointer entity_selection_user_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Filtre une ligne d'entité avec le texte de recherche courant.
|
||||||
|
*/
|
||||||
|
static gboolean sidebar_filter_entity_row(
|
||||||
|
GtkListBoxRow *row,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Sidebar *sidebar = user_data;
|
||||||
|
const EntityRecord *entity_record = NULL;
|
||||||
|
const char *search_text = NULL;
|
||||||
|
char *normalized_search = NULL;
|
||||||
|
char *normalized_value = NULL;
|
||||||
|
char *normalized_type = NULL;
|
||||||
|
gboolean visible = TRUE;
|
||||||
|
|
||||||
|
if (sidebar == NULL || row == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
search_text = gtk_editable_get_text(
|
||||||
|
GTK_EDITABLE(sidebar->entity_search_entry)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (search_text == NULL || search_text[0] == '\0')
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_record = g_object_get_data(
|
||||||
|
G_OBJECT(row),
|
||||||
|
"entity-record"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entity_record == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized_search = g_utf8_casefold(search_text, -1);
|
||||||
|
normalized_value = g_utf8_casefold(
|
||||||
|
entity_record_get_value(entity_record),
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
normalized_type = g_utf8_casefold(
|
||||||
|
entity_record_get_type_identifier(entity_record),
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
|
||||||
|
visible = normalized_search != NULL &&
|
||||||
|
((normalized_value != NULL &&
|
||||||
|
strstr(normalized_value, normalized_search) != NULL) ||
|
||||||
|
(normalized_type != NULL &&
|
||||||
|
strstr(normalized_type, normalized_search) != NULL));
|
||||||
|
|
||||||
|
g_free(normalized_type);
|
||||||
|
g_free(normalized_value);
|
||||||
|
g_free(normalized_search);
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Réapplique le filtre local des entités.
|
||||||
|
*/
|
||||||
|
static void sidebar_on_entity_search_changed(
|
||||||
|
GtkSearchEntry *search_entry,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Sidebar *sidebar = user_data;
|
||||||
|
|
||||||
|
(void) search_entry;
|
||||||
|
|
||||||
|
if (sidebar != NULL && sidebar->entity_list_box != NULL)
|
||||||
|
{
|
||||||
|
gtk_list_box_invalidate_filter(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Relaie la sélection provenant de la liste d'entités.
|
||||||
|
*/
|
||||||
|
static void sidebar_on_entity_row_selected(
|
||||||
|
GtkListBox *list_box,
|
||||||
|
GtkListBoxRow *row,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Sidebar *sidebar = user_data;
|
||||||
|
const EntityRecord *entity_record = NULL;
|
||||||
|
const char *entity_identifier = NULL;
|
||||||
|
|
||||||
|
(void) list_box;
|
||||||
|
|
||||||
|
if (sidebar == NULL ||
|
||||||
|
sidebar->entity_selection_callback == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row != NULL)
|
||||||
|
{
|
||||||
|
entity_record = g_object_get_data(
|
||||||
|
G_OBJECT(row),
|
||||||
|
"entity-record"
|
||||||
|
);
|
||||||
|
entity_identifier = entity_record_get_identifier(entity_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar->entity_selection_callback(
|
||||||
|
entity_identifier,
|
||||||
|
sidebar->entity_selection_user_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Relaie la sélection provenant de EvidenceTreeView.
|
* @brief Relaie la sélection provenant de EvidenceTreeView.
|
||||||
*/
|
*/
|
||||||
|
|
@ -585,6 +724,129 @@ Sidebar *sidebar_new(void)
|
||||||
"Preuves"
|
"Preuves"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Page Entités : recherche locale, compteur et liste sélectionnable.
|
||||||
|
*/
|
||||||
|
sidebar->entity_page = gtk_box_new(
|
||||||
|
GTK_ORIENTATION_VERTICAL,
|
||||||
|
8
|
||||||
|
);
|
||||||
|
sidebar->entity_search_entry = gtk_search_entry_new();
|
||||||
|
sidebar->entity_count_label = gtk_label_new("0 entité");
|
||||||
|
sidebar->entity_scrolled_window = gtk_scrolled_window_new();
|
||||||
|
sidebar->entity_list_box = gtk_list_box_new();
|
||||||
|
sidebar->entity_empty_label = gtk_label_new(
|
||||||
|
"Aucune entité active dans cette enquête."
|
||||||
|
);
|
||||||
|
sidebar->entity_no_result_label = gtk_label_new(
|
||||||
|
"Aucune entité ne correspond à la recherche."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sidebar->entity_page == NULL ||
|
||||||
|
sidebar->entity_search_entry == NULL ||
|
||||||
|
sidebar->entity_count_label == NULL ||
|
||||||
|
sidebar->entity_scrolled_window == NULL ||
|
||||||
|
sidebar->entity_list_box == NULL ||
|
||||||
|
sidebar->entity_empty_label == NULL ||
|
||||||
|
sidebar->entity_no_result_label == NULL)
|
||||||
|
{
|
||||||
|
sidebar_free(sidebar);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_set_margin_start(sidebar->entity_search_entry, 8);
|
||||||
|
gtk_widget_set_margin_end(sidebar->entity_search_entry, 8);
|
||||||
|
gtk_search_entry_set_placeholder_text(
|
||||||
|
GTK_SEARCH_ENTRY(sidebar->entity_search_entry),
|
||||||
|
"Rechercher une entité"
|
||||||
|
);
|
||||||
|
gtk_widget_set_halign(
|
||||||
|
sidebar->entity_count_label,
|
||||||
|
GTK_ALIGN_START
|
||||||
|
);
|
||||||
|
gtk_widget_set_margin_start(sidebar->entity_count_label, 12);
|
||||||
|
gtk_widget_add_css_class(
|
||||||
|
sidebar->entity_count_label,
|
||||||
|
"dim-label"
|
||||||
|
);
|
||||||
|
gtk_widget_set_hexpand(sidebar->entity_scrolled_window, TRUE);
|
||||||
|
gtk_widget_set_vexpand(sidebar->entity_scrolled_window, TRUE);
|
||||||
|
gtk_scrolled_window_set_policy(
|
||||||
|
GTK_SCROLLED_WINDOW(sidebar->entity_scrolled_window),
|
||||||
|
GTK_POLICY_NEVER,
|
||||||
|
GTK_POLICY_AUTOMATIC
|
||||||
|
);
|
||||||
|
gtk_scrolled_window_set_has_frame(
|
||||||
|
GTK_SCROLLED_WINDOW(sidebar->entity_scrolled_window),
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
gtk_widget_set_hexpand(sidebar->entity_list_box, TRUE);
|
||||||
|
gtk_scrolled_window_set_child(
|
||||||
|
GTK_SCROLLED_WINDOW(sidebar->entity_scrolled_window),
|
||||||
|
sidebar->entity_list_box
|
||||||
|
);
|
||||||
|
gtk_list_box_set_selection_mode(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box),
|
||||||
|
GTK_SELECTION_SINGLE
|
||||||
|
);
|
||||||
|
gtk_list_box_set_filter_func(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box),
|
||||||
|
sidebar_filter_entity_row,
|
||||||
|
sidebar,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
gtk_label_set_wrap(
|
||||||
|
GTK_LABEL(sidebar->entity_no_result_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
gtk_widget_set_margin_start(sidebar->entity_no_result_label, 20);
|
||||||
|
gtk_widget_set_margin_end(sidebar->entity_no_result_label, 20);
|
||||||
|
gtk_widget_set_margin_top(sidebar->entity_no_result_label, 20);
|
||||||
|
gtk_list_box_set_placeholder(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box),
|
||||||
|
sidebar->entity_no_result_label
|
||||||
|
);
|
||||||
|
gtk_widget_set_halign(sidebar->entity_empty_label, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_valign(sidebar->entity_empty_label, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_vexpand(sidebar->entity_empty_label, TRUE);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(sidebar->entity_empty_label), TRUE);
|
||||||
|
|
||||||
|
g_signal_connect(
|
||||||
|
sidebar->entity_search_entry,
|
||||||
|
"search-changed",
|
||||||
|
G_CALLBACK(sidebar_on_entity_search_changed),
|
||||||
|
sidebar
|
||||||
|
);
|
||||||
|
g_signal_connect(
|
||||||
|
sidebar->entity_list_box,
|
||||||
|
"row-selected",
|
||||||
|
G_CALLBACK(sidebar_on_entity_row_selected),
|
||||||
|
sidebar
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(sidebar->entity_page),
|
||||||
|
sidebar->entity_search_entry
|
||||||
|
);
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(sidebar->entity_page),
|
||||||
|
sidebar->entity_count_label
|
||||||
|
);
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(sidebar->entity_page),
|
||||||
|
sidebar->entity_scrolled_window
|
||||||
|
);
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(sidebar->entity_page),
|
||||||
|
sidebar->entity_empty_label
|
||||||
|
);
|
||||||
|
gtk_stack_add_titled(
|
||||||
|
GTK_STACK(sidebar->stack),
|
||||||
|
sidebar->entity_page,
|
||||||
|
SIDEBAR_PAGE_ENTITY,
|
||||||
|
"Entités"
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* L'onglet Dossier reste sélectionné au démarrage.
|
* L'onglet Dossier reste sélectionné au démarrage.
|
||||||
*/
|
*/
|
||||||
|
|
@ -751,6 +1013,126 @@ void sidebar_set_evidence_selection_callback(
|
||||||
user_data;
|
user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sidebar_set_entity_model(
|
||||||
|
Sidebar *sidebar,
|
||||||
|
const InvestigationGraphModel *graph_model
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GPtrArray *entity_records = NULL;
|
||||||
|
GtkWidget *child = NULL;
|
||||||
|
guint entity_index = 0;
|
||||||
|
char *count_text = NULL;
|
||||||
|
|
||||||
|
if (sidebar == NULL || sidebar->entity_list_box == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((child = gtk_widget_get_first_child(
|
||||||
|
sidebar->entity_list_box
|
||||||
|
)) != NULL)
|
||||||
|
{
|
||||||
|
gtk_list_box_remove(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box),
|
||||||
|
child
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graph_model != NULL)
|
||||||
|
{
|
||||||
|
entity_records = investigation_graph_model_list_entities(
|
||||||
|
graph_model,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entity_index = 0;
|
||||||
|
entity_records != NULL && entity_index < entity_records->len;
|
||||||
|
entity_index++)
|
||||||
|
{
|
||||||
|
const EntityRecord *entity_record = g_ptr_array_index(
|
||||||
|
entity_records,
|
||||||
|
entity_index
|
||||||
|
);
|
||||||
|
GtkWidget *row = gtk_list_box_row_new();
|
||||||
|
GtkWidget *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||||
|
GtkWidget *value_label = gtk_label_new(
|
||||||
|
entity_record_get_value(entity_record)
|
||||||
|
);
|
||||||
|
GtkWidget *type_label = gtk_label_new(
|
||||||
|
entity_record_get_type_identifier(entity_record)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (row == NULL || content == NULL ||
|
||||||
|
value_label == NULL || type_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(value_label), 0.0F);
|
||||||
|
gtk_label_set_ellipsize(
|
||||||
|
GTK_LABEL(value_label),
|
||||||
|
PANGO_ELLIPSIZE_END
|
||||||
|
);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(type_label), 0.0F);
|
||||||
|
gtk_widget_add_css_class(type_label, "dim-label");
|
||||||
|
gtk_box_append(GTK_BOX(content), value_label);
|
||||||
|
gtk_box_append(GTK_BOX(content), type_label);
|
||||||
|
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), content);
|
||||||
|
g_object_set_data(
|
||||||
|
G_OBJECT(row),
|
||||||
|
"entity-record",
|
||||||
|
(gpointer) entity_record
|
||||||
|
);
|
||||||
|
gtk_list_box_append(
|
||||||
|
GTK_LIST_BOX(sidebar->entity_list_box),
|
||||||
|
row
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
count_text = g_strdup_printf(
|
||||||
|
"%u %s",
|
||||||
|
entity_records != NULL ? entity_records->len : 0U,
|
||||||
|
entity_records != NULL && entity_records->len > 1U
|
||||||
|
? "entités"
|
||||||
|
: "entité"
|
||||||
|
);
|
||||||
|
gtk_label_set_text(
|
||||||
|
GTK_LABEL(sidebar->entity_count_label),
|
||||||
|
count_text != NULL ? count_text : "0 entité"
|
||||||
|
);
|
||||||
|
gtk_widget_set_visible(
|
||||||
|
sidebar->entity_empty_label,
|
||||||
|
entity_records == NULL || entity_records->len == 0U
|
||||||
|
);
|
||||||
|
gtk_widget_set_visible(
|
||||||
|
sidebar->entity_scrolled_window,
|
||||||
|
entity_records != NULL && entity_records->len > 0U
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(count_text);
|
||||||
|
g_clear_pointer(&entity_records, g_ptr_array_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sidebar_set_entity_selection_callback(
|
||||||
|
Sidebar *sidebar,
|
||||||
|
SidebarEntitySelectionCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (sidebar == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar->entity_selection_callback = callback;
|
||||||
|
sidebar->entity_selection_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void sidebar_free(Sidebar *sidebar)
|
void sidebar_free(Sidebar *sidebar)
|
||||||
{
|
{
|
||||||
if (sidebar == NULL)
|
if (sidebar == NULL)
|
||||||
|
|
@ -773,6 +1155,9 @@ void sidebar_free(Sidebar *sidebar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebar->entity_selection_callback = NULL;
|
||||||
|
sidebar->entity_selection_user_data = 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.
|
||||||
* GTK gère leur destruction lorsque la fenêtre est détruite.
|
* GTK gère leur destruction lorsque la fenêtre est détruite.
|
||||||
|
|
|
||||||
|
|
@ -2045,6 +2045,22 @@ void workspace_set_selected_evidence(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void workspace_set_graph_loading(
|
void workspace_set_graph_loading(
|
||||||
Workspace *workspace
|
Workspace *workspace
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue