Compare commits

..

2 commits

Author SHA1 Message Date
grayTerminal-sh
ff512593da feat: comeback workspace button 2026-07-22 08:03:24 +02:00
grayTerminal-sh
0069e99529 Confirmer la réinitialisation de la disposition du graphe 2026-07-22 07:35:15 +02:00
6 changed files with 1009 additions and 7 deletions

View file

@ -18,6 +18,20 @@ typedef enum
APPLICATION_MESSAGE_DIALOG_INFORMATION
} ApplicationMessageDialogType;
/**
* @brief Callback appelé à la fermeture d'un dialogue de confirmation.
*
* Le callback est appelé une seule fois après un clic sur l'un des boutons
* ou après une fermeture manuelle de la fenêtre.
*
* @param confirmed TRUE si l'utilisateur confirme, sinon FALSE.
* @param user_data Données empruntées fournies par l'appelant.
*/
typedef void (*ApplicationMessageDialogConfirmationCallback)(
gboolean confirmed,
gpointer user_data
);
/**
* @brief Affiche une fenêtre modale contenant un message.
*
@ -35,4 +49,31 @@ void application_message_dialog_present(
const char *message
);
/**
* @brief Affiche un dialogue modal demandant une confirmation.
*
* Le bouton d'annulation transmet FALSE. Le bouton de confirmation transmet
* TRUE. Fermer la fenêtre manuellement équivaut à une annulation.
*
* callback et user_data sont empruntés jusqu'à la fermeture du dialogue.
* confirm_label peut être NULL ou vide pour utiliser « Confirmer ».
*
* @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.
* @param confirm_label Libellé du bouton de confirmation, ou NULL.
* @param callback Callback facultatif recevant la réponse.
* @param user_data Données empruntées transmises au callback.
*/
void application_message_dialog_present_confirmation(
GtkWindow *parent_window,
ApplicationMessageDialogType message_type,
const char *title,
const char *message,
const char *confirm_label,
ApplicationMessageDialogConfirmationCallback callback,
gpointer user_data
);
#endif

View file

