feat(ui): display application errors in GTK

This commit is contained in:
grayTerminal-sh 2026-07-17 10:05:29 +02:00
parent 9ee7ee61c1
commit f7b521cda6
19 changed files with 2987 additions and 1 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,537 @@
# Ticket #032 — Factoriser le chargement et linstallation dune enquête
## Contexte
Après le ticket #031.1, lapplication sait :
- créer une enquête ;
- ouvrir une enquête existante ;
- remplacer la session active ;
- fermer proprement lapplication.
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 à lappelant
```
---
# Travail à réaliser
## 1. Ajouter un domaine derreur privé
Dans `src/core/application.c`, ajouter un domaine derreur 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 quelle possède encore
```
### Validation de `GError`
La fonction doit respecter la convention GLib :
```c
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
```
Lutilisation 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 lerreur de `InvestigationSession`
Si :
```c
investigation_session_open()
```
échoue, la fonction doit conserver lerreur métier dorigine et lui ajouter du contexte.
Exemple conceptuel :
```text
Impossible douvrir lenquête : la base SQLite est absente
```
Ne pas remplacer lerreur précise par un simple :
```text
Erreur inconnue
```
lorsquun `GError` est disponible.
---
## 4. Refactoriser louverture dune 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 lannulation ;
2. appeler `application_open_and_install_investigation()` ;
3. journaliser lerreur ;
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);
}
}
```
Lannulation ne doit pas produire de warning.
---
## 5. Refactoriser la création dune enquête
`application_on_create_investigation()` doit conserver uniquement :
1. la création physique du projet ;
2. lappel à 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
Lenquê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 lancien é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 lapplication ;
2. créer une enquête ;
3. vérifier le titre ;
4. vérifier la barre détat ;
5. vérifier larborescence.
## Ouverture valide
1. fermer et relancer ;
2. ouvrir lenquê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 douverture
1. ouvrir une enquête valide A ;
2. tenter douvrir un dossier invalide ;
3. vérifier que A reste active ;
4. vérifier que lerreur est journalisée.
## Échec après création
Provoquer si possible un échec douverture après création.
Vérifier :
- le dossier créé reste présent ;
- lancienne 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 dacceptation
- [ ] Une seule fonction ouvre une session et construit son arbre.
- [ ] La création utilise cette fonction.
- [ ] Louverture utilise cette fonction.
- [ ] `application_install_session()` nest pas dupliquée.
- [ ] Lancienne session reste active en cas déchec.
- [ ] Lancien 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.
- [ ] Lannulation ne produit pas derreur.
- [ ] Aucun code SQLite direct nest ajouté.
- [ ] Aucun comportement GTK visible nest cassé.
- [ ] `make` réussit.
- [ ] `make test` réussit.
- [ ] `git diff --check` ne retourne aucune erreur.
---
# Audit attendu
La logique douverture ne doit apparaître quune 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 louverture doivent appeler le helper :
```bash
rg -n \
'application_open_and_install_investigation' \
src/core/application.c
```
Vérifier labsence 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 nest 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 dentré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.

View file

@ -0,0 +1,694 @@
# Ticket #033 — Afficher les erreurs dans linterface GTK
## Contexte
Le ticket #032 a centralisé le chargement dune enquête dans :
```c
application_open_and_install_investigation()
```
Cette fonction produit maintenant des `GError` précis et conserve lancienne 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 dapplications ne verra pas ces messages.
## Objectif
Créer un module GTK réutilisable capable dafficher un message derreur compréhensible dans une fenêtre modale.
Le contrôleur doit conserver deux niveaux dinformation :
```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 lenquête.
---
# Travail à réaliser
## 1. Créer len-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 limplé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 dune 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` nest 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 limplé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 nest nécessaire dans ce ticket.
---
## 6. Ajouter la source au Makefile
Ajouter :
```text
src/views/application_message_dialog.c
```
à la liste des sources de lapplication.
Le module doit être compilé avec les mêmes options strictes :
```text
-std=c17
-Wall
-Wextra
-Wpedantic
-Werror
```
---
# Intégration dans `Application`
## 7. Ajouter len-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 douverture
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."
);
```
Lerreur doit être affichée avant :
```c
g_clear_error(&error);
```
Lannulation 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 lenquê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 lappel au dialogue.
---
# Exemple dinté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 lapplication ;
2. ouvrir une enquête valide A ;
3. cliquer sur `Ouvrir une enquête` ;
4. sélectionner un dossier qui nest pas une enquête.
Vérifier :
- une fenêtre derreur 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 louverture 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 quune nouvelle erreur reste affichable après fermeture de la précédente.
---
# Critères dacceptation
- [ ] 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 lorsquelle existe.
- [ ] Le message revient à la ligne.
- [ ] Le message est sélectionnable.
- [ ] Les arguments `NULL` sont acceptés.
- [ ] Un bouton `Fermer` fonctionne.
- [ ] Les erreurs douverture sont visibles dans GTK.
- [ ] Les erreurs de création sont visibles dans GTK.
- [ ] Les `g_warning()` techniques sont conservés.
- [ ] Lannulation ne produit aucun dialogue.
- [ ] Lancienne session est conservée en cas déchec.
- [ ] Aucun appel SQLite direct nest ajouté.
- [ ] Aucun `exit()` direct nest ajouté.
- [ ] `make` réussit.
- [ ] `make test` réussit.
- [ ] `git diff --check` ne retourne aucune erreur.
---
# Audit attendu
Vérifier lutilisation 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 labsence 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 labsence 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, lutilisateur ne dépendra plus du terminal pour comprendre pourquoi une enquête na 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 lexécution des traitements longs.

View 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.

View file

@ -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
); );

View 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

Binary file not shown.

BIN
tests/test_error Executable file

Binary file not shown.

BIN
tests/test_investigation_dao Executable file

Binary file not shown.

BIN
tests/test_investigation_node Executable file

Binary file not shown.

BIN
tests/test_investigation_project Executable file

Binary file not shown.

BIN
tests/test_investigation_record Executable file

Binary file not shown.

BIN
tests/test_investigation_session Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/test_statement Executable file

Binary file not shown.

BIN
tests/test_transaction Executable file

Binary file not shown.