feat(widget): create navigation sidebar

This commit is contained in:
grayTerminal-sh 2026-07-12 22:20:00 +02:00
parent a8e66c40be
commit d16e0229c4
5 changed files with 451 additions and 14 deletions

View file

@ -0,0 +1,151 @@
# Ticket #005
## Titre
Créer le panneau de navigation latéral (`Sidebar`).
---
## Objectif
Créer la structure principale de l'interface avec :
- un panneau latéral à gauche ;
- une zone de travail à droite ;
- une séparation redimensionnable entre les deux zones.
Le panneau latéral servira plus tard à afficher l'arborescence du dossier d'enquête.
---
## Responsabilités
Le module `Sidebar` doit :
- créer un panneau GTK réutilisable ;
- posséder une largeur initiale raisonnable ;
- afficher temporairement un titre ;
- exposer son widget racine en lecture seule.
Le module `MainWindow` doit :
- intégrer le panneau latéral ;
- conserver la zone de travail principale ;
- permettre le redimensionnement des deux zones.
---
## Hors périmètre
Ce ticket ne doit pas :
- afficher l'arborescence réelle des fichiers ;
- lire le contenu d'un dossier ;
- surveiller les changements du système de fichiers ;
- créer ou supprimer des fichiers ;
- ajouter le bouton permettant de masquer le panneau ;
- communiquer avec SQLite.
---
## Architecture
```text
MainWindow
├── Sidebar
├── Workspace
└── StatusLabel
```
Le module `Sidebar` appartient à la couche `widgets`.
Le module ne contient aucune logique métier.
---
## Fichiers concernés
```text
include/widgets/sidebar.h
src/widgets/sidebar.c
include/views/main_window.h
src/views/main_window.c
```
---
## Interface publique attendue
```c
Sidebar *sidebar_new(void);
GtkWidget *sidebar_get_widget(
const Sidebar *sidebar
);
void sidebar_free(Sidebar *sidebar);
```
---
## Interface graphique attendue
```text
┌───────────────────────┬──────────────────────────────────────┐
│ Dossier d'enquête │ │
│ │ │
│ │ │
│ │ Zone de travail │
│ │ │
│ │ │
├───────────────────────┴──────────────────────────────────────┤
│ Aucune enquête ouverte │
└──────────────────────────────────────────────────────────────┘
```
La séparation entre le panneau gauche et la zone centrale doit pouvoir être déplacée horizontalement.
---
## Contraintes techniques
- utiliser GTK4 ;
- utiliser un `GtkPaned` horizontal dans `MainWindow` ;
- ne pas utiliser de variable globale ;
- conserver une structure `Sidebar` opaque ;
- respecter les conventions de nommage du projet ;
- compiler en C17 sans warning.
---
## Critères d'acceptation
- [ ] Le projet compile sans warning.
- [ ] Le panneau latéral apparaît à gauche.
- [ ] La zone de travail apparaît à droite.
- [ ] La séparation peut être déplacée avec la souris.
- [ ] Le panneau est créé par le module `Sidebar`.
- [ ] `MainWindow` ne construit pas directement le contenu interne du panneau.
- [ ] Aucun état global.
- [ ] Les fonctions publiques sont documentées avec Doxygen.
- [ ] La fermeture de l'application ne provoque aucune erreur GTK.
---
## Tests
- lancer l'application ;
- sélectionner un dossier ;
- déplacer la séparation ;
- agrandir et réduire la fenêtre ;
- fermer l'application ;
- vérifier l'absence de warning ou de message critique.
---
## Commit attendu
```text
feat(widget): create navigation sidebar
```

View file

@ -0,0 +1,49 @@
/******************************************************************************
* @file sidebar.h
* @brief Interface publique du panneau de navigation latéral.
******************************************************************************/
#ifndef LABFY_INVESTIGATION_SIDEBAR_H
#define LABFY_INVESTIGATION_SIDEBAR_H
#include <gtk/gtk.h>
/**
* @brief Représentation opaque du panneau latéral.
*
* La structure réelle est définie dans sidebar.c.
* Les autres modules manipulent uniquement un pointeur vers Sidebar.
*/
typedef struct Sidebar Sidebar;
/**
* @brief Crée un nouveau panneau de navigation latéral.
*
* @return Un nouveau panneau latéral, ou NULL en cas d'échec.
*/
Sidebar *sidebar_new(void);
/**
* @brief Retourne le widget GTK racine du panneau latéral.
*
* Le widget retourné appartient au module Sidebar et ne doit pas être libéré
* directement par le code appelant.
*
* @param sidebar Panneau latéral à consulter.
*
* @return Le widget GTK racine, ou NULL si sidebar est NULL.
*/
GtkWidget *sidebar_get_widget(
const Sidebar *sidebar
);
/**
* @brief Libère la structure d'encapsulation du panneau latéral.
*
* Cette fonction accepte NULL.
*
* @param sidebar Panneau latéral à libérer.
*/
void sidebar_free(Sidebar *sidebar);
#endif

