feat(gui): connect investigation model to sidebar

This commit is contained in:
grayTerminal-sh 2026-07-13 17:27:45 +02:00
parent a0e6743ac8
commit f071527d15
7 changed files with 382 additions and 20 deletions

View file

@ -0,0 +1,240 @@
# Ticket #011
## Titre
Connecter le modèle d'arborescence à la `Sidebar`.
---
## Objectif
Relier l'enquête sélectionnée au panneau latéral.
Après la sélection d'un dossier, l'application doit :
1. créer l'objet `Investigation` ;
2. construire son `InvestigationTreeModel` ;
3. transmettre le modèle à la `Sidebar` ;
4. afficher temporairement le nom du nœud racine dans le panneau latéral.
Ce ticket valide la communication entre le Core et l'interface graphique.
---
## Architecture
```text
FolderDialog
Application
├── Investigation
└── InvestigationTreeModel
Sidebar
```
Le module `Application` reste responsable de la coordination.
La `Sidebar` ne parcourt jamais directement le système de fichiers.
---
## Responsabilités
### Application
Le module `Application` doit :
- recevoir le dossier sélectionné ;
- créer l'objet `Investigation` ;
- construire le modèle avec `InvestigationTreeBuilder` ;
- conserver le modèle pendant toute la durée de l'enquête ;
- transmettre le modèle à `MainWindow` ;
- libérer l'ancien modèle avant d'en ouvrir un nouveau ;
- libérer le modèle à la fermeture de l'application.
### MainWindow
Le module `MainWindow` doit :
- recevoir un modèle d'arborescence ;
- le transmettre au composant `Sidebar`.
### Sidebar
Le module `Sidebar` doit :
- recevoir un `InvestigationTreeModel` en lecture seule ;
- lire le nœud racine ;
- afficher temporairement son nom dans le titre du panneau ;
- ne jamais détruire le modèle reçu.
---
## Principe de propriété
`Application` est propriétaire de :
```text
Investigation
InvestigationTreeModel
MainWindow
```
La `Sidebar` reçoit uniquement une référence non propriétaire vers le modèle.
Elle ne doit jamais appeler :
```c
investigation_tree_model_free(tree_model);
```
Le modèle est libéré uniquement par `Application`.
---
## Fichiers concernés
```text
src/core/application.c
include/views/main_window.h
src/views/main_window.c
include/widgets/sidebar.h
src/widgets/sidebar.c
```
Aucun nouveau module n'est nécessaire.
---
## Interfaces publiques à ajouter
### MainWindow
```c
void main_window_set_tree_model(
MainWindow *main_window,
const InvestigationTreeModel *tree_model
);
```
### Sidebar
```c
void sidebar_set_tree_model(
Sidebar *sidebar,
const InvestigationTreeModel *tree_model
);
```
---
## Comportement attendu
Avant l'ouverture d'une enquête, la `Sidebar` affiche :
```text
Dossier d'enquête
```
Après la sélection du dossier :
```text
Template
```
ou le nom réel du dossier racine sélectionné.
À ce stade, les enfants ne sont pas encore affichés.
---
## Hors périmètre
Ce ticket ne doit pas :
- afficher les enfants du nœud racine ;
- utiliser `GtkTreeListModel` ;
- créer un explorateur de fichiers complet ;
- permettre de sélectionner un nœud ;
- ouvrir un fichier ;
- rafraîchir automatiquement le modèle ;
- modifier le système de fichiers ;
- communiquer avec SQLite.
---
## Gestion des erreurs
Si la construction du modèle échoue :
- l'application ne doit pas planter ;
- l'ancien modèle doit rester valide jusqu'à son remplacement explicite ;
- un message d'erreur doit être affiché dans le terminal ;
- la `Sidebar` ne doit recevoir aucun pointeur invalide.
Si une nouvelle enquête est ouverte avec succès :
1. créer la nouvelle enquête ;
2. construire le nouveau modèle ;
3. seulement ensuite libérer l'ancienne enquête et l'ancien modèle ;
4. installer les nouveaux objets.
Cette séquence évite de perdre l'enquête actuellement ouverte en cas d'échec.
---
## Contraintes techniques
- C17 ;
- aucun état global ;
- aucune lecture du système de fichiers dans `Sidebar` ;
- aucun transfert de propriété vers `Sidebar` ;
- documentation Doxygen ;
- compilation sans warning ;
- absence de `Gtk-CRITICAL` ;
- respect des conventions de nommage.
---
## Critères d'acceptation
- [ ] Le projet compile sans warning.
- [ ] `make test` reste valide.
- [ ] La sélection d'un dossier construit un modèle.
- [ ] `Application` conserve le modèle.
- [ ] `MainWindow` transmet le modèle à `Sidebar`.
- [ ] La `Sidebar` affiche le nom du nœud racine.
- [ ] L'ouverture successive de deux dossiers fonctionne.
- [ ] L'ancien modèle est correctement libéré.
- [ ] Une erreur de construction ne provoque pas de crash.
- [ ] La fermeture de l'application libère le modèle.
- [ ] Aucun code de parcours du disque n'apparaît dans `Sidebar`.
- [ ] Aucun enfant n'est encore affiché.
---
## Tests manuels
1. Lancer l'application.
2. Sélectionner le dossier `Template`.
3. Vérifier que la `Sidebar` affiche `Template`.
4. Fermer l'application.
5. Vérifier l'absence de warning critique.
6. Ouvrir successivement deux dossiers différents.
7. Vérifier que le titre de la `Sidebar` est mis à jour.
8. Vérifier que `make test` reste entièrement valide.
---
## Commit attendu
```text
feat(gui): connect investigation model to sidebar
```

