labfy-investigation/src/widgets/evidence_tree_view.c
2026-07-20 11:57:56 +02:00

1027 lines
19 KiB
C

/******************************************************************************
* @file evidence_tree_view.c
* @brief Affichage GTK hiérarchique des catégories et preuves importées.
******************************************************************************/
#include "widgets/evidence_tree_view.h"
#include "widgets/evidence_category_item.h"
#include "widgets/evidence_list_item.h"
#include <gio/gio.h>
#include <glib-object.h>
#include <gtk/gtk.h>
/**
* @struct EvidenceTreeView
* @brief État privé de la vue hiérarchique.
*/
struct EvidenceTreeView
{
GtkWidget *root_widget;
GtkWidget *list_view;
GtkListItemFactory *factory;
GtkSingleSelection *selection_model;
EvidenceCategoryModel *category_model;
EvidenceTreeViewSelectionCallback selection_callback;
gpointer selection_user_data;
};
/**
* @brief Retourne le modèle enfant d'une catégorie.
*
* Les preuves sont des feuilles et retournent donc NULL.
*
* @param item Catégorie ou preuve examinée.
* @param user_data Données privées inutilisées.
*
* @return Nouveau GListModel possédé par GtkTreeListModel, ou NULL.
*/
static GListModel *evidence_tree_view_create_children_model(
gpointer item,
gpointer user_data
)
{
EvidenceCategoryItem *evidence_category_item =
NULL;
GListModel *children =
NULL;
(void) user_data;
if (item == NULL ||
!EVIDENCE_IS_CATEGORY_ITEM(item))
{
return NULL;
}
evidence_category_item =
EVIDENCE_CATEGORY_ITEM(
item
);
children =
evidence_category_item_get_children(
evidence_category_item
);
if (children == NULL ||
g_list_model_get_n_items(children) == 0)
{
return NULL;
}
/*
* GtkTreeListModel prend possession de la référence retournée.
*/
return g_object_ref(
children
);
}
/**
* @brief Retourne une icône adaptée au type métier d'une preuve.
*/
static const char *evidence_tree_view_get_evidence_icon_name(
const EvidenceListItem *evidence_list_item
)
{
const char *type_identifier =
NULL;
if (evidence_list_item == NULL)
{
return "text-x-generic-symbolic";
}
type_identifier =
evidence_list_item_get_type_identifier(
evidence_list_item
);
if (g_strcmp0(
type_identifier,
"image"
) == 0)
{
return "image-x-generic-symbolic";
}
if (g_strcmp0(
type_identifier,
"video"
) == 0)
{
return "video-x-generic-symbolic";
}
if (g_strcmp0(
type_identifier,
"audio"
) == 0)
{
return "audio-x-generic-symbolic";
}
if (g_strcmp0(
type_identifier,
"email"
) == 0)
{
return "mail-unread-symbolic";
}
return "text-x-generic-symbolic";
}
/**
* @brief Crée les widgets constituant une ligne.
*
* Structure :
*
* GtkTreeExpander
* └── GtkBox
* ├── GtkImage
* ├── GtkLabel principal
* └── GtkLabel secondaire
*/
static void evidence_tree_view_factory_setup(
GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer user_data
)
{
GtkWidget *expander =
NULL;
GtkWidget *row_box =
NULL;
GtkWidget *image =
NULL;
GtkWidget *primary_label =
NULL;
GtkWidget *secondary_label =
NULL;
(void) factory;
(void) user_data;
expander =
gtk_tree_expander_new();
row_box =
gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
6
);
image =
gtk_image_new();
primary_label =
gtk_label_new(
NULL
);
secondary_label =
gtk_label_new(
NULL
);
gtk_image_set_pixel_size(
GTK_IMAGE(image),
16
);
gtk_label_set_xalign(
GTK_LABEL(primary_label),
0.0F
);
gtk_label_set_ellipsize(
GTK_LABEL(primary_label),
PANGO_ELLIPSIZE_END
);
gtk_widget_set_hexpand(
primary_label,
TRUE
);
gtk_widget_set_halign(
primary_label,
GTK_ALIGN_FILL
);
gtk_label_set_xalign(
GTK_LABEL(secondary_label),
1.0F
);
gtk_widget_set_halign(
secondary_label,
GTK_ALIGN_END
);
gtk_widget_add_css_class(
secondary_label,
"dim-label"
);
gtk_widget_set_margin_end(
row_box,
8
);
gtk_box_append(
GTK_BOX(row_box),
image
);
gtk_box_append(
GTK_BOX(row_box),
primary_label
);
gtk_box_append(
GTK_BOX(row_box),
secondary_label
);
gtk_tree_expander_set_child(
GTK_TREE_EXPANDER(expander),
row_box
);
gtk_list_item_set_child(
list_item,
expander
);
/*
* Le focus doit rester dans GtkTreeExpander pour que les commandes
* clavier d'ouverture et de fermeture restent accessibles.
*/
gtk_list_item_set_focusable(
list_item,
FALSE
);
}
/**
* @brief Relie une ligne GTK à une catégorie ou une preuve.
*/
static void evidence_tree_view_factory_bind(
GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer user_data
)
{
GtkTreeListRow *tree_row =
NULL;
GObject *item =
NULL;
GtkWidget *expander =
NULL;
GtkWidget *row_box =
NULL;
GtkWidget *image =
NULL;
GtkWidget *primary_label =
NULL;
GtkWidget *secondary_label =
NULL;
char *count_text =
NULL;
(void) factory;
(void) user_data;
tree_row =
GTK_TREE_LIST_ROW(
gtk_list_item_get_item(
list_item
)
);
if (tree_row == NULL)
{
return;
}
item =
gtk_tree_list_row_get_item(
tree_row
);
if (item == NULL)
{
return;
}
expander =
gtk_list_item_get_child(
list_item
);
if (expander == NULL)
{
return;
}
row_box =
gtk_tree_expander_get_child(
GTK_TREE_EXPANDER(
expander
)
);
if (row_box == NULL)
{
return;
}
image =
gtk_widget_get_first_child(
row_box
);
if (image == NULL)
{
return;
}
primary_label =
gtk_widget_get_next_sibling(
image
);
if (primary_label == NULL)
{
return;
}
secondary_label =
gtk_widget_get_next_sibling(
primary_label
);
if (secondary_label == NULL)
{
return;
}
gtk_tree_expander_set_list_row(
GTK_TREE_EXPANDER(
expander
),
tree_row
);
gtk_widget_remove_css_class(
primary_label,
"heading"
);
gtk_widget_set_tooltip_text(
row_box,
NULL
);
if (EVIDENCE_IS_CATEGORY_ITEM(item))
{
EvidenceCategoryItem *evidence_category_item =
EVIDENCE_CATEGORY_ITEM(
item
);
gtk_image_set_from_icon_name(
GTK_IMAGE(image),
"folder-symbolic"
);
gtk_label_set_text(
GTK_LABEL(primary_label),
evidence_category_item_get_label(
evidence_category_item
)
);
gtk_widget_add_css_class(
primary_label,
"heading"
);
count_text =
g_strdup_printf(
"%u",
evidence_category_item_get_count(
evidence_category_item
)
);
gtk_label_set_text(
GTK_LABEL(secondary_label),
count_text != NULL
? count_text
: ""
);
g_free(
count_text
);
return;
}
if (EVIDENCE_IS_LIST_ITEM(item))
{
EvidenceListItem *evidence_list_item =
EVIDENCE_LIST_ITEM(
item
);
const char *original_name =
evidence_list_item_get_original_name(
evidence_list_item
);
gtk_image_set_from_icon_name(
GTK_IMAGE(image),
evidence_tree_view_get_evidence_icon_name(
evidence_list_item
)
);
gtk_label_set_text(
GTK_LABEL(primary_label),
original_name != NULL
? original_name
: ""
);
gtk_label_set_text(
GTK_LABEL(secondary_label),
evidence_list_item_get_integrity_status_label(
evidence_list_item
)
);
gtk_widget_set_tooltip_text(
row_box,
original_name
);
return;
}
gtk_image_clear(
GTK_IMAGE(image)
);
gtk_label_set_text(
GTK_LABEL(primary_label),
""
);
gtk_label_set_text(
GTK_LABEL(secondary_label),
""
);
}
/**
* @brief Détache le contenu d'une ligne réutilisée par GTK.
*/
static void evidence_tree_view_factory_unbind(
GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer user_data
)
{
GtkWidget *expander =
NULL;
GtkWidget *row_box =
NULL;
GtkWidget *image =
NULL;
GtkWidget *primary_label =
NULL;
GtkWidget *secondary_label =
NULL;
(void) factory;
(void) user_data;
expander =
gtk_list_item_get_child(
list_item
);
if (expander == NULL)
{
return;
}
row_box =
gtk_tree_expander_get_child(
GTK_TREE_EXPANDER(
expander
)
);
if (row_box != NULL)
{
image =
gtk_widget_get_first_child(
row_box
);
if (image != NULL)
{
primary_label =
gtk_widget_get_next_sibling(
image
);
}
if (primary_label != NULL)
{
secondary_label =
gtk_widget_get_next_sibling(
primary_label
);
}
}
gtk_tree_expander_set_list_row(
GTK_TREE_EXPANDER(
expander
),
NULL
);
if (image != NULL)
{
gtk_image_clear(
GTK_IMAGE(image)
);
}
if (primary_label != NULL)
{
gtk_widget_remove_css_class(
primary_label,
"heading"
);
gtk_label_set_text(
GTK_LABEL(primary_label),
""
);
}
if (secondary_label != NULL)
{
gtk_label_set_text(
GTK_LABEL(secondary_label),
""
);
}
if (row_box != NULL)
{
gtk_widget_set_tooltip_text(
row_box,
NULL
);
}
}
/**
* @brief Transmet une sélection au callback public.
*/
static void evidence_tree_view_emit_selection(
EvidenceTreeView *evidence_tree_view,
const char *evidence_identifier
)
{
if (evidence_tree_view == NULL ||
evidence_tree_view->selection_callback == NULL)
{
return;
}
evidence_tree_view->selection_callback(
evidence_identifier,
evidence_tree_view->selection_user_data
);
}
/**
* @brief Réagit au changement de ligne sélectionnée.
*/
static void evidence_tree_view_on_selection_changed(
GObject *selection_model,
GParamSpec *property_spec,
gpointer user_data
)
{
EvidenceTreeView *evidence_tree_view =
user_data;
GObject *selected_item =
NULL;
GtkTreeListRow *tree_row =
NULL;
GObject *item =
NULL;
const char *evidence_identifier =
NULL;
(void) property_spec;
if (evidence_tree_view == NULL)
{
return;
}
selected_item =
gtk_single_selection_get_selected_item(
GTK_SINGLE_SELECTION(
selection_model
)
);
if (selected_item == NULL ||
!GTK_IS_TREE_LIST_ROW(selected_item))
{
evidence_tree_view_emit_selection(
evidence_tree_view,
NULL
);
return;
}
tree_row =
GTK_TREE_LIST_ROW(
selected_item
);
item =
gtk_tree_list_row_get_item(
tree_row
);
if (item != NULL &&
EVIDENCE_IS_LIST_ITEM(item))
{
evidence_identifier =
evidence_list_item_get_identifier(
EVIDENCE_LIST_ITEM(
item
)
);
}
/*
* Une catégorie sélectionnée transmet NULL.
*/
evidence_tree_view_emit_selection(
evidence_tree_view,
evidence_identifier
);
}
/**
* @brief Retire le modèle actuellement affiché.
*/
static void evidence_tree_view_clear_model(
EvidenceTreeView *evidence_tree_view
)
{
if (evidence_tree_view == NULL)
{
return;
}
if (evidence_tree_view->list_view != NULL)
{
gtk_list_view_set_model(
GTK_LIST_VIEW(
evidence_tree_view->list_view
),
NULL
);
}
g_clear_object(
&evidence_tree_view->selection_model
);
evidence_tree_view->category_model =
NULL;
evidence_tree_view_emit_selection(
evidence_tree_view,
NULL
);
}
EvidenceTreeView *evidence_tree_view_new(void)
{
EvidenceTreeView *evidence_tree_view =
NULL;
evidence_tree_view =
g_try_new0(
EvidenceTreeView,
1
);
if (evidence_tree_view == NULL)
{
return NULL;
}
evidence_tree_view->factory =
GTK_LIST_ITEM_FACTORY(
gtk_signal_list_item_factory_new()
);
if (evidence_tree_view->factory == NULL)
{
evidence_tree_view_free(
evidence_tree_view
);
return NULL;
}
g_signal_connect(
evidence_tree_view->factory,
"setup",
G_CALLBACK(
evidence_tree_view_factory_setup
),
NULL
);
g_signal_connect(
evidence_tree_view->factory,
"bind",
G_CALLBACK(
evidence_tree_view_factory_bind
),
NULL
);
g_signal_connect(
evidence_tree_view->factory,
"unbind",
G_CALLBACK(
evidence_tree_view_factory_unbind
),
NULL
);
evidence_tree_view->list_view =
gtk_list_view_new(
NULL,
g_object_ref(
evidence_tree_view->factory
)
);
if (evidence_tree_view->list_view == NULL)
{
evidence_tree_view_free(
evidence_tree_view
);
return NULL;
}
evidence_tree_view->root_widget =
gtk_scrolled_window_new();
if (evidence_tree_view->root_widget == NULL)
{
evidence_tree_view_free(
evidence_tree_view
);
return NULL;
}
gtk_widget_set_hexpand(
evidence_tree_view->root_widget,
TRUE
);
gtk_widget_set_vexpand(
evidence_tree_view->root_widget,
TRUE
);
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(
evidence_tree_view->root_widget
),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC
);
gtk_scrolled_window_set_child(
GTK_SCROLLED_WINDOW(
evidence_tree_view->root_widget
),
evidence_tree_view->list_view
);
return evidence_tree_view;
}
GtkWidget *evidence_tree_view_get_widget(
const EvidenceTreeView *evidence_tree_view
)
{
if (evidence_tree_view == NULL)
{
return NULL;
}
return evidence_tree_view->root_widget;
}
void evidence_tree_view_set_model(
EvidenceTreeView *evidence_tree_view,
EvidenceCategoryModel *evidence_category_model
)
{
GListModel *category_list_model =
NULL;
GtkTreeListModel *tree_list_model =
NULL;
GtkSingleSelection *selection_model =
NULL;
if (evidence_tree_view == NULL)
{
return;
}
evidence_tree_view_clear_model(
evidence_tree_view
);
if (evidence_category_model == NULL)
{
return;
}
category_list_model =
evidence_category_model_get_list_model(
evidence_category_model
);
if (category_list_model == NULL)
{
return;
}
tree_list_model =
gtk_tree_list_model_new(
g_object_ref(
category_list_model
),
FALSE,
FALSE,
evidence_tree_view_create_children_model,
NULL,
NULL
);
if (tree_list_model == NULL)
{
return;
}
selection_model =
gtk_single_selection_new(
G_LIST_MODEL(
tree_list_model
)
);
if (selection_model == NULL)
{
g_object_unref(
tree_list_model
);
return;
}
gtk_single_selection_set_autoselect(
selection_model,
FALSE
);
gtk_single_selection_set_can_unselect(
selection_model,
TRUE
);
g_signal_connect(
selection_model,
"notify::selected-item",
G_CALLBACK(
evidence_tree_view_on_selection_changed
),
evidence_tree_view
);
evidence_tree_view->selection_model =
selection_model;
evidence_tree_view->category_model =
evidence_category_model;
gtk_list_view_set_model(
GTK_LIST_VIEW(
evidence_tree_view->list_view
),
GTK_SELECTION_MODEL(
evidence_tree_view->selection_model
)
);
}
void evidence_tree_view_set_selection_callback(
EvidenceTreeView *evidence_tree_view,
EvidenceTreeViewSelectionCallback callback,
gpointer user_data
)
{
if (evidence_tree_view == NULL)
{
return;
}
evidence_tree_view->selection_callback =
callback;
evidence_tree_view->selection_user_data =
user_data;
}
void evidence_tree_view_free(
EvidenceTreeView *evidence_tree_view
)
{
if (evidence_tree_view == NULL)
{
return;
}
evidence_tree_view_clear_model(
evidence_tree_view
);
g_clear_object(
&evidence_tree_view->factory
);
/*
* root_widget et list_view sont gérés par l'arbre GTK
* une fois intégrés dans la fenêtre.
*/
g_free(
evidence_tree_view
);
}