feat(ui): add investigation creation and opening workflows
This commit is contained in:
parent
3260b476e5
commit
a4f7eac949
16 changed files with 538 additions and 8 deletions
301
docs/tickets/open/TICKET-031.md
Normal file
301
docs/tickets/open/TICKET-031.md
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
# Ticket #031 — Ouvrir une enquête existante depuis GTK
|
||||
|
||||
## Contexte
|
||||
|
||||
Les tickets #029 et #030 ont permis de :
|
||||
|
||||
- créer une enquête depuis GTK ;
|
||||
- initialiser son arborescence et sa base SQLite ;
|
||||
- ouvrir une `InvestigationSession` ;
|
||||
- construire le `InvestigationTreeModel` ;
|
||||
- installer la session dans `Application` ;
|
||||
- afficher le nom et le chemin de l’enquête dans `MainWindow`.
|
||||
|
||||
L’application ne permet cependant pas encore d’ouvrir explicitement une enquête existante.
|
||||
|
||||
L’ancien sélecteur automatique au démarrage a été supprimé afin de ne pas forcer l’utilisateur à choisir un dossier à chaque lancement.
|
||||
|
||||
## Objectif
|
||||
|
||||
Ajouter un bouton :
|
||||
|
||||
```text
|
||||
Ouvrir une enquête
|
||||
```
|
||||
|
||||
Ce bouton doit permettre de sélectionner le dossier racine d’une enquête existante, puis de l’ouvrir avec le flux déjà présent :
|
||||
|
||||
```text
|
||||
FolderDialog
|
||||
↓
|
||||
investigation_session_open()
|
||||
↓
|
||||
investigation_tree_builder_build()
|
||||
↓
|
||||
application_install_session()
|
||||
↓
|
||||
MainWindow mise à jour
|
||||
```
|
||||
|
||||
La logique d’installation d’une session ne doit pas être dupliquée.
|
||||
|
||||
## Travail à réaliser
|
||||
|
||||
### 1. Ajouter le bouton dans `MainWindow`
|
||||
|
||||
Modifier :
|
||||
|
||||
```text
|
||||
include/views/main_window.h
|
||||
src/views/main_window.c
|
||||
```
|
||||
|
||||
Ajouter un bouton visible dans la barre d’actions :
|
||||
|
||||
```text
|
||||
Ouvrir une enquête
|
||||
```
|
||||
|
||||
La barre doit contenir au minimum :
|
||||
|
||||
```text
|
||||
[ Nouvelle enquête ] [ Ouvrir une enquête ]
|
||||
```
|
||||
|
||||
Ajouter dans la structure privée :
|
||||
|
||||
```c
|
||||
GtkWidget *open_investigation_button;
|
||||
```
|
||||
|
||||
### 2. Ajouter le type de callback
|
||||
|
||||
Dans `include/views/main_window.h`, ajouter :
|
||||
|
||||
```c
|
||||
typedef void (*MainWindowOpenInvestigationCallback)(
|
||||
gpointer user_data
|
||||
);
|
||||
```
|
||||
|
||||
Puis déclarer :
|
||||
|
||||
```c
|
||||
void main_window_set_open_investigation_callback(
|
||||
MainWindow *main_window,
|
||||
MainWindowOpenInvestigationCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Conserver le callback dans `MainWindow`
|
||||
|
||||
Ajouter dans la structure privée :
|
||||
|
||||
```c
|
||||
MainWindowOpenInvestigationCallback
|
||||
open_investigation_callback;
|
||||
|
||||
gpointer
|
||||
open_investigation_user_data;
|
||||
```
|
||||
|
||||
Ajouter un callback privé :
|
||||
|
||||
```c
|
||||
static void main_window_on_open_investigation_clicked(
|
||||
GtkButton *button,
|
||||
gpointer user_data
|
||||
);
|
||||
```
|
||||
|
||||
Il doit appeler le callback configuré uniquement s’il existe.
|
||||
|
||||
`MainWindow` ne doit pas ouvrir elle-même la session.
|
||||
|
||||
### 4. Relier le bouton à `Application`
|
||||
|
||||
Dans `src/core/application.c`, réactiver l’utilisation de :
|
||||
|
||||
```c
|
||||
#include "views/folder_dialog.h"
|
||||
```
|
||||
|
||||
Ajouter :
|
||||
|
||||
```c
|
||||
static void application_on_open_investigation_requested(
|
||||
gpointer user_data
|
||||
);
|
||||
```
|
||||
|
||||
Cette fonction doit ouvrir le sélecteur avec :
|
||||
|
||||
```c
|
||||
folder_dialog_select_folder(
|
||||
main_window_get_window(application->main_window),
|
||||
application_on_folder_selected,
|
||||
application
|
||||
);
|
||||
```
|
||||
|
||||
### 5. Réutiliser `application_on_folder_selected()`
|
||||
|
||||
La fonction doit :
|
||||
|
||||
1. accepter l’annulation ;
|
||||
2. ouvrir la session avec `investigation_session_open()` ;
|
||||
3. récupérer le chemin racine depuis `InvestigationProject` ;
|
||||
4. construire l’arbre avec `investigation_tree_builder_build()` ;
|
||||
5. installer les objets avec `application_install_session()`.
|
||||
|
||||
En cas d’échec :
|
||||
|
||||
```text
|
||||
ancienne session conservée
|
||||
ancien arbre conservé
|
||||
nouvelle session libérée
|
||||
nouvel arbre libéré
|
||||
warning explicite
|
||||
```
|
||||
|
||||
### 6. Enregistrer le callback dans `application_on_activate()`
|
||||
|
||||
Après la création de `MainWindow`, appeler :
|
||||
|
||||
```c
|
||||
main_window_set_open_investigation_callback(
|
||||
application->main_window,
|
||||
application_on_open_investigation_requested,
|
||||
application
|
||||
);
|
||||
```
|
||||
|
||||
Le sélecteur ne doit pas être lancé automatiquement au démarrage.
|
||||
|
||||
## Tests manuels
|
||||
|
||||
### Ouverture valide
|
||||
|
||||
1. lancer l’application ;
|
||||
2. cliquer sur `Ouvrir une enquête` ;
|
||||
3. sélectionner une enquête créée avec le ticket #030 ;
|
||||
4. vérifier l’affichage de l’arborescence ;
|
||||
5. vérifier le titre ;
|
||||
6. vérifier la barre d’état ;
|
||||
7. vérifier l’absence d’erreur SQLite.
|
||||
|
||||
### Annulation
|
||||
|
||||
Ouvrir le sélecteur puis annuler.
|
||||
|
||||
Vérifier :
|
||||
|
||||
```text
|
||||
aucun crash
|
||||
aucun changement de session
|
||||
aucun changement d’arbre
|
||||
```
|
||||
|
||||
### Dossier invalide
|
||||
|
||||
Sélectionner un dossier sans base SQLite.
|
||||
|
||||
Vérifier :
|
||||
|
||||
```text
|
||||
warning explicite
|
||||
aucune base créée
|
||||
ancienne session conservée
|
||||
```
|
||||
|
||||
### Remplacement
|
||||
|
||||
1. ouvrir une enquête A ;
|
||||
2. ouvrir une enquête B ;
|
||||
3. vérifier que B remplace A.
|
||||
|
||||
### Échec pendant le remplacement
|
||||
|
||||
1. ouvrir une enquête valide A ;
|
||||
2. tenter d’ouvrir un dossier invalide ;
|
||||
3. vérifier que A reste active.
|
||||
|
||||
## Critères d’acceptation
|
||||
|
||||
- [ ] Le bouton `Ouvrir une enquête` est visible.
|
||||
- [ ] Le bouton ouvre un sélecteur de dossier.
|
||||
- [ ] Le sélecteur ne s’ouvre pas automatiquement au démarrage.
|
||||
- [ ] Une enquête valide peut être ouverte.
|
||||
- [ ] L’arborescence est affichée.
|
||||
- [ ] Le titre est mis à jour.
|
||||
- [ ] La barre d’état est mise à jour.
|
||||
- [ ] L’annulation ne modifie aucun état.
|
||||
- [ ] Un dossier invalide est refusé.
|
||||
- [ ] Une base absente n’est pas créée.
|
||||
- [ ] L’ancienne session reste active en cas d’échec.
|
||||
- [ ] L’ancien arbre reste actif en cas d’échec.
|
||||
- [ ] `application_install_session()` est réutilisée.
|
||||
- [ ] Aucun appel SQLite direct n’est ajouté.
|
||||
- [ ] `make` réussit.
|
||||
- [ ] `make test` réussit.
|
||||
- [ ] `git diff --check` ne retourne aucune erreur.
|
||||
|
||||
## Audit attendu
|
||||
|
||||
```bash
|
||||
rg -n 'open_investigation|Ouvrir une enquête' include/views/main_window.h src/views/main_window.c src/core/application.c
|
||||
```
|
||||
|
||||
```bash
|
||||
rg -n 'folder_dialog_select_folder' src/core/application.c
|
||||
```
|
||||
|
||||
```bash
|
||||
rg -n 'sqlite3_|#include <sqlite3.h>' src/core/application.c src/views/main_window.c
|
||||
```
|
||||
|
||||
Résultat attendu pour la dernière commande :
|
||||
|
||||
```text
|
||||
aucune sortie
|
||||
```
|
||||
|
||||
## Fichiers principalement concernés
|
||||
|
||||
```text
|
||||
include/views/main_window.h
|
||||
src/views/main_window.c
|
||||
src/core/application.c
|
||||
```
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
À la fin du ticket, l’application doit proposer deux actions explicites :
|
||||
|
||||
```text
|
||||
Nouvelle enquête
|
||||
Ouvrir une enquête
|
||||
```
|
||||
|
||||
L’utilisateur doit pouvoir créer une enquête, fermer l’application, puis la rouvrir plus tard depuis GTK.
|
||||
|
||||
## 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 investigation opening action"
|
||||
git push
|
||||
```
|
||||
|
||||
|
|
@ -137,6 +137,31 @@ void main_window_set_status(
|
|||
const char *status_text
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Callback appelé lorsque l'utilisateur demande l'ouverture
|
||||
* d'une enquête existante.
|
||||
*
|
||||
* @param user_data Données utilisateur associées au callback.
|
||||
*/
|
||||
typedef void (*MainWindowOpenInvestigationCallback)(
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback du bouton « Ouvrir une enquête ».
|
||||
*
|
||||
* La fenêtre 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_open_investigation_callback(
|
||||
MainWindow *main_window,
|
||||
MainWindowOpenInvestigationCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Libère les ressources de la fenêtre.
|
||||
*
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -12,6 +12,7 @@
|
|||
#include "models/investigation_record.h"
|
||||
#include "core/investigation_tree_builder.h"
|
||||
#include "views/create_investigation_dialog.h"
|
||||
#include "views/folder_dialog.h"
|
||||
#include "views/main_window.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
|
@ -128,6 +129,118 @@ static gboolean application_install_session(
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite le dossier d'enquête sélectionné par l'utilisateur.
|
||||
*
|
||||
* @param folder_path Chemin sélectionné, ou NULL en cas d'annulation.
|
||||
* @param user_data Pointeur vers Application.
|
||||
*/
|
||||
static void application_on_folder_selected(
|
||||
const char *folder_path,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
Application *application = user_data;
|
||||
|
||||
InvestigationSession *new_session = NULL;
|
||||
InvestigationTreeModel *new_tree_model = NULL;
|
||||
|
||||
const InvestigationProject *project = NULL;
|
||||
const char *root_path = NULL;
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
if (application == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (folder_path == NULL)
|
||||
{
|
||||
g_print("Ouverture annulée.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
new_session = investigation_session_open(
|
||||
folder_path,
|
||||
&error
|
||||
);
|
||||
|
||||
if (new_session == NULL)
|
||||
{
|
||||
g_warning(
|
||||
"Impossible d'ouvrir l'enquête : %s",
|
||||
error != NULL
|
||||
? error->message
|
||||
: "erreur inconnue"
|
||||
);
|
||||
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
project = investigation_session_get_project(
|
||||
new_session
|
||||
);
|
||||
|
||||
if (project != NULL)
|
||||
{
|
||||
root_path = investigation_project_get_root_path(
|
||||
project
|
||||
);
|
||||
}
|
||||
|
||||
if (root_path == NULL ||
|
||||
root_path[0] == '\0')
|
||||
{
|
||||
g_warning(
|
||||
"La session ne fournit aucun chemin racine valide."
|
||||
);
|
||||
|
||||
investigation_session_close(
|
||||
new_session
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
new_tree_model = investigation_tree_builder_build(
|
||||
root_path
|
||||
);
|
||||
|
||||
if (new_tree_model == NULL)
|
||||
{
|
||||
g_warning(
|
||||
"Impossible de construire l'arborescence de l'enquête."
|
||||
);
|
||||
|
||||
investigation_session_close(
|
||||
new_session
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!application_install_session(
|
||||
application,
|
||||
new_session,
|
||||
new_tree_model
|
||||
))
|
||||
{
|
||||
g_warning(
|
||||
"Impossible d'installer l'enquête dans l'application."
|
||||
);
|
||||
|
||||
investigation_tree_model_free(
|
||||
new_tree_model
|
||||
);
|
||||
|
||||
investigation_session_close(
|
||||
new_session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite la demande de création d'une nouvelle enquête.
|
||||
*
|
||||
|
|
@ -303,6 +416,32 @@ static void application_on_new_investigation_requested(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ouvre le sélecteur d'une enquête existante.
|
||||
*
|
||||
* @param user_data Pointeur vers Application.
|
||||
*/
|
||||
static void application_on_open_investigation_requested(
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
Application *application = user_data;
|
||||
|
||||
if (application == NULL ||
|
||||
application->main_window == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
folder_dialog_select_folder(
|
||||
main_window_get_window(
|
||||
application->main_window
|
||||
),
|
||||
application_on_folder_selected,
|
||||
application
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite la sélection d'un nœud dans l'arborescence.
|
||||
*
|
||||
|
|
@ -414,17 +553,15 @@ static void application_on_activate(
|
|||
application
|
||||
);
|
||||
|
||||
main_window_set_open_investigation_callback(
|
||||
application->main_window,
|
||||
application_on_open_investigation_requested,
|
||||
application
|
||||
);
|
||||
|
||||
main_window_present(
|
||||
application->main_window
|
||||
);
|
||||
|
||||
/* folder_dialog_select_folder(
|
||||
main_window_get_window(
|
||||
application->main_window
|
||||
),
|
||||
application_on_folder_selected,
|
||||
application
|
||||
); */
|
||||
}
|
||||
|
||||
Application *application_new(void)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ struct MainWindow
|
|||
GtkWidget *main_box;
|
||||
GtkWidget *action_bar;
|
||||
GtkWidget *new_investigation_button;
|
||||
GtkWidget *open_investigation_button;
|
||||
GtkWidget *main_paned;
|
||||
GtkWidget *status_label;
|
||||
|
||||
|
|
@ -62,6 +63,12 @@ struct MainWindow
|
|||
|
||||
gpointer
|
||||
new_investigation_user_data;
|
||||
|
||||
MainWindowOpenInvestigationCallback
|
||||
open_investigation_callback;
|
||||
|
||||
gpointer
|
||||
open_investigation_user_data;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -90,6 +97,32 @@ static void main_window_on_new_investigation_clicked(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transmet la demande d'ouverture d'une enquête au contrôleur.
|
||||
*
|
||||
* @param button Bouton ayant reçu le clic.
|
||||
* @param user_data Pointeur vers MainWindow.
|
||||
*/
|
||||
static void main_window_on_open_investigation_clicked(
|
||||
GtkButton *button,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
MainWindow *main_window = user_data;
|
||||
|
||||
(void) button;
|
||||
|
||||
if (main_window == NULL ||
|
||||
main_window->open_investigation_callback == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
main_window->open_investigation_callback(
|
||||
main_window->open_investigation_user_data
|
||||
);
|
||||
}
|
||||
|
||||
MainWindow *main_window_new(GtkApplication *application)
|
||||
{
|
||||
MainWindow *main_window = NULL;
|
||||
|
|
@ -168,11 +201,21 @@ MainWindow *main_window_new(GtkApplication *application)
|
|||
"Nouvelle enquête"
|
||||
);
|
||||
|
||||
main_window->open_investigation_button =
|
||||
gtk_button_new_with_label(
|
||||
"Ouvrir une enquête"
|
||||
);
|
||||
|
||||
gtk_box_append(
|
||||
GTK_BOX(main_window->action_bar),
|
||||
main_window->new_investigation_button
|
||||
);
|
||||
|
||||
gtk_box_append(
|
||||
GTK_BOX(main_window->action_bar),
|
||||
main_window->open_investigation_button
|
||||
);
|
||||
|
||||
g_signal_connect(
|
||||
main_window->new_investigation_button,
|
||||
"clicked",
|
||||
|
|
@ -182,6 +225,15 @@ MainWindow *main_window_new(GtkApplication *application)
|
|||
main_window
|
||||
);
|
||||
|
||||
g_signal_connect(
|
||||
main_window->open_investigation_button,
|
||||
"clicked",
|
||||
G_CALLBACK(
|
||||
main_window_on_open_investigation_clicked
|
||||
),
|
||||
main_window
|
||||
);
|
||||
|
||||
/*
|
||||
* GtkPaned sépare horizontalement le panneau latéral
|
||||
* et la zone de travail.
|
||||
|
|
@ -533,6 +585,21 @@ void main_window_set_new_investigation_callback(
|
|||
main_window->new_investigation_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_open_investigation_callback(
|
||||
MainWindow *main_window,
|
||||
MainWindowOpenInvestigationCallback callback,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
if (main_window == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
main_window->open_investigation_callback = callback;
|
||||
main_window->open_investigation_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_selected_node(
|
||||
MainWindow *main_window,
|
||||
const InvestigationNode *node
|
||||
|
|
|
|||
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