View file

@ -6,6 +6,8 @@
#ifndef LABFY_INVESTIGATION_MAIN_WINDOW_H
#define LABFY_INVESTIGATION_MAIN_WINDOW_H
#include "core/investigation_tree_model.h"
#include <gtk/gtk.h>
/**
@ -40,6 +42,20 @@ GtkWindow *main_window_get_window(
const MainWindow *main_window
);
/**
* @brief Transmet un modèle d'arborescence à la fenêtre principale.
*
* La fenêtre principale ne devient pas propriétaire du modèle.
* Elle le transmet uniquement à la Sidebar.
*
* @param main_window Fenêtre principale à mettre à jour.
* @param tree_model Modèle d'arborescence en lecture seule.
*/
void main_window_set_tree_model(
MainWindow *main_window,
const InvestigationTreeModel *tree_model
);
/**
* @brief Libère les ressources de la fenêtre.
*

View file

@ -6,6 +6,8 @@
#ifndef LABFY_INVESTIGATION_SIDEBAR_H
#define LABFY_INVESTIGATION_SIDEBAR_H
#include "core/investigation_tree_model.h"
#include <gtk/gtk.h>
/**
@ -37,6 +39,22 @@ GtkWidget *sidebar_get_widget(
const Sidebar *sidebar
);
/**
* @brief Associe un modèle d'arborescence à la barre latérale.
*
* La barre latérale ne devient pas propriétaire du modèle.
* Le modèle doit rester valide pendant toute la durée de son utilisation.
*
* Pour ce ticket, seul le nom du nœud racine est affiché dans le titre.
*
* @param sidebar Barre latérale à mettre à jour.
* @param tree_model Modèle d'arborescence en lecture seule.
*/
void sidebar_set_tree_model(
Sidebar *sidebar,
const InvestigationTreeModel *tree_model
);
/**
* @brief Libère la structure d'encapsulation du panneau latéral.
*

Binary file not shown.

View file

@ -7,6 +7,8 @@
#include "views/folder_dialog.h"
#include "core/investigation.h"
#include "views/main_window.h"
#include "core/investigation_tree_builder.h"
#include "core/investigation_tree_model.h"
#include <gtk/gtk.h>
@ -33,6 +35,7 @@ struct Application
GtkApplication *gtk_application;
MainWindow *main_window;
Investigation *investigation;
InvestigationTreeModel *tree_model;
};
/**
@ -51,17 +54,8 @@ static void application_on_folder_selected(
)
{
Application *application = user_data;
const char *root_path = NULL;
const char *database_path = NULL;
/*
* Une éventuelle enquête précédemment ouverte doit être libérée avant
* d'en créer une nouvelle.
*/
investigation_free(application->investigation);
application->investigation = NULL;
application->investigation = investigation_new(folder_path);
Investigation *new_investigation = NULL;
InvestigationTreeModel *new_tree_model = NULL;
if (application == NULL)
{
@ -74,23 +68,47 @@ static void application_on_folder_selected(
return;
}
if (application->investigation == NULL)
new_investigation = investigation_new(folder_path);
if (new_investigation == NULL)
{
g_warning(
"Impossible de créer l'enquête à partir du dossier sélectionné."
);
return;
}
root_path = investigation_get_root_path(
application->investigation
);
database_path = investigation_get_database_path(
application->investigation
new_tree_model = investigation_tree_builder_build(
investigation_get_root_path(new_investigation)
);
g_print("Dossier racine : %s\n", root_path);
g_print("Base de données : %s\n", database_path);
if (new_tree_model == NULL)
{
g_warning(
"Impossible de construire l'arborescence de l'enquête."
);
investigation_free(new_investigation);
return;
}
/*
* Les nouveaux objets sont valides.
* On peut maintenant remplacer les anciens sans perdre l'enquête
* déjà ouverte en cas d'échec.
*/
investigation_tree_model_free(application->tree_model);
investigation_free(application->investigation);
application->tree_model = new_tree_model;
application->investigation = new_investigation;
main_window_set_tree_model(
application->main_window,
application->tree_model
);
}
static void application_on_activate(
GtkApplication *gtk_application,
gpointer user_data
@ -182,8 +200,8 @@ void application_free(Application *application)
return;
}
investigation_tree_model_free(application->tree_model);
investigation_free(application->investigation);
main_window_free(application->main_window);
if (application->gtk_application != NULL)

View file

@ -268,6 +268,22 @@ GtkWindow *main_window_get_window(
return main_window->window;
}
void main_window_set_tree_model(
MainWindow *main_window,
const InvestigationTreeModel *tree_model
)
{
if (main_window == NULL)
{
return;
}
sidebar_set_tree_model(
main_window->sidebar,
tree_model
);
}
void main_window_free(MainWindow *main_window)
{
if (main_window == NULL)

View file

@ -4,6 +4,7 @@
******************************************************************************/
#include "widgets/sidebar.h"
#include "core/investigation_node.h"
#include <glib.h>
@ -97,6 +98,59 @@ GtkWidget *sidebar_get_widget(
return sidebar->root_widget;
}
void sidebar_set_tree_model(
Sidebar *sidebar,
const InvestigationTreeModel *tree_model
)
{
const InvestigationNode *root_node = NULL;
const char *root_name = NULL;
if (sidebar == NULL)
{
return;
}
if (tree_model == NULL)
{
gtk_label_set_text(
GTK_LABEL(sidebar->title_label),
"Dossier d'enquête"
);
return;
}
root_node = investigation_tree_model_get_root(tree_model);
if (root_node == NULL)
{
gtk_label_set_text(
GTK_LABEL(sidebar->title_label),
"Dossier d'enquête"
);
return;
}
root_name = investigation_node_get_name(root_node);
if (root_name == NULL || root_name[0] == '\0')
{
gtk_label_set_text(
GTK_LABEL(sidebar->title_label),
"Dossier d'enquête"
);
return;
}
gtk_label_set_text(
GTK_LABEL(sidebar->title_label),
root_name
);
}
void sidebar_free(Sidebar *sidebar)
{
if (sidebar == NULL)