diff --git a/docs/tickets/closed/TICKET-002.md b/docs/tickets/closed/TICKET-002.md new file mode 100644 index 0000000..9c2aa82 --- /dev/null +++ b/docs/tickets/closed/TICKET-002.md @@ -0,0 +1,38 @@ +# Ticket #002 + +## Titre + +Ajouter le sélecteur de dossier d’enquête. + +## Objectif + +Permettre à l’utilisateur de sélectionner un dossier d’enquête au lancement de l’application. + +## Responsabilités + +- afficher un dialogue GTK de sélection de dossier ; +- retourner le dossier sélectionné ; +- gérer l’annulation sans erreur ; +- transmettre le chemin au module Application. + +## Hors périmètre + +- création de `00_BaseDeDonnees` ; +- ouverture de SQLite ; +- initialisation du schéma ; +- validation complète d’une enquête. + +## Critères d’acceptation + +- [ ] Le dialogue s’ouvre au lancement. +- [ ] Un dossier peut être sélectionné. +- [ ] L’annulation est gérée proprement. +- [ ] Le chemin sélectionné est affiché dans la fenêtre. +- [ ] Aucun warning. +- [ ] Les fonctions publiques sont documentées. +- [ ] Aucun état global. + +## Commit attendu + +```text +feat(gui): add investigation folder selector diff --git a/labfy-investigation/include/views/folder_dialog.h b/labfy-investigation/include/views/folder_dialog.h new file mode 100644 index 0000000..843ed24 --- /dev/null +++ b/labfy-investigation/include/views/folder_dialog.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * @file folder_dialog.h + * @brief Interface du dialogue de sélection d'un dossier d'enquête. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_FOLDER_DIALOG_H +#define LABFY_INVESTIGATION_FOLDER_DIALOG_H + +#include + +/** + * @brief Fonction appelée après la fermeture du dialogue de sélection. + * + * @param folder_path Chemin du dossier sélectionné, ou NULL si l'utilisateur + * a annulé la sélection. + * @param user_data Données transmises lors de l'ouverture du dialogue. + * + * Le chemin reçu reste valide uniquement pendant l'appel de cette fonction. + * Il doit être copié si le code appelant souhaite le conserver. + * + * @param callback + * Fonction appelée lorsque le dialogue est fermé. + * + * Si l'utilisateur annule la sélection, + * folder_path vaut NULL. + */ +typedef void (*FolderDialogCallback)( + const char *folder_path, + gpointer user_data +); + +/** + * @brief Ouvre un dialogue permettant de sélectionner un dossier. + * + * @param parent Fenêtre parente du dialogue. + * Peut être NULL. + * @param callback Fonction appelée lorsque le dialogue est terminé. + * @param user_data Données transmises à la fonction de rappel. + */ +void folder_dialog_select_folder( + GtkWindow *parent, + FolderDialogCallback callback, + gpointer user_data +); + +#endif diff --git a/labfy-investigation/labfy-investigation b/labfy-investigation/labfy-investigation index 5c17dac..986bd25 100755 Binary files a/labfy-investigation/labfy-investigation and b/labfy-investigation/labfy-investigation differ diff --git a/labfy-investigation/src/core/application.c b/labfy-investigation/src/core/application.c index 79e6288..78fd0c1 100644 --- a/labfy-investigation/src/core/application.c +++ b/labfy-investigation/src/core/application.c @@ -4,6 +4,7 @@ ******************************************************************************/ #include "core/application.h" +#include "views/folder_dialog.h" #include @@ -39,6 +40,27 @@ struct Application * @param gtk_application Application GTK ayant reçu le signal. * @param user_data Données utilisateur associées au signal. */ + +static void application_on_folder_selected( + const char *folder_path, + gpointer user_data +) +{ + GtkWindow *window = GTK_WINDOW(user_data); + + if (folder_path == NULL) + { + gtk_window_set_title( + window, + "Labfy Investigation — aucune enquête sélectionnée" + ); + + return; + } + + gtk_window_set_title(window, folder_path); +} + static void application_on_activate(GtkApplication *gtk_application, gpointer user_data) { @@ -56,6 +78,12 @@ static void application_on_activate(GtkApplication *gtk_application, ); gtk_window_present(GTK_WINDOW(window)); + + folder_dialog_select_folder( + GTK_WINDOW(window), + application_on_folder_selected, + window + ); } Application *application_new(void) @@ -113,3 +141,5 @@ void application_free(Application *application) g_free(application); } + + diff --git a/labfy-investigation/src/views/folder_dialog.c b/labfy-investigation/src/views/folder_dialog.c new file mode 100644 index 0000000..f9799d2 --- /dev/null +++ b/labfy-investigation/src/views/folder_dialog.c @@ -0,0 +1,122 @@ +/****************************************************************************** + * @file folder_dialog.c + * @brief Implémentation du dialogue de sélection d'un dossier d'enquête. + ******************************************************************************/ + +#include "views/folder_dialog.h" + +#include + +/** + * @struct FolderDialogContext + * @brief Conserve les données nécessaires pendant l'opération asynchrone. + * + * Le dialogue GTK ne se termine pas immédiatement. Ce contexte permet donc + * de conserver le callback et ses données jusqu'à la fermeture du dialogue. + */ +typedef struct +{ + FolderDialogCallback callback; + gpointer user_data; +} FolderDialogContext; + +/** + * @brief Libère le contexte associé au dialogue. + * + * @param context Contexte à libérer. + */ +static void folder_dialog_context_free(FolderDialogContext *context) +{ + g_free(context); +} + +/** + * @brief Traite le résultat de la sélection du dossier. + * + * @param source_object Objet ayant lancé l'opération asynchrone. + * @param result Résultat transmis par GTK. + * @param user_data Contexte du dialogue. + */ +static void folder_dialog_on_folder_selected( + GObject *source_object, + GAsyncResult *result, + gpointer user_data +) +{ + GtkFileDialog *dialog = GTK_FILE_DIALOG(source_object); + FolderDialogContext *context = user_data; + GError *error = NULL; + GFile *folder = NULL; + char *folder_path = NULL; + + folder = gtk_file_dialog_select_folder_finish(dialog, result, &error); + + if (folder == NULL) + { + /* + * Une annulation est un comportement normal de l'utilisateur. + * Elle est transmise au code appelant sous la forme d'un chemin NULL. + */ + if (error != NULL && + !g_error_matches(error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED)) + { + g_warning( + "Impossible de sélectionner le dossier : %s", + error->message + ); + } + + if (context->callback != NULL) + { + context->callback(NULL, context->user_data); + } + + g_clear_error(&error); + folder_dialog_context_free(context); + return; + } + + folder_path = g_file_get_path(folder); + + if (context->callback != NULL) + { + context->callback(folder_path, context->user_data); + } + + g_free(folder_path); + g_object_unref(folder); + folder_dialog_context_free(context); +} + +void folder_dialog_select_folder( + GtkWindow *parent, + FolderDialogCallback callback, + gpointer user_data +) +{ + GtkFileDialog *dialog = NULL; + FolderDialogContext *context = NULL; + + context = g_new0(FolderDialogContext, 1); + context->callback = callback; + context->user_data = user_data; + + dialog = gtk_file_dialog_new(); + + gtk_file_dialog_set_title( + dialog, + "Sélectionner un dossier d'enquête" + ); + + gtk_file_dialog_set_modal(dialog, TRUE); + + gtk_file_dialog_select_folder( + dialog, + parent, + NULL, + folder_dialog_on_folder_selected, + context + ); + + g_object_unref(dialog); +}