feat(gui): display selected node in workspace

This commit is contained in:
grayTerminal-sh 2026-07-14 13:37:12 +02:00
parent 6938529fb2
commit ecac36fd8d
7 changed files with 474 additions and 0 deletions

View file

@ -0,0 +1,225 @@
# Ticket #015
## Titre
Afficher le nœud sélectionné dans le `Workspace`.
---
## Objectif
Afficher dans la zone de travail les informations principales du nœud sélectionné dans l'arborescence.
Lorsqu'un dossier ou un fichier est sélectionné, le `Workspace` doit afficher :
- son nom ;
- son type ;
- son parent éventuel ;
- son nombre d'enfants s'il s'agit d'un dossier.
Aucun fichier ne doit encore être ouvert.
---
## Architecture
```text
InvestigationTreeView
Application
MainWindow
Workspace
```
`Application` reste le coordinateur.
`InvestigationTreeView` détecte la sélection.
`Workspace` affiche les informations reçues.
---
## Responsabilités
### Application
Le module doit :
- recevoir le nœud sélectionné ;
- transmettre ce nœud à `MainWindow` ;
- ne pas modifier ni libérer le nœud.
### MainWindow
Le module doit :
- recevoir un nœud en lecture seule ;
- le transmettre au `Workspace`.
### Workspace
Le module doit :
- recevoir un `InvestigationNode` en lecture seule ;
- afficher son nom ;
- afficher son type ;
- afficher le nom de son parent si disponible ;
- afficher le nombre d'enfants pour un dossier ;
- restaurer la page d'accueil si le nœud vaut `NULL`.
---
## Principe de propriété
Le nœud sélectionné reste la propriété du `InvestigationTreeModel`.
`Application`, `MainWindow` et `Workspace` ne reçoivent qu'une référence non propriétaire :
```c
const InvestigationNode *node;
```
Aucun de ces modules ne doit appeler :
```c
investigation_node_free(node);
```
---
## Fichiers concernés
```text
include/widgets/workspace.h
src/widgets/workspace.c
include/views/main_window.h
src/views/main_window.c
src/core/application.c
```
Aucune modification du Core métier n'est nécessaire.
---
## Interfaces publiques à ajouter
### Workspace
```c
void workspace_set_selected_node(
Workspace *workspace,
const InvestigationNode *node
);
```
### MainWindow
```c
void main_window_set_selected_node(
MainWindow *main_window,
const InvestigationNode *node
);
```
---
## Comportement attendu
### Aucun nœud sélectionné
```text
Labfy Investigation
Aucune enquête ouverte
Sélectionnez ou créez une enquête
```
### Dossier sélectionné
```text
00_BaseDeDonnees
Type : dossier
Parent : Template
Enfants : 2
```
### Fichier sélectionné
```text
Enquete.sqlite
Type : fichier
Parent : 00_BaseDeDonnees
```
---
## Hors périmètre
Ce ticket ne doit pas :
- ouvrir le contenu d'un fichier ;
- afficher un aperçu ;
- lire les métadonnées du système de fichiers ;
- afficher la taille d'un fichier ;
- communiquer avec SQLite ;
- modifier l'arborescence ;
- gérer plusieurs sélections.
---
## Contraintes techniques
- C17 ;
- GTK4 uniquement ;
- aucun état global ;
- aucune propriété du nœud transférée au `Workspace` ;
- documentation Doxygen ;
- compilation sans warning ;
- aucun `Gtk-CRITICAL`.
---
## Critères d'acceptation
- [ ] Le projet compile sans warning.
- [ ] `make test` reste valide.
- [ ] La sélection d'un dossier met à jour le `Workspace`.
- [ ] La sélection d'un fichier met à jour le `Workspace`.
- [ ] Le nom est correct.
- [ ] Le type est correct.
- [ ] Le parent est correct.
- [ ] Le nombre d'enfants est correct pour un dossier.
- [ ] Une sélection `NULL` restaure la page d'accueil.
- [ ] Aucun module graphique ne libère le nœud.
- [ ] Aucun `Gtk-CRITICAL`.
---
## Tests manuels
1. Ouvrir une enquête.
2. Sélectionner un dossier.
3. Vérifier les informations affichées.
4. Sélectionner un fichier.
5. Vérifier les informations affichées.
6. Changer d'enquête.
7. Vérifier que l'ancien nœud n'est plus affiché.
8. Lancer `make test`.
---
## Commit attendu
```text
feat(gui): display selected node in workspace
```

View file