@ -49,6 +49,15 @@ typedef void (*MainWindowImportEvidenceCallback)(
gpointer user_data
);
/**
* @brief Callback appelé lorsque l'utilisateur revient au graphe.
*
* @param user_data Données utilisateur associées au callback.
*/
typedef void (*MainWindowShowGraphCallback)(
gpointer user_data
);
/**
* @brief Callback appelé lorsqu'une preuve est sélectionnée.
*
@ -432,6 +441,21 @@ void main_window_set_import_evidence_callback(
gpointer user_data
);
/**
* @brief Définit le callback du bouton « Revenir au graphe ».
*
* MainWindow transmet uniquement la demande au contrôleur.
*
* @param main_window Fenêtre principale.
* @param callback Fonction appelée lors du clic, ou NULL.
* @param user_data Données transmises au callback.
*/
void main_window_set_show_graph_callback(
MainWindow *main_window,
MainWindowShowGraphCallback callback,
gpointer user_data
);
/**
* @brief Active ou désactive l'action d'import d'une preuve.
*

View file

@ -4278,17 +4278,15 @@ static void application_on_graph_node_moved(
}
/**
* @brief Ferme proprement l'application.
* @brief Supprime les positions persistées et restaure la grille automatique.
*
* @param user_data Pointeur vers Application.
* Cette fonction est appelée uniquement après une confirmation explicite.
* L'affichage et le modèle en mémoire restent inchangés en cas d'échec SQLite.
*/
static void application_on_reset_graph_layout_requested(
gpointer user_data
static void application_reset_graph_layout(
Application *application
)
{
Application *application =
user_data;
Database *database =
NULL;
@ -4402,6 +4400,98 @@ static void application_on_reset_graph_layout_requested(
);
}
/**
* @brief Traite la réponse du dialogue de confirmation.
*/
static void application_on_reset_graph_layout_confirmed(
gboolean confirmed,
gpointer user_data
)
{
Application *application =
user_data;
if (!confirmed)
{
return;
}
application_reset_graph_layout(
application
);
}
/**
* @brief Demande confirmation avant toute suppression de position.
*/
static void application_on_show_graph_requested(
gpointer user_data
)
{
Application *application =
user_data;
if (application == NULL ||
application->main_window == NULL ||
application->graph_model == NULL)
{
return;
}
application_set_selected_evidence_identifier(
application,
NULL
);
/*
* Passer NULL restaure l'état principal du Workspace. Quand le graphe
* est prêt, workspace_show_default() affiche automatiquement le canvas.
*/
main_window_set_selected_evidence(
application->main_window,
NULL
);
main_window_set_status(
application->main_window,
"Graphe de l'enquête affiché."
);
}
/**
* @brief Demande confirmation avant toute suppression de position.
*/
static void application_on_reset_graph_layout_requested(
gpointer user_data
)
{
Application *application =
user_data;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->graph_model == NULL ||
application->graph_layout == NULL)
{
return;
}
application_message_dialog_present_confirmation(
main_window_get_window(
application->main_window
),
APPLICATION_MESSAGE_DIALOG_WARNING,
"Réinitialiser la disposition du graphe",
"Toutes les positions personnalisées des nœuds seront supprimées. "
"Le graphe retrouvera sa disposition automatique.\n\n"
"Cette action ne peut pas être annulée.",
"Réinitialiser",
application_on_reset_graph_layout_confirmed,
application
);
}
/**
* @brief Ferme proprement l'application.
*
@ -4504,6 +4594,12 @@ static void application_on_activate(
application
);
main_window_set_show_graph_callback(
application->main_window,
application_on_show_graph_requested,
application
);
main_window_set_new_investigation_callback(
application->main_window,
application_on_new_investigation_requested,

View file

@ -16,6 +16,25 @@
#define APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE \
"Aucun détail supplémentaire n'est disponible."
/**
* @brief Libellé par défaut du bouton de confirmation.
*/
#define APPLICATION_MESSAGE_DIALOG_DEFAULT_CONFIRM_LABEL \
"Confirmer"
/**
* @brief État privé d'un dialogue de confirmation.
*/
typedef struct
{
GtkWindow *dialog_window;
ApplicationMessageDialogConfirmationCallback callback;
gpointer user_data;
gboolean completed;
} ApplicationMessageDialogConfirmationContext;
/**
* @brief Retourne le libellé correspondant au type de message.
*
@ -335,3 +354,528 @@ void application_message_dialog_present(
dialog_window
);
}
/**
* @brief Libère le contexte associé à une fenêtre de confirmation.
*/
static void application_message_dialog_confirmation_context_free(
gpointer data
)
{
ApplicationMessageDialogConfirmationContext *context =
data;
if (context == NULL)
{
return;
}
context->dialog_window =
NULL;
context->callback =
NULL;
context->user_data =
NULL;
g_free(
context
);
}
/**
* @brief Termine une confirmation et transmet son résultat une seule fois.
*
* Lorsque destroy_window vaut TRUE, la fenêtre est détruite avant l'appel
* du callback. Les valeurs du callback sont donc copiées localement avant
* la destruction du contexte attaché à la fenêtre.
*/
static void application_message_dialog_complete_confirmation(
ApplicationMessageDialogConfirmationContext *context,
gboolean confirmed,
gboolean destroy_window
)
{
ApplicationMessageDialogConfirmationCallback callback =
NULL;
gpointer user_data =
NULL;
GtkWindow *dialog_window =
NULL;
if (context == NULL ||
context->completed)
{
return;
}
context->completed =
TRUE;
callback =
context->callback;
user_data =
context->user_data;
dialog_window =
context->dialog_window;
context->callback =
NULL;
context->user_data =
NULL;
if (destroy_window &&
dialog_window != NULL)
{
gtk_window_destroy(
dialog_window
);
}
if (callback != NULL)
{
callback(
confirmed,
user_data
);
}
}
/**
* @brief Annule le dialogue depuis son bouton.
*/
static void application_message_dialog_on_cancel_clicked(
GtkButton *button,
gpointer user_data
)
{
ApplicationMessageDialogConfirmationContext *context =
user_data;
(void) button;
application_message_dialog_complete_confirmation(
context,
FALSE,
TRUE
);
}
/**
* @brief Confirme le dialogue depuis son bouton principal.
*/
static void application_message_dialog_on_confirm_clicked(
GtkButton *button,
gpointer user_data
)
{
ApplicationMessageDialogConfirmationContext *context =
user_data;
(void) button;
application_message_dialog_complete_confirmation(
context,
TRUE,
TRUE
);
}
/**
* @brief Traite la fermeture manuelle comme une annulation.
*/
static gboolean application_message_dialog_on_confirmation_close_requested(
GtkWindow *dialog_window,
gpointer user_data
)
{
ApplicationMessageDialogConfirmationContext *context =
user_data;
(void) dialog_window;
application_message_dialog_complete_confirmation(
context,
FALSE,
FALSE
);
/*
* FALSE laisse GTK poursuivre la fermeture normale de la fenêtre.
*/
return FALSE;
}
void application_message_dialog_present_confirmation(
GtkWindow *parent_window,
ApplicationMessageDialogType message_type,
const char *title,
const char *message,
const char *confirm_label,
ApplicationMessageDialogConfirmationCallback callback,
gpointer user_data
)
{
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 *cancel_button =
NULL;
GtkWidget *confirm_button =
NULL;
ApplicationMessageDialogConfirmationContext *context =
NULL;
const char *safe_title =
NULL;
const char *safe_message =
NULL;
const char *safe_confirm_label =
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
);
safe_confirm_label =
confirm_label != NULL &&
confirm_label[0] != '\0'
? confirm_label
: APPLICATION_MESSAGE_DIALOG_DEFAULT_CONFIRM_LABEL;
type_text =
application_message_dialog_get_type_label(
message_type
);
context =
g_try_new0(
ApplicationMessageDialogConfirmationContext,
1
);
if (context == NULL)
{
return;
}
dialog_window =
GTK_WINDOW(
gtk_window_new()
);
context->dialog_window =
dialog_window;
context->callback =
callback;
context->user_data =
user_data;
context->completed =
FALSE;
g_object_set_data_full(
G_OBJECT(
dialog_window
),
"application-message-dialog-confirmation-context",
context,
application_message_dialog_confirmation_context_free
);
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
);
gtk_widget_add_css_class(
title_label,
"title-2"
);
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,
8
);
gtk_widget_set_halign(
button_box,
GTK_ALIGN_END
);
cancel_button =
gtk_button_new_with_label(
"Annuler"
);
confirm_button =
gtk_button_new_with_label(
safe_confirm_label
);
gtk_widget_add_css_class(
confirm_button,
"destructive-action"
);
g_signal_connect(
cancel_button,
"clicked",
G_CALLBACK(
application_message_dialog_on_cancel_clicked
),
context
);
g_signal_connect(
confirm_button,
"clicked",
G_CALLBACK(
application_message_dialog_on_confirm_clicked
),
context
);
g_signal_connect(
dialog_window,
"close-request",
G_CALLBACK(
application_message_dialog_on_confirmation_close_requested
),
context
);
gtk_box_append(
GTK_BOX(
button_box
),
cancel_button
);
gtk_box_append(
GTK_BOX(
button_box
),
confirm_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_widget_grab_focus(
cancel_button
);
gtk_window_present(
dialog_window
);
}

