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 0000000..6b47849 Binary files /dev/null and b/labfy-investigation/labfy-investigation differ 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); +}