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 "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 été 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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