diff --git a/include/core/person_entity_service.h b/include/core/person_entity_service.h index b07e9d8..28fa4e9 100644 --- a/include/core/person_entity_service.h +++ b/include/core/person_entity_service.h @@ -32,5 +32,15 @@ gboolean person_entity_service_create(Database *database, /** @brief Enregistre le rôle d'une personne existante. */ gboolean person_entity_service_update_role(Database *database, const char *entity_identifier, PersonRole role, GError **error); +/** + * @brief Enregistre le niveau de confiance d'une personne existante. + * @param database Connexion ouverte empruntée. + * @param entity_identifier UUID de la personne. + * @param confidence Valeur comprise entre 0 et 100. + * @param error Destination facultative d'une erreur. + * @return TRUE lorsque la modification est validée. + */ +gboolean person_entity_service_update_confidence(Database *database, + const char *entity_identifier, gint confidence, GError **error); G_END_DECLS #endif diff --git a/include/views/main_window.h b/include/views/main_window.h index 5ad9a97..1f46e80 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -152,6 +152,9 @@ typedef void (*MainWindowRelationSelectedCallback)( /** @brief Callback appelé pour catégoriser une personne. */ typedef void (*MainWindowPersonRoleCallback)(const char *entity_identifier, PersonRole role, gpointer user_data); +/** @brief Callback appelé pour modifier la confiance d'une personne. */ +typedef void (*MainWindowPersonConfidenceCallback)( + const char *entity_identifier, gint confidence, gpointer user_data); /** * @brief Callback appelé lors du déclenchement d'une action OSINT. @@ -266,6 +269,9 @@ void main_window_set_relation_evidences( /** @brief Définit le callback de catégorisation d'une personne. */ void main_window_set_person_role_callback(MainWindow *main_window, MainWindowPersonRoleCallback callback, gpointer user_data); +/** @brief Définit le callback de confiance d'une personne. */ +void main_window_set_person_confidence_callback(MainWindow *main_window, + MainWindowPersonConfidenceCallback callback, gpointer user_data); /** * @brief Définit le callback de déclenchement des actions OSINT. diff --git a/include/widgets/entity_details_panel.h b/include/widgets/entity_details_panel.h index 19f2cc3..699dfc0 100644 --- a/include/widgets/entity_details_panel.h +++ b/include/widgets/entity_details_panel.h @@ -53,6 +53,13 @@ typedef void (*EntityDetailsPanelPersonRoleCallback)( gpointer user_data ); +/** @brief Callback appelé pour enregistrer la confiance d'une personne. */ +typedef void (*EntityDetailsPanelPersonConfidenceCallback)( + const char *entity_identifier, + gint confidence, + gpointer user_data +); + /** * @brief Crée un volet de détails fermé. * @@ -135,6 +142,19 @@ void entity_details_panel_set_person_role_callback( gpointer user_data ); +/** + * @brief Définit le callback de modification de la confiance d'une personne. + * + * @param details_panel Volet à configurer. + * @param callback Callback à appeler, ou NULL. + * @param user_data Données empruntées du callback. + */ +void entity_details_panel_set_person_confidence_callback( + EntityDetailsPanel *details_panel, + EntityDetailsPanelPersonConfidenceCallback callback, + gpointer user_data +); + /** * @brief Indique si le volet est actuellement ouvert. * diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index e263596..4fad120 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -94,6 +94,9 @@ typedef void (*WorkspaceRelationSelectedCallback)( /** @brief Callback appelé pour catégoriser une personne. */ typedef void (*WorkspacePersonRoleCallback)(const char *entity_identifier, PersonRole role, gpointer user_data); +/** @brief Callback appelé pour modifier la confiance d'une personne. */ +typedef void (*WorkspacePersonConfidenceCallback)( + const char *entity_identifier, gint confidence, gpointer user_data); /** * @brief Callback appelé lors du déclenchement d'une action OSINT. @@ -348,6 +351,9 @@ void workspace_set_relation_evidences( /** @brief Définit le callback de catégorisation d'une personne. */ void workspace_set_person_role_callback(Workspace *workspace, WorkspacePersonRoleCallback callback, gpointer user_data); +/** @brief Définit le callback de confiance d'une personne. */ +void workspace_set_person_confidence_callback(Workspace *workspace, + WorkspacePersonConfidenceCallback callback, gpointer user_data); /** * @brief Affiche l'état de chargement du graphe. diff --git a/src/core/application.c b/src/core/application.c index 7668e2f..f60e09e 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -5004,6 +5004,35 @@ static void application_on_person_role_changed(const char *entity_identifier, investigation_project_get_database_path(project)); } +/** @brief Enregistre la confiance choisie dans la fiche personne. */ +static void application_on_person_confidence_changed( + const char *entity_identifier, gint confidence, gpointer user_data) +{ + Application *application = user_data; + Database *database = NULL; + const InvestigationProject *project = NULL; + GError *error = NULL; + if (application == NULL || application->session == NULL) return; + database = investigation_session_get_database(application->session); + project = investigation_session_get_project(application->session); + if (!person_entity_service_update_confidence(database, entity_identifier, + confidence, &error)) + { + application_present_error(application, "Confiance non enregistrée", + error != NULL ? error->message : + "Impossible de modifier la confiance de cette personne."); + g_clear_error(&error); + return; + } + g_free(application->pending_entity_selection_identifier); + application->pending_entity_selection_identifier = + g_strdup(entity_identifier); + main_window_set_status(application->main_window, + "Confiance enregistrée. Actualisation du graphe…"); + application_start_graph_loading(application, + investigation_project_get_database_path(project)); +} + /** @brief Libère le contexte d'édition d'une relation. */ static void application_edit_relation_context_free( ApplicationEditRelationContext *context) @@ -6674,6 +6703,8 @@ static void application_on_activate( application_on_relation_selected, application); main_window_set_person_role_callback(application->main_window, application_on_person_role_changed, application); + main_window_set_person_confidence_callback(application->main_window, + application_on_person_confidence_changed, application); main_window_set_osint_action_callback( application->main_window, diff --git a/src/core/person_entity_service.c b/src/core/person_entity_service.c index 3f7687a..ebc9f36 100644 --- a/src/core/person_entity_service.c +++ b/src/core/person_entity_service.c @@ -17,6 +17,68 @@ static gboolean person_entity_service_status_valid(const char *status) g_strcmp0(status, "confirmed") == 0; } +gboolean person_entity_service_update_confidence(Database *database, + const char *entity_identifier, gint confidence, GError **error) +{ + static const char validate_sql[] = + "SELECT 1 FROM entites e JOIN types_entite t ON t.id=e.type_id " + "WHERE e.id=? AND t.code='person';"; + static const char sql[] = + "UPDATE entites SET confiance=?, updated_at=? WHERE id=? AND " + "type_id=(SELECT id FROM types_entite WHERE code='person');"; + DatabaseStatement *statement = NULL; + GDateTime *now = NULL; + char *timestamp = NULL; + gboolean active = FALSE, success = FALSE; + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + if (database == NULL || entity_identifier == NULL || + !g_uuid_string_is_valid(entity_identifier) || confidence < 0 || + confidence > 100) + { + g_set_error_literal(error, g_quark_from_static_string( + "person-entity-service-error"), 4, + "La personne ou le niveau de confiance est invalide."); + return FALSE; + } + statement = database_statement_prepare(database, validate_sql); + if (statement == NULL || !database_statement_bind_text(statement, 1, + entity_identifier) || database_statement_step(statement) != + DATABASE_STATEMENT_STEP_ROW) + { + database_statement_finalize(statement); + g_set_error_literal(error, g_quark_from_static_string( + "person-entity-service-error"), 4, + "La personne sélectionnée n'existe pas."); + return FALSE; + } + database_statement_finalize(statement); statement = NULL; + now = g_date_time_new_now_utc(); + timestamp = now != NULL ? g_date_time_format(now, + "%Y-%m-%dT%H:%M:%SZ") : NULL; + if (timestamp == NULL || !database_transaction_begin(database)) + goto cleanup; + active = TRUE; + statement = database_statement_prepare(database, sql); + if (statement == NULL || + !database_statement_bind_int64(statement, 1, confidence) || + !database_statement_bind_text(statement, 2, timestamp) || + !database_statement_bind_text(statement, 3, entity_identifier) || + database_statement_step(statement) != DATABASE_STATEMENT_STEP_DONE) + goto cleanup; + database_statement_finalize(statement); statement = NULL; + if (!database_transaction_commit(database)) goto cleanup; + active = FALSE; success = TRUE; +cleanup: + database_statement_finalize(statement); + if (!success && active) database_transaction_rollback(database); + if (!success && error != NULL && *error == NULL) + g_set_error_literal(error, g_quark_from_static_string( + "person-entity-service-error"), 5, + "Le niveau de confiance n'a pas pu être enregistré."); + g_free(timestamp); g_clear_pointer(&now, g_date_time_unref); + return success; +} + gboolean person_entity_service_update_role(Database *database, const char *entity_identifier, PersonRole role, GError **error) { diff --git a/src/views/main_window.c b/src/views/main_window.c index 025065d..1b6cbe4 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -133,6 +133,8 @@ struct MainWindow gpointer edit_relation_user_data; MainWindowPersonRoleCallback person_role_callback; gpointer person_role_user_data; + MainWindowPersonConfidenceCallback person_confidence_callback; + gpointer person_confidence_user_data; MainWindowOsintActionCallback osint_action_callback; @@ -538,6 +540,17 @@ static void main_window_on_person_role_changed(const char *entity_identifier, main_window->person_role_user_data); } +/** @brief Relaie la modification de confiance d'une personne. */ +static void main_window_on_person_confidence_changed( + const char *entity_identifier, gint confidence, gpointer user_data) +{ + MainWindow *main_window = user_data; + if (main_window != NULL && + main_window->person_confidence_callback != NULL) + main_window->person_confidence_callback(entity_identifier, confidence, + main_window->person_confidence_user_data); +} + /** * @brief Relaie la demande de vérification provenant du Workspace. */ @@ -880,6 +893,8 @@ MainWindow *main_window_new( main_window_on_edit_relation_requested, main_window); workspace_set_person_role_callback(main_window->workspace, main_window_on_person_role_changed, main_window); + workspace_set_person_confidence_callback(main_window->workspace, + main_window_on_person_confidence_changed, main_window); workspace_set_osint_action_callback( main_window->workspace, @@ -1695,6 +1710,14 @@ void main_window_set_person_role_callback(MainWindow *main_window, main_window->person_role_user_data = user_data; } +void main_window_set_person_confidence_callback(MainWindow *main_window, + MainWindowPersonConfidenceCallback callback, gpointer user_data) +{ + if (main_window == NULL) return; + main_window->person_confidence_callback = callback; + main_window->person_confidence_user_data = user_data; +} + void main_window_set_quit_callback( MainWindow *main_window, MainWindowQuitCallback callback, diff --git a/src/widgets/entity_details_panel.c b/src/widgets/entity_details_panel.c index aa9fc8f..f36233f 100644 --- a/src/widgets/entity_details_panel.c +++ b/src/widgets/entity_details_panel.c @@ -31,6 +31,8 @@ struct EntityDetailsPanel GtkWidget *entity_identifier_label; GtkWidget *person_role_box; GtkDropDown *person_role_dropdown; + GtkSpinButton *person_confidence_spin; + GtkWidget *person_confidence_button; char *selected_entity_identifier; @@ -46,8 +48,26 @@ struct EntityDetailsPanel EntityDetailsPanelPersonRoleCallback person_role_callback; gpointer person_role_user_data; gboolean updating_person_role; + EntityDetailsPanelPersonConfidenceCallback person_confidence_callback; + gpointer person_confidence_user_data; }; +/** @brief Relaie la confiance explicitement enregistrée par l'utilisateur. */ +static void entity_details_panel_on_person_confidence_clicked( + GtkButton *button, gpointer user_data) +{ + EntityDetailsPanel *details_panel = user_data; + (void) button; + if (details_panel == NULL || + details_panel->person_confidence_callback == NULL || + details_panel->selected_entity_identifier == NULL) return; + details_panel->person_confidence_callback( + details_panel->selected_entity_identifier, + gtk_spin_button_get_value_as_int( + details_panel->person_confidence_spin), + details_panel->person_confidence_user_data); +} + /** @brief Relaie une catégorie choisie explicitement par l'utilisateur. */ static void entity_details_panel_on_person_role_changed(GObject *object, GParamSpec *parameter, gpointer user_data) @@ -734,9 +754,22 @@ EntityDetailsPanel *entity_details_panel_new(void) gtk_label_new("Catégorie dans l’enquête")); gtk_box_append(GTK_BOX(details_panel->person_role_box), GTK_WIDGET(details_panel->person_role_dropdown)); + details_panel->person_confidence_spin = GTK_SPIN_BUTTON( + gtk_spin_button_new_with_range(0.0, 100.0, 1.0)); + details_panel->person_confidence_button = gtk_button_new_with_label( + "Enregistrer la confiance"); + gtk_box_append(GTK_BOX(details_panel->person_role_box), + gtk_label_new("Niveau de confiance (%)")); + gtk_box_append(GTK_BOX(details_panel->person_role_box), + GTK_WIDGET(details_panel->person_confidence_spin)); + gtk_box_append(GTK_BOX(details_panel->person_role_box), + details_panel->person_confidence_button); gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box); g_signal_connect(details_panel->person_role_dropdown, "notify::selected", G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel); + g_signal_connect(details_panel->person_confidence_button, "clicked", + G_CALLBACK(entity_details_panel_on_person_confidence_clicked), + details_panel); if (details_panel->entity_value_label == NULL || details_panel->entity_type_label == NULL || @@ -861,6 +894,8 @@ void entity_details_panel_set_entity( details_panel->updating_person_role = TRUE; gtk_drop_down_set_selected(details_panel->person_role_dropdown, (guint) entity_record_get_person_role(entity_record)); + gtk_spin_button_set_value(details_panel->person_confidence_spin, + entity_record_get_confidence(entity_record)); details_panel->updating_person_role = FALSE; entity_details_panel_update_add_relation_button( @@ -1117,6 +1152,15 @@ void entity_details_panel_set_person_role_callback( details_panel->person_role_user_data = user_data; } +void entity_details_panel_set_person_confidence_callback( + EntityDetailsPanel *details_panel, + EntityDetailsPanelPersonConfidenceCallback callback, gpointer user_data) +{ + if (details_panel == NULL) return; + details_panel->person_confidence_callback = callback; + details_panel->person_confidence_user_data = user_data; +} + gboolean entity_details_panel_is_open( const EntityDetailsPanel *details_panel ) diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index 058ce01..68cef8a 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -136,6 +136,8 @@ struct Workspace gpointer edit_relation_user_data; WorkspacePersonRoleCallback person_role_callback; gpointer person_role_user_data; + WorkspacePersonConfidenceCallback person_confidence_callback; + gpointer person_confidence_user_data; WorkspaceOsintActionCallback osint_action_callback; @@ -882,6 +884,16 @@ static void workspace_on_person_role_changed(const char *entity_identifier, workspace->person_role_user_data); } +/** @brief Relaie la modification de confiance d'une personne. */ +static void workspace_on_person_confidence_changed( + const char *entity_identifier, gint confidence, gpointer user_data) +{ + Workspace *workspace = user_data; + if (workspace != NULL && workspace->person_confidence_callback != NULL) + workspace->person_confidence_callback(entity_identifier, confidence, + workspace->person_confidence_user_data); +} + /** * @brief Désélectionne le nœud lorsque le volet est fermé manuellement. */ @@ -1801,6 +1813,9 @@ Workspace *workspace_new(void) entity_details_panel_set_person_role_callback( workspace->entity_details_panel, workspace_on_person_role_changed, workspace); + entity_details_panel_set_person_confidence_callback( + workspace->entity_details_panel, + workspace_on_person_confidence_changed, workspace); gtk_overlay_set_child( GTK_OVERLAY( @@ -3258,6 +3273,14 @@ void workspace_set_person_role_callback(Workspace *workspace, workspace->person_role_user_data = user_data; } +void workspace_set_person_confidence_callback(Workspace *workspace, + WorkspacePersonConfidenceCallback callback, gpointer user_data) +{ + if (workspace == NULL) return; + workspace->person_confidence_callback = callback; + workspace->person_confidence_user_data = user_data; +} + void workspace_reset_graph_layout( Workspace *workspace )