diff --git a/docs/tickets/closed/TICKET-015.md b/docs/tickets/closed/TICKET-015.md new file mode 100644 index 0000000..0493f81 --- /dev/null +++ b/docs/tickets/closed/TICKET-015.md @@ -0,0 +1,225 @@ +# Ticket #015 + +## Titre + +Afficher le nœud sélectionné dans le `Workspace`. + +--- + +## Objectif + +Afficher dans la zone de travail les informations principales du nœud sélectionné dans l'arborescence. + +Lorsqu'un dossier ou un fichier est sélectionné, le `Workspace` doit afficher : + +- son nom ; +- son type ; +- son parent éventuel ; +- son nombre d'enfants s'il s'agit d'un dossier. + +Aucun fichier ne doit encore être ouvert. + +--- + +## Architecture + +```text +InvestigationTreeView + │ + ▼ +Application + │ + ▼ +MainWindow + │ + ▼ +Workspace +``` + +`Application` reste le coordinateur. + +`InvestigationTreeView` détecte la sélection. + +`Workspace` affiche les informations reçues. + +--- + +## Responsabilités + +### Application + +Le module doit : + +- recevoir le nœud sélectionné ; +- transmettre ce nœud à `MainWindow` ; +- ne pas modifier ni libérer le nœud. + +### MainWindow + +Le module doit : + +- recevoir un nœud en lecture seule ; +- le transmettre au `Workspace`. + +### Workspace + +Le module doit : + +- recevoir un `InvestigationNode` en lecture seule ; +- afficher son nom ; +- afficher son type ; +- afficher le nom de son parent si disponible ; +- afficher le nombre d'enfants pour un dossier ; +- restaurer la page d'accueil si le nœud vaut `NULL`. + +--- + +## Principe de propriété + +Le nœud sélectionné reste la propriété du `InvestigationTreeModel`. + +`Application`, `MainWindow` et `Workspace` ne reçoivent qu'une référence non propriétaire : + +```c +const InvestigationNode *node; +``` + +Aucun de ces modules ne doit appeler : + +```c +investigation_node_free(node); +``` + +--- + +## Fichiers concernés + +```text +include/widgets/workspace.h +src/widgets/workspace.c + +include/views/main_window.h +src/views/main_window.c + +src/core/application.c +``` + +Aucune modification du Core métier n'est nécessaire. + +--- + +## Interfaces publiques à ajouter + +### Workspace + +```c +void workspace_set_selected_node( + Workspace *workspace, + const InvestigationNode *node +); +``` + +### MainWindow + +```c +void main_window_set_selected_node( + MainWindow *main_window, + const InvestigationNode *node +); +``` + +--- + +## Comportement attendu + +### Aucun nœud sélectionné + +```text +Labfy Investigation + +Aucune enquête ouverte + +Sélectionnez ou créez une enquête +``` + +### Dossier sélectionné + +```text +00_BaseDeDonnees + +Type : dossier +Parent : Template +Enfants : 2 +``` + +### Fichier sélectionné + +```text +Enquete.sqlite + +Type : fichier +Parent : 00_BaseDeDonnees +``` + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- ouvrir le contenu d'un fichier ; +- afficher un aperçu ; +- lire les métadonnées du système de fichiers ; +- afficher la taille d'un fichier ; +- communiquer avec SQLite ; +- modifier l'arborescence ; +- gérer plusieurs sélections. + +--- + +## Contraintes techniques + +- C17 ; +- GTK4 uniquement ; +- aucun état global ; +- aucune propriété du nœud transférée au `Workspace` ; +- documentation Doxygen ; +- compilation sans warning ; +- aucun `Gtk-CRITICAL`. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste valide. +- [ ] La sélection d'un dossier met à jour le `Workspace`. +- [ ] La sélection d'un fichier met à jour le `Workspace`. +- [ ] Le nom est correct. +- [ ] Le type est correct. +- [ ] Le parent est correct. +- [ ] Le nombre d'enfants est correct pour un dossier. +- [ ] Une sélection `NULL` restaure la page d'accueil. +- [ ] Aucun module graphique ne libère le nœud. +- [ ] Aucun `Gtk-CRITICAL`. + +--- + +## Tests manuels + +1. Ouvrir une enquête. +2. Sélectionner un dossier. +3. Vérifier les informations affichées. +4. Sélectionner un fichier. +5. Vérifier les informations affichées. +6. Changer d'enquête. +7. Vérifier que l'ancien nœud n'est plus affiché. +8. Lancer `make test`. + +--- + +## Commit attendu + +```text +feat(gui): display selected node in workspace +``` diff --git a/include/views/main_window.h b/include/views/main_window.h index b2c9e0a..8253671 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -8,6 +8,7 @@ #include "core/investigation_tree_model.h" #include "widgets/investigation_tree_view.h" +#include "core/investigation_node.h" #include @@ -63,6 +64,19 @@ void main_window_set_tree_selection_callback( gpointer user_data ); +/** + * @brief Affiche dans le Workspace les informations du nœud sélectionné. + * + * La fenêtre principale ne devient pas propriétaire du nœud. + * + * @param main_window Fenêtre principale à mettre à jour. + * @param node Nœud sélectionné en lecture seule, ou NULL. + */ +void main_window_set_selected_node( + MainWindow *main_window, + const InvestigationNode *node +); + /** * @brief Libère les ressources de la fenêtre. * diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index 391f59e..9effabb 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -6,6 +6,9 @@ #ifndef LABFY_INVESTIGATION_WORKSPACE_H #define LABFY_INVESTIGATION_WORKSPACE_H + +#include "core/investigation_node.h" + #include /** @@ -37,6 +40,22 @@ GtkWidget *workspace_get_widget( const Workspace *workspace ); +/** + * @brief Affiche les informations du nœud sélectionné. + * + * Le Workspace ne devient pas propriétaire du nœud. + * Le nœud doit rester valide pendant toute la durée de son affichage. + * + * Passer NULL restaure la page d'accueil. + * + * @param workspace Zone de travail à mettre à jour. + * @param node Nœud sélectionné en lecture seule, ou NULL. + */ +void workspace_set_selected_node( + Workspace *workspace, + const InvestigationNode *node +); + /** * @brief Libère la structure d'encapsulation de la zone de travail. * diff --git a/labfy-investigation b/labfy-investigation index 0aea6e1..66f0a39 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/application.c b/src/core/application.c index 5e93ce7..1213b1f 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -129,6 +129,11 @@ static void application_on_tree_node_selected( return; } + main_window_set_selected_node( + application->main_window, + node + ); + if (node == NULL) { g_print("Aucun nœud sélectionné.\n"); diff --git a/src/views/main_window.c b/src/views/main_window.c index 5771933..7157d52 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -303,6 +303,22 @@ void main_window_set_tree_selection_callback( ); } +void main_window_set_selected_node( + MainWindow *main_window, + const InvestigationNode *node +) +{ + if (main_window == NULL) + { + return; + } + + workspace_set_selected_node( + main_window->workspace, + node + ); +} + void main_window_free(MainWindow *main_window) { if (main_window == NULL) diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index a8a6e67..b5d4a01 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -21,8 +21,34 @@ struct Workspace GtkWidget *title_label; GtkWidget *status_label; GtkWidget *instruction_label; + + GtkWidget *node_box; + GtkWidget *node_name_label; + GtkWidget *node_type_label; + GtkWidget *node_parent_label; + GtkWidget *node_children_label; }; +static void workspace_show_welcome( + Workspace *workspace +) +{ + if (workspace == NULL) + { + return; + } + + gtk_widget_set_visible( + workspace->welcome_box, + TRUE + ); + + gtk_widget_set_visible( + workspace->node_box, + FALSE + ); +} + Workspace *workspace_new(void) { Workspace *workspace = NULL; @@ -102,11 +128,64 @@ Workspace *workspace_new(void) workspace->instruction_label ); + workspace->node_box = gtk_box_new( + GTK_ORIENTATION_VERTICAL, + 12 + ); + + if (workspace->node_box == NULL) + { + workspace_free(workspace); + return NULL; + } + + gtk_widget_set_halign( + workspace->node_box, + GTK_ALIGN_CENTER + ); + + gtk_widget_set_valign( + workspace->node_box, + GTK_ALIGN_CENTER + ); + + workspace->node_name_label = gtk_label_new(NULL); + workspace->node_type_label = gtk_label_new(NULL); + workspace->node_parent_label = gtk_label_new(NULL); + workspace->node_children_label = gtk_label_new(NULL); + + gtk_box_append( + GTK_BOX(workspace->node_box), + workspace->node_name_label + ); + + gtk_box_append( + GTK_BOX(workspace->node_box), + workspace->node_type_label + ); + + gtk_box_append( + GTK_BOX(workspace->node_box), + workspace->node_parent_label + ); + + gtk_box_append( + GTK_BOX(workspace->node_box), + 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 + ); + + workspace_show_welcome(workspace); + return workspace; } @@ -122,6 +201,122 @@ GtkWidget *workspace_get_widget( return workspace->root_widget; } +void workspace_set_selected_node( + Workspace *workspace, + const InvestigationNode *node +) +{ + const char *node_name = NULL; + const InvestigationNode *parent_node = NULL; + const char *parent_name = NULL; + InvestigationNodeType node_type; + size_t children_count = 0; + + char *type_text = NULL; + char *parent_text = NULL; + char *children_text = NULL; + + if (workspace == NULL) + { + return; + } + + if (node == NULL) + { + workspace_show_welcome(workspace); + return; + } + + node_name = investigation_node_get_name(node); + node_type = investigation_node_get_type(node); + parent_node = investigation_node_get_parent(node); + + gtk_label_set_text( + GTK_LABEL(workspace->node_name_label), + node_name != NULL ? node_name : "(sans nom)" + ); + + if (node_type == INVESTIGATION_NODE_DIRECTORY) + { + type_text = g_strdup("Type : dossier"); + } + else + { + type_text = g_strdup("Type : fichier"); + } + + gtk_label_set_text( + GTK_LABEL(workspace->node_type_label), + type_text + ); + + if (parent_node != NULL) + { + parent_name = investigation_node_get_name(parent_node); + + parent_text = g_strdup_printf( + "Parent : %s", + parent_name != NULL ? parent_name : "(sans nom)" + ); + } + else + { + parent_text = g_strdup("Parent : aucun"); + } + + gtk_label_set_text( + GTK_LABEL(workspace->node_parent_label), + parent_text + ); + + if (node_type == INVESTIGATION_NODE_DIRECTORY) + { + children_count = + investigation_node_get_children_count(node); + + children_text = g_strdup_printf( + "Enfants : %zu", + children_count + ); + + gtk_label_set_text( + GTK_LABEL(workspace->node_children_label), + children_text + ); + + gtk_widget_set_visible( + workspace->node_children_label, + TRUE + ); + } + else + { + gtk_label_set_text( + GTK_LABEL(workspace->node_children_label), + "" + ); + + gtk_widget_set_visible( + workspace->node_children_label, + FALSE + ); + } + + gtk_widget_set_visible( + workspace->welcome_box, + FALSE + ); + + gtk_widget_set_visible( + workspace->node_box, + TRUE + ); + + g_free(children_text); + g_free(parent_text); + g_free(type_text); +} + void workspace_free(Workspace *workspace) { if (workspace == NULL)