@ -8,6 +8,7 @@
#include "core/investigation_tree_model.h"
#include "widgets/investigation_tree_view.h"
#include "core/investigation_node.h"
#include <gtk/gtk.h>
@ -63,6 +64,19 @@ void main_window_set_tree_selection_callback(
gpointer user_data
);
/**
* @brief Affiche dans le Workspace les informations du nœud sélectionné.
*
* La fenêtre principale ne devient pas propriétaire du nœud.
*
* @param main_window Fenêtre principale à mettre à jour.
* @param node Nœud sélectionné en lecture seule, ou NULL.
*/
void main_window_set_selected_node(
MainWindow *main_window,
const InvestigationNode *node
);
/**
* @brief Libère les ressources de la fenêtre.
*

View file

@ -6,6 +6,9 @@
#ifndef LABFY_INVESTIGATION_WORKSPACE_H
#define LABFY_INVESTIGATION_WORKSPACE_H
#include "core/investigation_node.h"
#include <gtk/gtk.h>
/**
@ -37,6 +40,22 @@ GtkWidget *workspace_get_widget(
const Workspace *workspace
);
/**
* @brief Affiche les informations du nœud sélectionné.
*
* Le Workspace ne devient pas propriétaire du nœud.
* Le nœud doit rester valide pendant toute la durée de son affichage.
*
* Passer NULL restaure la page d'accueil.
*
* @param workspace Zone de travail à mettre à jour.
* @param node Nœud sélectionné en lecture seule, ou NULL.
*/
void workspace_set_selected_node(
Workspace *workspace,
const InvestigationNode *node
);
/**
* @brief Libère la structure d'encapsulation de la zone de travail.
*

Binary file not shown.

View file

@ -129,6 +129,11 @@ static void application_on_tree_node_selected(
return;
}
main_window_set_selected_node(
application->main_window,
node
);
if (node == NULL)
{
g_print("Aucun nœud sélectionné.\n");

View file

@ -303,6 +303,22 @@ void main_window_set_tree_selection_callback(
);
}
void main_window_set_selected_node(
MainWindow *main_window,
const InvestigationNode *node
)
{
if (main_window == NULL)
{
return;
}
workspace_set_selected_node(
main_window->workspace,
node
);
}
void main_window_free(MainWindow *main_window)
{
if (main_window == NULL)

View file

@ -21,8 +21,34 @@ struct Workspace
GtkWidget *title_label;
GtkWidget *status_label;
GtkWidget *instruction_label;
GtkWidget *node_box;
GtkWidget *node_name_label;
GtkWidget *node_type_label;
GtkWidget *node_parent_label;
GtkWidget *node_children_label;
};
static void workspace_show_welcome(
Workspace *workspace
)
{
if (workspace == NULL)
{
return;
}
gtk_widget_set_visible(
workspace->welcome_box,
TRUE
);
gtk_widget_set_visible(
workspace->node_box,
FALSE
);
}
Workspace *workspace_new(void)
{
Workspace *workspace = NULL;
@ -102,11 +128,64 @@ Workspace *workspace_new(void)
workspace->instruction_label
);
workspace->node_box = gtk_box_new(
GTK_ORIENTATION_VERTICAL,
12
);
if (workspace->node_box == NULL)
{
workspace_free(workspace);
return NULL;
}
gtk_widget_set_halign(
workspace->node_box,
GTK_ALIGN_CENTER
);
gtk_widget_set_valign(
workspace->node_box,
GTK_ALIGN_CENTER
);
workspace->node_name_label = gtk_label_new(NULL);
workspace->node_type_label = gtk_label_new(NULL);
workspace->node_parent_label = gtk_label_new(NULL);
workspace->node_children_label = gtk_label_new(NULL);
gtk_box_append(
GTK_BOX(workspace->node_box),
workspace->node_name_label
);
gtk_box_append(
GTK_BOX(workspace->node_box),
workspace->node_type_label
);
gtk_box_append(
GTK_BOX(workspace->node_box),
workspace->node_parent_label
);
gtk_box_append(
GTK_BOX(workspace->node_box),
workspace->node_children_label
);
gtk_box_append(
GTK_BOX(workspace->root_widget),
workspace->welcome_box
);
gtk_box_append(
GTK_BOX(workspace->root_widget),
workspace->node_box
);
workspace_show_welcome(workspace);
return workspace;
}
@ -122,6 +201,122 @@ GtkWidget *workspace_get_widget(
return workspace->root_widget;
}
void workspace_set_selected_node(
Workspace *workspace,
const InvestigationNode *node
)
{
const char *node_name = NULL;
const InvestigationNode *parent_node = NULL;
const char *parent_name = NULL;
InvestigationNodeType node_type;
size_t children_count = 0;
char *type_text = NULL;
char *parent_text = NULL;
char *children_text = NULL;
if (workspace == NULL)
{
return;
}
if (node == NULL)
{
workspace_show_welcome(workspace);
return;
}
node_name = investigation_node_get_name(node);
node_type = investigation_node_get_type(node);
parent_node = investigation_node_get_parent(node);
gtk_label_set_text(
GTK_LABEL(workspace->node_name_label),
node_name != NULL ? node_name : "(sans nom)"
);
if (node_type == INVESTIGATION_NODE_DIRECTORY)
{
type_text = g_strdup("Type : dossier");
}
else
{
type_text = g_strdup("Type : fichier");
}
gtk_label_set_text(
GTK_LABEL(workspace->node_type_label),
type_text
);
if (parent_node != NULL)
{
parent_name = investigation_node_get_name(parent_node);
parent_text = g_strdup_printf(
"Parent : %s",
parent_name != NULL ? parent_name : "(sans nom)"
);
}
else
{
parent_text = g_strdup("Parent : aucun");
}
gtk_label_set_text(
GTK_LABEL(workspace->node_parent_label),
parent_text
);
if (node_type == INVESTIGATION_NODE_DIRECTORY)
{
children_count =
investigation_node_get_children_count(node);
children_text = g_strdup_printf(
"Enfants : %zu",
children_count
);
gtk_label_set_text(
GTK_LABEL(workspace->node_children_label),
children_text
);
gtk_widget_set_visible(
workspace->node_children_label,
TRUE
);
}
else
{
gtk_label_set_text(
GTK_LABEL(workspace->node_children_label),
""
);
gtk_widget_set_visible(
workspace->node_children_label,
FALSE
);
}
gtk_widget_set_visible(
workspace->welcome_box,
FALSE
);
gtk_widget_set_visible(
workspace->node_box,
TRUE
);
g_free(children_text);
g_free(parent_text);
g_free(type_text);
}
void workspace_free(Workspace *workspace)
{
if (workspace == NULL)