feat(widget): create navigation sidebar
This commit is contained in:
parent
a8e66c40be
commit
d16e0229c4
5 changed files with 451 additions and 14 deletions
151
docs/tickets/closed/TICKET-005.md
Normal file
151
docs/tickets/closed/TICKET-005.md
Normal 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
|
||||
```
|
||||
49
labfy-investigation/include/widgets/sidebar.h
Normal file
49
labfy-investigation/include/widgets/sidebar.h
Normal 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
|
||||
BIN
labfy-investigation/labfy-investigation
Executable file
BIN
labfy-investigation/labfy-investigation
Executable file
Binary file not shown.
|
|
@ -4,6 +4,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "views/main_window.h"
|
||||
#include "widgets/sidebar.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
|
@ -17,24 +18,32 @@
|
|||
*/
|
||||
#define MAIN_WINDOW_DEFAULT_HEIGHT 650
|
||||
|
||||
/**
|
||||
* @brief Position initiale de la séparation horizontale.
|
||||
*/
|
||||
#define MAIN_WINDOW_SIDEBAR_POSITION 250
|
||||
|
||||
/**
|
||||
* @struct MainWindow
|
||||
* @brief Représentation interne de la fenêtre principale.
|
||||
*
|
||||
* Cette structure reste privée au module. Les autres fichiers utilisent
|
||||
* uniquement le type opaque déclaré dans main_window.h.
|
||||
* Cette structure reste privée au module. Elle conserve les composants
|
||||
* nécessaires à l'organisation générale de l'interface.
|
||||
*/
|
||||
struct MainWindow
|
||||
{
|
||||
GtkWindow *window;
|
||||
GtkWidget *main_box;
|
||||
GtkWidget *main_paned;
|
||||
GtkWidget *workspace;
|
||||
GtkWidget *status_label;
|
||||
Sidebar *sidebar;
|
||||
};
|
||||
|
||||
MainWindow *main_window_new(GtkApplication *application)
|
||||
{
|
||||
MainWindow *main_window = NULL;
|
||||
GtkWidget *main_box = NULL;
|
||||
GtkWidget *workspace = NULL;
|
||||
GtkWidget *sidebar_widget = NULL;
|
||||
|
||||
if (application == NULL)
|
||||
{
|
||||
|
|
@ -64,18 +73,124 @@ MainWindow *main_window_new(GtkApplication *application)
|
|||
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,
|
||||
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,
|
||||
0
|
||||
);
|
||||
|
||||
gtk_widget_set_hexpand(workspace, TRUE);
|
||||
gtk_widget_set_vexpand(workspace, TRUE);
|
||||
gtk_widget_set_hexpand(
|
||||
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(
|
||||
"Aucune enquête ouverte"
|
||||
|
|
@ -106,19 +221,25 @@ MainWindow *main_window_new(GtkApplication *application)
|
|||
6
|
||||
);
|
||||
|
||||
/*
|
||||
* Assemblage vertical :
|
||||
*
|
||||
* GtkPaned
|
||||
* Barre d'état
|
||||
*/
|
||||
gtk_box_append(
|
||||
GTK_BOX(main_box),
|
||||
workspace
|
||||
GTK_BOX(main_window->main_box),
|
||||
main_window->main_paned
|
||||
);
|
||||
|
||||
gtk_box_append(
|
||||
GTK_BOX(main_box),
|
||||
GTK_BOX(main_window->main_box),
|
||||
main_window->status_label
|
||||
);
|
||||
|
||||
gtk_window_set_child(
|
||||
main_window->window,
|
||||
main_box
|
||||
main_window->main_box
|
||||
);
|
||||
|
||||
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.
|
||||
* MainWindow ne possède que sa structure d'encapsulation.
|
||||
* La structure Sidebar a été allouée par sidebar_new().
|
||||
* 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);
|
||||
}
|
||||
|
|
|
|||
113
labfy-investigation/src/widgets/sidebar.c
Normal file
113
labfy-investigation/src/widgets/sidebar.c
Normal 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);
|
||||
}
|
||||
Loading…
Reference in a new issue