diff --git a/docs/tickets/open/TICKET-031.md b/docs/tickets/open/TICKET-031.md new file mode 100644 index 0000000..07b4684 --- /dev/null +++ b/docs/tickets/open/TICKET-031.md @@ -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 ' 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 +``` + diff --git a/include/views/main_window.h b/include/views/main_window.h index 3fce982..b753945 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -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. * diff --git a/labfy-investigation b/labfy-investigation index 4c62c19..a1c6bd7 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/application.c b/src/core/application.c index 6afad4b..a5a95bd 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -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 @@ -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) diff --git a/src/views/main_window.c b/src/views/main_window.c index 94ab0c4..5bd8210 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -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 diff --git a/tests/test_database b/tests/test_database new file mode 100755 index 0000000..2a1ae25 Binary files /dev/null and b/tests/test_database differ diff --git a/tests/test_error b/tests/test_error new file mode 100755 index 0000000..1185635 Binary files /dev/null and b/tests/test_error differ diff --git a/tests/test_investigation_dao b/tests/test_investigation_dao new file mode 100755 index 0000000..0234ddd Binary files /dev/null and b/tests/test_investigation_dao differ diff --git a/tests/test_investigation_node b/tests/test_investigation_node new file mode 100755 index 0000000..b7614ef Binary files /dev/null and b/tests/test_investigation_node differ diff --git a/tests/test_investigation_project b/tests/test_investigation_project new file mode 100755 index 0000000..3bd6369 Binary files /dev/null and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_record b/tests/test_investigation_record new file mode 100755 index 0000000..ba2880b Binary files /dev/null and b/tests/test_investigation_record differ diff --git a/tests/test_investigation_session b/tests/test_investigation_session new file mode 100755 index 0000000..04f8d66 Binary files /dev/null and b/tests/test_investigation_session differ diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder new file mode 100755 index 0000000..d6cd484 Binary files /dev/null and b/tests/test_investigation_tree_builder differ diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model new file mode 100755 index 0000000..2c823e1 Binary files /dev/null and b/tests/test_investigation_tree_model differ diff --git a/tests/test_statement b/tests/test_statement new file mode 100755 index 0000000..79b937b Binary files /dev/null and b/tests/test_statement differ diff --git a/tests/test_transaction b/tests/test_transaction new file mode 100755 index 0000000..3533f19 Binary files /dev/null and b/tests/test_transaction differ