From d16e0229c455e8189ac50382a72157142d5cc247 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Sun, 12 Jul 2026 22:20:00 +0200 Subject: [PATCH] feat(widget): create navigation sidebar --- docs/tickets/closed/TICKET-005.md | 151 +++++++++++++++++ labfy-investigation/include/widgets/sidebar.h | 49 ++++++ labfy-investigation/labfy-investigation | Bin 0 -> 46728 bytes labfy-investigation/src/views/main_window.c | 152 ++++++++++++++++-- labfy-investigation/src/widgets/sidebar.c | 113 +++++++++++++ 5 files changed, 451 insertions(+), 14 deletions(-) create mode 100644 docs/tickets/closed/TICKET-005.md create mode 100644 labfy-investigation/include/widgets/sidebar.h create mode 100755 labfy-investigation/labfy-investigation create mode 100644 labfy-investigation/src/widgets/sidebar.c diff --git a/docs/tickets/closed/TICKET-005.md b/docs/tickets/closed/TICKET-005.md new file mode 100644 index 0000000..6ea9dff --- /dev/null +++ b/docs/tickets/closed/TICKET-005.md @@ -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 +``` diff --git a/labfy-investigation/include/widgets/sidebar.h b/labfy-investigation/include/widgets/sidebar.h new file mode 100644 index 0000000..65d08b7 --- /dev/null +++ b/labfy-investigation/include/widgets/sidebar.h @@ -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 + +/** + * @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 diff --git a/labfy-investigation/labfy-investigation b/labfy-investigation/labfy-investigation new file mode 100755 index 0000000000000000000000000000000000000000..6b47849d4537181c5aca9be1c7f8489d77a5f4a0 GIT binary patch literal 46728 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 literal 0 HcmV?d00001 diff --git a/labfy-investigation/src/views/main_window.c b/labfy-investigation/src/views/main_window.c index 5f3edaa..bb68b47 100644 --- a/labfy-investigation/src/views/main_window.c +++ b/labfy-investigation/src/views/main_window.c @@ -4,6 +4,7 @@ ******************************************************************************/ #include "views/main_window.h" +#include "widgets/sidebar.h" #include @@ -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); } diff --git a/labfy-investigation/src/widgets/sidebar.c b/labfy-investigation/src/widgets/sidebar.c new file mode 100644 index 0000000..b8ed4a2 --- /dev/null +++ b/labfy-investigation/src/widgets/sidebar.c @@ -0,0 +1,113 @@ +/****************************************************************************** + * @file sidebar.c + * @brief Implémentation du panneau de navigation latéral. + ******************************************************************************/ + +#include "widgets/sidebar.h" + +#include + +/** + * @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); +}