feat(view): create main window

This commit is contained in:
grayTerminal-sh 2026-07-12 20:15:49 +02:00
parent d8242f20ca
commit a8e66c40be
7 changed files with 468 additions and 28 deletions

View file

@ -6,3 +6,9 @@
- Création du module `Application`. - Création du module `Application`.
- Intégration de GTK4. - Intégration de GTK4.
- Affichage de la première fenêtre. - Affichage de la première fenêtre.
### Added
- Module `MainWindow`.
- Séparation de la fenêtre principale du module `Application`.
- Première architecture de la fenêtre principale.

View file

@ -0,0 +1,57 @@
# Ticket #003
## Titre
Créer le module `Investigation`.
## Objectif
Créer l'objet métier représentant une enquête ouverte par Labfy Investigation.
Le module doit rester indépendant de GTK et de l'interface graphique.
## Responsabilités
Le module doit :
- mémoriser le chemin racine de l'enquête ;
- construire le chemin de la base de données modèle ;
- exposer ces chemins en lecture seule ;
- gérer proprement sa mémoire ;
- vérifier qu'un chemin d'enquête est exploitable.
## Hors périmètre
Ce ticket ne doit pas :
- ouvrir SQLite ;
- créer la base de données ;
- modifier l'arborescence de l'enquête ;
- afficher de message GTK ;
- gérer les preuves ou les entités.
## Dépendances
- libc ;
- GLib uniquement si nécessaire pour la gestion des chaînes et chemins.
Aucune dépendance GTK ou SQLite.
## API attendue
Le module doit permettre un usage proche de :
```c
Investigation *investigation = NULL;
investigation = investigation_new("/chemin/vers/enquete");
if (investigation == NULL)
{
/* Gestion de l'erreur */
}
const char *root_path = investigation_get_root_path(investigation);
const char *database_path = investigation_get_database_path(investigation);
investigation_free(investigation);

View file

@ -0,0 +1,128 @@
# Ticket #004
## Titre
Créer la fenêtre principale (`MainWindow`).
---
## Objectif
Créer la fenêtre principale de Labfy Investigation.
Cette fenêtre deviendra le point central de l'interface utilisateur.
À ce stade, elle ne contient qu'une structure vide destinée à accueillir les futurs composants.
---
## Responsabilités
Le module `MainWindow` doit :
- créer la fenêtre principale ;
- définir son titre ;
- définir sa taille par défaut ;
- afficher une zone centrale vide ;
- afficher une barre d'état.
---
## Hors périmètre
Ce ticket ne doit pas :
- afficher l'arborescence des fichiers ;
- ouvrir une enquête ;
- afficher les preuves ;
- communiquer avec SQLite ;
- gérer les menus.
---
## Architecture
Le module appartient à la couche **Views**.
Il est responsable uniquement de l'interface graphique.
Aucune logique métier ne doit être présente dans ce module.
---
## Dépendances
- GTK4
Aucune dépendance vers SQLite.
---
## Fichiers concernés
```text
include/views/main_window.h
src/views/main_window.c
```
---
## Interface attendue
```c
MainWindow *main_window_new(GtkApplication *application);
GtkWindow *main_window_get_window(
const MainWindow *main_window
);
void main_window_present(MainWindow *main_window);
void main_window_free(MainWindow *main_window);
```
---
## Interface graphique attendue
```text
┌───────────────────────────────────────────────────────────────┐
│ Labfy Investigation │
├───────────────────────────────────────────────────────────────┤
│ │
│ │
│ Zone de travail │
│ │
│ │
├───────────────────────────────────────────────────────────────┤
│ Aucune enquête ouverte │
└───────────────────────────────────────────────────────────────┘
```
---
## Critères d'acceptation
- [ ] Le projet compile sans warning.
- [ ] Une fenêtre s'ouvre correctement.
- [ ] La fenêtre est créée par le module `MainWindow`.
- [ ] `Application` n'appelle plus directement `gtk_application_window_new()`.
- [ ] Aucun état global.
- [ ] Documentation Doxygen.
- [ ] Gestion mémoire correcte.
---
## Tests
- Lancement de l'application.
- Fermeture de la fenêtre.
- Vérification de l'absence de fuite mémoire.
---
## Commit attendu
```text
feat(view): create main window
```

View file

@ -0,0 +1,50 @@
/******************************************************************************
* @file main_window.h
* @brief Interface publique de la fenêtre principale.
******************************************************************************/
#ifndef LABFY_INVESTIGATION_MAIN_WINDOW_H
#define LABFY_INVESTIGATION_MAIN_WINDOW_H
#include <gtk/gtk.h>
/**
* @brief Représentation opaque de la fenêtre principale.
*/
typedef struct MainWindow MainWindow;
/**
* @brief Crée une nouvelle fenêtre principale.
*
* @param application Application GTK.
*
* @return Une nouvelle fenêtre ou NULL en cas d'échec.
*/
MainWindow *main_window_new(GtkApplication *application);
/**
* @brief Affiche la fenêtre principale.
*
* @param main_window Fenêtre à afficher.
*/
void main_window_present(MainWindow *main_window);
/**
* @brief Retourne la fenêtre GTK associée.
*
* @param main_window Fenêtre principale.
*
* @return Fenêtre GTK.
*/
GtkWindow *main_window_get_window(
const MainWindow *main_window
);
/**
* @brief Libère les ressources de la fenêtre.
*
* @param main_window Fenêtre à détruire.
*/
void main_window_free(MainWindow *main_window);
#endif

View file

@ -6,6 +6,7 @@
#include "core/application.h" #include "core/application.h"
#include "views/folder_dialog.h" #include "views/folder_dialog.h"
#include "core/investigation.h" #include "core/investigation.h"
#include "views/main_window.h"
#include <gtk/gtk.h> #include <gtk/gtk.h>
@ -30,6 +31,8 @@
struct Application struct Application
{ {
GtkApplication *gtk_application; GtkApplication *gtk_application;
MainWindow *main_window;
Investigation *investigation;
}; };
/** /**
@ -47,57 +50,83 @@ static void application_on_folder_selected(
gpointer user_data gpointer user_data
) )
{ {
GtkWindow *window = GTK_WINDOW(user_data); Application *application = user_data;
Investigation *investigation = NULL;
const char *root_path = NULL; const char *root_path = NULL;
const char *database_path = NULL; const char *database_path = NULL;
/*
* Une éventuelle enquête précédemment ouverte doit être libérée avant
* d'en créer une nouvelle.
*/
investigation_free(application->investigation);
application->investigation = NULL;
application->investigation = investigation_new(folder_path);
if (application == NULL)
{
return;
}
if (folder_path == NULL) if (folder_path == NULL)
{ {
g_print("Sélection annulée.\n"); g_print("Sélection annulée.\n");
return; return;
} }
investigation = investigation_new(folder_path); if (application->investigation == NULL)
if (investigation == NULL)
{ {
g_warning("Impossible de créer l'enquête à partir du dossier sélectionné."); g_warning(
"Impossible de créer l'enquête à partir du dossier sélectionné."
);
return; return;
} }
root_path = investigation_get_root_path(
root_path = investigation_get_root_path(investigation); application->investigation
database_path = investigation_get_database_path(investigation); );
database_path = investigation_get_database_path(
application->investigation
);
g_print("Dossier racine : %s\n", root_path); g_print("Dossier racine : %s\n", root_path);
g_print("Base de données : %s\n", database_path); g_print("Base de données : %s\n", database_path);
investigation_free(investigation);
gtk_window_set_title(window, "Labfy Investigation");
} }
static void application_on_activate(GtkApplication *gtk_application, static void application_on_activate(
gpointer user_data) GtkApplication *gtk_application,
gpointer user_data
)
{ {
GtkWidget *window = NULL; Application *application = user_data;
(void)user_data; if (application == NULL)
{
return;
}
window = gtk_application_window_new(gtk_application); /*
* Le signal "activate" peut être reçu plusieurs fois. Si la fenêtre
* existe déjà, il suffit de la présenter au lieu d'en créer une seconde.
*/
if (application->main_window != NULL)
{
main_window_present(application->main_window);
return;
}
gtk_window_set_title(GTK_WINDOW(window), "Labfy Investigation"); application->main_window = main_window_new(gtk_application);
gtk_window_set_default_size(
GTK_WINDOW(window),
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT
);
gtk_window_present(GTK_WINDOW(window)); if (application->main_window == NULL)
{
g_warning("Impossible de créer la fenêtre principale.");
return;
}
main_window_present(application->main_window);
folder_dialog_select_folder( folder_dialog_select_folder(
GTK_WINDOW(window), main_window_get_window(application->main_window),
application_on_folder_selected, application_on_folder_selected,
window application
); );
} }
@ -128,7 +157,11 @@ Application *application_new(void)
return application; return application;
} }
int application_run(Application *application, int argc, char **argv) int application_run(
Application *application,
int argc,
char **argv
)
{ {
if (application == NULL || application->gtk_application == NULL) if (application == NULL || application->gtk_application == NULL)
{ {
@ -149,6 +182,10 @@ void application_free(Application *application)
return; return;
} }
investigation_free(application->investigation);
main_window_free(application->main_window);
if (application->gtk_application != NULL) if (application->gtk_application != NULL)
{ {
g_object_unref(application->gtk_application); g_object_unref(application->gtk_application);

View file

@ -0,0 +1,162 @@
/******************************************************************************
* @file main_window.c
* @brief Implémentation de la fenêtre principale.
******************************************************************************/
#include "views/main_window.h"
#include <glib.h>
/**
* @brief Largeur initiale de la fenêtre principale.
*/
#define MAIN_WINDOW_DEFAULT_WIDTH 1000
/**
* @brief Hauteur initiale de la fenêtre principale.
*/
#define MAIN_WINDOW_DEFAULT_HEIGHT 650
/**
* @struct MainWindow
* @brief Représentation interne de la fenêtre principale.
*
* Cette structure reste privée au module. Les autres fichiers utilisent
* uniquement le type opaque déclaré dans main_window.h.
*/
struct MainWindow
{
GtkWindow *window;
GtkWidget *status_label;
};
MainWindow *main_window_new(GtkApplication *application)
{
MainWindow *main_window = NULL;
GtkWidget *main_box = NULL;
GtkWidget *workspace = NULL;
if (application == NULL)
{
return NULL;
}
main_window = g_new0(MainWindow, 1);
main_window->window = GTK_WINDOW(
gtk_application_window_new(application)
);
if (main_window->window == NULL)
{
main_window_free(main_window);
return NULL;
}
gtk_window_set_title(
main_window->window,
"Labfy Investigation"
);
gtk_window_set_default_size(
main_window->window,
MAIN_WINDOW_DEFAULT_WIDTH,
MAIN_WINDOW_DEFAULT_HEIGHT
);
main_box = gtk_box_new(
GTK_ORIENTATION_VERTICAL,
0
);
workspace = gtk_box_new(
GTK_ORIENTATION_VERTICAL,
0
);
gtk_widget_set_hexpand(workspace, TRUE);
gtk_widget_set_vexpand(workspace, TRUE);
main_window->status_label = gtk_label_new(
"Aucune enquête ouverte"
);
gtk_widget_set_halign(
main_window->status_label,
GTK_ALIGN_START
);
gtk_widget_set_margin_start(
main_window->status_label,
8
);
gtk_widget_set_margin_end(
main_window->status_label,
8
);
gtk_widget_set_margin_top(
main_window->status_label,
6
);
gtk_widget_set_margin_bottom(
main_window->status_label,
6
);
gtk_box_append(
GTK_BOX(main_box),
workspace
);
gtk_box_append(
GTK_BOX(main_box),
main_window->status_label
);
gtk_window_set_child(
main_window->window,
main_box
);
return main_window;
}
void main_window_present(MainWindow *main_window)
{
if (main_window == NULL || main_window->window == NULL)
{
return;
}
gtk_window_present(main_window->window);
}
GtkWindow *main_window_get_window(
const MainWindow *main_window
)
{
if (main_window == NULL)
{
return NULL;
}
return main_window->window;
}
void main_window_free(MainWindow *main_window)
{
if (main_window == NULL)
{
return;
}
/*
* La fenêtre principale est possédée et détruite par GTK.
* MainWindow ne possède que sa structure d'encapsulation.
*/
g_free(main_window);
}