diff --git a/docs/tickets/open/TICKET-031.md b/docs/tickets/closed/TICKET-031.md similarity index 100% rename from docs/tickets/open/TICKET-031.md rename to docs/tickets/closed/TICKET-031.md diff --git a/docs/tickets/closed/TICKET-31.1.md b/docs/tickets/closed/TICKET-31.1.md new file mode 100644 index 0000000..dd49b30 --- /dev/null +++ b/docs/tickets/closed/TICKET-31.1.md @@ -0,0 +1,280 @@ +# Ticket #031.1 — Ajouter une fermeture propre de l’application + +## Contexte + +L’application permet désormais : + +- de créer une enquête ; +- d’ouvrir une enquête existante ; +- de remplacer proprement la session active. + +La fermeture se fait encore avec : + +```text +Ctrl+C +``` + +Cette méthode interrompt brutalement le processus et ne constitue pas un parcours utilisateur normal. + +## Objectif + +Ajouter un bouton : + +```text +Quitter +``` + +permettant de fermer proprement l’application GTK. + +La fermeture doit déclencher le cycle normal de nettoyage : + +```text +GtkApplication + ↓ +fin de la boucle principale + ↓ +application_free() + ↓ +InvestigationTreeModel libéré + ↓ +InvestigationSession fermée + ↓ +Database fermée + ↓ +MainWindow libérée +``` + +## Travail à réaliser + +### 1. Ajouter le bouton dans `MainWindow` + +Modifier : + +```text +include/views/main_window.h +src/views/main_window.c +``` + +La barre d’actions doit devenir : + +```text +[ Nouvelle enquête ] [ Ouvrir une enquête ] [ Quitter ] +``` + +Ajouter dans la structure privée : + +```c +GtkWidget *quit_button; +``` + +### 2. Ajouter le type de callback + +Dans `include/views/main_window.h`, ajouter : + +```c +typedef void (*MainWindowQuitCallback)( + gpointer user_data +); +``` + +Puis déclarer : + +```c +void main_window_set_quit_callback( + MainWindow *main_window, + MainWindowQuitCallback callback, + gpointer user_data +); +``` + +### 3. Conserver le callback dans `MainWindow` + +Ajouter dans la structure privée : + +```c +MainWindowQuitCallback + quit_callback; + +gpointer + quit_user_data; +``` + +### 4. Ajouter le callback privé du bouton + +Dans `src/views/main_window.c`, ajouter : + +```c +static void main_window_on_quit_clicked( + GtkButton *button, + gpointer user_data +); +``` + +Cette fonction doit : + +- accepter `button == NULL` ; +- accepter `main_window == NULL` ; +- ne rien faire si aucun callback n’est défini ; +- appeler le callback configuré sinon. + +### 5. Créer le bouton + +Dans `main_window_new()` : + +```c +main_window->quit_button = + gtk_button_new_with_label( + "Quitter" + ); +``` + +Ajouter le bouton à `action_bar`. + +Relier son signal `clicked` à : + +```c +main_window_on_quit_clicked +``` + +### 6. Ajouter le setter public + +Implémenter : + +```c +void main_window_set_quit_callback( + MainWindow *main_window, + MainWindowQuitCallback callback, + gpointer user_data +) +{ + if (main_window == NULL) + { + return; + } + + main_window->quit_callback = callback; + main_window->quit_user_data = user_data; +} +``` + +### 7. Ajouter le contrôleur dans `Application` + +Dans `src/core/application.c`, ajouter : + +```c +static void application_on_quit_requested( + gpointer user_data +); +``` + +Cette fonction doit utiliser : + +```c +g_application_quit( + G_APPLICATION( + application->gtk_application + ) +); +``` + +Aucun appel direct à `exit()` ne doit être ajouté. + +### 8. Relier le callback dans `application_on_activate()` + +Ajouter : + +```c +main_window_set_quit_callback( + application->main_window, + application_on_quit_requested, + application +); +``` + +## Tests manuels + +### Fermeture sans enquête + +1. lancer l’application ; +2. cliquer sur `Quitter` ; +3. vérifier que le processus se termine normalement. + +### Fermeture avec une enquête ouverte + +1. ouvrir ou créer une enquête ; +2. cliquer sur `Quitter` ; +3. vérifier que la fenêtre se ferme ; +4. vérifier qu’aucun crash n’apparaît ; +5. relancer l’application ; +6. rouvrir la même enquête. + +### Fermeture après remplacement de session + +1. ouvrir une enquête A ; +2. ouvrir une enquête B ; +3. cliquer sur `Quitter` ; +4. vérifier l’absence de crash ou de double libération. + +## Critères d’acceptation + +- [ ] Le bouton `Quitter` est visible. +- [ ] Le bouton ferme l’application. +- [ ] Aucun `exit()` direct n’est utilisé. +- [ ] `g_application_quit()` est utilisé. +- [ ] `MainWindow` ne connaît pas `GtkApplication`. +- [ ] Le nettoyage reste géré par `Application`. +- [ ] La fermeture fonctionne sans enquête ouverte. +- [ ] La fermeture fonctionne avec une enquête ouverte. +- [ ] La fermeture fonctionne après un changement d’enquête. +- [ ] `make` réussit. +- [ ] `make test` réussit. +- [ ] `git diff --check` ne retourne aucune erreur. + +## Audit attendu + +```bash +rg -n \ + 'quit_button|quit_callback|g_application_quit' \ + include/views/main_window.h \ + src/views/main_window.c \ + src/core/application.c +``` + +La commande suivante ne doit rien afficher : + +```bash +rg -n \ + '\bexit\s*\(' \ + src/core/application.c \ + src/views/main_window.c +``` + +## Fichiers concernés + +```text +include/views/main_window.h +src/views/main_window.c +src/core/application.c +``` + +## Commit attendu + +```bash +make clean +make +make test +git diff --check +git status --short +``` + +```bash +git add \ + include/views/main_window.h \ + src/views/main_window.c \ + src/core/application.c +``` + +```bash +git commit -m "feat(ui): add clean application quit action" +git push +``` diff --git a/include/views/main_window.h b/include/views/main_window.h index b753945..112f07c 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -147,6 +147,15 @@ typedef void (*MainWindowOpenInvestigationCallback)( gpointer user_data ); +/** + * @brief Callback appelé lorsque l'utilisateur demande à quitter. + * + * @param user_data Données utilisateur associées au callback. + */ +typedef void (*MainWindowQuitCallback)( + gpointer user_data +); + /** * @brief Définit le callback du bouton « Ouvrir une enquête ». * @@ -162,6 +171,21 @@ void main_window_set_open_investigation_callback( gpointer user_data ); +/** + * @brief Définit le callback du bouton « Quitter ». + * + * MainWindow transmet uniquement la demande au contrôleur. + * + * @param main_window Fenêtre principale. + * @param callback Fonction appelée lors du clic. + * @param user_data Données transmises au callback. + */ +void main_window_set_quit_callback( + MainWindow *main_window, + MainWindowQuitCallback callback, + gpointer user_data +); + /** * @brief Libère les ressources de la fenêtre. * diff --git a/labfy-investigation b/labfy-investigation index a1c6bd7..0825d5b 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/application.c b/src/core/application.c index a5a95bd..f08dda5 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -498,6 +498,30 @@ static void application_on_tree_node_selected( } } +/** + * @brief Ferme proprement l'application. + * + * @param user_data Pointeur vers Application. + */ +static void application_on_quit_requested( + gpointer user_data +) +{ + Application *application = user_data; + + if (application == NULL || + application->gtk_application == NULL) + { + return; + } + + g_application_quit( + G_APPLICATION( + application->gtk_application + ) + ); +} + /** * @brief Crée la fenêtre principale lors de l'activation. * @@ -559,6 +583,12 @@ static void application_on_activate( application ); + main_window_set_quit_callback( + application->main_window, + application_on_quit_requested, + application + ); + main_window_present( application->main_window ); diff --git a/src/views/main_window.c b/src/views/main_window.c index 5bd8210..4e395ab 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -54,6 +54,7 @@ struct MainWindow GtkWidget *open_investigation_button; GtkWidget *main_paned; GtkWidget *status_label; + GtkWidget *quit_button; Sidebar *sidebar; Workspace *workspace; @@ -69,6 +70,12 @@ struct MainWindow gpointer open_investigation_user_data; + + MainWindowQuitCallback + quit_callback; + + gpointer + quit_user_data; }; /** @@ -123,6 +130,32 @@ static void main_window_on_open_investigation_clicked( ); } +/** + * @brief Transmet la demande de fermeture au contrôleur. + * + * @param button Bouton ayant reçu le clic. + * @param user_data Pointeur vers MainWindow. + */ +static void main_window_on_quit_clicked( + GtkButton *button, + gpointer user_data +) +{ + MainWindow *main_window = user_data; + + (void) button; + + if (main_window == NULL || + main_window->quit_callback == NULL) + { + return; + } + + main_window->quit_callback( + main_window->quit_user_data + ); +} + MainWindow *main_window_new(GtkApplication *application) { MainWindow *main_window = NULL; @@ -206,6 +239,11 @@ MainWindow *main_window_new(GtkApplication *application) "Ouvrir une enquête" ); + main_window->quit_button = + gtk_button_new_with_label( + "Quitter" + ); + gtk_box_append( GTK_BOX(main_window->action_bar), main_window->new_investigation_button @@ -216,6 +254,11 @@ MainWindow *main_window_new(GtkApplication *application) main_window->open_investigation_button ); + gtk_box_append( + GTK_BOX(main_window->action_bar), + main_window->quit_button + ); + g_signal_connect( main_window->new_investigation_button, "clicked", @@ -234,6 +277,15 @@ MainWindow *main_window_new(GtkApplication *application) main_window ); + g_signal_connect( + main_window->quit_button, + "clicked", + G_CALLBACK( + main_window_on_quit_clicked + ), + main_window + ); + /* * GtkPaned sépare horizontalement le panneau latéral * et la zone de travail. @@ -600,6 +652,21 @@ void main_window_set_open_investigation_callback( main_window->open_investigation_user_data = user_data; } +void main_window_set_quit_callback( + MainWindow *main_window, + MainWindowQuitCallback callback, + gpointer user_data +) +{ + if (main_window == NULL) + { + return; + } + + main_window->quit_callback = callback; + main_window->quit_user_data = user_data; +} + void main_window_set_selected_node( MainWindow *main_window, const InvestigationNode *node @@ -616,7 +683,9 @@ void main_window_set_selected_node( ); } -void main_window_free(MainWindow *main_window) +void main_window_free( + MainWindow *main_window +) { if (main_window == NULL) { @@ -624,13 +693,31 @@ void main_window_free(MainWindow *main_window) } /* - * La structure Sidebar a été allouée par sidebar_new(). - * MainWindow en est donc propriétaire et doit la libérer. + * La fenêtre et tous ses widgets doivent être détruits pendant que + * les structures MainWindow, Sidebar et Workspace existent encore. * - * Les widgets GTK, eux, restent gérés par GTK. + * Certains widgets possèdent des callbacks dont user_data pointe vers + * ces structures. */ - workspace_free(main_window->workspace); - sidebar_free(main_window->sidebar); + if (main_window->window != NULL) + { + gtk_window_destroy( + main_window->window + ); + + main_window->window = NULL; + } + + workspace_free( + main_window->workspace + ); + + sidebar_free( + main_window->sidebar + ); + + main_window->workspace = NULL; + main_window->sidebar = NULL; g_free(main_window); } diff --git a/tests/test_database b/tests/test_database deleted file mode 100755 index 2a1ae25..0000000 Binary files a/tests/test_database and /dev/null differ diff --git a/tests/test_error b/tests/test_error deleted file mode 100755 index 1185635..0000000 Binary files a/tests/test_error and /dev/null differ diff --git a/tests/test_investigation_dao b/tests/test_investigation_dao deleted file mode 100755 index 0234ddd..0000000 Binary files a/tests/test_investigation_dao and /dev/null differ diff --git a/tests/test_investigation_node b/tests/test_investigation_node deleted file mode 100755 index b7614ef..0000000 Binary files a/tests/test_investigation_node and /dev/null differ diff --git a/tests/test_investigation_project b/tests/test_investigation_project deleted file mode 100755 index 3bd6369..0000000 Binary files a/tests/test_investigation_project and /dev/null differ diff --git a/tests/test_investigation_record b/tests/test_investigation_record deleted file mode 100755 index ba2880b..0000000 Binary files a/tests/test_investigation_record and /dev/null differ diff --git a/tests/test_investigation_session b/tests/test_investigation_session deleted file mode 100755 index 04f8d66..0000000 Binary files a/tests/test_investigation_session and /dev/null differ diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder deleted file mode 100755 index d6cd484..0000000 Binary files a/tests/test_investigation_tree_builder and /dev/null differ diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model deleted file mode 100755 index 2c823e1..0000000 Binary files a/tests/test_investigation_tree_model and /dev/null differ diff --git a/tests/test_statement b/tests/test_statement deleted file mode 100755 index 79b937b..0000000 Binary files a/tests/test_statement and /dev/null differ diff --git a/tests/test_transaction b/tests/test_transaction deleted file mode 100755 index 3533f19..0000000 Binary files a/tests/test_transaction and /dev/null differ