Binary file not shown.

View file

@ -4,6 +4,7 @@
******************************************************************************/ ******************************************************************************/
#include "views/main_window.h" #include "views/main_window.h"
#include "widgets/sidebar.h"
#include <glib.h> #include <glib.h>
@ -17,24 +18,32 @@
*/ */
#define MAIN_WINDOW_DEFAULT_HEIGHT 650 #define MAIN_WINDOW_DEFAULT_HEIGHT 650
/**
* @brief Position initiale de la séparation horizontale.
*/
#define MAIN_WINDOW_SIDEBAR_POSITION 250
/** /**
* @struct MainWindow * @struct MainWindow
* @brief Représentation interne de la fenêtre principale. * @brief Représentation interne de la fenêtre principale.
* *
* Cette structure reste privée au module. Les autres fichiers utilisent * Cette structure reste privée au module. Elle conserve les composants
* uniquement le type opaque déclaré dans main_window.h. * nécessaires à l'organisation générale de l'interface.
*/ */
struct MainWindow struct MainWindow
{ {
GtkWindow *window; GtkWindow *window;
GtkWidget *main_box;
GtkWidget *main_paned;
GtkWidget *workspace;
GtkWidget *status_label; GtkWidget *status_label;
Sidebar *sidebar;
}; };
MainWindow *main_window_new(GtkApplication *application) MainWindow *main_window_new(GtkApplication *application)
{ {
MainWindow *main_window = NULL; MainWindow *main_window = NULL;
GtkWidget *main_box = NULL; GtkWidget *sidebar_widget = NULL;
GtkWidget *workspace = NULL;
if (application == NULL) if (application == NULL)
{ {
@ -64,18 +73,124 @@ MainWindow *main_window_new(GtkApplication *application)
MAIN_WINDOW_DEFAULT_HEIGHT MAIN_WINDOW_DEFAULT_HEIGHT
); );
main_box = gtk_box_new( /*
* La boîte principale organise verticalement :
*
* 1. la zone centrale ;
* 2. la barre d'état.
*/
main_window->main_box = gtk_box_new(
GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
0 0
); );
workspace = gtk_box_new( /*
* GtkPaned sépare horizontalement le panneau latéral
* et la zone de travail.
*/
main_window->main_paned = gtk_paned_new(
GTK_ORIENTATION_HORIZONTAL
);
gtk_widget_set_hexpand(
main_window->main_paned,
TRUE
);
gtk_widget_set_vexpand(
main_window->main_paned,
TRUE
);
/*
* Création du panneau latéral via son propre module.
*
* MainWindow ne connaît pas son contenu interne.
*/
main_window->sidebar = sidebar_new();
if (main_window->sidebar == NULL)
{
main_window_free(main_window);
return NULL;
}
sidebar_widget = sidebar_get_widget(
main_window->sidebar
);
if (sidebar_widget == NULL)
{
main_window_free(main_window);
return NULL;
}
/*
* La zone de travail est encore vide.
* Elle accueillera plus tard les pages de l'application.
*/
main_window->workspace = gtk_box_new(
GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
0 0
); );
gtk_widget_set_hexpand(workspace, TRUE); gtk_widget_set_hexpand(
gtk_widget_set_vexpand(workspace, TRUE); main_window->workspace,
TRUE
);
gtk_widget_set_vexpand(
main_window->workspace,
TRUE
);
/*
* Placement des deux composants dans GtkPaned.
*/
gtk_paned_set_start_child(
GTK_PANED(main_window->main_paned),
sidebar_widget
);
gtk_paned_set_end_child(
GTK_PANED(main_window->main_paned),
main_window->workspace
);
/*
* Position initiale de la poignée de séparation.
*/
gtk_paned_set_position(
GTK_PANED(main_window->main_paned),
MAIN_WINDOW_SIDEBAR_POSITION
);
/*
* La sidebar conserve sa largeur lorsque la fenêtre est agrandie.
* La zone de travail récupère l'espace supplémentaire.
*/
gtk_paned_set_resize_start_child(
GTK_PANED(main_window->main_paned),
FALSE
);
gtk_paned_set_resize_end_child(
GTK_PANED(main_window->main_paned),
TRUE
);
/*
* Les deux panneaux peuvent être réduits manuellement.
*/
gtk_paned_set_shrink_start_child(
GTK_PANED(main_window->main_paned),
TRUE
);
gtk_paned_set_shrink_end_child(
GTK_PANED(main_window->main_paned),
TRUE
);
main_window->status_label = gtk_label_new( main_window->status_label = gtk_label_new(
"Aucune enquête ouverte" "Aucune enquête ouverte"
@ -106,19 +221,25 @@ MainWindow *main_window_new(GtkApplication *application)
6 6
); );
/*
* Assemblage vertical :
*
* GtkPaned
* Barre d'état
*/
gtk_box_append( gtk_box_append(
GTK_BOX(main_box), GTK_BOX(main_window->main_box),
workspace main_window->main_paned
); );
gtk_box_append( gtk_box_append(
GTK_BOX(main_box), GTK_BOX(main_window->main_box),
main_window->status_label main_window->status_label
); );
gtk_window_set_child( gtk_window_set_child(
main_window->window, main_window->window,
main_box main_window->main_box
); );
return main_window; return main_window;
@ -154,9 +275,12 @@ void main_window_free(MainWindow *main_window)
} }
/* /*
* La fenêtre principale est possédée et détruite par GTK. * La structure Sidebar a é allouée par sidebar_new().
* MainWindow ne possède que sa structure d'encapsulation. * MainWindow en est donc propriétaire et doit la libérer.
*
* Les widgets GTK, eux, restent gérés par GTK.
*/ */
sidebar_free(main_window->sidebar);
g_free(main_window); g_free(main_window);
} }

