refactor(gui): use GtkStack in workspace

This commit is contained in:
grayTerminal-sh 2026-07-14 14:34:10 +02:00
parent ecac36fd8d
commit 33014a5316
3 changed files with 373 additions and 67 deletions

View file

@ -0,0 +1,289 @@
# Ticket #016
## Titre
Structurer le `Workspace` avec `GtkStack`.
---
## Objectif
Remplacer la gestion manuelle de visibilité des pages du `Workspace` par un
`GtkStack`.
Le `Workspace` doit pouvoir afficher proprement plusieurs pages internes sans
modifier son API publique actuelle.
Ce ticket prépare l'ajout futur de nouvelles vues :
- aperçu texte ;
- aperçu image ;
- aperçu PDF ;
- chronologie ;
- fiches d'entités ;
- rapports.
---
## Architecture
```text
Workspace
└── GtkStack
├── WelcomePage
└── NodeInformationPage
```
Plus tard :
```text
Workspace
└── GtkStack
├── WelcomePage
├── NodeInformationPage
├── TextPreviewPage
├── ImagePreviewPage
├── PdfPreviewPage
└── EntityPage
```
Le `Workspace` reste responsable de son affichage interne.
---
## Responsabilités
Le module `Workspace` doit :
- créer un `GtkStack` comme widget racine interne ;
- ajouter une page d'accueil ;
- ajouter une page d'informations sur le nœud sélectionné ;
- afficher la page d'accueil lorsqu'aucun nœud n'est sélectionné ;
- afficher la page d'informations lorsqu'un nœud est sélectionné ;
- conserver l'API publique existante.
---
## API publique conservée
Aucune nouvelle fonction publique n'est requise.
L'API reste :
```c
Workspace *workspace_new(void);
GtkWidget *workspace_get_widget(
const Workspace *workspace
);
void workspace_set_selected_node(
Workspace *workspace,
const InvestigationNode *node
);
void workspace_free(
Workspace *workspace
);
```
---
## Fichiers concernés
```text
src/widgets/workspace.c
```
Éventuellement :
```text
include/widgets/workspace.h
```
uniquement pour mettre à jour la documentation.
Aucun autre module ne doit être modifié.
---
## Structure interne attendue
```c
struct Workspace
{
GtkWidget *root_widget;
GtkWidget *stack;
GtkWidget *welcome_page;
GtkWidget *welcome_title_label;
GtkWidget *welcome_status_label;
GtkWidget *welcome_instruction_label;
GtkWidget *node_page;
GtkWidget *node_name_label;
GtkWidget *node_type_label;
GtkWidget *node_parent_label;
GtkWidget *node_children_label;
};
```
Les noms exacts peuvent varier, mais doivent rester explicites.
---
## Pages attendues
### Page d'accueil
Nom interne :
```text
welcome
```
Contenu :
```text
Labfy Investigation
Aucune enquête ouverte
Sélectionnez ou créez une enquête
```
### Page d'informations
Nom interne :
```text
node-information
```
Contenu pour un dossier :
```text
00_BaseDeDonnees
Type : dossier
Parent : Template
Enfants : 2
```
Contenu pour un fichier :
```text
Enquete.sqlite
Type : fichier
Parent : 00_BaseDeDonnees
```
---
## Comportement attendu
Lorsque :
```c
workspace_set_selected_node(workspace, NULL);
```
le `GtkStack` doit afficher :
```text
welcome
```
Lorsqu'un nœud valide est transmis :
```c
workspace_set_selected_node(workspace, node);
```
le `GtkStack` doit afficher :
```text
node-information
```
---
## Hors périmètre
Ce ticket ne doit pas :
- ouvrir un fichier ;
- afficher son contenu ;
- ajouter une barre de navigation ;
- ajouter une animation personnalisée ;
- afficher plusieurs nœuds simultanément ;
- modifier le Core ;
- communiquer avec SQLite.
---
## Contraintes techniques
- GTK4 uniquement ;
- utiliser `GtkStack` ;
- aucune gestion manuelle de visibilité entre les pages ;
- aucun état global ;
- aucun changement de propriété des nœuds ;
- documentation cohérente ;
- compilation sans warning ;
- aucun `Gtk-CRITICAL`.
---
## Gestion de la propriété
Le `Workspace` possède uniquement sa structure d'encapsulation.
Les widgets sont gérés par l'arbre GTK une fois intégrés dans la fenêtre.
Le nœud reçu reste la propriété du `InvestigationTreeModel`.
Le `Workspace` ne doit jamais appeler :
```c
investigation_node_free(node);
```
---
## Critères d'acceptation
- [ ] Le projet compile sans warning.
- [ ] `make test` reste entièrement valide.
- [ ] `GtkStack` est utilisé.
- [ ] La page d'accueil apparaît sans sélection.
- [ ] La page d'informations apparaît après sélection.
- [ ] Le passage d'une page à l'autre fonctionne.
- [ ] Aucun appel manuel à `gtk_widget_set_visible()` n'est utilisé pour changer de page.
- [ ] L'API publique de `Workspace` reste stable.
- [ ] Aucun autre module n'est modifié.
- [ ] Aucun `Gtk-CRITICAL`.
---
## Tests manuels
1. Lancer l'application.
2. Ouvrir une enquête.
3. Vérifier que la page d'accueil est affichée sans sélection.
4. Sélectionner un dossier.
5. Vérifier l'affichage de la page d'informations.
6. Sélectionner un fichier.
7. Vérifier la mise à jour de la même page.
8. Changer d'enquête.
9. Vérifier le retour à la page d'accueil.
10. Lancer `make test`.
---
## Commit attendu
```text
refactor(gui): use GtkStack in workspace
```

