feat(ui): display application errors in GTK
This commit is contained in:
parent
9ee7ee61c1
commit
f7b521cda6
19 changed files with 2987 additions and 1 deletions
1299
docs/ROADMAP.md
1299
docs/ROADMAP.md
File diff suppressed because it is too large
Load diff
537
docs/tickets/closed/TICKET-032.md
Normal file
537
docs/tickets/closed/TICKET-032.md
Normal file
|
|
@ -0,0 +1,537 @@
|
||||||
|
# Ticket #032 — Factoriser le chargement et l’installation d’une enquête
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Après le ticket #031.1, l’application sait :
|
||||||
|
|
||||||
|
- créer une enquête ;
|
||||||
|
- ouvrir une enquête existante ;
|
||||||
|
- remplacer la session active ;
|
||||||
|
- fermer proprement l’application.
|
||||||
|
|
||||||
|
Cependant, les flux « Nouvelle enquête » et « Ouvrir une enquête » dupliquent encore une partie importante de la logique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
investigation_session_open()
|
||||||
|
→ récupération du projet
|
||||||
|
→ récupération du chemin racine
|
||||||
|
→ investigation_tree_builder_build()
|
||||||
|
→ application_install_session()
|
||||||
|
→ nettoyage en cas d’échec
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette duplication augmentera avec les futures fonctions :
|
||||||
|
|
||||||
|
- enquêtes récentes ;
|
||||||
|
- ouverture depuis la ligne de commande ;
|
||||||
|
- restauration de session ;
|
||||||
|
- ouverture depuis un rapport ou une archive.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Créer dans `src/core/application.c` une fonction interne unique qui ouvre et installe une enquête à partir de son dossier racine.
|
||||||
|
|
||||||
|
Contrat attendu :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static gboolean application_open_and_install_investigation(
|
||||||
|
Application *application,
|
||||||
|
const char *root_path,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette fonction doit garantir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
succès :
|
||||||
|
Application devient propriétaire de la nouvelle session
|
||||||
|
Application devient propriétaire du nouvel arbre
|
||||||
|
ancienne session libérée
|
||||||
|
ancien arbre libéré
|
||||||
|
fenêtre mise à jour
|
||||||
|
|
||||||
|
échec :
|
||||||
|
ancienne session conservée
|
||||||
|
ancien arbre conservé
|
||||||
|
nouvelle session libérée
|
||||||
|
nouvel arbre libéré
|
||||||
|
erreur transmise à l’appelant
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Travail à réaliser
|
||||||
|
|
||||||
|
## 1. Ajouter un domaine d’erreur privé
|
||||||
|
|
||||||
|
Dans `src/core/application.c`, ajouter un domaine d’erreur uniquement utilisé par le contrôleur.
|
||||||
|
|
||||||
|
Exemple :
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
APPLICATION_OPEN_ERROR_INVALID_ARGUMENT,
|
||||||
|
APPLICATION_OPEN_ERROR_INVALID_PROJECT,
|
||||||
|
APPLICATION_OPEN_ERROR_TREE_BUILD,
|
||||||
|
APPLICATION_OPEN_ERROR_INSTALL
|
||||||
|
} ApplicationOpenError;
|
||||||
|
```
|
||||||
|
|
||||||
|
Ajouter :
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define APPLICATION_OPEN_ERROR \
|
||||||
|
application_open_error_quark()
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static GQuark application_open_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"labfy-investigation-application-open-error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le domaine reste privé à `application.c`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Créer la fonction factorisée
|
||||||
|
|
||||||
|
Ajouter :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static gboolean application_open_and_install_investigation(
|
||||||
|
Application *application,
|
||||||
|
const char *root_path,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction doit :
|
||||||
|
|
||||||
|
1. valider `application` ;
|
||||||
|
2. valider `application->main_window` ;
|
||||||
|
3. valider `root_path` ;
|
||||||
|
4. ouvrir une nouvelle `InvestigationSession` ;
|
||||||
|
5. récupérer son `InvestigationProject` ;
|
||||||
|
6. récupérer le chemin racine canonique ;
|
||||||
|
7. construire un nouvel `InvestigationTreeModel` ;
|
||||||
|
8. appeler `application_install_session()` ;
|
||||||
|
9. transférer la propriété uniquement en cas de succès.
|
||||||
|
|
||||||
|
### Règle de propriété
|
||||||
|
|
||||||
|
Avant `application_install_session()` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
la fonction possède new_session
|
||||||
|
la fonction possède new_tree_model
|
||||||
|
```
|
||||||
|
|
||||||
|
Après succès :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Application possède new_session
|
||||||
|
Application possède new_tree_model
|
||||||
|
```
|
||||||
|
|
||||||
|
Après échec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
la fonction doit libérer les objets qu’elle possède encore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation de `GError`
|
||||||
|
|
||||||
|
La fonction doit respecter la convention GLib :
|
||||||
|
|
||||||
|
```c
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
L’utilisation de `g_return_val_if_fail()` est acceptable ici pour vérifier le contrat du développeur.
|
||||||
|
|
||||||
|
Les erreurs utilisateur doivent être produites avec :
|
||||||
|
|
||||||
|
```c
|
||||||
|
g_set_error()
|
||||||
|
g_set_error_literal()
|
||||||
|
g_propagate_prefixed_error()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Propager l’erreur de `InvestigationSession`
|
||||||
|
|
||||||
|
Si :
|
||||||
|
|
||||||
|
```c
|
||||||
|
investigation_session_open()
|
||||||
|
```
|
||||||
|
|
||||||
|
échoue, la fonction doit conserver l’erreur métier d’origine et lui ajouter du contexte.
|
||||||
|
|
||||||
|
Exemple conceptuel :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Impossible d’ouvrir l’enquête : la base SQLite est absente
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne pas remplacer l’erreur précise par un simple :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Erreur inconnue
|
||||||
|
```
|
||||||
|
|
||||||
|
lorsqu’un `GError` est disponible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Refactoriser l’ouverture d’une enquête existante
|
||||||
|
|
||||||
|
`application_on_folder_selected()` ne doit plus contenir directement :
|
||||||
|
|
||||||
|
```c
|
||||||
|
investigation_session_open()
|
||||||
|
investigation_tree_builder_build()
|
||||||
|
application_install_session()
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle doit seulement :
|
||||||
|
|
||||||
|
1. accepter l’annulation ;
|
||||||
|
2. appeler `application_open_and_install_investigation()` ;
|
||||||
|
3. journaliser l’erreur ;
|
||||||
|
4. libérer le `GError`.
|
||||||
|
|
||||||
|
Structure attendue :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void application_on_folder_selected(
|
||||||
|
const char *folder_path,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Application *application = user_data;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (application == NULL ||
|
||||||
|
folder_path == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!application_open_and_install_investigation(
|
||||||
|
application,
|
||||||
|
folder_path,
|
||||||
|
&error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d'ouvrir l'enquête '%s' : %s",
|
||||||
|
folder_path,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L’annulation ne doit pas produire de warning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Refactoriser la création d’une enquête
|
||||||
|
|
||||||
|
`application_on_create_investigation()` doit conserver uniquement :
|
||||||
|
|
||||||
|
1. la création physique du projet ;
|
||||||
|
2. l’appel à la fonction factorisée ;
|
||||||
|
3. le message spécifique indiquant que le projet existe sur le disque si son ouverture échoue.
|
||||||
|
|
||||||
|
Le flux devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
investigation_project_create()
|
||||||
|
→ application_open_and_install_investigation()
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction ne doit plus reconstruire elle-même :
|
||||||
|
|
||||||
|
```text
|
||||||
|
session
|
||||||
|
projet
|
||||||
|
chemin canonique
|
||||||
|
arbre
|
||||||
|
installation
|
||||||
|
```
|
||||||
|
|
||||||
|
En cas d’échec après création, le dossier créé reste volontairement sur le disque.
|
||||||
|
|
||||||
|
Le message doit être explicite :
|
||||||
|
|
||||||
|
```text
|
||||||
|
L’enquête a été créée dans « ... », mais son ouverture a échoué : ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Ne pas modifier `application_install_session()`
|
||||||
|
|
||||||
|
Cette fonction conserve sa responsabilité actuelle :
|
||||||
|
|
||||||
|
- valider les objets prêts à installer ;
|
||||||
|
- libérer l’ancien état ;
|
||||||
|
- transférer la propriété ;
|
||||||
|
- mettre à jour `MainWindow`.
|
||||||
|
|
||||||
|
Le nouveau helper prépare les objets.
|
||||||
|
|
||||||
|
`application_install_session()` réalise le remplacement final.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pseudo-code de la fonction factorisée
|
||||||
|
|
||||||
|
```c
|
||||||
|
static gboolean application_open_and_install_investigation(
|
||||||
|
Application *application,
|
||||||
|
const char *root_path,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
InvestigationSession *new_session = NULL;
|
||||||
|
InvestigationTreeModel *new_tree_model = NULL;
|
||||||
|
const InvestigationProject *project = NULL;
|
||||||
|
const char *canonical_root_path = NULL;
|
||||||
|
GError *session_error = NULL;
|
||||||
|
|
||||||
|
valider les arguments;
|
||||||
|
|
||||||
|
new_session = investigation_session_open(
|
||||||
|
root_path,
|
||||||
|
&session_error
|
||||||
|
);
|
||||||
|
|
||||||
|
si échec :
|
||||||
|
propager session_error avec contexte;
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
project = investigation_session_get_project(
|
||||||
|
new_session
|
||||||
|
);
|
||||||
|
|
||||||
|
valider project;
|
||||||
|
|
||||||
|
canonical_root_path =
|
||||||
|
investigation_project_get_root_path(project);
|
||||||
|
|
||||||
|
valider canonical_root_path;
|
||||||
|
|
||||||
|
new_tree_model =
|
||||||
|
investigation_tree_builder_build(
|
||||||
|
canonical_root_path
|
||||||
|
);
|
||||||
|
|
||||||
|
si échec :
|
||||||
|
produire GError;
|
||||||
|
fermer new_session;
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
si application_install_session() échoue :
|
||||||
|
produire GError;
|
||||||
|
libérer new_tree_model;
|
||||||
|
fermer new_session;
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tests manuels
|
||||||
|
|
||||||
|
## Création valide
|
||||||
|
|
||||||
|
1. lancer l’application ;
|
||||||
|
2. créer une enquête ;
|
||||||
|
3. vérifier le titre ;
|
||||||
|
4. vérifier la barre d’état ;
|
||||||
|
5. vérifier l’arborescence.
|
||||||
|
|
||||||
|
## Ouverture valide
|
||||||
|
|
||||||
|
1. fermer et relancer ;
|
||||||
|
2. ouvrir l’enquête créée ;
|
||||||
|
3. vérifier le même résultat.
|
||||||
|
|
||||||
|
## Remplacement valide
|
||||||
|
|
||||||
|
1. ouvrir une enquête A ;
|
||||||
|
2. ouvrir une enquête B ;
|
||||||
|
3. vérifier que B remplace A.
|
||||||
|
|
||||||
|
## Échec d’ouverture
|
||||||
|
|
||||||
|
1. ouvrir une enquête valide A ;
|
||||||
|
2. tenter d’ouvrir un dossier invalide ;
|
||||||
|
3. vérifier que A reste active ;
|
||||||
|
4. vérifier que l’erreur est journalisée.
|
||||||
|
|
||||||
|
## Échec après création
|
||||||
|
|
||||||
|
Provoquer si possible un échec d’ouverture après création.
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
- le dossier créé reste présent ;
|
||||||
|
- l’ancienne session reste active ;
|
||||||
|
- aucun double `free` ;
|
||||||
|
- aucun crash.
|
||||||
|
|
||||||
|
## Annulation
|
||||||
|
|
||||||
|
Annuler le sélecteur de dossier.
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
- aucun warning ;
|
||||||
|
- aucun changement d’état ;
|
||||||
|
- aucun crash.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Critères d’acceptation
|
||||||
|
|
||||||
|
- [ ] Une seule fonction ouvre une session et construit son arbre.
|
||||||
|
- [ ] La création utilise cette fonction.
|
||||||
|
- [ ] L’ouverture utilise cette fonction.
|
||||||
|
- [ ] `application_install_session()` n’est pas dupliquée.
|
||||||
|
- [ ] L’ancienne session reste active en cas d’échec.
|
||||||
|
- [ ] L’ancien arbre reste actif en cas d’échec.
|
||||||
|
- [ ] Les nouveaux objets sont libérés en cas d’échec.
|
||||||
|
- [ ] Les erreurs de `InvestigationSession` sont propagées.
|
||||||
|
- [ ] L’annulation ne produit pas d’erreur.
|
||||||
|
- [ ] Aucun code SQLite direct n’est ajouté.
|
||||||
|
- [ ] Aucun comportement GTK visible n’est cassé.
|
||||||
|
- [ ] `make` réussit.
|
||||||
|
- [ ] `make test` réussit.
|
||||||
|
- [ ] `git diff --check` ne retourne aucune erreur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Audit attendu
|
||||||
|
|
||||||
|
La logique d’ouverture ne doit apparaître qu’une seule fois :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'investigation_session_open|investigation_tree_builder_build' \
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
une occurrence de investigation_session_open
|
||||||
|
une occurrence de investigation_tree_builder_build
|
||||||
|
```
|
||||||
|
|
||||||
|
La création et l’ouverture doivent appeler le helper :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'application_open_and_install_investigation' \
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier l’absence de SQLite direct :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'sqlite3_|#include <sqlite3.h>' \
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune sortie
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier le format :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fichiers concernés
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune modification publique n’est normalement nécessaire dans :
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/core/application.h
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Commit attendu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
git diff --check
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --cached --stat
|
||||||
|
git diff --cached
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "refactor(core): centralize investigation loading"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Résultat attendu
|
||||||
|
|
||||||
|
Après ce ticket, tous les futurs points d’entrée utiliseront le même flux :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Création
|
||||||
|
Ouverture manuelle
|
||||||
|
Enquêtes récentes
|
||||||
|
Ligne de commande
|
||||||
|
Restauration de session
|
||||||
|
↓
|
||||||
|
application_open_and_install_investigation()
|
||||||
|
```
|
||||||
|
|
||||||
|
Le ticket #033 pourra ensuite afficher graphiquement les `GError` déjà correctement produits par ce flux.
|
||||||
694
docs/tickets/closed/TICKET-033.md
Normal file
694
docs/tickets/closed/TICKET-033.md
Normal file
|
|
@ -0,0 +1,694 @@
|
||||||
|
# Ticket #033 — Afficher les erreurs dans l’interface GTK
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Le ticket #032 a centralisé le chargement d’une enquête dans :
|
||||||
|
|
||||||
|
```c
|
||||||
|
application_open_and_install_investigation()
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette fonction produit maintenant des `GError` précis et conserve l’ancienne session en cas d’échec.
|
||||||
|
|
||||||
|
Actuellement, les erreurs sont seulement écrites dans le terminal avec :
|
||||||
|
|
||||||
|
```c
|
||||||
|
g_warning()
|
||||||
|
```
|
||||||
|
|
||||||
|
Un utilisateur qui lance Labfy Investigation depuis son menu d’applications ne verra pas ces messages.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Créer un module GTK réutilisable capable d’afficher un message d’erreur compréhensible dans une fenêtre modale.
|
||||||
|
|
||||||
|
Le contrôleur doit conserver deux niveaux d’information :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Terminal :
|
||||||
|
message technique complet avec g_warning()
|
||||||
|
|
||||||
|
Interface GTK :
|
||||||
|
titre clair + message compréhensible
|
||||||
|
```
|
||||||
|
|
||||||
|
Le module doit rester générique afin de pouvoir afficher plus tard :
|
||||||
|
|
||||||
|
- erreurs ;
|
||||||
|
- avertissements ;
|
||||||
|
- informations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Architecture attendue
|
||||||
|
|
||||||
|
Créer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/views/application_message_dialog.h
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Responsabilités :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Application
|
||||||
|
├── décide quand afficher une erreur
|
||||||
|
├── fournit le titre
|
||||||
|
└── fournit le message
|
||||||
|
|
||||||
|
ApplicationMessageDialog
|
||||||
|
├── construit la fenêtre GTK
|
||||||
|
├── affiche le contenu
|
||||||
|
└── gère sa fermeture
|
||||||
|
```
|
||||||
|
|
||||||
|
Le module de dialogue ne doit connaître ni :
|
||||||
|
|
||||||
|
- `Application` ;
|
||||||
|
- `InvestigationSession` ;
|
||||||
|
- SQLite ;
|
||||||
|
- les règles métier de l’enquête.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Travail à réaliser
|
||||||
|
|
||||||
|
## 1. Créer l’en-tête public
|
||||||
|
|
||||||
|
Créer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/views/application_message_dialog.h
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu attendu :
|
||||||
|
|
||||||
|
```c
|
||||||
|
/******************************************************************************
|
||||||
|
* @file application_message_dialog.h
|
||||||
|
* @brief Fenêtre modale affichant un message à l'utilisateur.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H
|
||||||
|
#define LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nature du message affiché.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
APPLICATION_MESSAGE_DIALOG_ERROR,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_WARNING,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION
|
||||||
|
} ApplicationMessageDialogType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche une fenêtre modale contenant un message.
|
||||||
|
*
|
||||||
|
* Les chaînes sont copiées par les widgets GTK.
|
||||||
|
*
|
||||||
|
* @param parent_window Fenêtre parente, ou NULL.
|
||||||
|
* @param message_type Type de message.
|
||||||
|
* @param title Titre de la fenêtre.
|
||||||
|
* @param message Message principal.
|
||||||
|
*/
|
||||||
|
void application_message_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Créer l’implémentation GTK
|
||||||
|
|
||||||
|
Créer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Le dialogue doit être construit avec des widgets GTK4 simples afin de rester indépendant d’une API de dialogue plus récente.
|
||||||
|
|
||||||
|
Structure visuelle recommandée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Titre │
|
||||||
|
│ │
|
||||||
|
│ Message pouvant occuper plusieurs lignes │
|
||||||
|
│ │
|
||||||
|
│ [ Fermer ] │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Le module doit utiliser au minimum :
|
||||||
|
|
||||||
|
- `GtkWindow` ;
|
||||||
|
- `GtkBox` ;
|
||||||
|
- `GtkLabel` ;
|
||||||
|
- `GtkButton`.
|
||||||
|
|
||||||
|
Propriétés recommandées :
|
||||||
|
|
||||||
|
```c
|
||||||
|
gtk_window_set_modal(dialog_window, TRUE);
|
||||||
|
gtk_window_set_destroy_with_parent(dialog_window, TRUE);
|
||||||
|
gtk_window_set_resizable(dialog_window, FALSE);
|
||||||
|
```
|
||||||
|
|
||||||
|
Si `parent_window` n’est pas `NULL` :
|
||||||
|
|
||||||
|
```c
|
||||||
|
gtk_window_set_transient_for(
|
||||||
|
dialog_window,
|
||||||
|
parent_window
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Le message doit :
|
||||||
|
|
||||||
|
- revenir automatiquement à la ligne ;
|
||||||
|
- être aligné à gauche ;
|
||||||
|
- être sélectionnable pour permettre sa copie ;
|
||||||
|
- avoir une largeur raisonnable.
|
||||||
|
|
||||||
|
Exemple :
|
||||||
|
|
||||||
|
```c
|
||||||
|
gtk_label_set_wrap(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_selectable(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_xalign(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
0.0F
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_max_width_chars(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
70
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Gérer les arguments invalides
|
||||||
|
|
||||||
|
La fonction doit accepter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
parent_window == NULL
|
||||||
|
title == NULL
|
||||||
|
title vide
|
||||||
|
message == NULL
|
||||||
|
message vide
|
||||||
|
```
|
||||||
|
|
||||||
|
Valeurs de remplacement recommandées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Titre :
|
||||||
|
Erreur
|
||||||
|
Avertissement
|
||||||
|
Information
|
||||||
|
|
||||||
|
Message :
|
||||||
|
Aucun détail supplémentaire n'est disponible.
|
||||||
|
```
|
||||||
|
|
||||||
|
Le titre par défaut dépend de `message_type`.
|
||||||
|
|
||||||
|
Une valeur inconnue de `message_type` doit être traitée comme une information ou un avertissement, sans crash.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Ajouter un callback privé de fermeture
|
||||||
|
|
||||||
|
Dans l’implémentation, ajouter :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void application_message_dialog_on_close_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce callback doit :
|
||||||
|
|
||||||
|
1. ignorer proprement `button` ;
|
||||||
|
2. vérifier le pointeur de fenêtre ;
|
||||||
|
3. appeler `gtk_window_destroy()`.
|
||||||
|
|
||||||
|
Exemple :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void application_message_dialog_on_close_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWindow *dialog_window = user_data;
|
||||||
|
|
||||||
|
(void) button;
|
||||||
|
|
||||||
|
if (dialog_window == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_destroy(
|
||||||
|
dialog_window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune structure allouée manuellement ne doit être nécessaire pour ce premier dialogue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Différencier visuellement les types
|
||||||
|
|
||||||
|
Le dialogue doit au minimum afficher un libellé distinct :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Erreur
|
||||||
|
Avertissement
|
||||||
|
Information
|
||||||
|
```
|
||||||
|
|
||||||
|
Une différenciation légère peut être ajoutée avec des classes CSS GTK existantes sur le titre ou le bouton.
|
||||||
|
|
||||||
|
La couleur ne doit jamais être le seul moyen de distinguer le type.
|
||||||
|
|
||||||
|
Aucune feuille CSS spécifique n’est nécessaire dans ce ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Ajouter la source au Makefile
|
||||||
|
|
||||||
|
Ajouter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
```
|
||||||
|
|
||||||
|
à la liste des sources de l’application.
|
||||||
|
|
||||||
|
Le module doit être compilé avec les mêmes options strictes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
-std=c17
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wpedantic
|
||||||
|
-Werror
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Intégration dans `Application`
|
||||||
|
|
||||||
|
## 7. Ajouter l’en-tête
|
||||||
|
|
||||||
|
Dans :
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
ajouter :
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "views/application_message_dialog.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Créer un helper privé dans `application.c`
|
||||||
|
|
||||||
|
Ajouter :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void application_present_error(
|
||||||
|
Application *application,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Implémentation attendue :
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void application_present_error(
|
||||||
|
Application *application,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWindow *parent_window = NULL;
|
||||||
|
|
||||||
|
if (application != NULL &&
|
||||||
|
application->main_window != NULL)
|
||||||
|
{
|
||||||
|
parent_window = main_window_get_window(
|
||||||
|
application->main_window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
application_message_dialog_present(
|
||||||
|
parent_window,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_ERROR,
|
||||||
|
title,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce helper centralise le choix du parent GTK.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Afficher les erreurs d’ouverture
|
||||||
|
|
||||||
|
Dans :
|
||||||
|
|
||||||
|
```c
|
||||||
|
application_on_folder_selected()
|
||||||
|
```
|
||||||
|
|
||||||
|
conserver le `g_warning()` existant.
|
||||||
|
|
||||||
|
Ajouter ensuite :
|
||||||
|
|
||||||
|
```c
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ouverture impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "L'enquête sélectionnée n'a pas pu être ouverte."
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
L’erreur doit être affichée avant :
|
||||||
|
|
||||||
|
```c
|
||||||
|
g_clear_error(&error);
|
||||||
|
```
|
||||||
|
|
||||||
|
L’annulation du sélecteur ne doit toujours afficher aucun message.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Afficher les erreurs de création
|
||||||
|
|
||||||
|
Dans :
|
||||||
|
|
||||||
|
```c
|
||||||
|
application_on_create_investigation()
|
||||||
|
```
|
||||||
|
|
||||||
|
traiter les deux échecs.
|
||||||
|
|
||||||
|
### Échec de création physique
|
||||||
|
|
||||||
|
Conserver le `g_warning()` puis afficher :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Titre :
|
||||||
|
Création impossible
|
||||||
|
|
||||||
|
Message :
|
||||||
|
L'enquête n'a pas pu être créée dans le dossier sélectionné.
|
||||||
|
```
|
||||||
|
|
||||||
|
Le message peut contenir :
|
||||||
|
|
||||||
|
- le nom de l’enquête ;
|
||||||
|
- le dossier parent.
|
||||||
|
|
||||||
|
### Projet créé mais impossible à ouvrir
|
||||||
|
|
||||||
|
Conserver le `g_warning()` puis afficher un message indiquant clairement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
L'enquête a été créée sur le disque, mais Labfy n'a pas pu l'ouvrir.
|
||||||
|
```
|
||||||
|
|
||||||
|
Ajouter ensuite le détail du `GError`.
|
||||||
|
|
||||||
|
Le message doit éviter de laisser croire que le dossier créé a été supprimé.
|
||||||
|
|
||||||
|
Une chaîne dynamique peut être construite avec :
|
||||||
|
|
||||||
|
```c
|
||||||
|
g_strdup_printf()
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle doit être libérée après l’appel au dialogue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Exemple d’intégration
|
||||||
|
|
||||||
|
```c
|
||||||
|
char *user_message = NULL;
|
||||||
|
|
||||||
|
user_message = g_strdup_printf(
|
||||||
|
"L'enquête a été créée dans :\n%s\n\n"
|
||||||
|
"Elle n'a cependant pas pu être ouverte :\n%s",
|
||||||
|
created_root_path,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enquête créée mais non ouverte",
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gestion de plusieurs erreurs
|
||||||
|
|
||||||
|
Ce ticket ne crée pas encore de file de notifications.
|
||||||
|
|
||||||
|
Si plusieurs erreurs surviennent successivement, plusieurs fenêtres peuvent être affichées.
|
||||||
|
|
||||||
|
La gestion centralisée des tâches et notifications arrivera avec les tickets #034 et #035.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tests manuels
|
||||||
|
|
||||||
|
## Dossier invalide
|
||||||
|
|
||||||
|
1. lancer l’application ;
|
||||||
|
2. ouvrir une enquête valide A ;
|
||||||
|
3. cliquer sur `Ouvrir une enquête` ;
|
||||||
|
4. sélectionner un dossier qui n’est pas une enquête.
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
- une fenêtre d’erreur apparaît ;
|
||||||
|
- le message explique l’échec ;
|
||||||
|
- A reste active ;
|
||||||
|
- le terminal contient encore le `g_warning()` ;
|
||||||
|
- aucun crash.
|
||||||
|
|
||||||
|
## Annulation
|
||||||
|
|
||||||
|
1. ouvrir le sélecteur ;
|
||||||
|
2. annuler.
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
- aucun dialogue ;
|
||||||
|
- aucun warning ;
|
||||||
|
- aucun changement de session.
|
||||||
|
|
||||||
|
## Création dans un emplacement invalide
|
||||||
|
|
||||||
|
Provoquer si possible un échec de création :
|
||||||
|
|
||||||
|
- dossier non accessible en écriture ;
|
||||||
|
- nom déjà utilisé ;
|
||||||
|
- emplacement invalide.
|
||||||
|
|
||||||
|
Vérifier :
|
||||||
|
|
||||||
|
- dialogue visible ;
|
||||||
|
- message compréhensible ;
|
||||||
|
- aucune session remplacée.
|
||||||
|
|
||||||
|
## Projet créé mais ouverture échouée
|
||||||
|
|
||||||
|
Provoquer si possible un échec après la création.
|
||||||
|
|
||||||
|
Vérifier que le dialogue précise :
|
||||||
|
|
||||||
|
- que le dossier existe ;
|
||||||
|
- où il se trouve ;
|
||||||
|
- que seule l’ouverture a échoué.
|
||||||
|
|
||||||
|
## Fermeture du dialogue
|
||||||
|
|
||||||
|
Tester :
|
||||||
|
|
||||||
|
- bouton `Fermer` ;
|
||||||
|
- bouton de fermeture de la fenêtre ;
|
||||||
|
- fermeture de la fenêtre principale.
|
||||||
|
|
||||||
|
Aucun segfault ne doit apparaître.
|
||||||
|
|
||||||
|
## Répétition
|
||||||
|
|
||||||
|
Produire plusieurs erreurs successives.
|
||||||
|
|
||||||
|
Vérifier qu’une nouvelle erreur reste affichable après fermeture de la précédente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Critères d’acceptation
|
||||||
|
|
||||||
|
- [ ] Le module `application_message_dialog` existe.
|
||||||
|
- [ ] Le module ne dépend pas du cœur métier.
|
||||||
|
- [ ] Le dialogue est modal.
|
||||||
|
- [ ] Le dialogue possède une fenêtre parente lorsqu’elle existe.
|
||||||
|
- [ ] Le message revient à la ligne.
|
||||||
|
- [ ] Le message est sélectionnable.
|
||||||
|
- [ ] Les arguments `NULL` sont acceptés.
|
||||||
|
- [ ] Un bouton `Fermer` fonctionne.
|
||||||
|
- [ ] Les erreurs d’ouverture sont visibles dans GTK.
|
||||||
|
- [ ] Les erreurs de création sont visibles dans GTK.
|
||||||
|
- [ ] Les `g_warning()` techniques sont conservés.
|
||||||
|
- [ ] L’annulation ne produit aucun dialogue.
|
||||||
|
- [ ] L’ancienne session est conservée en cas d’échec.
|
||||||
|
- [ ] Aucun appel SQLite direct n’est ajouté.
|
||||||
|
- [ ] Aucun `exit()` direct n’est ajouté.
|
||||||
|
- [ ] `make` réussit.
|
||||||
|
- [ ] `make test` réussit.
|
||||||
|
- [ ] `git diff --check` ne retourne aucune erreur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Audit attendu
|
||||||
|
|
||||||
|
Vérifier l’utilisation du module :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'application_message_dialog|application_present_error' \
|
||||||
|
include/views/application_message_dialog.h \
|
||||||
|
src/views/application_message_dialog.c \
|
||||||
|
src/core/application.c \
|
||||||
|
Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier que les erreurs restent journalisées :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'g_warning' \
|
||||||
|
src/core/application.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier l’absence de logique métier dans le dialogue :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'Investigation|Database|sqlite3_' \
|
||||||
|
include/views/application_message_dialog.h \
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune sortie
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier l’absence de sortie forcée :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n \
|
||||||
|
'\bexit\s*\(' \
|
||||||
|
src/core/application.c \
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune sortie
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fichiers concernés
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/views/application_message_dialog.h
|
||||||
|
src/views/application_message_dialog.c
|
||||||
|
src/core/application.c
|
||||||
|
Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Commit attendu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
git diff --check
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add \
|
||||||
|
include/views/application_message_dialog.h \
|
||||||
|
src/views/application_message_dialog.c \
|
||||||
|
src/core/application.c \
|
||||||
|
Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --cached --stat
|
||||||
|
git diff --cached
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat(ui): display application errors in GTK"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Résultat attendu
|
||||||
|
|
||||||
|
Après ce ticket, l’utilisateur ne dépendra plus du terminal pour comprendre pourquoi une enquête n’a pas pu être créée ou ouverte.
|
||||||
|
|
||||||
|
Le ticket #034 pourra ensuite introduire le gestionnaire de tâches asynchrones sans mélanger la présentation des erreurs avec l’exécution des traitements longs.
|
||||||
38
include/views/application_message_dialog.h
Normal file
38
include/views/application_message_dialog.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file application_message_dialog.h
|
||||||
|
* @brief Fenêtre modale affichant un message à l'utilisateur.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H
|
||||||
|
#define LABFY_INVESTIGATION_APPLICATION_MESSAGE_DIALOG_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nature du message affiché.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
APPLICATION_MESSAGE_DIALOG_ERROR,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_WARNING,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION
|
||||||
|
} ApplicationMessageDialogType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche une fenêtre modale contenant un message.
|
||||||
|
*
|
||||||
|
* Les chaînes sont copiées par les widgets GTK.
|
||||||
|
*
|
||||||
|
* @param parent_window Fenêtre parente, ou NULL.
|
||||||
|
* @param message_type Type de message.
|
||||||
|
* @param title Titre de la fenêtre, ou NULL.
|
||||||
|
* @param message Message principal, ou NULL.
|
||||||
|
*/
|
||||||
|
void application_message_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
Binary file not shown.
|
|
@ -14,6 +14,7 @@
|
||||||
#include "views/create_investigation_dialog.h"
|
#include "views/create_investigation_dialog.h"
|
||||||
#include "views/folder_dialog.h"
|
#include "views/folder_dialog.h"
|
||||||
#include "views/main_window.h"
|
#include "views/main_window.h"
|
||||||
|
#include "views/application_message_dialog.h"
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
|
@ -65,6 +66,37 @@ struct Application
|
||||||
InvestigationTreeModel *tree_model;
|
InvestigationTreeModel *tree_model;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche une erreur dans une fenêtre GTK.
|
||||||
|
*
|
||||||
|
* @param application Application propriétaire de la fenêtre principale.
|
||||||
|
* @param title Titre compréhensible par l'utilisateur.
|
||||||
|
* @param message Description de l'erreur.
|
||||||
|
*/
|
||||||
|
static void application_present_error(
|
||||||
|
Application *application,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWindow *parent_window = NULL;
|
||||||
|
|
||||||
|
if (application != NULL &&
|
||||||
|
application->main_window != NULL)
|
||||||
|
{
|
||||||
|
parent_window = main_window_get_window(
|
||||||
|
application->main_window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
application_message_dialog_present(
|
||||||
|
parent_window,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_ERROR,
|
||||||
|
title,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Installe une nouvelle session et son arbre dans l'application.
|
* @brief Installe une nouvelle session et son arbre dans l'application.
|
||||||
*
|
*
|
||||||
|
|
@ -390,6 +422,14 @@ static void application_on_folder_selected(
|
||||||
: "erreur inconnue"
|
: "erreur inconnue"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Ouverture impossible",
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "L'enquête sélectionnée n'a pas pu être ouverte."
|
||||||
|
);
|
||||||
|
|
||||||
g_clear_error(
|
g_clear_error(
|
||||||
&error
|
&error
|
||||||
);
|
);
|
||||||
|
|
@ -412,6 +452,8 @@ static void application_on_create_investigation(
|
||||||
Application *application = user_data;
|
Application *application = user_data;
|
||||||
|
|
||||||
char *created_root_path = NULL;
|
char *created_root_path = NULL;
|
||||||
|
char *user_message = NULL;
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
if (application == NULL)
|
if (application == NULL)
|
||||||
|
|
@ -441,6 +483,25 @@ static void application_on_create_investigation(
|
||||||
parent_directory
|
parent_directory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
user_message = g_strdup_printf(
|
||||||
|
"L'enquête « %s » n'a pas pu être créée dans :\n"
|
||||||
|
"%s\n\n"
|
||||||
|
"Vérifiez que le dossier existe et que vous disposez "
|
||||||
|
"des droits d'écriture nécessaires.",
|
||||||
|
investigation_name,
|
||||||
|
parent_directory
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Création impossible",
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -459,11 +520,31 @@ static void application_on_create_investigation(
|
||||||
: "erreur inconnue"
|
: "erreur inconnue"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
user_message = g_strdup_printf(
|
||||||
|
"L'enquête a bien été créée sur le disque dans :\n"
|
||||||
|
"%s\n\n"
|
||||||
|
"Labfy Investigation n'a cependant pas pu l'ouvrir :\n"
|
||||||
|
"%s",
|
||||||
|
created_root_path,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Aucun détail supplémentaire n'est disponible."
|
||||||
|
);
|
||||||
|
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Enquête créée mais non ouverte",
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
user_message
|
||||||
|
);
|
||||||
|
|
||||||
g_clear_error(
|
g_clear_error(
|
||||||
&error
|
&error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
created_root_path
|
created_root_path
|
||||||
);
|
);
|
||||||
|
|
|
||||||
337
src/views/application_message_dialog.c
Normal file
337
src/views/application_message_dialog.c
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file application_message_dialog.c
|
||||||
|
* @brief Implémentation de la fenêtre modale de message.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "views/application_message_dialog.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Largeur minimale du dialogue.
|
||||||
|
*/
|
||||||
|
#define APPLICATION_MESSAGE_DIALOG_DEFAULT_WIDTH 480
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Message utilisé lorsqu'aucun détail n'est disponible.
|
||||||
|
*/
|
||||||
|
#define APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE \
|
||||||
|
"Aucun détail supplémentaire n'est disponible."
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le libellé correspondant au type de message.
|
||||||
|
*
|
||||||
|
* @param message_type Type du message.
|
||||||
|
*
|
||||||
|
* @return Libellé statique du type.
|
||||||
|
*/
|
||||||
|
static const char *application_message_dialog_get_type_label(
|
||||||
|
ApplicationMessageDialogType message_type
|
||||||
|
)
|
||||||
|
{
|
||||||
|
switch (message_type)
|
||||||
|
{
|
||||||
|
case APPLICATION_MESSAGE_DIALOG_ERROR:
|
||||||
|
return "Erreur";
|
||||||
|
|
||||||
|
case APPLICATION_MESSAGE_DIALOG_WARNING:
|
||||||
|
return "Avertissement";
|
||||||
|
|
||||||
|
case APPLICATION_MESSAGE_DIALOG_INFORMATION:
|
||||||
|
return "Information";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Information";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le titre à utiliser pour le dialogue.
|
||||||
|
*
|
||||||
|
* @param message_type Type du message.
|
||||||
|
* @param title Titre fourni par l'appelant.
|
||||||
|
*
|
||||||
|
* @return Titre fourni ou titre par défaut.
|
||||||
|
*/
|
||||||
|
static const char *application_message_dialog_get_safe_title(
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (title != NULL &&
|
||||||
|
title[0] != '\0')
|
||||||
|
{
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return application_message_dialog_get_type_label(
|
||||||
|
message_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne un message toujours valide.
|
||||||
|
*
|
||||||
|
* @param message Message fourni par l'appelant.
|
||||||
|
*
|
||||||
|
* @return Message fourni ou message par défaut.
|
||||||
|
*/
|
||||||
|
static const char *application_message_dialog_get_safe_message(
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (message != NULL &&
|
||||||
|
message[0] != '\0')
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ferme le dialogue lorsque l'utilisateur clique sur le bouton.
|
||||||
|
*
|
||||||
|
* @param button Bouton ayant reçu le clic.
|
||||||
|
* @param user_data Pointeur vers la fenêtre du dialogue.
|
||||||
|
*/
|
||||||
|
static void application_message_dialog_on_close_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWindow *dialog_window = user_data;
|
||||||
|
|
||||||
|
(void) button;
|
||||||
|
|
||||||
|
if (dialog_window == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_destroy(
|
||||||
|
dialog_window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void application_message_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWindow *dialog_window = NULL;
|
||||||
|
|
||||||
|
GtkWidget *main_box = NULL;
|
||||||
|
GtkWidget *type_label = NULL;
|
||||||
|
GtkWidget *title_label = NULL;
|
||||||
|
GtkWidget *message_label = NULL;
|
||||||
|
GtkWidget *button_box = NULL;
|
||||||
|
GtkWidget *close_button = NULL;
|
||||||
|
|
||||||
|
const char *safe_title = NULL;
|
||||||
|
const char *safe_message = NULL;
|
||||||
|
const char *type_text = NULL;
|
||||||
|
|
||||||
|
safe_title =
|
||||||
|
application_message_dialog_get_safe_title(
|
||||||
|
message_type,
|
||||||
|
title
|
||||||
|
);
|
||||||
|
|
||||||
|
safe_message =
|
||||||
|
application_message_dialog_get_safe_message(
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
|
type_text =
|
||||||
|
application_message_dialog_get_type_label(
|
||||||
|
message_type
|
||||||
|
);
|
||||||
|
|
||||||
|
dialog_window = GTK_WINDOW(
|
||||||
|
gtk_window_new()
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_title(
|
||||||
|
dialog_window,
|
||||||
|
safe_title
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_default_size(
|
||||||
|
dialog_window,
|
||||||
|
APPLICATION_MESSAGE_DIALOG_DEFAULT_WIDTH,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_modal(
|
||||||
|
dialog_window,
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_destroy_with_parent(
|
||||||
|
dialog_window,
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_resizable(
|
||||||
|
dialog_window,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parent_window != NULL)
|
||||||
|
{
|
||||||
|
gtk_window_set_transient_for(
|
||||||
|
dialog_window,
|
||||||
|
parent_window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main_box = gtk_box_new(
|
||||||
|
GTK_ORIENTATION_VERTICAL,
|
||||||
|
12
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_margin_start(
|
||||||
|
main_box,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_margin_end(
|
||||||
|
main_box,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_margin_top(
|
||||||
|
main_box,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_margin_bottom(
|
||||||
|
main_box,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
type_label = gtk_label_new(
|
||||||
|
type_text
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_halign(
|
||||||
|
type_label,
|
||||||
|
GTK_ALIGN_START
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_xalign(
|
||||||
|
GTK_LABEL(type_label),
|
||||||
|
0.0F
|
||||||
|
);
|
||||||
|
|
||||||
|
title_label = gtk_label_new(
|
||||||
|
safe_title
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_halign(
|
||||||
|
title_label,
|
||||||
|
GTK_ALIGN_START
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_xalign(
|
||||||
|
GTK_LABEL(title_label),
|
||||||
|
0.0F
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_wrap(
|
||||||
|
GTK_LABEL(title_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
message_label = gtk_label_new(
|
||||||
|
safe_message
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_halign(
|
||||||
|
message_label,
|
||||||
|
GTK_ALIGN_FILL
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_xalign(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
0.0F
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_wrap(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_wrap_mode(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
PANGO_WRAP_WORD_CHAR
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_selectable(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_label_set_max_width_chars(
|
||||||
|
GTK_LABEL(message_label),
|
||||||
|
70
|
||||||
|
);
|
||||||
|
|
||||||
|
button_box = gtk_box_new(
|
||||||
|
GTK_ORIENTATION_HORIZONTAL,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_widget_set_halign(
|
||||||
|
button_box,
|
||||||
|
GTK_ALIGN_END
|
||||||
|
);
|
||||||
|
|
||||||
|
close_button = gtk_button_new_with_label(
|
||||||
|
"Fermer"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_signal_connect(
|
||||||
|
close_button,
|
||||||
|
"clicked",
|
||||||
|
G_CALLBACK(
|
||||||
|
application_message_dialog_on_close_clicked
|
||||||
|
),
|
||||||
|
dialog_window
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(button_box),
|
||||||
|
close_button
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(main_box),
|
||||||
|
type_label
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(main_box),
|
||||||
|
title_label
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(main_box),
|
||||||
|
message_label
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(main_box),
|
||||||
|
button_box
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_set_child(
|
||||||
|
dialog_window,
|
||||||
|
main_box
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_window_present(
|
||||||
|
dialog_window
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
tests/test_database
Executable file
BIN
tests/test_database
Executable file
Binary file not shown.
BIN
tests/test_error
Executable file
BIN
tests/test_error
Executable file
Binary file not shown.
BIN
tests/test_investigation_dao
Executable file
BIN
tests/test_investigation_dao
Executable file
Binary file not shown.
BIN
tests/test_investigation_node
Executable file
BIN
tests/test_investigation_node
Executable file
Binary file not shown.
BIN
tests/test_investigation_project
Executable file
BIN
tests/test_investigation_project
Executable file
Binary file not shown.
BIN
tests/test_investigation_record
Executable file
BIN
tests/test_investigation_record
Executable file
Binary file not shown.
BIN
tests/test_investigation_session
Executable file
BIN
tests/test_investigation_session
Executable file
Binary file not shown.
BIN
tests/test_investigation_tree_builder
Executable file
BIN
tests/test_investigation_tree_builder
Executable file
Binary file not shown.
BIN
tests/test_investigation_tree_model
Executable file
BIN
tests/test_investigation_tree_model
Executable file
Binary file not shown.
BIN
tests/test_statement
Executable file
BIN
tests/test_statement
Executable file
Binary file not shown.
BIN
tests/test_transaction
Executable file
BIN
tests/test_transaction
Executable file
Binary file not shown.
Loading…
Reference in a new issue