diff --git a/Makefile b/Makefile index 26497c7..7d20cf9 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,10 @@ TEST_EVIDENCE_IMPORT_TASK := tests/test_evidence_import_task TEST_EVIDENCE_TYPE := tests/test_evidence_type TEST_EVIDENCE_TYPE_DAO := tests/test_evidence_type_dao TEST_EVIDENCE_IMPORT_DIALOG := tests/test_evidence_import_dialog +TEST_EVIDENCE_LIST_ITEM := tests/test_evidence_list_item +TEST_EVIDENCE_LIST_MODEL := tests/test_evidence_list_model +TEST_EVIDENCE_CATEGORY_ITEM := tests/test_evidence_category_item +TEST_EVIDENCE_CATEGORY_MODEL := tests/test_evidence_category_model all: $(TARGET) @@ -149,6 +153,19 @@ $(TEST_EVIDENCE_RECORD): \ src/models/evidence_record.c $(CC) $(EVIDENCE_RECORD_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_EVIDENCE_LIST_ITEM): \ + tests/test_evidence_list_item.c \ + src/widgets/evidence_list_item.c \ + src/models/evidence_record.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + +$(TEST_EVIDENCE_LIST_MODEL): \ + tests/test_evidence_list_model.c \ + src/widgets/evidence_list_model.c \ + src/widgets/evidence_list_item.c \ + src/models/evidence_record.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + $(TEST_INVESTIGATION_DAO): \ tests/test_investigation_dao.c \ src/dao/investigation_dao.c \ @@ -304,6 +321,21 @@ $(TEST_EVIDENCE_IMPORT_DIALOG): \ -o $@ \ $(EVIDENCE_IMPORT_DIALOG_TEST_LDFLAGS) +$(TEST_EVIDENCE_CATEGORY_ITEM): \ + tests/test_evidence_category_item.c \ + src/widgets/evidence_category_item.c \ + src/widgets/evidence_list_item.c \ + src/models/evidence_record.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + +$(TEST_EVIDENCE_CATEGORY_MODEL): \ + tests/test_evidence_category_model.c \ + src/widgets/evidence_category_model.c \ + src/widgets/evidence_category_item.c \ + src/widgets/evidence_list_item.c \ + src/models/evidence_record.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ @@ -315,6 +347,10 @@ test: \ $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ $(TEST_EVIDENCE_RECORD) \ + $(TEST_EVIDENCE_LIST_ITEM) \ + $(TEST_EVIDENCE_LIST_MODEL) \ + $(TEST_EVIDENCE_CATEGORY_ITEM) \ + $(TEST_EVIDENCE_CATEGORY_MODEL) \ $(TEST_EVIDENCE_TYPE) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_EVIDENCE_DAO) \ @@ -342,6 +378,10 @@ test: \ @$(TEST_TRANSACTION) @$(TEST_ERROR) @$(TEST_INVESTIGATION_RECORD) + @$(TEST_EVIDENCE_LIST_ITEM) + @$(TEST_EVIDENCE_LIST_MODEL) + @$(TEST_EVIDENCE_CATEGORY_ITEM) + @$(TEST_EVIDENCE_CATEGORY_MODEL) @$(TEST_EVIDENCE_RECORD) @$(TEST_EVIDENCE_TYPE) @$(TEST_INVESTIGATION_DAO) @@ -380,6 +420,10 @@ clean: $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ $(TEST_EVIDENCE_RECORD) \ + $(TEST_EVIDENCE_LIST_ITEM) \ + $(TEST_EVIDENCE_LIST_MODEL) \ + $(TEST_EVIDENCE_CATEGORY_ITEM) \ + $(TEST_EVIDENCE_CATEGORY_MODEL) \ $(TEST_EVIDENCE_TYPE) \ $(TEST_EVIDENCE_TYPE_DAO) \ $(TEST_INVESTIGATION_DAO) \ diff --git a/include/views/main_window.h b/include/views/main_window.h index 29c81a6..453492f 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -10,6 +10,7 @@ #include "widgets/investigation_tree_view.h" #include "core/investigation_node.h" #include "core/task_manager.h" +#include "widgets/evidence_category_model.h" #include @@ -37,6 +38,20 @@ typedef void (*MainWindowImportEvidenceCallback)( gpointer user_data ); +/** + * @brief Callback appelé lorsqu'une preuve est sélectionnée. + * + * L'identifiant est emprunté et uniquement valide pendant l'appel. + * Une sélection vide ou une catégorie transmet NULL. + * + * @param evidence_identifier UUID de la preuve, ou NULL. + * @param user_data Données privées fournies par l'appelant. + */ +typedef void (*MainWindowEvidenceSelectionCallback)( + const char *evidence_identifier, + gpointer user_data +); + /** * @brief Définit le callback du bouton « Nouvelle enquête ». * @@ -100,6 +115,22 @@ void main_window_set_tree_model( const InvestigationTreeModel *tree_model ); +/** + * @brief Transmet le modèle des catégories de preuves à la Sidebar. + * + * MainWindow ne devient pas propriétaire du modèle. + * Le modèle doit rester valide pendant son affichage. + * + * Passer NULL vide l'onglet Preuves. + * + * @param main_window Fenêtre principale. + * @param evidence_category_model Modèle de catégories, ou NULL. + */ +void main_window_set_evidence_model( + MainWindow *main_window, + EvidenceCategoryModel *evidence_category_model +); + /** * @brief Met à jour l'identité de l'enquête affichée. * @@ -128,6 +159,21 @@ void main_window_set_tree_selection_callback( gpointer user_data ); +/** + * @brief Définit le callback de sélection d'une preuve. + * + * MainWindow transmet uniquement le callback à la Sidebar. + * + * @param main_window Fenêtre principale. + * @param callback Callback facultatif. + * @param user_data Données privées transmises au callback. + */ +void main_window_set_evidence_selection_callback( + MainWindow *main_window, + MainWindowEvidenceSelectionCallback callback, + gpointer user_data +); + /** * @brief Affiche dans le Workspace les informations du nœud sélectionné. * diff --git a/include/widgets/evidence_category_item.h b/include/widgets/evidence_category_item.h new file mode 100644 index 0000000..40234ca --- /dev/null +++ b/include/widgets/evidence_category_item.h @@ -0,0 +1,100 @@ +/****************************************************************************** + * @file evidence_category_item.h + * @brief Catégorie GTK regroupant plusieurs preuves affichées. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_CATEGORY_ITEM_H +#define LABFY_INVESTIGATION_EVIDENCE_CATEGORY_ITEM_H + +#include "widgets/evidence_list_item.h" + +#include +#include + +G_BEGIN_DECLS + +/** + * @brief Type GObject de EvidenceCategoryItem. + */ +#define EVIDENCE_TYPE_CATEGORY_ITEM \ + (evidence_category_item_get_type()) + +G_DECLARE_FINAL_TYPE( + EvidenceCategoryItem, + evidence_category_item, + EVIDENCE, + CATEGORY_ITEM, + GObject +) + +/** + * @brief Crée une catégorie de preuves. + * + * Le code et le libellé sont copiés et nettoyés. + * + * @param type_identifier Code métier stable de la catégorie. + * @param label Libellé destiné à l'utilisateur. + * + * @return Nouvelle catégorie, ou NULL. + */ +EvidenceCategoryItem *evidence_category_item_new( + const char *type_identifier, + const char *label +); + +/** + * @brief Ajoute une preuve à la catégorie. + * + * La catégorie conserve sa propre référence sur evidence_list_item. + * + * @param evidence_category_item Catégorie cible. + * @param evidence_list_item Preuve d'affichage à ajouter. + * + * @return TRUE en cas de succès. + */ +gboolean evidence_category_item_append( + EvidenceCategoryItem *evidence_category_item, + EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne le code métier de la catégorie. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_category_item_get_type_identifier( + const EvidenceCategoryItem *evidence_category_item +); + +/** + * @brief Retourne le libellé utilisateur. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_category_item_get_label( + const EvidenceCategoryItem *evidence_category_item +); + +/** + * @brief Retourne le nombre de preuves de la catégorie. + * + * @return Nombre de preuves, ou 0. + */ +guint evidence_category_item_get_count( + const EvidenceCategoryItem *evidence_category_item +); + +/** + * @brief Retourne le modèle contenant les preuves. + * + * La référence retournée est empruntée. + * + * @return GListModel emprunté, ou NULL. + */ +GListModel *evidence_category_item_get_children( + EvidenceCategoryItem *evidence_category_item +); + +G_END_DECLS + +#endif diff --git a/include/widgets/evidence_category_model.h b/include/widgets/evidence_category_model.h new file mode 100644 index 0000000..262195e --- /dev/null +++ b/include/widgets/evidence_category_model.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * @file evidence_category_model.h + * @brief Modèle GTK regroupant les preuves par catégorie. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_CATEGORY_MODEL_H +#define LABFY_INVESTIGATION_EVIDENCE_CATEGORY_MODEL_H + +#include +#include + +G_BEGIN_DECLS + +/** + * @brief Modèle opaque contenant des EvidenceCategoryItem. + */ +typedef struct EvidenceCategoryModel EvidenceCategoryModel; + +/** + * @brief Codes d'erreur produits par EvidenceCategoryModel. + */ +typedef enum +{ + /** + * Un argument public est invalide. + */ + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ARGUMENT, + + /** + * Le modèle contient un élément invalide. + */ + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ITEM, + + /** + * Une catégorie n'a pas pu être créée. + */ + EVIDENCE_CATEGORY_MODEL_ERROR_CATEGORY, + + /** + * Une allocation a échoué. + */ + EVIDENCE_CATEGORY_MODEL_ERROR_MEMORY +} EvidenceCategoryModelError; + +/** + * @brief Domaine d'erreur du modèle de catégories. + */ +#define EVIDENCE_CATEGORY_MODEL_ERROR \ + evidence_category_model_error_quark() + +/** + * @brief Retourne le domaine d'erreur du module. + * + * @return Quark associé à EvidenceCategoryModel. + */ +GQuark evidence_category_model_error_quark(void); + +/** + * @brief Crée un modèle de catégories vide. + * + * @return Nouveau modèle, ou NULL. + */ +EvidenceCategoryModel *evidence_category_model_new(void); + +/** + * @brief Libère le modèle. + * + * Cette fonction accepte NULL. + * + * @param evidence_category_model Modèle à libérer. + */ +void evidence_category_model_free( + EvidenceCategoryModel *evidence_category_model +); + +/** + * @brief Retourne le modèle GTK contenant les catégories. + * + * La référence retournée est empruntée. + * + * @return GListModel emprunté, ou NULL. + */ +GListModel *evidence_category_model_get_list_model( + EvidenceCategoryModel *evidence_category_model +); + +/** + * @brief Retourne le nombre de catégories. + * + * @return Nombre de catégories, ou 0. + */ +guint evidence_category_model_get_count( + const EvidenceCategoryModel *evidence_category_model +); + +/** + * @brief Regroupe les preuves par type métier. + * + * evidence_items doit contenir des EvidenceListItem. + * + * type_labels est une table facultative associant : + * + * - clé : code métier du type ; + * - valeur : libellé utilisateur. + * + * La table est uniquement empruntée pendant l'appel. + * Lorsqu'un libellé est absent, le code métier est utilisé. + * + * Le nouveau contenu est entièrement préparé avant que l'ancien modèle + * soit remplacé. + * + * @param evidence_category_model Modèle à actualiser. + * @param evidence_items Modèle contenant des EvidenceListItem. + * @param type_labels Correspondance code vers libellé, ou NULL. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE en cas de succès. + */ +gboolean evidence_category_model_replace( + EvidenceCategoryModel *evidence_category_model, + GListModel *evidence_items, + GHashTable *type_labels, + GError **error +); + +G_END_DECLS + +#endif diff --git a/include/widgets/evidence_list_item.h b/include/widgets/evidence_list_item.h new file mode 100644 index 0000000..d0a2a5a --- /dev/null +++ b/include/widgets/evidence_list_item.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * @file evidence_list_item.h + * @brief Objet d'affichage GTK représentant une preuve importée. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_LIST_ITEM_H +#define LABFY_INVESTIGATION_EVIDENCE_LIST_ITEM_H + +#include "models/evidence_record.h" + +#include + +G_BEGIN_DECLS + +/** + * @brief Type GObject de EvidenceListItem. + */ +#define EVIDENCE_TYPE_LIST_ITEM \ + (evidence_list_item_get_type()) + +G_DECLARE_FINAL_TYPE( + EvidenceListItem, + evidence_list_item, + EVIDENCE, + LIST_ITEM, + GObject +) + +/** + * @brief Crée un élément d'affichage depuis une preuve métier. + * + * Les valeurs nécessaires à l'interface sont copiées. + * L'objet ne conserve aucun pointeur emprunté vers EvidenceRecord. + * + * L'appelant possède la référence retournée et doit la libérer + * avec g_object_unref() ou g_clear_object(). + * + * @param evidence_record Preuve métier valide. + * + * @return Nouvel élément d'affichage, ou NULL. + */ +EvidenceListItem *evidence_list_item_new( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne l'identifiant UUID de la preuve. + * + * Cet identifiant est utilisé pour signaler une sélection à Application. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_list_item_get_identifier( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne le nom original de la preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_list_item_get_original_name( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne l'identifiant métier du type de preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_list_item_get_type_identifier( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne la taille de la preuve. + * + * @return Taille en octets, ou 0. + */ +guint64 evidence_list_item_get_size_bytes( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne la date UTC de collecte. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_list_item_get_collected_at( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne la source déclarée. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_list_item_get_source( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne l'état d'intégrité. + * + * @return État d'intégrité enregistré. + */ +EvidenceIntegrityStatus evidence_list_item_get_integrity_status( + const EvidenceListItem *evidence_list_item +); + +/** + * @brief Retourne un libellé utilisateur pour l'état d'intégrité. + * + * La chaîne retournée est statique et ne doit pas être libérée. + * + * @return Libellé d'état. + */ +const char *evidence_list_item_get_integrity_status_label( + const EvidenceListItem *evidence_list_item +); + +G_END_DECLS + +#endif diff --git a/include/widgets/evidence_list_model.h b/include/widgets/evidence_list_model.h new file mode 100644 index 0000000..d8c099c --- /dev/null +++ b/include/widgets/evidence_list_model.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * @file evidence_list_model.h + * @brief Modèle GTK en mémoire contenant la liste des preuves affichées. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_LIST_MODEL_H +#define LABFY_INVESTIGATION_EVIDENCE_LIST_MODEL_H + +#include +#include + +G_BEGIN_DECLS + +/** + * @brief Modèle opaque contenant des EvidenceListItem. + */ +typedef struct EvidenceListModel EvidenceListModel; + +/** + * @brief Codes d'erreur produits par EvidenceListModel. + */ +typedef enum +{ + /** + * Un argument public est invalide. + */ + EVIDENCE_LIST_MODEL_ERROR_INVALID_ARGUMENT, + + /** + * Une allocation a échoué. + */ + EVIDENCE_LIST_MODEL_ERROR_MEMORY, + + /** + * Un EvidenceRecord ne peut pas produire un élément d'affichage. + */ + EVIDENCE_LIST_MODEL_ERROR_INVALID_RECORD +} EvidenceListModelError; + +/** + * @brief Domaine d'erreur du modèle de liste. + */ +#define EVIDENCE_LIST_MODEL_ERROR \ + evidence_list_model_error_quark() + +/** + * @brief Retourne le domaine d'erreur du modèle. + * + * @return Quark GLib associé à EvidenceListModel. + */ +GQuark evidence_list_model_error_quark(void); + +/** + * @brief Crée un modèle de liste vide. + * + * @return Nouveau modèle, ou NULL. + */ +EvidenceListModel *evidence_list_model_new(void); + +/** + * @brief Libère le modèle et tous ses éléments. + * + * Cette fonction accepte NULL. + * + * @param evidence_list_model Modèle à libérer. + */ +void evidence_list_model_free( + EvidenceListModel *evidence_list_model +); + +/** + * @brief Retourne le GListModel utilisé par les widgets GTK. + * + * La référence retournée est empruntée. + * + * @param evidence_list_model Modèle valide. + * + * @return GListModel emprunté, ou NULL. + */ +GListModel *evidence_list_model_get_list_model( + EvidenceListModel *evidence_list_model +); + +/** + * @brief Retourne le nombre d'éléments affichés. + * + * @param evidence_list_model Modèle de liste. + * + * @return Nombre d'éléments, ou 0. + */ +guint evidence_list_model_get_count( + const EvidenceListModel *evidence_list_model +); + +/** + * @brief Remplace complètement le contenu du modèle. + * + * evidence_records doit contenir des pointeurs EvidenceRecord valides. + * + * Tous les nouveaux EvidenceListItem sont construits avant la modification + * du modèle existant. En cas d'erreur, l'ancien contenu reste inchangé. + * + * Le tableau et les EvidenceRecord restent la propriété de l'appelant. + * + * @param evidence_list_model Modèle à mettre à jour. + * @param evidence_records Tableau de EvidenceRecord. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE en cas de succès. + */ +gboolean evidence_list_model_replace( + EvidenceListModel *evidence_list_model, + const GPtrArray *evidence_records, + GError **error +); + +G_END_DECLS + +#endif diff --git a/include/widgets/evidence_tree_view.h b/include/widgets/evidence_tree_view.h new file mode 100644 index 0000000..1e81db4 --- /dev/null +++ b/include/widgets/evidence_tree_view.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * @file evidence_tree_view.h + * @brief Affichage GTK hiérarchique des catégories et preuves importées. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_TREE_VIEW_H +#define LABFY_INVESTIGATION_EVIDENCE_TREE_VIEW_H + +#include "widgets/evidence_category_model.h" + +#include + +G_BEGIN_DECLS + +/** + * @brief Vue opaque affichant les preuves regroupées par catégorie. + */ +typedef struct EvidenceTreeView EvidenceTreeView; + +/** + * @brief Callback appelé lorsqu'une preuve est sélectionnée. + * + * L'identifiant est emprunté et uniquement valide pendant l'appel. + * + * Lorsque la sélection est vide ou correspond à une catégorie, + * evidence_identifier vaut NULL. + * + * @param evidence_identifier UUID de la preuve sélectionnée, ou NULL. + * @param user_data Données privées fournies par l'appelant. + */ +typedef void (*EvidenceTreeViewSelectionCallback)( + const char *evidence_identifier, + gpointer user_data +); + +/** + * @brief Crée une vue hiérarchique vide. + * + * @return Nouvelle vue, ou NULL. + */ +EvidenceTreeView *evidence_tree_view_new(void); + +/** + * @brief Retourne le widget GTK racine. + * + * Le widget retourné est emprunté. + * + * @param evidence_tree_view Vue à consulter. + * + * @return GtkScrolledWindow racine, ou NULL. + */ +GtkWidget *evidence_tree_view_get_widget( + const EvidenceTreeView *evidence_tree_view +); + +/** + * @brief Installe un modèle de catégories. + * + * La vue ne devient pas propriétaire de EvidenceCategoryModel. + * Le modèle doit rester valide pendant son utilisation. + * + * Passer NULL vide la vue. + * + * @param evidence_tree_view Vue à actualiser. + * @param evidence_category_model Modèle de catégories, ou NULL. + */ +void evidence_tree_view_set_model( + EvidenceTreeView *evidence_tree_view, + EvidenceCategoryModel *evidence_category_model +); + +/** + * @brief Définit le callback de sélection. + * + * @param evidence_tree_view Vue à configurer. + * @param callback Callback facultatif. + * @param user_data Données privées transmises au callback. + */ +void evidence_tree_view_set_selection_callback( + EvidenceTreeView *evidence_tree_view, + EvidenceTreeViewSelectionCallback callback, + gpointer user_data +); + +/** + * @brief Libère la structure d'encapsulation. + * + * Les widgets GTK intégrés dans la fenêtre restent gérés par l'arbre GTK. + * Cette fonction accepte NULL. + * + * @param evidence_tree_view Vue à libérer. + */ +void evidence_tree_view_free( + EvidenceTreeView *evidence_tree_view +); + +G_END_DECLS + +#endif diff --git a/include/widgets/sidebar.h b/include/widgets/sidebar.h index f69b1fe..362b16b 100644 --- a/include/widgets/sidebar.h +++ b/include/widgets/sidebar.h @@ -8,6 +8,7 @@ #include "core/investigation_tree_model.h" #include "widgets/investigation_tree_view.h" +#include "widgets/evidence_category_model.h" #include @@ -19,6 +20,20 @@ */ typedef struct Sidebar Sidebar; +/** + * @brief Callback appelé lorsqu'une preuve est sélectionnée. + * + * L'identifiant est emprunté et uniquement valide pendant l'appel. + * Une sélection vide ou une catégorie transmet NULL. + * + * @param evidence_identifier UUID de la preuve, ou NULL. + * @param user_data Données privées fournies par l'appelant. + */ +typedef void (*SidebarEvidenceSelectionCallback)( + const char *evidence_identifier, + gpointer user_data +); + /** * @brief Crée un nouveau panneau de navigation latéral. * @@ -71,6 +86,33 @@ void sidebar_set_selection_callback( gpointer user_data ); +/** + * @brief Installe le modèle des catégories de preuves. + * + * La Sidebar ne devient pas propriétaire du modèle. + * Passer NULL rétablit l'état vide. + * + * @param sidebar Barre latérale à actualiser. + * @param evidence_category_model Modèle de catégories, ou NULL. + */ +void sidebar_set_evidence_model( + Sidebar *sidebar, + EvidenceCategoryModel *evidence_category_model +); + +/** + * @brief Définit le callback de sélection d'une preuve. + * + * @param sidebar Barre latérale à configurer. + * @param callback Callback facultatif. + * @param user_data Données privées transmises au callback. + */ +void sidebar_set_evidence_selection_callback( + Sidebar *sidebar, + SidebarEvidenceSelectionCallback callback, + gpointer user_data +); + /** * @brief Libère la structure d'encapsulation du panneau latéral. * diff --git a/src/core/application.c b/src/core/application.c index 03921e5..1e7c1f8 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -23,6 +23,10 @@ #include "models/evidence_record.h" #include "dao/evidence_type_dao.h" #include "views/evidence_import_dialog.h" +#include "dao/evidence_dao.h" +#include "models/evidence_type.h" +#include "widgets/evidence_list_model.h" +#include "widgets/evidence_category_model.h" #include #include @@ -46,7 +50,8 @@ typedef enum APPLICATION_OPEN_ERROR_INVALID_ARGUMENT, APPLICATION_OPEN_ERROR_INVALID_PROJECT, APPLICATION_OPEN_ERROR_TREE_BUILD, - APPLICATION_OPEN_ERROR_INSTALL + APPLICATION_OPEN_ERROR_INSTALL, + APPLICATION_OPEN_ERROR_EVIDENCE } ApplicationOpenError; /** @@ -81,10 +86,12 @@ struct Application InvestigationTreeModel *tree_model; TaskManager *task_manager; ToolInitializer *tool_initializer; + EvidenceListModel *evidence_list_model; + EvidenceCategoryModel *evidence_category_model; }; /** - * @brief Contexte conservé jusqu’à la fin d’un import. + * @brief Contexte ApplicationOpenErrorconservé jusqu’à la fin d’un import. */ typedef struct { @@ -390,6 +397,469 @@ static void application_present_error( const char *message ); +/** + * @brief Charge les preuves de la session et construit leurs modèles GTK. + * + * Les nouveaux modèles sont entièrement préparés avant de remplacer + * ceux actuellement affichés. + * + * En cas d'échec, les anciens modèles restent la propriété de + * l'application et ne sont pas modifiés. + */ +static gboolean application_refresh_evidence_models( + Application *application, + GError **error +) +{ + Database *database = NULL; + + EvidenceDao *evidence_dao = NULL; + EvidenceTypeDao *evidence_type_dao = NULL; + + GPtrArray *evidence_records = NULL; + GPtrArray *evidence_types = NULL; + + GHashTable *type_labels = NULL; + + EvidenceListModel *new_evidence_list_model = + NULL; + + EvidenceCategoryModel *new_evidence_category_model = + NULL; + + GError *local_error = NULL; + + guint type_index = 0; + + gboolean refreshed = FALSE; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (application == NULL || + application->main_window == NULL || + application->session == NULL) + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "L'application ou la session d'enquête est invalide." + ); + + return FALSE; + } + + database = + investigation_session_get_database( + application->session + ); + + if (database == NULL) + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "La session ne fournit aucune connexion SQLite." + ); + + return FALSE; + } + + evidence_dao = + evidence_dao_new( + database, + &local_error + ); + + if (evidence_dao == NULL) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de créer le DAO des preuves : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + else + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible de créer le DAO des preuves." + ); + } + + goto cleanup; + } + + evidence_records = + evidence_dao_list_all( + evidence_dao, + &local_error + ); + + if (evidence_records == NULL) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de charger les preuves : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + else + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible de charger les preuves." + ); + } + + goto cleanup; + } + + evidence_type_dao = + evidence_type_dao_new( + database, + &local_error + ); + + if (evidence_type_dao == NULL) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de créer le DAO des types de preuve : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + else + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible de créer le DAO des types de preuve." + ); + } + + goto cleanup; + } + + evidence_types = + evidence_type_dao_list_all( + evidence_type_dao, + &local_error + ); + + if (evidence_types == NULL) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de charger les types de preuve : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + else + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible de charger les types de preuve." + ); + } + + goto cleanup; + } + + type_labels = + g_hash_table_new_full( + g_str_hash, + g_str_equal, + g_free, + g_free + ); + + if (type_labels == NULL) + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible d'allouer la table des libellés." + ); + + goto cleanup; + } + + for (type_index = 0; + type_index < evidence_types->len; + type_index++) + { + const EvidenceType *evidence_type = + NULL; + + const char *type_code = NULL; + const char *type_label = NULL; + + evidence_type = + g_ptr_array_index( + evidence_types, + type_index + ); + + if (evidence_type == NULL) + { + continue; + } + + type_code = + evidence_type_get_code( + evidence_type + ); + + type_label = + evidence_type_get_label( + evidence_type + ); + + if (type_code == NULL || + type_code[0] == '\0' || + type_label == NULL || + type_label[0] == '\0') + { + continue; + } + + g_hash_table_replace( + type_labels, + g_strdup( + type_code + ), + g_strdup( + type_label + ) + ); + } + + new_evidence_list_model = + evidence_list_model_new(); + + new_evidence_category_model = + evidence_category_model_new(); + + if (new_evidence_list_model == NULL || + new_evidence_category_model == NULL) + { + g_set_error_literal( + error, + APPLICATION_OPEN_ERROR, + APPLICATION_OPEN_ERROR_EVIDENCE, + "Impossible d'allouer les modèles d'affichage des preuves." + ); + + goto cleanup; + } + + if (!evidence_list_model_replace( + new_evidence_list_model, + evidence_records, + &local_error + )) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de préparer la liste des preuves : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + + goto cleanup; + } + + if (!evidence_category_model_replace( + new_evidence_category_model, + evidence_list_model_get_list_model( + new_evidence_list_model + ), + type_labels, + &local_error + )) + { + if (local_error != NULL) + { + if (error != NULL) + { + g_propagate_prefixed_error( + error, + local_error, + "Impossible de regrouper les preuves : " + ); + + local_error = NULL; + } + else + { + g_clear_error( + &local_error + ); + } + } + + goto cleanup; + } + + /* + * La vue doit être détachée avant la libération de son ancien modèle. + */ + main_window_set_evidence_model( + application->main_window, + NULL + ); + + evidence_category_model_free( + application->evidence_category_model + ); + + evidence_list_model_free( + application->evidence_list_model + ); + + application->evidence_list_model = + new_evidence_list_model; + + application->evidence_category_model = + new_evidence_category_model; + + new_evidence_list_model = + NULL; + + new_evidence_category_model = + NULL; + + main_window_set_evidence_model( + application->main_window, + application->evidence_category_model + ); + + refreshed = TRUE; + +cleanup: + + g_clear_error( + &local_error + ); + + evidence_category_model_free( + new_evidence_category_model + ); + + evidence_list_model_free( + new_evidence_list_model + ); + + if (type_labels != NULL) + { + g_hash_table_unref( + type_labels + ); + } + + if (evidence_types != NULL) + { + g_ptr_array_unref( + evidence_types + ); + } + + if (evidence_records != NULL) + { + g_ptr_array_unref( + evidence_records + ); + } + + evidence_type_dao_free( + evidence_type_dao + ); + + evidence_dao_free( + evidence_dao + ); + + return refreshed; +} + /** * @brief Installe une nouvelle session et son arbre dans l'application. * @@ -416,6 +886,8 @@ static gboolean application_install_session( const char *root_path = NULL; const char *investigation_name = NULL; + GError *evidence_error = NULL; + if (application == NULL || application->main_window == NULL || new_session == NULL || @@ -485,6 +957,54 @@ static gboolean application_install_session( TRUE ); + /* + * L'ancienne liste ne doit jamais rester affichée après + * un changement d'enquête. + */ + main_window_set_evidence_model( + application->main_window, + NULL + ); + + evidence_category_model_free( + application->evidence_category_model + ); + + evidence_list_model_free( + application->evidence_list_model + ); + + application->evidence_category_model = + NULL; + + application->evidence_list_model = + NULL; + + if (!application_refresh_evidence_models( + application, + &evidence_error + )) + { + g_warning( + "Impossible de charger les preuves de l'enquête : %s", + evidence_error != NULL + ? evidence_error->message + : "erreur inconnue" + ); + + application_present_error( + application, + "Preuves indisponibles", + evidence_error != NULL + ? evidence_error->message + : "Les preuves enregistrées n'ont pas pu être chargées." + ); + + g_clear_error( + &evidence_error + ); + } + return TRUE; } @@ -2123,6 +2643,22 @@ void application_free( application->session ); + if (application->main_window != NULL) + { + main_window_set_evidence_model( + application->main_window, + NULL + ); + } + + evidence_category_model_free( + application->evidence_category_model + ); + + evidence_list_model_free( + application->evidence_list_model + ); + /* * MainWindow contient TaskPanel. * Il doit être détruit avant TaskManager. diff --git a/src/views/main_window.c b/src/views/main_window.c index 0a9dc3a..14ba85b 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -14,7 +14,7 @@ /** * @brief Largeur initiale de la fenêtre principale. */ -#define MAIN_WINDOW_DEFAULT_WIDTH 1000 +#define MAIN_WINDOW_DEFAULT_WIDTH 1100 /** * @brief Hauteur initiale de la fenêtre principale. @@ -24,7 +24,7 @@ /** * @brief Position initiale de la séparation horizontale. */ -#define MAIN_WINDOW_SIDEBAR_POSITION 250 +#define MAIN_WINDOW_SIDEBAR_POSITION 320 /** * @brief Position initiale de la séparation verticale. @@ -716,6 +716,22 @@ void main_window_set_tree_model( ); } +void main_window_set_evidence_model( + MainWindow *main_window, + EvidenceCategoryModel *evidence_category_model +) +{ + if (main_window == NULL) + { + return; + } + + sidebar_set_evidence_model( + main_window->sidebar, + evidence_category_model + ); +} + void main_window_set_investigation( MainWindow *main_window, const char *investigation_name, @@ -848,6 +864,24 @@ void main_window_set_tree_selection_callback( ); } +void main_window_set_evidence_selection_callback( + MainWindow *main_window, + MainWindowEvidenceSelectionCallback callback, + gpointer user_data +) +{ + if (main_window == NULL) + { + return; + } + + sidebar_set_evidence_selection_callback( + main_window->sidebar, + callback, + user_data + ); +} + void main_window_set_new_investigation_callback( MainWindow *main_window, MainWindowNewInvestigationCallback callback, diff --git a/src/widgets/evidence_category_item.c b/src/widgets/evidence_category_item.c new file mode 100644 index 0000000..72b0757 --- /dev/null +++ b/src/widgets/evidence_category_item.c @@ -0,0 +1,331 @@ +/****************************************************************************** + * @file evidence_category_item.c + * @brief Implémentation d'une catégorie de preuves affichées. + ******************************************************************************/ + +#include "widgets/evidence_category_item.h" + +/** + * @brief État interne de la catégorie. + */ +struct _EvidenceCategoryItem +{ + GObject parent_instance; + + char *type_identifier; + char *label; + + GListStore *children; +}; + +G_DEFINE_TYPE( + EvidenceCategoryItem, + evidence_category_item, + G_TYPE_OBJECT +) + +/** + * @brief Copie et nettoie une chaîne obligatoire. + */ +static char *evidence_category_item_duplicate_required_text( + const char *value +) +{ + char *normalized_value = NULL; + + if (value == NULL) + { + return NULL; + } + + normalized_value = + g_strdup( + value + ); + + if (normalized_value == NULL) + { + return NULL; + } + + g_strstrip( + normalized_value + ); + + if (normalized_value[0] == '\0') + { + g_free( + normalized_value + ); + + return NULL; + } + + return normalized_value; +} + +/** + * @brief Libère les ressources possédées par la catégorie. + */ +static void evidence_category_item_finalize( + GObject *object +) +{ + EvidenceCategoryItem *evidence_category_item = + EVIDENCE_CATEGORY_ITEM( + object + ); + + g_clear_pointer( + &evidence_category_item->type_identifier, + g_free + ); + + g_clear_pointer( + &evidence_category_item->label, + g_free + ); + + g_clear_object( + &evidence_category_item->children + ); + + G_OBJECT_CLASS( + evidence_category_item_parent_class + )->finalize( + object + ); +} + +/** + * @brief Initialise la classe GObject. + */ +static void evidence_category_item_class_init( + EvidenceCategoryItemClass *evidence_category_item_class +) +{ + GObjectClass *object_class = + G_OBJECT_CLASS( + evidence_category_item_class + ); + + object_class->finalize = + evidence_category_item_finalize; +} + +/** + * @brief Initialise une catégorie vide. + */ +static void evidence_category_item_init( + EvidenceCategoryItem *evidence_category_item +) +{ + evidence_category_item->children = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); +} + +EvidenceCategoryItem *evidence_category_item_new( + const char *type_identifier, + const char *label +) +{ + EvidenceCategoryItem *evidence_category_item = + NULL; + + char *normalized_type_identifier = + NULL; + + char *normalized_label = + NULL; + + normalized_type_identifier = + evidence_category_item_duplicate_required_text( + type_identifier + ); + + normalized_label = + evidence_category_item_duplicate_required_text( + label + ); + + if (normalized_type_identifier == NULL || + normalized_label == NULL) + { + g_free( + normalized_label + ); + + g_free( + normalized_type_identifier + ); + + return NULL; + } + + evidence_category_item = + g_object_new( + EVIDENCE_TYPE_CATEGORY_ITEM, + NULL + ); + + if (evidence_category_item == NULL || + evidence_category_item->children == NULL) + { + g_clear_object( + &evidence_category_item + ); + + g_free( + normalized_label + ); + + g_free( + normalized_type_identifier + ); + + return NULL; + } + + evidence_category_item->type_identifier = + normalized_type_identifier; + + evidence_category_item->label = + normalized_label; + + return evidence_category_item; +} + +/** + * @brief Compare deux preuves par nom original puis par identifiant. + */ +static gint evidence_category_item_compare_list_items( + gconstpointer first_pointer, + gconstpointer second_pointer, + gpointer user_data +) +{ + const EvidenceListItem *first_item = + first_pointer; + + const EvidenceListItem *second_item = + second_pointer; + + const char *first_name = NULL; + const char *second_name = NULL; + + gint comparison = 0; + + (void) user_data; + + first_name = + evidence_list_item_get_original_name( + first_item + ); + + second_name = + evidence_list_item_get_original_name( + second_item + ); + + comparison = + g_utf8_collate( + first_name, + second_name + ); + + if (comparison != 0) + { + return comparison; + } + + return g_strcmp0( + evidence_list_item_get_identifier( + first_item + ), + evidence_list_item_get_identifier( + second_item + ) + ); +} + +gboolean evidence_category_item_append( + EvidenceCategoryItem *evidence_category_item, + EvidenceListItem *evidence_list_item +) +{ + if (evidence_category_item == NULL || + evidence_category_item->children == NULL || + evidence_list_item == NULL) + { + return FALSE; + } + + g_list_store_insert_sorted( + evidence_category_item->children, + G_OBJECT( + evidence_list_item + ), + evidence_category_item_compare_list_items, + NULL + ); + + return TRUE; +} + +const char *evidence_category_item_get_type_identifier( + const EvidenceCategoryItem *evidence_category_item +) +{ + if (evidence_category_item == NULL) + { + return NULL; + } + + return evidence_category_item->type_identifier; +} + +const char *evidence_category_item_get_label( + const EvidenceCategoryItem *evidence_category_item +) +{ + if (evidence_category_item == NULL) + { + return NULL; + } + + return evidence_category_item->label; +} + +guint evidence_category_item_get_count( + const EvidenceCategoryItem *evidence_category_item +) +{ + if (evidence_category_item == NULL || + evidence_category_item->children == NULL) + { + return 0; + } + + return g_list_model_get_n_items( + G_LIST_MODEL( + evidence_category_item->children + ) + ); +} + +GListModel *evidence_category_item_get_children( + EvidenceCategoryItem *evidence_category_item +) +{ + if (evidence_category_item == NULL || + evidence_category_item->children == NULL) + { + return NULL; + } + + return G_LIST_MODEL( + evidence_category_item->children + ); +} diff --git a/src/widgets/evidence_category_model.c b/src/widgets/evidence_category_model.c new file mode 100644 index 0000000..e7d0b39 --- /dev/null +++ b/src/widgets/evidence_category_model.c @@ -0,0 +1,548 @@ +/****************************************************************************** + * @file evidence_category_model.c + * @brief Regroupement des preuves par catégorie pour l'affichage GTK. + ******************************************************************************/ + +#include "widgets/evidence_category_model.h" + +#include "widgets/evidence_category_item.h" +#include "widgets/evidence_list_item.h" + +/** + * @brief État interne du modèle. + */ +struct EvidenceCategoryModel +{ + GListStore *categories; +}; + +GQuark evidence_category_model_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-evidence-category-model-error" + ); +} + +/** + * @brief Compare deux catégories par libellé puis par code métier. + */ +static gint evidence_category_model_compare_categories( + gconstpointer first_pointer, + gconstpointer second_pointer, + gpointer user_data +) +{ + const EvidenceCategoryItem *first_category = + first_pointer; + + const EvidenceCategoryItem *second_category = + second_pointer; + + const char *first_label = NULL; + const char *second_label = NULL; + + gint comparison = 0; + + (void) user_data; + + first_label = + evidence_category_item_get_label( + first_category + ); + + second_label = + evidence_category_item_get_label( + second_category + ); + + comparison = + g_utf8_collate( + first_label, + second_label + ); + + if (comparison != 0) + { + return comparison; + } + + return g_strcmp0( + evidence_category_item_get_type_identifier( + first_category + ), + evidence_category_item_get_type_identifier( + second_category + ) + ); +} + +/** + * @brief Vérifie qu'une chaîne obligatoire contient du texte. + */ +static gboolean evidence_category_model_value_has_text( + const char *value +) +{ + return value != NULL && + value[0] != '\0'; +} + +/** + * @brief Libère les références temporaires d'un tableau d'ajouts. + */ +static void evidence_category_model_release_additions( + gpointer *additions, + guint addition_count +) +{ + guint addition_index = 0; + + if (additions == NULL) + { + return; + } + + for (addition_index = 0; + addition_index < addition_count; + addition_index++) + { + g_clear_object( + &additions[addition_index] + ); + } + + g_free( + additions + ); +} + +EvidenceCategoryModel *evidence_category_model_new(void) +{ + EvidenceCategoryModel *evidence_category_model = + NULL; + + evidence_category_model = + g_try_new0( + EvidenceCategoryModel, + 1 + ); + + if (evidence_category_model == NULL) + { + return NULL; + } + + evidence_category_model->categories = + g_list_store_new( + EVIDENCE_TYPE_CATEGORY_ITEM + ); + + if (evidence_category_model->categories == NULL) + { + g_free( + evidence_category_model + ); + + return NULL; + } + + return evidence_category_model; +} + +void evidence_category_model_free( + EvidenceCategoryModel *evidence_category_model +) +{ + if (evidence_category_model == NULL) + { + return; + } + + g_clear_object( + &evidence_category_model->categories + ); + + g_free( + evidence_category_model + ); +} + +GListModel *evidence_category_model_get_list_model( + EvidenceCategoryModel *evidence_category_model +) +{ + if (evidence_category_model == NULL || + evidence_category_model->categories == NULL) + { + return NULL; + } + + return G_LIST_MODEL( + evidence_category_model->categories + ); +} + +guint evidence_category_model_get_count( + const EvidenceCategoryModel *evidence_category_model +) +{ + if (evidence_category_model == NULL || + evidence_category_model->categories == NULL) + { + return 0; + } + + return g_list_model_get_n_items( + G_LIST_MODEL( + evidence_category_model->categories + ) + ); +} + +gboolean evidence_category_model_replace( + EvidenceCategoryModel *evidence_category_model, + GListModel *evidence_items, + GHashTable *type_labels, + GError **error +) +{ + GListStore *new_categories = NULL; + + GHashTable *categories_by_type = + NULL; + + EvidenceListItem *evidence_list_item = + NULL; + + EvidenceCategoryItem *evidence_category_item = + NULL; + + gpointer *additions = NULL; + + const char *type_identifier = NULL; + const char *category_label = NULL; + + guint evidence_count = 0; + guint evidence_index = 0; + + guint new_category_count = 0; + guint category_index = 0; + guint previous_category_count = 0; + + gboolean replaced = FALSE; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (evidence_category_model == NULL || + evidence_category_model->categories == NULL || + evidence_items == NULL) + { + g_set_error_literal( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ARGUMENT, + "Le modèle de catégories ou la liste de preuves est invalide." + ); + + return FALSE; + } + + if (!g_type_is_a( + g_list_model_get_item_type( + evidence_items + ), + EVIDENCE_TYPE_LIST_ITEM + )) + { + g_set_error_literal( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ARGUMENT, + "Le modèle fourni ne contient pas des EvidenceListItem." + ); + + return FALSE; + } + + new_categories = + g_list_store_new( + EVIDENCE_TYPE_CATEGORY_ITEM + ); + + categories_by_type = + g_hash_table_new_full( + g_str_hash, + g_str_equal, + g_free, + NULL + ); + + if (new_categories == NULL || + categories_by_type == NULL) + { + g_set_error_literal( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_MEMORY, + "Impossible de préparer le regroupement des preuves." + ); + + goto cleanup; + } + + evidence_count = + g_list_model_get_n_items( + evidence_items + ); + + for (evidence_index = 0; + evidence_index < evidence_count; + evidence_index++) + { + evidence_list_item = + g_list_model_get_item( + evidence_items, + evidence_index + ); + + if (evidence_list_item == NULL || + !EVIDENCE_IS_LIST_ITEM( + evidence_list_item + )) + { + g_set_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ITEM, + "La preuve située à l'index %u est invalide.", + evidence_index + ); + + goto cleanup; + } + + type_identifier = + evidence_list_item_get_type_identifier( + evidence_list_item + ); + + if (!evidence_category_model_value_has_text( + type_identifier + )) + { + g_set_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ITEM, + "La preuve située à l'index %u ne possède aucun type.", + evidence_index + ); + + goto cleanup; + } + + evidence_category_item = + g_hash_table_lookup( + categories_by_type, + type_identifier + ); + + if (evidence_category_item == NULL) + { + category_label = NULL; + + if (type_labels != NULL) + { + category_label = + g_hash_table_lookup( + type_labels, + type_identifier + ); + } + + /* + * Un type inconnu reste affichable. + * Son code métier devient son libellé de secours. + */ + if (!evidence_category_model_value_has_text( + category_label + )) + { + category_label = + type_identifier; + } + + evidence_category_item = + evidence_category_item_new( + type_identifier, + category_label + ); + + if (evidence_category_item == NULL) + { + g_set_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_CATEGORY, + "Impossible de créer la catégorie '%s'.", + type_identifier + ); + + goto cleanup; + } + + g_list_store_insert_sorted( + new_categories, + G_OBJECT( + evidence_category_item + ), + evidence_category_model_compare_categories, + NULL + ); + + /* + * La table emprunte le pointeur. + * Le GListStore possède la référence. + */ + g_hash_table_insert( + categories_by_type, + g_strdup( + type_identifier + ), + evidence_category_item + ); + + g_object_unref( + evidence_category_item + ); + } + + if (!evidence_category_item_append( + evidence_category_item, + evidence_list_item + )) + { + g_set_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_CATEGORY, + "Impossible d'ajouter la preuve %u à sa catégorie.", + evidence_index + ); + + goto cleanup; + } + + g_clear_object( + &evidence_list_item + ); + + evidence_category_item = + NULL; + } + + new_category_count = + g_list_model_get_n_items( + G_LIST_MODEL( + new_categories + ) + ); + + previous_category_count = + g_list_model_get_n_items( + G_LIST_MODEL( + evidence_category_model->categories + ) + ); + + if (new_category_count == 0) + { + g_list_store_remove_all( + evidence_category_model->categories + ); + + replaced = TRUE; + + goto cleanup; + } + + additions = + g_try_new0( + gpointer, + new_category_count + ); + + if (additions == NULL) + { + g_set_error_literal( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_MEMORY, + "Impossible de préparer le remplacement des catégories." + ); + + goto cleanup; + } + + for (category_index = 0; + category_index < new_category_count; + category_index++) + { + additions[category_index] = + g_list_model_get_item( + G_LIST_MODEL( + new_categories + ), + category_index + ); + + if (additions[category_index] == NULL) + { + g_set_error_literal( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_CATEGORY, + "Impossible de lire une catégorie préparée." + ); + + goto cleanup; + } + } + + g_list_store_splice( + evidence_category_model->categories, + 0, + previous_category_count, + additions, + new_category_count + ); + + replaced = TRUE; + +cleanup: + + g_clear_object( + &evidence_list_item + ); + + evidence_category_model_release_additions( + additions, + new_category_count + ); + + if (categories_by_type != NULL) + { + g_hash_table_unref( + categories_by_type + ); + } + + g_clear_object( + &new_categories + ); + + return replaced; +} diff --git a/src/widgets/evidence_list_item.c b/src/widgets/evidence_list_item.c new file mode 100644 index 0000000..283c702 --- /dev/null +++ b/src/widgets/evidence_list_item.c @@ -0,0 +1,315 @@ +/****************************************************************************** + * @file evidence_list_item.c + * @brief Implémentation de EvidenceListItem. + ******************************************************************************/ + +#include "widgets/evidence_list_item.h" + +/** + * @brief Données privées copiées pour l'affichage GTK. + */ +struct _EvidenceListItem +{ + GObject parent_instance; + + char *identifier; + char *original_name; + char *type_identifier; + guint64 size_bytes; + char *collected_at; + char *source; + + EvidenceIntegrityStatus integrity_status; +}; + +G_DEFINE_TYPE( + EvidenceListItem, + evidence_list_item, + G_TYPE_OBJECT +) + +/** + * @brief Libère les données possédées par l'objet. + */ +static void evidence_list_item_finalize( + GObject *object +) +{ + EvidenceListItem *evidence_list_item = + EVIDENCE_LIST_ITEM( + object + ); + + g_clear_pointer( + &evidence_list_item->original_name, + g_free + ); + + g_clear_pointer( + &evidence_list_item->type_identifier, + g_free + ); + + g_clear_pointer( + &evidence_list_item->collected_at, + g_free + ); + + g_clear_pointer( + &evidence_list_item->source, + g_free + ); + + G_OBJECT_CLASS( + evidence_list_item_parent_class + )->finalize( + object + ); +} + +/** + * @brief Initialise la classe GObject. + */ +static void evidence_list_item_class_init( + EvidenceListItemClass *evidence_list_item_class +) +{ + GObjectClass *object_class = + G_OBJECT_CLASS( + evidence_list_item_class + ); + + object_class->finalize = + evidence_list_item_finalize; +} + +/** + * @brief Initialise une nouvelle instance vide. + */ +static void evidence_list_item_init( + EvidenceListItem *evidence_list_item +) +{ + evidence_list_item->integrity_status = + EVIDENCE_INTEGRITY_STATUS_UNKNOWN; +} + +EvidenceListItem *evidence_list_item_new( + const EvidenceRecord *evidence_record +) +{ + EvidenceListItem *evidence_list_item = NULL; + + const char *identifier = NULL; + const char *original_name = NULL; + const char *type_identifier = NULL; + + if (evidence_record == NULL) + { + return NULL; + } + + identifier = + evidence_record_get_identifier( + evidence_record + ); + + original_name = + evidence_record_get_original_name( + evidence_record + ); + + type_identifier = + evidence_record_get_type_identifier( + evidence_record + ); + + /* + * Ces deux valeurs sont obligatoires dans EvidenceRecord. + * Cette vérification protège néanmoins l'adaptateur d'affichage. + */ + if (identifier == NULL || + original_name == NULL || + original_name[0] == '\0' || + type_identifier == NULL || + type_identifier[0] == '\0') + { + return NULL; + } + + evidence_list_item = + g_object_new( + EVIDENCE_TYPE_LIST_ITEM, + NULL + ); + + evidence_list_item->identifier = + g_strdup( + identifier + ); + + if (evidence_list_item == NULL) + { + return NULL; + } + + evidence_list_item->original_name = + g_strdup( + original_name + ); + + evidence_list_item->type_identifier = + g_strdup( + type_identifier + ); + + evidence_list_item->size_bytes = + evidence_record_get_size_bytes( + evidence_record + ); + + evidence_list_item->collected_at = + g_strdup( + evidence_record_get_collected_at( + evidence_record + ) + ); + + evidence_list_item->source = + g_strdup( + evidence_record_get_source( + evidence_record + ) + ); + + evidence_list_item->integrity_status = + evidence_record_get_integrity_status( + evidence_record + ); + + if (evidence_list_item->identifier == NULL || + evidence_list_item->original_name == NULL || + evidence_list_item->type_identifier == NULL) + { + g_object_unref( + evidence_list_item + ); + + return NULL; + } + + return evidence_list_item; +} + +const char *evidence_list_item_get_identifier( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return NULL; + } + + return evidence_list_item->identifier; +} + +const char *evidence_list_item_get_original_name( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return NULL; + } + + return evidence_list_item->original_name; +} + +const char *evidence_list_item_get_type_identifier( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return NULL; + } + + return evidence_list_item->type_identifier; +} + +guint64 evidence_list_item_get_size_bytes( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return 0; + } + + return evidence_list_item->size_bytes; +} + +const char *evidence_list_item_get_collected_at( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return NULL; + } + + return evidence_list_item->collected_at; +} + +const char *evidence_list_item_get_source( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return NULL; + } + + return evidence_list_item->source; +} + +EvidenceIntegrityStatus evidence_list_item_get_integrity_status( + const EvidenceListItem *evidence_list_item +) +{ + if (evidence_list_item == NULL) + { + return EVIDENCE_INTEGRITY_STATUS_UNKNOWN; + } + + return evidence_list_item->integrity_status; +} + +const char *evidence_list_item_get_integrity_status_label( + const EvidenceListItem *evidence_list_item +) +{ + EvidenceIntegrityStatus integrity_status = + evidence_list_item_get_integrity_status( + evidence_list_item + ); + + switch (integrity_status) + { + case EVIDENCE_INTEGRITY_STATUS_VALID: + return "Valide"; + + case EVIDENCE_INTEGRITY_STATUS_MISSING: + return "Fichier absent"; + + case EVIDENCE_INTEGRITY_STATUS_MODIFIED: + return "Modifiée"; + + case EVIDENCE_INTEGRITY_STATUS_ERROR: + return "Erreur"; + + case EVIDENCE_INTEGRITY_STATUS_UNKNOWN: + default: + return "Non vérifiée"; + } +} diff --git a/src/widgets/evidence_list_model.c b/src/widgets/evidence_list_model.c new file mode 100644 index 0000000..6d2c227 --- /dev/null +++ b/src/widgets/evidence_list_model.c @@ -0,0 +1,239 @@ +/****************************************************************************** + * @file evidence_list_model.c + * @brief Implémentation du modèle de liste des preuves. + ******************************************************************************/ + +#include "widgets/evidence_list_model.h" + +#include "widgets/evidence_list_item.h" + +/** + * @brief État interne du modèle de liste. + */ +struct EvidenceListModel +{ + GListStore *items; +}; + +GQuark evidence_list_model_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-evidence-list-model-error" + ); +} + +EvidenceListModel *evidence_list_model_new(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + evidence_list_model = + g_try_new0( + EvidenceListModel, + 1 + ); + + if (evidence_list_model == NULL) + { + return NULL; + } + + evidence_list_model->items = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); + + if (evidence_list_model->items == NULL) + { + g_free( + evidence_list_model + ); + + return NULL; + } + + return evidence_list_model; +} + +void evidence_list_model_free( + EvidenceListModel *evidence_list_model +) +{ + if (evidence_list_model == NULL) + { + return; + } + + g_clear_object( + &evidence_list_model->items + ); + + g_free( + evidence_list_model + ); +} + +GListModel *evidence_list_model_get_list_model( + EvidenceListModel *evidence_list_model +) +{ + if (evidence_list_model == NULL || + evidence_list_model->items == NULL) + { + return NULL; + } + + return G_LIST_MODEL( + evidence_list_model->items + ); +} + +guint evidence_list_model_get_count( + const EvidenceListModel *evidence_list_model +) +{ + if (evidence_list_model == NULL || + evidence_list_model->items == NULL) + { + return 0; + } + + return g_list_model_get_n_items( + G_LIST_MODEL( + evidence_list_model->items + ) + ); +} + +gboolean evidence_list_model_replace( + EvidenceListModel *evidence_list_model, + const GPtrArray *evidence_records, + GError **error +) +{ + GPtrArray *new_items = NULL; + + guint record_index = 0; + guint previous_count = 0; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (evidence_list_model == NULL || + evidence_list_model->items == NULL || + evidence_records == NULL) + { + g_set_error_literal( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_INVALID_ARGUMENT, + "Le modèle ou le tableau de preuves est invalide." + ); + + return FALSE; + } + + /* + * Le tableau libère notre référence locale sur chaque objet. + * GListStore prendra ensuite sa propre référence lors du splice. + */ + new_items = + g_ptr_array_new_with_free_func( + g_object_unref + ); + + if (new_items == NULL) + { + g_set_error_literal( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_MEMORY, + "Impossible d'allouer la nouvelle liste de preuves." + ); + + return FALSE; + } + + for (record_index = 0; + record_index < evidence_records->len; + record_index++) + { + const EvidenceRecord *evidence_record = + NULL; + + EvidenceListItem *evidence_list_item = + NULL; + + evidence_record = + g_ptr_array_index( + evidence_records, + record_index + ); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + if (evidence_list_item == NULL) + { + g_set_error( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_INVALID_RECORD, + "La preuve située à l'index %u est invalide.", + record_index + ); + + g_ptr_array_unref( + new_items + ); + + return FALSE; + } + + g_ptr_array_add( + new_items, + evidence_list_item + ); + } + + previous_count = + g_list_model_get_n_items( + G_LIST_MODEL( + evidence_list_model->items + ) + ); + + if (new_items->len == 0) + { + g_list_store_remove_all( + evidence_list_model->items + ); + } + else + { + /* + * Tous les éléments sont prêts. + * Le remplacement peut maintenant être effectué en une opération. + */ + g_list_store_splice( + evidence_list_model->items, + 0, + previous_count, + new_items->pdata, + new_items->len + ); + } + + /* + * GListStore possède désormais une référence sur chaque élément. + */ + g_ptr_array_unref( + new_items + ); + + return TRUE; +} diff --git a/src/widgets/evidence_tree_view.c b/src/widgets/evidence_tree_view.c new file mode 100644 index 0000000..4c0a860 --- /dev/null +++ b/src/widgets/evidence_tree_view.c @@ -0,0 +1,1027 @@ +/****************************************************************************** + * @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 +#include +#include + +/** + * @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 + ); +} diff --git a/src/widgets/sidebar.c b/src/widgets/sidebar.c index 4cd364a..b885575 100644 --- a/src/widgets/sidebar.c +++ b/src/widgets/sidebar.c @@ -6,13 +6,38 @@ #include "widgets/sidebar.h" #include "core/investigation_node.h" #include "widgets/investigation_tree_view.h" +#include "widgets/evidence_tree_view.h" #include /** * @brief Largeur initiale du panneau latéral. */ -#define SIDEBAR_DEFAULT_WIDTH 250 +#define SIDEBAR_DEFAULT_WIDTH 320 + +/** + * @brief Identifiant de la page contenant l'arborescence. + */ +#define SIDEBAR_PAGE_FOLDER \ + "folder" + +/** + * @brief Identifiant de la page contenant les preuves. + */ +#define SIDEBAR_PAGE_EVIDENCE \ + "evidence" + +/** + * @brief État vide de l'onglet Preuves. + */ +#define SIDEBAR_EVIDENCE_STATE_EMPTY \ + "empty" + +/** + * @brief État contenant l'arbre des preuves. + */ +#define SIDEBAR_EVIDENCE_STATE_TREE \ + "tree" /** * @struct Sidebar @@ -25,23 +50,74 @@ struct Sidebar { GtkWidget *root_widget; GtkWidget *title_label; + + GtkWidget *page_switcher; + GtkWidget *stack; + + GtkWidget *folder_page; + + GtkWidget *evidence_page; + GtkWidget *evidence_content_stack; + GtkWidget *evidence_empty_page; + GtkWidget *evidence_empty_label; + InvestigationTreeView *tree_view; + EvidenceTreeView *evidence_tree_view; + + SidebarEvidenceSelectionCallback + evidence_selection_callback; + + gpointer evidence_selection_user_data; }; +/** + * @brief Relaie la sélection provenant de EvidenceTreeView. + */ +static void sidebar_on_evidence_selected( + const char *evidence_identifier, + gpointer user_data +) +{ + Sidebar *sidebar = + user_data; + + if (sidebar == NULL || + sidebar->evidence_selection_callback == NULL) + { + return; + } + + sidebar->evidence_selection_callback( + evidence_identifier, + sidebar->evidence_selection_user_data + ); +} + Sidebar *sidebar_new(void) { Sidebar *sidebar = NULL; - sidebar = g_new0(Sidebar, 1); - GtkWidget *tree_view_widget = NULL; - sidebar->root_widget = gtk_box_new( - GTK_ORIENTATION_VERTICAL, - 0 - ); + GtkWidget *tree_view_widget = NULL; + GtkWidget *evidence_tree_widget = NULL; + + sidebar = + g_new0( + Sidebar, + 1 + ); + + sidebar->root_widget = + gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 0 + ); if (sidebar->root_widget == NULL) { - sidebar_free(sidebar); + sidebar_free( + sidebar + ); + return NULL; } @@ -51,9 +127,22 @@ Sidebar *sidebar_new(void) -1 ); - sidebar->title_label = gtk_label_new( - "Dossier d'enquête" - ); + /* + * Titre de l'enquête active. + */ + sidebar->title_label = + gtk_label_new( + "Dossier d'enquête" + ); + + if (sidebar->title_label == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } gtk_widget_set_halign( sidebar->title_label, @@ -77,29 +166,155 @@ Sidebar *sidebar_new(void) gtk_widget_set_margin_bottom( sidebar->title_label, - 12 + 8 ); gtk_box_append( - GTK_BOX(sidebar->root_widget), + GTK_BOX( + sidebar->root_widget + ), sidebar->title_label ); - sidebar->tree_view = investigation_tree_view_new(); + /* + * GtkStack contenant les pages Dossier et Preuves. + */ + sidebar->stack = + gtk_stack_new(); - if (sidebar->tree_view == NULL) + if (sidebar->stack == NULL) { - sidebar_free(sidebar); + sidebar_free( + sidebar + ); + return NULL; } - tree_view_widget = investigation_tree_view_get_widget( - sidebar->tree_view + gtk_widget_set_hexpand( + sidebar->stack, + TRUE ); + gtk_widget_set_vexpand( + sidebar->stack, + TRUE + ); + + gtk_stack_set_transition_type( + GTK_STACK( + sidebar->stack + ), + GTK_STACK_TRANSITION_TYPE_CROSSFADE + ); + + /* + * Boutons permettant de sélectionner une page du GtkStack. + */ + sidebar->page_switcher = + gtk_stack_switcher_new(); + + if (sidebar->page_switcher == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_stack_switcher_set_stack( + GTK_STACK_SWITCHER( + sidebar->page_switcher + ), + GTK_STACK( + sidebar->stack + ) + ); + + gtk_widget_set_hexpand( + sidebar->page_switcher, + TRUE + ); + + gtk_widget_set_halign( + sidebar->page_switcher, + GTK_ALIGN_CENTER + ); + + gtk_widget_set_margin_start( + sidebar->page_switcher, + 8 + ); + + gtk_widget_set_margin_end( + sidebar->page_switcher, + 8 + ); + + gtk_widget_set_margin_bottom( + sidebar->page_switcher, + 8 + ); + + gtk_box_append( + GTK_BOX( + sidebar->root_widget + ), + sidebar->page_switcher + ); + + /* + * Page Dossier. + */ + sidebar->folder_page = + gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 0 + ); + + if (sidebar->folder_page == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_widget_set_hexpand( + sidebar->folder_page, + TRUE + ); + + gtk_widget_set_vexpand( + sidebar->folder_page, + TRUE + ); + + sidebar->tree_view = + investigation_tree_view_new(); + + if (sidebar->tree_view == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + tree_view_widget = + investigation_tree_view_get_widget( + sidebar->tree_view + ); + if (tree_view_widget == NULL) { - sidebar_free(sidebar); + sidebar_free( + sidebar + ); + return NULL; } @@ -114,10 +329,279 @@ Sidebar *sidebar_new(void) ); gtk_box_append( - GTK_BOX(sidebar->root_widget), + GTK_BOX( + sidebar->folder_page + ), tree_view_widget ); + gtk_stack_add_titled( + GTK_STACK( + sidebar->stack + ), + sidebar->folder_page, + SIDEBAR_PAGE_FOLDER, + "Dossier" + ); + + /* + * Page Preuves. + */ + sidebar->evidence_page = + gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 0 + ); + + if (sidebar->evidence_page == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_widget_set_hexpand( + sidebar->evidence_page, + TRUE + ); + + gtk_widget_set_vexpand( + sidebar->evidence_page, + TRUE + ); + + /* + * Stack interne alternant entre l'état vide + * et l'arborescence des preuves. + */ + sidebar->evidence_content_stack = + gtk_stack_new(); + + if (sidebar->evidence_content_stack == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_widget_set_hexpand( + sidebar->evidence_content_stack, + TRUE + ); + + gtk_widget_set_vexpand( + sidebar->evidence_content_stack, + TRUE + ); + + gtk_stack_set_transition_type( + GTK_STACK( + sidebar->evidence_content_stack + ), + GTK_STACK_TRANSITION_TYPE_CROSSFADE + ); + + /* + * État vide. + */ + sidebar->evidence_empty_page = + gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 0 + ); + + if (sidebar->evidence_empty_page == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_widget_set_hexpand( + sidebar->evidence_empty_page, + TRUE + ); + + gtk_widget_set_vexpand( + sidebar->evidence_empty_page, + TRUE + ); + + sidebar->evidence_empty_label = + gtk_label_new( + "Aucune preuve importée dans cette enquête." + ); + + if (sidebar->evidence_empty_label == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_label_set_wrap( + GTK_LABEL( + sidebar->evidence_empty_label + ), + TRUE + ); + + gtk_label_set_justify( + GTK_LABEL( + sidebar->evidence_empty_label + ), + GTK_JUSTIFY_CENTER + ); + + gtk_widget_set_halign( + sidebar->evidence_empty_label, + GTK_ALIGN_CENTER + ); + + gtk_widget_set_valign( + sidebar->evidence_empty_label, + GTK_ALIGN_CENTER + ); + + gtk_widget_set_hexpand( + sidebar->evidence_empty_label, + TRUE + ); + + gtk_widget_set_vexpand( + sidebar->evidence_empty_label, + TRUE + ); + + gtk_widget_set_margin_start( + sidebar->evidence_empty_label, + 20 + ); + + gtk_widget_set_margin_end( + sidebar->evidence_empty_label, + 20 + ); + + gtk_box_append( + GTK_BOX( + sidebar->evidence_empty_page + ), + sidebar->evidence_empty_label + ); + + gtk_stack_add_named( + GTK_STACK( + sidebar->evidence_content_stack + ), + sidebar->evidence_empty_page, + SIDEBAR_EVIDENCE_STATE_EMPTY + ); + + /* + * Arborescence des catégories et preuves. + */ + sidebar->evidence_tree_view = + evidence_tree_view_new(); + + if (sidebar->evidence_tree_view == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + evidence_tree_widget = + evidence_tree_view_get_widget( + sidebar->evidence_tree_view + ); + + if (evidence_tree_widget == NULL) + { + sidebar_free( + sidebar + ); + + return NULL; + } + + gtk_widget_set_hexpand( + evidence_tree_widget, + TRUE + ); + + gtk_widget_set_vexpand( + evidence_tree_widget, + TRUE + ); + + evidence_tree_view_set_selection_callback( + sidebar->evidence_tree_view, + sidebar_on_evidence_selected, + sidebar + ); + + gtk_stack_add_named( + GTK_STACK( + sidebar->evidence_content_stack + ), + evidence_tree_widget, + SIDEBAR_EVIDENCE_STATE_TREE + ); + + /* + * Aucun modèle n'est encore installé. + */ + gtk_stack_set_visible_child_name( + GTK_STACK( + sidebar->evidence_content_stack + ), + SIDEBAR_EVIDENCE_STATE_EMPTY + ); + + gtk_box_append( + GTK_BOX( + sidebar->evidence_page + ), + sidebar->evidence_content_stack + ); + + gtk_stack_add_titled( + GTK_STACK( + sidebar->stack + ), + sidebar->evidence_page, + SIDEBAR_PAGE_EVIDENCE, + "Preuves" + ); + + /* + * L'onglet Dossier reste sélectionné au démarrage. + */ + gtk_stack_set_visible_child_name( + GTK_STACK( + sidebar->stack + ), + SIDEBAR_PAGE_FOLDER + ); + + gtk_box_append( + GTK_BOX( + sidebar->root_widget + ), + sidebar->stack + ); + return sidebar; } @@ -209,6 +693,64 @@ void sidebar_set_selection_callback( ); } +void sidebar_set_evidence_model( + Sidebar *sidebar, + EvidenceCategoryModel *evidence_category_model +) +{ + if (sidebar == NULL || + sidebar->evidence_tree_view == NULL || + sidebar->evidence_content_stack == NULL) + { + return; + } + + evidence_tree_view_set_model( + sidebar->evidence_tree_view, + evidence_category_model + ); + + if (evidence_category_model == NULL || + evidence_category_model_get_count( + evidence_category_model + ) == 0) + { + gtk_stack_set_visible_child_name( + GTK_STACK( + sidebar->evidence_content_stack + ), + SIDEBAR_EVIDENCE_STATE_EMPTY + ); + + return; + } + + gtk_stack_set_visible_child_name( + GTK_STACK( + sidebar->evidence_content_stack + ), + SIDEBAR_EVIDENCE_STATE_TREE + ); +} + +void sidebar_set_evidence_selection_callback( + Sidebar *sidebar, + SidebarEvidenceSelectionCallback callback, + gpointer user_data +) +{ + if (sidebar == NULL) + { + return; + } + + sidebar->evidence_selection_callback = + callback; + + sidebar->evidence_selection_user_data = + user_data; +} + void sidebar_free(Sidebar *sidebar) { if (sidebar == NULL) @@ -218,6 +760,19 @@ void sidebar_free(Sidebar *sidebar) investigation_tree_view_free(sidebar->tree_view); + if (sidebar->evidence_tree_view != NULL) + { + evidence_tree_view_set_selection_callback( + sidebar->evidence_tree_view, + NULL, + NULL + ); + + evidence_tree_view_free( + sidebar->evidence_tree_view + ); + } + /* * 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. diff --git a/tests/test_evidence_category_item.c b/tests/test_evidence_category_item.c new file mode 100644 index 0000000..e123aa3 --- /dev/null +++ b/tests/test_evidence_category_item.c @@ -0,0 +1,332 @@ +/****************************************************************************** + * @file test_evidence_category_item.c + * @brief Tests unitaires de EvidenceCategoryItem. + ******************************************************************************/ + +#include "widgets/evidence_category_item.h" + +#include + +/** + * @brief Crée une preuve métier valide. + */ +static EvidenceRecord *test_evidence_category_item_create_record(void) +{ + EvidenceRecord *evidence_record = NULL; + + GError *error = NULL; + + evidence_record = + evidence_record_new( + "550e8400-e29b-41d4-a716-446655440000", + "capture-instagram.png", + "550e8400-e29b-41d4-a716-446655440000.png", + "01_Preuves_Originales/Documents/" + "550e8400-e29b-41d4-a716-446655440000.png", + "image", + 4096, + "00000000000000000000000000000000" + "00000000000000000000000000000000", + "2026-07-20T07:16:01Z", + "2026-07-19T18:30:00Z", + "Conversation Instagram", + "Capture d'écran reçue.", + EVIDENCE_INTEGRITY_STATUS_UNKNOWN, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + return evidence_record; +} + +/** + * @brief Vérifie la création d'une catégorie vide. + */ +static void test_evidence_category_item_new_valid(void) +{ + EvidenceCategoryItem *evidence_category_item = + NULL; + + evidence_category_item = + evidence_category_item_new( + " image ", + " Images " + ); + + g_assert_nonnull( + evidence_category_item + ); + + g_assert_cmpstr( + evidence_category_item_get_type_identifier( + evidence_category_item + ), + ==, + "image" + ); + + g_assert_cmpstr( + evidence_category_item_get_label( + evidence_category_item + ), + ==, + "Images" + ); + + g_assert_cmpuint( + evidence_category_item_get_count( + evidence_category_item + ), + ==, + 0 + ); + + g_assert_nonnull( + evidence_category_item_get_children( + evidence_category_item + ) + ); + + g_assert_cmpuint( + g_list_model_get_item_type( + evidence_category_item_get_children( + evidence_category_item + ) + ), + ==, + EVIDENCE_TYPE_LIST_ITEM + ); + + g_object_unref( + evidence_category_item + ); +} + +/** + * @brief Vérifie l'ajout et la propriété des preuves. + */ +static void test_evidence_category_item_append(void) +{ + EvidenceCategoryItem *evidence_category_item = + NULL; + + EvidenceRecord *evidence_record = + NULL; + + EvidenceListItem *evidence_list_item = + NULL; + + EvidenceListItem *stored_item = + NULL; + + evidence_category_item = + evidence_category_item_new( + "image", + "Images" + ); + + evidence_record = + test_evidence_category_item_create_record(); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + g_assert_nonnull( + evidence_category_item + ); + + g_assert_nonnull( + evidence_list_item + ); + + g_assert_true( + evidence_category_item_append( + evidence_category_item, + evidence_list_item + ) + ); + + /* + * La catégorie possède maintenant sa propre référence. + */ + g_object_unref( + evidence_list_item + ); + + evidence_record_free( + evidence_record + ); + + g_assert_cmpuint( + evidence_category_item_get_count( + evidence_category_item + ), + ==, + 1 + ); + + stored_item = + g_list_model_get_item( + evidence_category_item_get_children( + evidence_category_item + ), + 0 + ); + + g_assert_nonnull( + stored_item + ); + + g_assert_cmpstr( + evidence_list_item_get_identifier( + stored_item + ), + ==, + "550e8400-e29b-41d4-a716-446655440000" + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + stored_item + ), + ==, + "capture-instagram.png" + ); + + g_object_unref( + stored_item + ); + + g_object_unref( + evidence_category_item + ); +} + +/** + * @brief Vérifie les arguments invalides. + */ +static void test_evidence_category_item_invalid_arguments(void) +{ + EvidenceCategoryItem *evidence_category_item = + NULL; + + g_assert_null( + evidence_category_item_new( + NULL, + "Images" + ) + ); + + g_assert_null( + evidence_category_item_new( + "image", + NULL + ) + ); + + g_assert_null( + evidence_category_item_new( + " ", + "Images" + ) + ); + + g_assert_null( + evidence_category_item_new( + "image", + " " + ) + ); + + evidence_category_item = + evidence_category_item_new( + "image", + "Images" + ); + + g_assert_nonnull( + evidence_category_item + ); + + g_assert_false( + evidence_category_item_append( + evidence_category_item, + NULL + ) + ); + + g_assert_false( + evidence_category_item_append( + NULL, + NULL + ) + ); + + g_assert_null( + evidence_category_item_get_type_identifier( + NULL + ) + ); + + g_assert_null( + evidence_category_item_get_label( + NULL + ) + ); + + g_assert_cmpuint( + evidence_category_item_get_count( + NULL + ), + ==, + 0 + ); + + g_assert_null( + evidence_category_item_get_children( + NULL + ) + ); + + g_clear_object( + &evidence_category_item + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/evidence-category-item/new-valid", + test_evidence_category_item_new_valid + ); + + g_test_add_func( + "/evidence-category-item/append", + test_evidence_category_item_append + ); + + g_test_add_func( + "/evidence-category-item/invalid-arguments", + test_evidence_category_item_invalid_arguments + ); + + return g_test_run(); +} diff --git a/tests/test_evidence_category_model.c b/tests/test_evidence_category_model.c new file mode 100644 index 0000000..8a75dd2 --- /dev/null +++ b/tests/test_evidence_category_model.c @@ -0,0 +1,637 @@ +/****************************************************************************** + * @file test_evidence_category_model.c + * @brief Tests unitaires de EvidenceCategoryModel. + ******************************************************************************/ + +#include "widgets/evidence_category_model.h" + +#include "widgets/evidence_category_item.h" +#include "widgets/evidence_list_item.h" + +#include "models/evidence_record.h" + +#include + +/** + * @brief Crée un élément d'affichage valide. + */ +static EvidenceListItem *test_evidence_category_model_create_item( + const char *identifier, + const char *original_name, + const char *type_identifier +) +{ + EvidenceRecord *evidence_record = NULL; + EvidenceListItem *evidence_list_item = NULL; + + char *internal_name = NULL; + char *relative_path = NULL; + + GError *error = NULL; + + internal_name = + g_strdup_printf( + "%s.bin", + identifier + ); + + relative_path = + g_strdup_printf( + "01_Preuves_Originales/Documents/%s.bin", + identifier + ); + + g_assert_nonnull( + internal_name + ); + + g_assert_nonnull( + relative_path + ); + + evidence_record = + evidence_record_new( + identifier, + original_name, + internal_name, + relative_path, + type_identifier, + 4096, + "00000000000000000000000000000000" + "00000000000000000000000000000000", + "2026-07-20T07:16:01Z", + "2026-07-19T18:30:00Z", + NULL, + NULL, + EVIDENCE_INTEGRITY_STATUS_UNKNOWN, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + g_assert_nonnull( + evidence_list_item + ); + + evidence_record_free( + evidence_record + ); + + g_free( + relative_path + ); + + g_free( + internal_name + ); + + return evidence_list_item; +} + +/** + * @brief Ajoute une preuve à un GListStore. + */ +static void test_evidence_category_model_append_item( + GListStore *evidence_items, + EvidenceListItem *evidence_list_item +) +{ + g_list_store_append( + evidence_items, + G_OBJECT( + evidence_list_item + ) + ); + + g_object_unref( + evidence_list_item + ); +} + +/** + * @brief Crée la table des libellés de types. + */ +static GHashTable *test_evidence_category_model_create_labels(void) +{ + GHashTable *type_labels = + NULL; + + type_labels = + g_hash_table_new_full( + g_str_hash, + g_str_equal, + g_free, + g_free + ); + + g_hash_table_insert( + type_labels, + g_strdup("document"), + g_strdup("Documents") + ); + + g_hash_table_insert( + type_labels, + g_strdup("image"), + g_strdup("Images") + ); + + return type_labels; +} + +/** + * @brief Vérifie la création d'un modèle vide. + */ +static void test_evidence_category_model_new_empty(void) +{ + EvidenceCategoryModel *evidence_category_model = + NULL; + + evidence_category_model = + evidence_category_model_new(); + + g_assert_nonnull( + evidence_category_model + ); + + g_assert_nonnull( + evidence_category_model_get_list_model( + evidence_category_model + ) + ); + + g_assert_cmpuint( + evidence_category_model_get_count( + evidence_category_model + ), + ==, + 0 + ); + + g_assert_cmpuint( + g_list_model_get_item_type( + evidence_category_model_get_list_model( + evidence_category_model + ) + ), + ==, + EVIDENCE_TYPE_CATEGORY_ITEM + ); + + evidence_category_model_free( + evidence_category_model + ); + + evidence_category_model_free( + NULL + ); +} + +/** + * @brief Vérifie le regroupement et le tri. + */ +static void test_evidence_category_model_group_and_sort(void) +{ + EvidenceCategoryModel *evidence_category_model = + NULL; + + GListStore *evidence_items = + NULL; + + GHashTable *type_labels = + NULL; + + EvidenceCategoryItem *documents_category = + NULL; + + EvidenceCategoryItem *images_category = + NULL; + + EvidenceListItem *first_image = + NULL; + + evidence_category_model = + evidence_category_model_new(); + + evidence_items = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); + + type_labels = + test_evidence_category_model_create_labels(); + + test_evidence_category_model_append_item( + evidence_items, + test_evidence_category_model_create_item( + "550e8400-e29b-41d4-a716-446655440000", + "zeta.png", + "image" + ) + ); + + test_evidence_category_model_append_item( + evidence_items, + test_evidence_category_model_create_item( + "550e8400-e29b-41d4-a716-446655440001", + "rapport.pdf", + "document" + ) + ); + + test_evidence_category_model_append_item( + evidence_items, + test_evidence_category_model_create_item( + "550e8400-e29b-41d4-a716-446655440002", + "alpha.png", + "image" + ) + ); + + g_assert_true( + evidence_category_model_replace( + evidence_category_model, + G_LIST_MODEL( + evidence_items + ), + type_labels, + NULL + ) + ); + + g_assert_cmpuint( + evidence_category_model_get_count( + evidence_category_model + ), + ==, + 2 + ); + + documents_category = + g_list_model_get_item( + evidence_category_model_get_list_model( + evidence_category_model + ), + 0 + ); + + images_category = + g_list_model_get_item( + evidence_category_model_get_list_model( + evidence_category_model + ), + 1 + ); + + g_assert_cmpstr( + evidence_category_item_get_label( + documents_category + ), + ==, + "Documents" + ); + + g_assert_cmpuint( + evidence_category_item_get_count( + documents_category + ), + ==, + 1 + ); + + g_assert_cmpstr( + evidence_category_item_get_label( + images_category + ), + ==, + "Images" + ); + + g_assert_cmpuint( + evidence_category_item_get_count( + images_category + ), + ==, + 2 + ); + + first_image = + g_list_model_get_item( + evidence_category_item_get_children( + images_category + ), + 0 + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + first_image + ), + ==, + "alpha.png" + ); + + g_object_unref( + first_image + ); + + g_object_unref( + images_category + ); + + g_object_unref( + documents_category + ); + + g_hash_table_unref( + type_labels + ); + + g_object_unref( + evidence_items + ); + + evidence_category_model_free( + evidence_category_model + ); +} + +/** + * @brief Vérifie le libellé de secours pour un type inconnu. + */ +static void test_evidence_category_model_unknown_type(void) +{ + EvidenceCategoryModel *evidence_category_model = + NULL; + + GListStore *evidence_items = + NULL; + + EvidenceCategoryItem *category = + NULL; + + evidence_category_model = + evidence_category_model_new(); + + evidence_items = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); + + test_evidence_category_model_append_item( + evidence_items, + test_evidence_category_model_create_item( + "550e8400-e29b-41d4-a716-446655440003", + "archive.bin", + "unknown" + ) + ); + + g_assert_true( + evidence_category_model_replace( + evidence_category_model, + G_LIST_MODEL( + evidence_items + ), + NULL, + NULL + ) + ); + + category = + g_list_model_get_item( + evidence_category_model_get_list_model( + evidence_category_model + ), + 0 + ); + + g_assert_cmpstr( + evidence_category_item_get_label( + category + ), + ==, + "unknown" + ); + + g_object_unref( + category + ); + + g_object_unref( + evidence_items + ); + + evidence_category_model_free( + evidence_category_model + ); +} + +/** + * @brief Vérifie les remplacements successifs et les erreurs. + */ +static void test_evidence_category_model_replace_and_clear(void) +{ + EvidenceCategoryModel *evidence_category_model = + NULL; + + GListStore *valid_items = + NULL; + + GListStore *empty_items = + NULL; + + GListStore *invalid_items = + NULL; + + GObject *invalid_object = + NULL; + + GError *error = NULL; + + evidence_category_model = + evidence_category_model_new(); + + valid_items = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); + + empty_items = + g_list_store_new( + EVIDENCE_TYPE_LIST_ITEM + ); + + invalid_items = + g_list_store_new( + G_TYPE_OBJECT + ); + + test_evidence_category_model_append_item( + valid_items, + test_evidence_category_model_create_item( + "550e8400-e29b-41d4-a716-446655440004", + "preuve.txt", + "document" + ) + ); + + g_assert_true( + evidence_category_model_replace( + evidence_category_model, + G_LIST_MODEL( + valid_items + ), + NULL, + NULL + ) + ); + + invalid_object = + g_object_new( + G_TYPE_OBJECT, + NULL + ); + + g_list_store_append( + invalid_items, + invalid_object + ); + + g_object_unref( + invalid_object + ); + + g_assert_false( + evidence_category_model_replace( + evidence_category_model, + G_LIST_MODEL( + invalid_items + ), + NULL, + &error + ) + ); + + g_assert_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + /* + * L'ancien contenu est conservé après l'échec. + */ + g_assert_cmpuint( + evidence_category_model_get_count( + evidence_category_model + ), + ==, + 1 + ); + + g_assert_true( + evidence_category_model_replace( + evidence_category_model, + G_LIST_MODEL( + empty_items + ), + NULL, + NULL + ) + ); + + g_assert_cmpuint( + evidence_category_model_get_count( + evidence_category_model + ), + ==, + 0 + ); + + g_assert_false( + evidence_category_model_replace( + NULL, + G_LIST_MODEL( + valid_items + ), + NULL, + &error + ) + ); + + g_assert_error( + error, + EVIDENCE_CATEGORY_MODEL_ERROR, + EVIDENCE_CATEGORY_MODEL_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_object_unref( + invalid_items + ); + + g_object_unref( + empty_items + ); + + g_object_unref( + valid_items + ); + + evidence_category_model_free( + evidence_category_model + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/evidence-category-model/new-empty", + test_evidence_category_model_new_empty + ); + + g_test_add_func( + "/evidence-category-model/group-and-sort", + test_evidence_category_model_group_and_sort + ); + + g_test_add_func( + "/evidence-category-model/unknown-type", + test_evidence_category_model_unknown_type + ); + + g_test_add_func( + "/evidence-category-model/replace-and-clear", + test_evidence_category_model_replace_and_clear + ); + + return g_test_run(); +} diff --git a/tests/test_evidence_list_item.c b/tests/test_evidence_list_item.c new file mode 100644 index 0000000..a4ed04a --- /dev/null +++ b/tests/test_evidence_list_item.c @@ -0,0 +1,336 @@ +/****************************************************************************** + * @file test_evidence_list_item.c + * @brief Tests unitaires de EvidenceListItem. + ******************************************************************************/ + +#include "widgets/evidence_list_item.h" + +#include + +/** + * @brief Crée une preuve métier valide pour les tests. + */ +static EvidenceRecord *test_evidence_list_item_create_record( + const char *collected_at, + const char *source, + EvidenceIntegrityStatus integrity_status +) +{ + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + evidence_record = + evidence_record_new( + "550e8400-e29b-41d4-a716-446655440000", + "capture_originale.png", + "550e8400-e29b-41d4-a716-446655440000.png", + "01_Preuves_Originales/Documents/" + "550e8400-e29b-41d4-a716-446655440000.png", + "image", + 4096, + "00000000000000000000000000000000" + "00000000000000000000000000000000", + "2026-07-20T07:16:01Z", + collected_at, + source, + "Capture reçue pendant la conversation.", + integrity_status, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + return evidence_record; +} + +/** + * @brief Vérifie la copie des valeurs nominales. + */ +static void test_evidence_list_item_new_valid(void) +{ + EvidenceRecord *evidence_record = NULL; + EvidenceListItem *evidence_list_item = NULL; + + evidence_record = + test_evidence_list_item_create_record( + "2026-07-19T18:30:00Z", + "Conversation Instagram", + EVIDENCE_INTEGRITY_STATUS_VALID + ); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + g_assert_nonnull( + evidence_list_item + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + evidence_list_item + ), + ==, + "capture_originale.png" + ); + + g_assert_cmpstr( + evidence_list_item_get_type_identifier( + evidence_list_item + ), + ==, + "image" + ); + + g_assert_cmpuint( + evidence_list_item_get_size_bytes( + evidence_list_item + ), + ==, + 4096 + ); + + g_assert_cmpstr( + evidence_list_item_get_collected_at( + evidence_list_item + ), + ==, + "2026-07-19T18:30:00Z" + ); + + g_assert_cmpstr( + evidence_list_item_get_source( + evidence_list_item + ), + ==, + "Conversation Instagram" + ); + + g_assert_cmpint( + evidence_list_item_get_integrity_status( + evidence_list_item + ), + ==, + EVIDENCE_INTEGRITY_STATUS_VALID + ); + + g_assert_cmpstr( + evidence_list_item_get_integrity_status_label( + evidence_list_item + ), + ==, + "Valide" + ); + + g_object_unref( + evidence_list_item + ); + + evidence_record_free( + evidence_record + ); +} + +/** + * @brief Vérifie que les chaînes ne dépendent plus du modèle métier. + */ +static void test_evidence_list_item_copies_strings(void) +{ + EvidenceRecord *evidence_record = NULL; + EvidenceListItem *evidence_list_item = NULL; + + evidence_record = + test_evidence_list_item_create_record( + "2026-07-19T18:30:00Z", + "Email reçu", + EVIDENCE_INTEGRITY_STATUS_UNKNOWN + ); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + g_assert_nonnull( + evidence_list_item + ); + + evidence_record_free( + evidence_record + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + evidence_list_item + ), + ==, + "capture_originale.png" + ); + + g_assert_cmpstr( + evidence_list_item_get_source( + evidence_list_item + ), + ==, + "Email reçu" + ); + + g_object_unref( + evidence_list_item + ); +} + +/** + * @brief Vérifie les valeurs facultatives absentes. + */ +static void test_evidence_list_item_optional_values(void) +{ + EvidenceRecord *evidence_record = NULL; + EvidenceListItem *evidence_list_item = NULL; + + evidence_record = + test_evidence_list_item_create_record( + NULL, + NULL, + EVIDENCE_INTEGRITY_STATUS_UNKNOWN + ); + + evidence_list_item = + evidence_list_item_new( + evidence_record + ); + + g_assert_nonnull( + evidence_list_item + ); + + g_assert_null( + evidence_list_item_get_collected_at( + evidence_list_item + ) + ); + + g_assert_null( + evidence_list_item_get_source( + evidence_list_item + ) + ); + + g_assert_cmpstr( + evidence_list_item_get_integrity_status_label( + evidence_list_item + ), + ==, + "Non vérifiée" + ); + + g_object_unref( + evidence_list_item + ); + + evidence_record_free( + evidence_record + ); +} + +/** + * @brief Vérifie les arguments NULL. + */ +static void test_evidence_list_item_null_values(void) +{ + EvidenceListItem *evidence_list_item = NULL; + + g_assert_null( + evidence_list_item_new( + NULL + ) + ); + + g_assert_null( + evidence_list_item_get_original_name( + NULL + ) + ); + + g_assert_null( + evidence_list_item_get_type_identifier( + NULL + ) + ); + + g_assert_cmpuint( + evidence_list_item_get_size_bytes( + NULL + ), + ==, + 0 + ); + + g_assert_null( + evidence_list_item_get_collected_at( + NULL + ) + ); + + g_assert_null( + evidence_list_item_get_source( + NULL + ) + ); + + g_assert_cmpint( + evidence_list_item_get_integrity_status( + NULL + ), + ==, + EVIDENCE_INTEGRITY_STATUS_UNKNOWN + ); + + /* + * g_clear_object() accepte un pointeur contenant NULL. + */ + g_clear_object( + &evidence_list_item + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/evidence-list-item/new-valid", + test_evidence_list_item_new_valid + ); + + g_test_add_func( + "/evidence-list-item/copies-strings", + test_evidence_list_item_copies_strings + ); + + g_test_add_func( + "/evidence-list-item/optional-values", + test_evidence_list_item_optional_values + ); + + g_test_add_func( + "/evidence-list-item/null-values", + test_evidence_list_item_null_values + ); + + return g_test_run(); +} diff --git a/tests/test_evidence_list_model.c b/tests/test_evidence_list_model.c new file mode 100644 index 0000000..fda8ff5 --- /dev/null +++ b/tests/test_evidence_list_model.c @@ -0,0 +1,592 @@ +/****************************************************************************** + * @file test_evidence_list_model.c + * @brief Tests unitaires de EvidenceListModel. + ******************************************************************************/ + +#include "widgets/evidence_list_model.h" + +#include "widgets/evidence_list_item.h" + +#include + +/** + * @brief Crée une preuve valide pour les tests. + */ +static EvidenceRecord *test_evidence_list_model_create_record( + const char *uuid, + const char *original_name, + const char *source +) +{ + EvidenceRecord *evidence_record = NULL; + + char *internal_name = NULL; + char *relative_path = NULL; + + GError *error = NULL; + + internal_name = + g_strdup_printf( + "%s.bin", + uuid + ); + + relative_path = + g_strdup_printf( + "01_Preuves_Originales/Documents/%s.bin", + uuid + ); + + g_assert_nonnull( + internal_name + ); + + g_assert_nonnull( + relative_path + ); + + evidence_record = + evidence_record_new( + uuid, + original_name, + internal_name, + relative_path, + "document", + 4096, + "00000000000000000000000000000000" + "00000000000000000000000000000000", + "2026-07-20T07:16:01Z", + "2026-07-19T18:30:00Z", + source, + "Description de test.", + EVIDENCE_INTEGRITY_STATUS_UNKNOWN, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_free( + relative_path + ); + + g_free( + internal_name + ); + + return evidence_record; +} + +/** + * @brief Crée un tableau qui libère automatiquement ses preuves. + */ +static GPtrArray *test_evidence_list_model_create_records(void) +{ + return g_ptr_array_new_with_free_func( + (GDestroyNotify) evidence_record_free + ); +} + +/** + * @brief Vérifie la création d'un modèle vide. + */ +static void test_evidence_list_model_new_empty(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + GListModel *list_model = + NULL; + + evidence_list_model = + evidence_list_model_new(); + + g_assert_nonnull( + evidence_list_model + ); + + list_model = + evidence_list_model_get_list_model( + evidence_list_model + ); + + g_assert_nonnull( + list_model + ); + + g_assert_cmpuint( + evidence_list_model_get_count( + evidence_list_model + ), + ==, + 0 + ); + + g_assert_cmpuint( + g_list_model_get_item_type( + list_model + ), + ==, + EVIDENCE_TYPE_LIST_ITEM + ); + + evidence_list_model_free( + evidence_list_model + ); + + evidence_list_model_free( + NULL + ); +} + +/** + * @brief Vérifie l'alimentation nominale du modèle. + */ +static void test_evidence_list_model_replace_records(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + GPtrArray *evidence_records = + NULL; + + EvidenceListItem *first_item = + NULL; + + evidence_list_model = + evidence_list_model_new(); + + evidence_records = + test_evidence_list_model_create_records(); + + g_ptr_array_add( + evidence_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440000", + "premiere-preuve.pdf", + "Email reçu" + ) + ); + + g_ptr_array_add( + evidence_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440001", + "deuxieme-preuve.png", + "Conversation Instagram" + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + evidence_records, + NULL + ) + ); + + g_assert_cmpuint( + evidence_list_model_get_count( + evidence_list_model + ), + ==, + 2 + ); + + first_item = + g_list_model_get_item( + evidence_list_model_get_list_model( + evidence_list_model + ), + 0 + ); + + g_assert_nonnull( + first_item + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + first_item + ), + ==, + "premiere-preuve.pdf" + ); + + g_object_unref( + first_item + ); + + /* + * La liste doit rester valide après libération des EvidenceRecord. + */ + g_ptr_array_unref( + evidence_records + ); + + first_item = + g_list_model_get_item( + evidence_list_model_get_list_model( + evidence_list_model + ), + 0 + ); + + g_assert_cmpstr( + evidence_list_item_get_source( + first_item + ), + ==, + "Email reçu" + ); + + g_object_unref( + first_item + ); + + evidence_list_model_free( + evidence_list_model + ); +} + +/** + * @brief Vérifie qu'un rafraîchissement remplace l'ancien contenu. + */ +static void test_evidence_list_model_successive_replacements(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + GPtrArray *first_records = + NULL; + + GPtrArray *second_records = + NULL; + + EvidenceListItem *remaining_item = + NULL; + + evidence_list_model = + evidence_list_model_new(); + + first_records = + test_evidence_list_model_create_records(); + + second_records = + test_evidence_list_model_create_records(); + + g_ptr_array_add( + first_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440002", + "ancienne-preuve.txt", + NULL + ) + ); + + g_ptr_array_add( + second_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440003", + "nouvelle-preuve.txt", + NULL + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + first_records, + NULL + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + second_records, + NULL + ) + ); + + g_assert_cmpuint( + evidence_list_model_get_count( + evidence_list_model + ), + ==, + 1 + ); + + remaining_item = + g_list_model_get_item( + evidence_list_model_get_list_model( + evidence_list_model + ), + 0 + ); + + g_assert_cmpstr( + evidence_list_item_get_original_name( + remaining_item + ), + ==, + "nouvelle-preuve.txt" + ); + + g_object_unref( + remaining_item + ); + + g_ptr_array_unref( + second_records + ); + + g_ptr_array_unref( + first_records + ); + + evidence_list_model_free( + evidence_list_model + ); +} + +/** + * @brief Vérifie qu'un tableau vide efface le modèle. + */ +static void test_evidence_list_model_replace_empty(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + GPtrArray *evidence_records = + NULL; + + GPtrArray *empty_records = + NULL; + + evidence_list_model = + evidence_list_model_new(); + + evidence_records = + test_evidence_list_model_create_records(); + + empty_records = + test_evidence_list_model_create_records(); + + g_ptr_array_add( + evidence_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440004", + "preuve-temporaire.txt", + NULL + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + evidence_records, + NULL + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + empty_records, + NULL + ) + ); + + g_assert_cmpuint( + evidence_list_model_get_count( + evidence_list_model + ), + ==, + 0 + ); + + g_ptr_array_unref( + empty_records + ); + + g_ptr_array_unref( + evidence_records + ); + + evidence_list_model_free( + evidence_list_model + ); +} + +/** + * @brief Vérifie les arguments invalides et le remplacement atomique. + */ +static void test_evidence_list_model_invalid_records(void) +{ + EvidenceListModel *evidence_list_model = + NULL; + + GPtrArray *valid_records = + NULL; + + GPtrArray *invalid_records = + NULL; + + GError *error = NULL; + + evidence_list_model = + evidence_list_model_new(); + + valid_records = + test_evidence_list_model_create_records(); + + invalid_records = + test_evidence_list_model_create_records(); + + g_ptr_array_add( + valid_records, + test_evidence_list_model_create_record( + "550e8400-e29b-41d4-a716-446655440005", + "preuve-conservee.txt", + NULL + ) + ); + + g_assert_true( + evidence_list_model_replace( + evidence_list_model, + valid_records, + NULL + ) + ); + + /* + * NULL représente ici une ligne métier invalide. + */ + g_ptr_array_add( + invalid_records, + NULL + ); + + g_assert_false( + evidence_list_model_replace( + evidence_list_model, + invalid_records, + &error + ) + ); + + g_assert_error( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_INVALID_RECORD + ); + + g_clear_error( + &error + ); + + /* + * L'ancien élément doit toujours être présent. + */ + g_assert_cmpuint( + evidence_list_model_get_count( + evidence_list_model + ), + ==, + 1 + ); + + g_assert_false( + evidence_list_model_replace( + NULL, + valid_records, + &error + ) + ); + + g_assert_error( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + evidence_list_model_replace( + evidence_list_model, + NULL, + &error + ) + ); + + g_assert_error( + error, + EVIDENCE_LIST_MODEL_ERROR, + EVIDENCE_LIST_MODEL_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_ptr_array_unref( + invalid_records + ); + + g_ptr_array_unref( + valid_records + ); + + evidence_list_model_free( + evidence_list_model + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/evidence-list-model/new-empty", + test_evidence_list_model_new_empty + ); + + g_test_add_func( + "/evidence-list-model/replace-records", + test_evidence_list_model_replace_records + ); + + g_test_add_func( + "/evidence-list-model/successive-replacements", + test_evidence_list_model_successive_replacements + ); + + g_test_add_func( + "/evidence-list-model/replace-empty", + test_evidence_list_model_replace_empty + ); + + g_test_add_func( + "/evidence-list-model/invalid-records", + test_evidence_list_model_invalid_records + ); + + return g_test_run(); +}