View file

@ -59,6 +59,7 @@ struct MainWindow
GtkWidget *new_investigation_button;
GtkWidget *open_investigation_button;
GtkWidget *import_evidence_button;
GtkWidget *show_graph_button;
GtkWidget *content_paned;
GtkWidget *main_paned;
GtkWidget *status_label;
@ -86,6 +87,12 @@ struct MainWindow
gpointer
import_evidence_user_data;
MainWindowShowGraphCallback
show_graph_callback;
gpointer
show_graph_user_data;
MainWindowVerifyEvidenceCallback
verify_evidence_callback;
@ -190,6 +197,33 @@ static void main_window_on_import_evidence_clicked(
);
}
/**
* @brief Transmet la demande de fermeture au contrôleur.
*
* @param button Bouton ayant reçu le clic.
* @param user_data Pointeur vers MainWindow.
*/
static void main_window_on_show_graph_clicked(
GtkButton *button,
gpointer user_data
)
{
MainWindow *main_window =
user_data;
(void) button;
if (main_window == NULL ||
main_window->show_graph_callback == NULL)
{
return;
}
main_window->show_graph_callback(
main_window->show_graph_user_data
);
}
/**
* @brief Transmet la demande de fermeture au contrôleur.
*
@ -436,6 +470,12 @@ MainWindow *main_window_new(
"Importer une preuve"
);
main_window->show_graph_button =
main_window_create_action_button(
"go-previous-symbolic",
"Revenir au graphe"
);
main_window->quit_button =
main_window_create_action_button(
"application-exit-symbolic",
@ -461,6 +501,11 @@ MainWindow *main_window_new(
FALSE
);
gtk_widget_set_sensitive(
main_window->show_graph_button,
FALSE
);
gtk_box_append(
GTK_BOX(main_window->action_bar),
main_window->new_investigation_button
@ -476,6 +521,11 @@ MainWindow *main_window_new(
main_window->import_evidence_button
);
gtk_box_append(
GTK_BOX(main_window->action_bar),
main_window->show_graph_button
);
gtk_box_append(
GTK_BOX(main_window->action_bar),
action_spacer
@ -513,6 +563,15 @@ MainWindow *main_window_new(
main_window
);
g_signal_connect(
main_window->show_graph_button,
"clicked",
G_CALLBACK(
main_window_on_show_graph_clicked
),
main_window
);
g_signal_connect(
main_window->quit_button,
"clicked",
@ -937,6 +996,11 @@ void main_window_set_graph_loading(
return;
}
gtk_widget_set_sensitive(
main_window->show_graph_button,
FALSE
);
workspace_set_graph_loading(
main_window->workspace
);
@ -958,6 +1022,11 @@ void main_window_set_graph(
graph_model,
graph_layout
);
gtk_widget_set_sensitive(
main_window->show_graph_button,
graph_model != NULL
);
}
void main_window_set_graph_error(
@ -970,6 +1039,11 @@ void main_window_set_graph_error(
return;
}
gtk_widget_set_sensitive(
main_window->show_graph_button,
FALSE
);
workspace_set_graph_error(
main_window->workspace,
message
@ -985,6 +1059,11 @@ void main_window_clear_graph(
return;
}
gtk_widget_set_sensitive(
main_window->show_graph_button,
FALSE
);
workspace_clear_graph(
main_window->workspace
);
@ -1113,6 +1192,24 @@ void main_window_set_import_evidence_callback(
user_data;
}
void main_window_set_show_graph_callback(
MainWindow *main_window,
MainWindowShowGraphCallback callback,
gpointer user_data
)
{
if (main_window == NULL)
{
return;
}
main_window->show_graph_callback =
callback;
main_window->show_graph_user_data =
user_data;
}
void main_window_set_import_evidence_enabled(
MainWindow *main_window,
gboolean enabled
@ -1324,6 +1421,12 @@ void main_window_free(
main_window->reset_graph_layout_user_data =
NULL;
main_window->show_graph_callback =
NULL;
main_window->show_graph_user_data =
NULL;
main_window_clear_graph(
main_window
);
@ -1354,3 +1457,4 @@ void main_window_free(
main_window
);
}

View file

@ -49,6 +49,8 @@ struct Workspace
GtkWidget *graph_view_page;
GtkWidget *graph_toolbar;
GtkWidget *reset_graph_layout_button;
GtkWidget *graph_help_button;
GtkWidget *graph_help_popover;
InvestigationGraphView *graph_view;
EntityDetailsPanel *entity_details_panel;
@ -1174,6 +1176,175 @@ Workspace *workspace_new(void)
TRUE
);
workspace->graph_help_button =
gtk_menu_button_new();
workspace->graph_help_popover =
gtk_popover_new();
GtkWidget *graph_help_content =
gtk_box_new(
GTK_ORIENTATION_VERTICAL,
8
);
GtkWidget *graph_help_title =
gtk_label_new(
"Commandes du graphe"
);
GtkWidget *graph_help_text =
gtk_label_new(
"• Clic : sélectionner un nœud\n"
"• Glisser un nœud : le déplacer\n"
"• Maj + glisser : déplacer la vue\n"
"• Ctrl + molette : zoomer\n"
"• Pincement : zoomer"
);
if (workspace->graph_help_button == NULL ||
workspace->graph_help_popover == NULL ||
graph_help_content == NULL ||
graph_help_title == NULL ||
graph_help_text == NULL)
{
workspace_free(
workspace
);
return NULL;
}
gtk_menu_button_set_label(
GTK_MENU_BUTTON(
workspace->graph_help_button
),
"?"
);
gtk_widget_add_css_class(
workspace->graph_help_button,
"flat"
);
gtk_widget_set_tooltip_text(
workspace->graph_help_button,
"Afficher les commandes du graphe"
);
gtk_widget_set_can_target(
workspace->graph_help_button,
TRUE
);
gtk_widget_set_halign(
workspace->graph_help_button,
GTK_ALIGN_END
);
gtk_widget_set_valign(
workspace->graph_help_button,
GTK_ALIGN_START
);
gtk_widget_set_margin_end(
workspace->graph_help_button,
12
);
gtk_widget_set_margin_top(
workspace->graph_help_button,
12
);
gtk_widget_set_margin_start(
graph_help_content,
16
);
gtk_widget_set_margin_end(
graph_help_content,
16
);
gtk_widget_set_margin_top(
graph_help_content,
14
);
gtk_widget_set_margin_bottom(
graph_help_content,
14
);
gtk_label_set_xalign(
GTK_LABEL(
graph_help_title
),
0.0F
);
gtk_widget_add_css_class(
graph_help_title,
"heading"
);
gtk_label_set_xalign(
GTK_LABEL(
graph_help_text
),
0.0F
);
gtk_label_set_wrap(
GTK_LABEL(
graph_help_text
),
TRUE
);
gtk_label_set_wrap_mode(
GTK_LABEL(
graph_help_text
),
PANGO_WRAP_WORD_CHAR
);
gtk_label_set_max_width_chars(
GTK_LABEL(
graph_help_text
),
44
);
gtk_box_append(
GTK_BOX(
graph_help_content
),
graph_help_title
);
gtk_box_append(
GTK_BOX(
graph_help_content
),
graph_help_text
);
gtk_popover_set_child(
GTK_POPOVER(
workspace->graph_help_popover
),
graph_help_content
);
gtk_menu_button_set_popover(
GTK_MENU_BUTTON(
workspace->graph_help_button
),
workspace->graph_help_popover
);
gtk_overlay_add_overlay(
GTK_OVERLAY(
workspace->graph_view_page
@ -1193,6 +1364,22 @@ Workspace *workspace_new(void)
TRUE
);
gtk_overlay_add_overlay(
GTK_OVERLAY(
workspace->graph_view_page
),
workspace->graph_help_button
);
gtk_overlay_set_clip_overlay(
GTK_OVERLAY(
workspace->graph_view_page
),
workspace->graph_help_button,
TRUE
);
gtk_stack_add_named(
GTK_STACK(workspace->stack),
workspace->graph_view_page,
@ -2022,6 +2209,12 @@ void workspace_free(Workspace *workspace)
workspace->reset_graph_layout_button =
NULL;
workspace->graph_help_button =
NULL;
workspace->graph_help_popover =
NULL;
g_clear_pointer(
&workspace->selected_evidence_identifier,
g_free