diff --git a/docs/tickets/closed/TICKET-016.md b/docs/tickets/closed/TICKET-016.md new file mode 100644 index 0000000..945b6ae --- /dev/null +++ b/docs/tickets/closed/TICKET-016.md @@ -0,0 +1,289 @@ +# Ticket #016 + +## Titre + +Structurer le `Workspace` avec `GtkStack`. + +--- + +## Objectif + +Remplacer la gestion manuelle de visibilité des pages du `Workspace` par un +`GtkStack`. + +Le `Workspace` doit pouvoir afficher proprement plusieurs pages internes sans +modifier son API publique actuelle. + +Ce ticket prépare l'ajout futur de nouvelles vues : + +- aperçu texte ; +- aperçu image ; +- aperçu PDF ; +- chronologie ; +- fiches d'entités ; +- rapports. + +--- + +## Architecture + +```text +Workspace +└── GtkStack + ├── WelcomePage + └── NodeInformationPage +``` + +Plus tard : + +```text +Workspace +└── GtkStack + ├── WelcomePage + ├── NodeInformationPage + ├── TextPreviewPage + ├── ImagePreviewPage + ├── PdfPreviewPage + └── EntityPage +``` + +Le `Workspace` reste responsable de son affichage interne. + +--- + +## Responsabilités + +Le module `Workspace` doit : + +- créer un `GtkStack` comme widget racine interne ; +- ajouter une page d'accueil ; +- ajouter une page d'informations sur le nœud sélectionné ; +- afficher la page d'accueil lorsqu'aucun nœud n'est sélectionné ; +- afficher la page d'informations lorsqu'un nœud est sélectionné ; +- conserver l'API publique existante. + +--- + +## API publique conservée + +Aucune nouvelle fonction publique n'est requise. + +L'API reste : + +```c +Workspace *workspace_new(void); + +GtkWidget *workspace_get_widget( + const Workspace *workspace +); + +void workspace_set_selected_node( + Workspace *workspace, + const InvestigationNode *node +); + +void workspace_free( + Workspace *workspace +); +``` + +--- + +## Fichiers concernés + +```text +src/widgets/workspace.c +``` + +Éventuellement : + +```text +include/widgets/workspace.h +``` + +uniquement pour mettre à jour la documentation. + +Aucun autre module ne doit être modifié. + +--- + +## Structure interne attendue + +```c +struct Workspace +{ + GtkWidget *root_widget; + GtkWidget *stack; + + GtkWidget *welcome_page; + GtkWidget *welcome_title_label; + GtkWidget *welcome_status_label; + GtkWidget *welcome_instruction_label; + + GtkWidget *node_page; + GtkWidget *node_name_label; + GtkWidget *node_type_label; + GtkWidget *node_parent_label; + GtkWidget *node_children_label; +}; +``` + +Les noms exacts peuvent varier, mais doivent rester explicites. + +--- + +## Pages attendues + +### Page d'accueil + +Nom interne : + +```text +welcome +``` + +Contenu : + +```text +Labfy Investigation + +Aucune enquête ouverte + +Sélectionnez ou créez une enquête +``` + +### Page d'informations + +Nom interne : + +```text +node-information +``` + +Contenu pour un dossier : + +```text +00_BaseDeDonnees + +Type : dossier +Parent : Template +Enfants : 2 +``` + +Contenu pour un fichier : + +```text +Enquete.sqlite + +Type : fichier +Parent : 00_BaseDeDonnees +``` + +--- + +## Comportement attendu + +Lorsque : + +```c +workspace_set_selected_node(workspace, NULL); +``` + +le `GtkStack` doit afficher : + +```text +welcome +``` + +Lorsqu'un nœud valide est transmis : + +```c +workspace_set_selected_node(workspace, node); +``` + +le `GtkStack` doit afficher : + +```text +node-information +``` + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- ouvrir un fichier ; +- afficher son contenu ; +- ajouter une barre de navigation ; +- ajouter une animation personnalisée ; +- afficher plusieurs nœuds simultanément ; +- modifier le Core ; +- communiquer avec SQLite. + +--- + +## Contraintes techniques + +- GTK4 uniquement ; +- utiliser `GtkStack` ; +- aucune gestion manuelle de visibilité entre les pages ; +- aucun état global ; +- aucun changement de propriété des nœuds ; +- documentation cohérente ; +- compilation sans warning ; +- aucun `Gtk-CRITICAL`. + +--- + +## Gestion de la propriété + +Le `Workspace` possède uniquement sa structure d'encapsulation. + +Les widgets sont gérés par l'arbre GTK une fois intégrés dans la fenêtre. + +Le nœud reçu reste la propriété du `InvestigationTreeModel`. + +Le `Workspace` ne doit jamais appeler : + +```c +investigation_node_free(node); +``` + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste entièrement valide. +- [ ] `GtkStack` est utilisé. +- [ ] La page d'accueil apparaît sans sélection. +- [ ] La page d'informations apparaît après sélection. +- [ ] Le passage d'une page à l'autre fonctionne. +- [ ] Aucun appel manuel à `gtk_widget_set_visible()` n'est utilisé pour changer de page. +- [ ] L'API publique de `Workspace` reste stable. +- [ ] Aucun autre module n'est modifié. +- [ ] Aucun `Gtk-CRITICAL`. + +--- + +## Tests manuels + +1. Lancer l'application. +2. Ouvrir une enquête. +3. Vérifier que la page d'accueil est affichée sans sélection. +4. Sélectionner un dossier. +5. Vérifier l'affichage de la page d'informations. +6. Sélectionner un fichier. +7. Vérifier la mise à jour de la même page. +8. Changer d'enquête. +9. Vérifier le retour à la page d'accueil. +10. Lancer `make test`. + +--- + +## Commit attendu + +```text +refactor(gui): use GtkStack in workspace +``` diff --git a/labfy-investigation b/labfy-investigation index 66f0a39..f51fc01 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index b5d4a01..9abb60a 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -7,28 +7,33 @@ #include +#define WORKSPACE_PAGE_WELCOME "welcome" +#define WORKSPACE_PAGE_NODE_INFORMATION "node-information" + /** * @struct Workspace * @brief Représentation interne de la zone de travail. - * - * Cette structure reste privée au module. Les autres fichiers utilisent - * uniquement le type opaque déclaré dans workspace.h. */ struct Workspace { GtkWidget *root_widget; - GtkWidget *welcome_box; - GtkWidget *title_label; - GtkWidget *status_label; - GtkWidget *instruction_label; + GtkWidget *stack; - GtkWidget *node_box; + GtkWidget *welcome_page; + GtkWidget *welcome_title_label; + GtkWidget *welcome_status_label; + GtkWidget *welcome_instruction_label; + + GtkWidget *node_page; GtkWidget *node_name_label; GtkWidget *node_type_label; GtkWidget *node_parent_label; GtkWidget *node_children_label; }; +/** + * @brief Affiche la page d'accueil. + */ static void workspace_show_welcome( Workspace *workspace ) @@ -38,14 +43,9 @@ static void workspace_show_welcome( return; } - gtk_widget_set_visible( - workspace->welcome_box, - TRUE - ); - - gtk_widget_set_visible( - workspace->node_box, - FALSE + gtk_stack_set_visible_child_name( + GTK_STACK(workspace->stack), + WORKSPACE_PAGE_WELCOME ); } @@ -76,76 +76,107 @@ Workspace *workspace_new(void) TRUE ); - workspace->welcome_box = gtk_box_new( + workspace->stack = gtk_stack_new(); + + if (workspace->stack == NULL) + { + workspace_free(workspace); + return NULL; + } + + gtk_widget_set_hexpand( + workspace->stack, + TRUE + ); + + gtk_widget_set_vexpand( + workspace->stack, + TRUE + ); + + gtk_box_append( + GTK_BOX(workspace->root_widget), + workspace->stack + ); + + /* + * Page d'accueil. + */ + workspace->welcome_page = gtk_box_new( GTK_ORIENTATION_VERTICAL, 12 ); - if (workspace->welcome_box == NULL) + if (workspace->welcome_page == NULL) { workspace_free(workspace); return NULL; } - /* - * Le conteneur d'accueil est centré horizontalement et verticalement - * dans l'espace disponible. - */ gtk_widget_set_halign( - workspace->welcome_box, + workspace->welcome_page, GTK_ALIGN_CENTER ); gtk_widget_set_valign( - workspace->welcome_box, + workspace->welcome_page, GTK_ALIGN_CENTER ); - workspace->title_label = gtk_label_new( + workspace->welcome_title_label = gtk_label_new( "Labfy Investigation" ); - workspace->status_label = gtk_label_new( + workspace->welcome_status_label = gtk_label_new( "Aucune enquête ouverte" ); - workspace->instruction_label = gtk_label_new( + workspace->welcome_instruction_label = gtk_label_new( "Sélectionnez ou créez une enquête" ); gtk_box_append( - GTK_BOX(workspace->welcome_box), - workspace->title_label + GTK_BOX(workspace->welcome_page), + workspace->welcome_title_label ); gtk_box_append( - GTK_BOX(workspace->welcome_box), - workspace->status_label + GTK_BOX(workspace->welcome_page), + workspace->welcome_status_label ); gtk_box_append( - GTK_BOX(workspace->welcome_box), - workspace->instruction_label + GTK_BOX(workspace->welcome_page), + workspace->welcome_instruction_label ); - workspace->node_box = gtk_box_new( - GTK_ORIENTATION_VERTICAL, - 12 + gtk_stack_add_named( + GTK_STACK(workspace->stack), + workspace->welcome_page, + WORKSPACE_PAGE_WELCOME ); - if (workspace->node_box == NULL) + /* + * Page d'informations sur le nœud. + */ + workspace->node_page = gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 12 + ); + + if (workspace->node_page == NULL) { workspace_free(workspace); return NULL; } gtk_widget_set_halign( - workspace->node_box, + workspace->node_page, GTK_ALIGN_CENTER ); gtk_widget_set_valign( - workspace->node_box, + workspace->node_page, GTK_ALIGN_CENTER ); @@ -155,33 +186,29 @@ Workspace *workspace_new(void) workspace->node_children_label = gtk_label_new(NULL); gtk_box_append( - GTK_BOX(workspace->node_box), + GTK_BOX(workspace->node_page), workspace->node_name_label ); gtk_box_append( - GTK_BOX(workspace->node_box), + GTK_BOX(workspace->node_page), workspace->node_type_label ); gtk_box_append( - GTK_BOX(workspace->node_box), + GTK_BOX(workspace->node_page), workspace->node_parent_label ); gtk_box_append( - GTK_BOX(workspace->node_box), + GTK_BOX(workspace->node_page), workspace->node_children_label ); - gtk_box_append( - GTK_BOX(workspace->root_widget), - workspace->welcome_box - ); - - gtk_box_append( - GTK_BOX(workspace->root_widget), - workspace->node_box + gtk_stack_add_named( + GTK_STACK(workspace->stack), + workspace->node_page, + WORKSPACE_PAGE_NODE_INFORMATION ); workspace_show_welcome(workspace); @@ -212,7 +239,7 @@ void workspace_set_selected_node( InvestigationNodeType node_type; size_t children_count = 0; - char *type_text = NULL; + const char *type_text = NULL; char *parent_text = NULL; char *children_text = NULL; @@ -238,11 +265,11 @@ void workspace_set_selected_node( if (node_type == INVESTIGATION_NODE_DIRECTORY) { - type_text = g_strdup("Type : dossier"); + type_text = "Type : dossier"; } else { - type_text = g_strdup("Type : fichier"); + type_text = "Type : fichier"; } gtk_label_set_text( @@ -302,19 +329,13 @@ void workspace_set_selected_node( ); } - gtk_widget_set_visible( - workspace->welcome_box, - FALSE - ); - - gtk_widget_set_visible( - workspace->node_box, - TRUE + gtk_stack_set_visible_child_name( + GTK_STACK(workspace->stack), + WORKSPACE_PAGE_NODE_INFORMATION ); g_free(children_text); g_free(parent_text); - g_free(type_text); } void workspace_free(Workspace *workspace) @@ -324,9 +345,5 @@ void workspace_free(Workspace *workspace) return; } - /* - * Les widgets GTK sont intégrés dans l'arbre de widgets de MainWindow. - * GTK gère donc leur destruction. - */ g_free(workspace); }