Binary file not shown.

View file

@ -7,28 +7,33 @@
#include <glib.h> #include <glib.h>
#define WORKSPACE_PAGE_WELCOME "welcome"
#define WORKSPACE_PAGE_NODE_INFORMATION "node-information"
/** /**
* @struct Workspace * @struct Workspace
* @brief Représentation interne de la zone de travail. * @brief Représentation interne de la zone de travail.
*
* Cette structure reste privée au module. Les autres fichiers utilisent
* uniquement le type opaque déclaré dans workspace.h.
*/ */
struct Workspace struct Workspace
{ {
GtkWidget *root_widget; GtkWidget *root_widget;
GtkWidget *welcome_box; GtkWidget *stack;
GtkWidget *title_label;
GtkWidget *status_label;
GtkWidget *instruction_label;
GtkWidget *node_box; GtkWidget *welcome_page;
GtkWidget *welcome_title_label;
GtkWidget *welcome_status_label;
GtkWidget *welcome_instruction_label;
GtkWidget *node_page;
GtkWidget *node_name_label; GtkWidget *node_name_label;
GtkWidget *node_type_label; GtkWidget *node_type_label;
GtkWidget *node_parent_label; GtkWidget *node_parent_label;
GtkWidget *node_children_label; GtkWidget *node_children_label;
}; };
/**
* @brief Affiche la page d'accueil.
*/
static void workspace_show_welcome( static void workspace_show_welcome(
Workspace *workspace Workspace *workspace
) )
@ -38,14 +43,9 @@ static void workspace_show_welcome(
return; return;
} }
gtk_widget_set_visible( gtk_stack_set_visible_child_name(
workspace->welcome_box, GTK_STACK(workspace->stack),
TRUE WORKSPACE_PAGE_WELCOME
);
gtk_widget_set_visible(
workspace->node_box,
FALSE
); );
} }
@ -76,76 +76,107 @@ Workspace *workspace_new(void)
TRUE TRUE
); );
workspace->welcome_box = gtk_box_new( workspace->stack = gtk_stack_new();
GTK_ORIENTATION_VERTICAL,
12
);
if (workspace->welcome_box == NULL) if (workspace->stack == NULL)
{ {
workspace_free(workspace); workspace_free(workspace);
return NULL; return NULL;
} }
gtk_widget_set_hexpand(
workspace->stack,
TRUE
);
gtk_widget_set_vexpand(
workspace->stack,
TRUE
);
gtk_box_append(
GTK_BOX(workspace->root_widget),
workspace->stack
);
/* /*
* Le conteneur d'accueil est centré horizontalement et verticalement * Page d'accueil.
* dans l'espace disponible.
*/ */
workspace->welcome_page = gtk_box_new(
GTK_ORIENTATION_VERTICAL,
12
);
if (workspace->welcome_page == NULL)
{
workspace_free(workspace);
return NULL;
}
gtk_widget_set_halign( gtk_widget_set_halign(
workspace->welcome_box, workspace->welcome_page,
GTK_ALIGN_CENTER GTK_ALIGN_CENTER
); );
gtk_widget_set_valign( gtk_widget_set_valign(
workspace->welcome_box, workspace->welcome_page,
GTK_ALIGN_CENTER GTK_ALIGN_CENTER
); );
workspace->title_label = gtk_label_new( workspace->welcome_title_label = gtk_label_new(
"Labfy Investigation" "Labfy Investigation"
); );
workspace->status_label = gtk_label_new( workspace->welcome_status_label = gtk_label_new(
"Aucune enquête ouverte" "Aucune enquête ouverte"
); );
workspace->instruction_label = gtk_label_new( workspace->welcome_instruction_label = gtk_label_new(
"Sélectionnez ou créez une enquête" "Sélectionnez ou créez une enquête"
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->welcome_box), GTK_BOX(workspace->welcome_page),
workspace->title_label workspace->welcome_title_label
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->welcome_box), GTK_BOX(workspace->welcome_page),
workspace->status_label workspace->welcome_status_label
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->welcome_box), GTK_BOX(workspace->welcome_page),
workspace->instruction_label workspace->welcome_instruction_label
); );
workspace->node_box = gtk_box_new( gtk_stack_add_named(
GTK_STACK(workspace->stack),
workspace->welcome_page,
WORKSPACE_PAGE_WELCOME
);
/*
* Page d'informations sur le nœud.
*/
workspace->node_page = gtk_box_new(
GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
12 12
); );
if (workspace->node_box == NULL) if (workspace->node_page == NULL)
{ {
workspace_free(workspace); workspace_free(workspace);
return NULL; return NULL;
} }
gtk_widget_set_halign( gtk_widget_set_halign(
workspace->node_box, workspace->node_page,
GTK_ALIGN_CENTER GTK_ALIGN_CENTER
); );
gtk_widget_set_valign( gtk_widget_set_valign(
workspace->node_box, workspace->node_page,
GTK_ALIGN_CENTER GTK_ALIGN_CENTER
); );
@ -155,33 +186,29 @@ Workspace *workspace_new(void)
workspace->node_children_label = gtk_label_new(NULL); workspace->node_children_label = gtk_label_new(NULL);
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->node_box), GTK_BOX(workspace->node_page),
workspace->node_name_label workspace->node_name_label
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->node_box), GTK_BOX(workspace->node_page),
workspace->node_type_label workspace->node_type_label
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->node_box), GTK_BOX(workspace->node_page),
workspace->node_parent_label workspace->node_parent_label
); );
gtk_box_append( gtk_box_append(
GTK_BOX(workspace->node_box), GTK_BOX(workspace->node_page),
workspace->node_children_label workspace->node_children_label
); );
gtk_box_append( gtk_stack_add_named(
GTK_BOX(workspace->root_widget), GTK_STACK(workspace->stack),
workspace->welcome_box workspace->node_page,
); WORKSPACE_PAGE_NODE_INFORMATION
gtk_box_append(
GTK_BOX(workspace->root_widget),
workspace->node_box
); );
workspace_show_welcome(workspace); workspace_show_welcome(workspace);
@ -212,7 +239,7 @@ void workspace_set_selected_node(
InvestigationNodeType node_type; InvestigationNodeType node_type;
size_t children_count = 0; size_t children_count = 0;
char *type_text = NULL; const char *type_text = NULL;
char *parent_text = NULL; char *parent_text = NULL;
char *children_text = NULL; char *children_text = NULL;
@ -238,11 +265,11 @@ void workspace_set_selected_node(
if (node_type == INVESTIGATION_NODE_DIRECTORY) if (node_type == INVESTIGATION_NODE_DIRECTORY)
{ {
type_text = g_strdup("Type : dossier"); type_text = "Type : dossier";
} }
else else
{ {
type_text = g_strdup("Type : fichier"); type_text = "Type : fichier";
} }
gtk_label_set_text( gtk_label_set_text(
@ -302,19 +329,13 @@ void workspace_set_selected_node(
); );
} }
gtk_widget_set_visible( gtk_stack_set_visible_child_name(
workspace->welcome_box, GTK_STACK(workspace->stack),
FALSE WORKSPACE_PAGE_NODE_INFORMATION
);
gtk_widget_set_visible(
workspace->node_box,
TRUE
); );
g_free(children_text); g_free(children_text);
g_free(parent_text); g_free(parent_text);
g_free(type_text);
} }
void workspace_free(Workspace *workspace) void workspace_free(Workspace *workspace)
@ -324,9 +345,5 @@ void workspace_free(Workspace *workspace)
return; return;
} }
/*
* Les widgets GTK sont intégrés dans l'arbre de widgets de MainWindow.
* GTK gère donc leur destruction.
*/
g_free(workspace); g_free(workspace);
} }