feat(ui): add clean application quit action
This commit is contained in:
parent
a4f7eac949
commit
2e08253aca
17 changed files with 427 additions and 6 deletions
280
docs/tickets/closed/TICKET-31.1.md
Normal file
280
docs/tickets/closed/TICKET-31.1.md
Normal file
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
@ -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.
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 été 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.
BIN
tests/test_error
BIN
tests/test_error
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.
Loading…
Reference in a new issue