View file

@ -0,0 +1,113 @@
/******************************************************************************
* @file sidebar.c
* @brief Implémentation du panneau de navigation latéral.
******************************************************************************/
#include "widgets/sidebar.h"
#include <glib.h>
/**
* @brief Largeur initiale du panneau latéral.
*/
#define SIDEBAR_DEFAULT_WIDTH 250
/**
* @struct Sidebar
* @brief Représentation interne du panneau latéral.
*
* Cette structure reste privée au module. Les autres fichiers utilisent
* uniquement le type opaque déclaré dans sidebar.h.
*/
struct Sidebar
{
GtkWidget *root_widget;
GtkWidget *title_label;
};
Sidebar *sidebar_new(void)
{
Sidebar *sidebar = NULL;
sidebar = g_new0(Sidebar, 1);
sidebar->root_widget = gtk_box_new(
GTK_ORIENTATION_VERTICAL,
0
);
if (sidebar->root_widget == NULL)
{
sidebar_free(sidebar);
return NULL;
}
gtk_widget_set_size_request(
sidebar->root_widget,
SIDEBAR_DEFAULT_WIDTH,
-1
);
sidebar->title_label = gtk_label_new(
"Dossier d'enquête"
);
gtk_widget_set_halign(
sidebar->title_label,
GTK_ALIGN_START
);
gtk_widget_set_margin_start(
sidebar->title_label,
12
);
gtk_widget_set_margin_end(
sidebar->title_label,
12
);
gtk_widget_set_margin_top(
sidebar->title_label,
12
);
gtk_widget_set_margin_bottom(
sidebar->title_label,
12
);
gtk_box_append(
GTK_BOX(sidebar->root_widget),
sidebar->title_label
);
return sidebar;
}
GtkWidget *sidebar_get_widget(
const Sidebar *sidebar
)
{
if (sidebar == NULL)
{
return NULL;
}
return sidebar->root_widget;
}
void sidebar_free(Sidebar *sidebar)
{
if (sidebar == NULL)
{
return;
}
/*
* 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.
* Nous libérons uniquement la structure Sidebar.
*/
g_free(sidebar);
}