feat(ui): add clean application quit action

This commit is contained in:
grayTerminal-sh 2026-07-17 09:00:18 +02:00
parent a4f7eac949
commit 2e08253aca
17 changed files with 427 additions and 6 deletions

View file

@ -0,0 +1,280 @@
# Ticket #031.1 — Ajouter une fermeture propre de lapplication
## Contexte
Lapplication permet désormais :
- de créer une enquête ;
- douvrir 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 lapplication 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 dactions 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 nest 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 lapplication ;
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 quaucun crash napparaît ;
5. relancer lapplication ;
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 labsence de crash ou de double libération.
## Critères dacceptation
- [ ] Le bouton `Quitter` est visible.
- [ ] Le bouton ferme lapplication.
- [ ] Aucun `exit()` direct nest 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 denquê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
```

View file

@ -147,6 +147,15 @@ typedef void (*MainWindowOpenInvestigationCallback)(
gpointer user_data 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 ». * @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 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. * @brief Libère les ressources de la fenêtre.
* *

Binary file not shown.

View file

@ -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. * @brief Crée la fenêtre principale lors de l'activation.
* *
@ -559,6 +583,12 @@ static void application_on_activate(
application application
); );
main_window_set_quit_callback(
application->main_window,
application_on_quit_requested,
application
);
main_window_present( main_window_present(
application->main_window application->main_window
); );

View file

@ -54,6 +54,7 @@ struct MainWindow
GtkWidget *open_investigation_button; GtkWidget *open_investigation_button;
GtkWidget *main_paned; GtkWidget *main_paned;
GtkWidget *status_label; GtkWidget *status_label;
GtkWidget *quit_button;
Sidebar *sidebar; Sidebar *sidebar;
Workspace *workspace; Workspace *workspace;
@ -69,6 +70,12 @@ struct MainWindow
gpointer gpointer
open_investigation_user_data; 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_new(GtkApplication *application)
{ {
MainWindow *main_window = NULL; MainWindow *main_window = NULL;
@ -206,6 +239,11 @@ MainWindow *main_window_new(GtkApplication *application)
"Ouvrir une enquête" "Ouvrir une enquête"
); );
main_window->quit_button =
gtk_button_new_with_label(
"Quitter"
);
gtk_box_append( gtk_box_append(
GTK_BOX(main_window->action_bar), GTK_BOX(main_window->action_bar),
main_window->new_investigation_button main_window->new_investigation_button
@ -216,6 +254,11 @@ MainWindow *main_window_new(GtkApplication *application)
main_window->open_investigation_button main_window->open_investigation_button
); );
gtk_box_append(
GTK_BOX(main_window->action_bar),
main_window->quit_button
);
g_signal_connect( g_signal_connect(
main_window->new_investigation_button, main_window->new_investigation_button,
"clicked", "clicked",
@ -234,6 +277,15 @@ MainWindow *main_window_new(GtkApplication *application)
main_window 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 * GtkPaned sépare horizontalement le panneau latéral
* et la zone de travail. * et la zone de travail.
@ -600,6 +652,21 @@ void main_window_set_open_investigation_callback(
main_window->open_investigation_user_data = user_data; 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( void main_window_set_selected_node(
MainWindow *main_window, MainWindow *main_window,
const InvestigationNode *node 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) if (main_window == NULL)
{ {
@ -624,13 +693,31 @@ void main_window_free(MainWindow *main_window)
} }
/* /*
* La structure Sidebar a é allouée par sidebar_new(). * La fenêtre et tous ses widgets doivent être détruits pendant que
* MainWindow en est donc propriétaire et doit la libérer. * 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); if (main_window->window != NULL)
sidebar_free(main_window->sidebar); {
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); g_free(main_window);
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.