feat(relations): attach evidence to relations

This commit is contained in:
grayTerminal-sh 2026-07-22 19:08:26 +02:00
parent f142de8663
commit f585041736
7 changed files with 544 additions and 13 deletions

View file

@ -68,6 +68,8 @@ GQuark create_relation_dialog_error_quark(void);
* @param parent Fenêtre GTK parente.
* @param source_entity_identifier UUID de l'entité source.
* @param entities Tableau de EntityRecord.
* @param evidence_records Tableau de EvidenceRecord disponibles.
* @param investigation_root_path Racine canonique de l'enquête.
* @param callback Fonction appelée après validation ou annulation.
* @param user_data Données privées transmises au callback.
* @param error Emplacement facultatif recevant une erreur immédiate.
@ -78,6 +80,8 @@ gboolean create_relation_dialog_present(
GtkWindow *parent,
const char *source_entity_identifier,
const GPtrArray *entities,
const GPtrArray *evidence_records,
const char *investigation_root_path,
CreateRelationDialogCallback callback,
gpointer user_data,
GError **error
@ -88,11 +92,26 @@ gboolean create_relation_dialog_present(
*
* La source et la cible sont conservées ; les champs métier sont modifiables.
* Le dialogue ne réalise aucune écriture SQLite.
*
* @param parent Fenêtre GTK parente.
* @param relation_record Relation existante à modifier.
* @param entities Tableau de EntityRecord.
* @param evidence_records Tableau de EvidenceRecord disponibles.
* @param selected_evidence_identifiers UUID des preuves déjà liées.
* @param investigation_root_path Racine canonique de l'enquête.
* @param callback Fonction appelée après validation ou annulation.
* @param user_data Données privées transmises au callback.
* @param error Emplacement facultatif recevant une erreur immédiate.
*
* @return TRUE si le dialogue a é créé et présenté.
*/
gboolean edit_relation_dialog_present(
GtkWindow *parent,
const RelationRecord *relation_record,
const GPtrArray *entities,
const GPtrArray *evidence_records,
const GPtrArray *selected_evidence_identifiers,
const char *investigation_root_path,
CreateRelationDialogCallback callback,
gpointer user_data,
GError **error
@ -171,6 +190,16 @@ gint create_relation_dialog_result_get_confidence(
const CreateRelationDialogResult *result
);
/**
* @brief Retourne les UUID de preuves sélectionnés.
*
* @param result Résultat validé du dialogue.
*
* @return Tableau emprunté d'UUID, ou NULL.
*/
const GPtrArray *create_relation_dialog_result_get_evidence_identifiers(
const CreateRelationDialogResult *result);
G_END_DECLS
#endif

View file

@ -143,6 +143,12 @@ typedef void (*MainWindowEditRelationCallback)(
gpointer user_data
);
/** @brief Callback appelé quand une relation est sélectionnée. */
typedef void (*MainWindowRelationSelectedCallback)(
const char *relation_identifier,
gpointer user_data
);
/** @brief Callback appelé pour catégoriser une personne. */
typedef void (*MainWindowPersonRoleCallback)(const char *entity_identifier,
PersonRole role, gpointer user_data);
@ -244,6 +250,19 @@ void main_window_set_edit_relation_callback(
gpointer user_data
);
/** @brief Définit le callback de sélection d'une relation. */
void main_window_set_relation_selected_callback(
MainWindow *main_window,
MainWindowRelationSelectedCallback callback,
gpointer user_data
);
/** @brief Affiche les preuves associées à la relation sélectionnée. */
void main_window_set_relation_evidences(
MainWindow *main_window,
const GPtrArray *evidence_records
);
/** @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);

View file

@ -85,6 +85,12 @@ typedef void (*WorkspaceEditRelationCallback)(
gpointer user_data
);
/** @brief Callback appelé quand une relation devient sélectionnée. */
typedef void (*WorkspaceRelationSelectedCallback)(
const char *relation_identifier,
gpointer user_data
);
/** @brief Callback appelé pour catégoriser une personne. */
typedef void (*WorkspacePersonRoleCallback)(const char *entity_identifier,
PersonRole role, gpointer user_data);
@ -315,6 +321,30 @@ void workspace_set_edit_relation_callback(
gpointer user_data
);
/**
* @brief Définit le callback de sélection d'une relation.
*
* @param workspace Zone de travail.
* @param callback Callback facultatif.
* @param user_data Données empruntées transmises au callback.
*/
void workspace_set_relation_selected_callback(
Workspace *workspace,
WorkspaceRelationSelectedCallback callback,
gpointer user_data
);
/**
* @brief Affiche les preuves associées à la relation sélectionnée.
*
* @param workspace Zone de travail.
* @param evidence_records Tableau emprunté de EvidenceRecord, ou NULL.
*/
void workspace_set_relation_evidences(
Workspace *workspace,
const GPtrArray *evidence_records
);
/** @brief Définit le callback de catégorisation d'une personne. */
void workspace_set_person_role_callback(Workspace *workspace,
WorkspacePersonRoleCallback callback, gpointer user_data);

View file

@ -41,6 +41,7 @@
#include "dao/evidence_dao.h"
#include "dao/entity_dao.h"
#include "dao/relation_dao.h"
#include "dao/relation_evidence_dao.h"
#include "dao/osint_execution_dao.h"
#include "models/evidence_type.h"
#include "widgets/evidence_list_model.h"
@ -4873,7 +4874,7 @@ static void application_on_create_relation_completed(
if (!relation_service_create(
relation_service,
relation_record,
NULL,
create_relation_dialog_result_get_evidence_identifiers(result),
&error
))
{
@ -5020,6 +5021,7 @@ static void application_on_edit_relation_completed(
Application *application = context != NULL ? context->application : NULL;
Database *database = NULL;
RelationDao *relation_dao = NULL;
RelationEvidenceDao *relation_evidence_dao = NULL;
RelationRecord *existing = NULL;
RelationRecord *updated = NULL;
const InvestigationProject *project = NULL;
@ -5033,7 +5035,8 @@ static void application_on_edit_relation_completed(
database = investigation_session_get_database(application->session);
project = investigation_session_get_project(application->session);
relation_dao = relation_dao_new(database, &error);
if (relation_dao == NULL) goto failure;
relation_evidence_dao = relation_evidence_dao_new(database, &error);
if (relation_dao == NULL || relation_evidence_dao == NULL) goto failure;
existing = relation_dao_find_by_identifier(relation_dao,
context->relation_identifier, &error);
if (existing == NULL) goto failure;
@ -5052,9 +5055,47 @@ static void application_on_edit_relation_completed(
relation_record_get_status(existing), &error);
if (updated == NULL || !database_transaction_begin(database)) goto failure;
transaction_active = TRUE;
if (!relation_dao_update(relation_dao, updated, &error) ||
!database_transaction_commit(database))
if (!relation_dao_update(relation_dao, updated, &error))
goto failure;
{
GPtrArray *existing_evidence =
relation_evidence_dao_list_evidence_identifiers(
relation_evidence_dao, context->relation_identifier, &error);
const GPtrArray *selected_evidence =
create_relation_dialog_result_get_evidence_identifiers(result);
if (existing_evidence == NULL) goto failure;
for (guint index = 0; index < existing_evidence->len; index++)
{
const char *identifier = g_ptr_array_index(existing_evidence, index);
gboolean retained = FALSE;
for (guint selected_index = 0;
selected_index < selected_evidence->len; selected_index++)
if (g_strcmp0(identifier, g_ptr_array_index(selected_evidence,
selected_index)) == 0) retained = TRUE;
if (!retained && !relation_evidence_dao_unlink(
relation_evidence_dao, context->relation_identifier,
identifier, &error))
{
g_ptr_array_unref(existing_evidence);
goto failure;
}
}
for (guint index = 0; index < selected_evidence->len; index++)
{
const char *identifier = g_ptr_array_index(selected_evidence, index);
gboolean exists = FALSE;
if (!relation_evidence_dao_exists(relation_evidence_dao,
context->relation_identifier, identifier, &exists, &error) ||
(!exists && !relation_evidence_dao_link(relation_evidence_dao,
context->relation_identifier, identifier, &error)))
{
g_ptr_array_unref(existing_evidence);
goto failure;
}
}
g_ptr_array_unref(existing_evidence);
}
if (!database_transaction_commit(database)) goto failure;
transaction_active = FALSE;
main_window_set_status(application->main_window,
"Relation modifiée. Actualisation du graphe…");
@ -5077,6 +5118,7 @@ cleanup:
relation_record_free(updated);
relation_record_free(existing);
relation_dao_free(relation_dao);
relation_evidence_dao_free(relation_evidence_dao);
create_relation_dialog_result_free(result);
application_edit_relation_context_free(context);
}
@ -5089,8 +5131,13 @@ static void application_on_edit_relation_requested(
Database *database = NULL;
RelationDao *relation_dao = NULL;
EntityDao *entity_dao = NULL;
EvidenceDao *evidence_dao = NULL;
RelationEvidenceDao *relation_evidence_dao = NULL;
RelationRecord *relation = NULL;
GPtrArray *entities = NULL;
GPtrArray *evidences = NULL;
GPtrArray *selected_evidences = NULL;
const InvestigationProject *project = NULL;
ApplicationEditRelationContext *context = NULL;
GError *error = NULL;
@ -5099,16 +5146,27 @@ static void application_on_edit_relation_requested(
database = investigation_session_get_database(application->session);
relation_dao = relation_dao_new(database, &error);
entity_dao = entity_dao_new(database, &error);
evidence_dao = evidence_dao_new(database, &error);
relation_evidence_dao = relation_evidence_dao_new(database, &error);
project = investigation_session_get_project(application->session);
if (relation_dao != NULL)
relation = relation_dao_find_by_identifier(relation_dao,
relation_identifier, &error);
if (entity_dao != NULL) entities = entity_dao_list_all(entity_dao, &error);
if (relation == NULL || entities == NULL) goto failure;
if (evidence_dao != NULL) evidences = evidence_dao_list_all(evidence_dao,
&error);
if (relation_evidence_dao != NULL) selected_evidences =
relation_evidence_dao_list_evidence_identifiers(relation_evidence_dao,
relation_identifier, &error);
if (relation == NULL || entities == NULL || evidences == NULL ||
selected_evidences == NULL || project == NULL) goto failure;
context = g_new0(ApplicationEditRelationContext, 1);
context->application = application;
context->relation_identifier = g_strdup(relation_identifier);
if (context->relation_identifier == NULL || !edit_relation_dialog_present(
main_window_get_window(application->main_window), relation, entities,
evidences, selected_evidences,
investigation_project_get_root_path(project),
application_on_edit_relation_completed, context, &error))
goto failure;
context = NULL;
@ -5121,11 +5179,63 @@ cleanup:
application_edit_relation_context_free(context);
g_clear_error(&error);
g_clear_pointer(&entities, g_ptr_array_unref);
g_clear_pointer(&evidences, g_ptr_array_unref);
g_clear_pointer(&selected_evidences, g_ptr_array_unref);
relation_record_free(relation);
entity_dao_free(entity_dao);
evidence_dao_free(evidence_dao);
relation_evidence_dao_free(relation_evidence_dao);
relation_dao_free(relation_dao);
}
/** @brief Charge les preuves liées à la relation affichée dans le volet. */
static void application_on_relation_selected(
const char *relation_identifier,
gpointer user_data)
{
Application *application = user_data;
Database *database = NULL;
RelationEvidenceDao *relation_evidence_dao = NULL;
EvidenceDao *evidence_dao = NULL;
GPtrArray *identifiers = NULL;
GPtrArray *records = NULL;
GError *error = NULL;
if (application == NULL || application->session == NULL ||
relation_identifier == NULL) return;
database = investigation_session_get_database(application->session);
relation_evidence_dao = relation_evidence_dao_new(database, &error);
if (relation_evidence_dao == NULL) goto cleanup;
evidence_dao = evidence_dao_new(database, &error);
if (evidence_dao == NULL) goto cleanup;
identifiers = relation_evidence_dao_list_evidence_identifiers(
relation_evidence_dao, relation_identifier, &error);
if (identifiers == NULL) goto cleanup;
records = g_ptr_array_new_with_free_func((GDestroyNotify)
evidence_record_free);
if (records == NULL) goto cleanup;
for (guint index = 0; index < identifiers->len; index++)
{
EvidenceRecord *record = evidence_dao_find_by_identifier(evidence_dao,
g_ptr_array_index(identifiers, index), &error);
if (record == NULL) goto cleanup;
g_ptr_array_add(records, record);
}
main_window_set_relation_evidences(application->main_window, records);
cleanup:
if (error != NULL)
{
g_warning("Impossible de charger les preuves de la relation : %s",
error->message);
main_window_set_relation_evidences(application->main_window, NULL);
}
g_clear_error(&error);
g_clear_pointer(&records, g_ptr_array_unref);
g_clear_pointer(&identifiers, g_ptr_array_unref);
evidence_dao_free(evidence_dao);
relation_evidence_dao_free(relation_evidence_dao);
}
static void application_on_add_relation_requested(
const char *source_entity_identifier,
gpointer user_data
@ -5140,8 +5250,12 @@ static void application_on_add_relation_requested(
EntityDao *entity_dao =
NULL;
EvidenceDao *evidence_dao = NULL;
const InvestigationProject *project = NULL;
GPtrArray *entities =
NULL;
GPtrArray *evidences = NULL;
GError *error =
NULL;
@ -5161,6 +5275,7 @@ static void application_on_add_relation_requested(
investigation_session_get_database(
application->session
);
project = investigation_session_get_project(application->session);
if (database == NULL)
{
@ -5202,11 +5317,16 @@ static void application_on_add_relation_requested(
&error
);
evidence_dao = evidence_dao_new(database, &error);
if (evidence_dao != NULL)
evidences = evidence_dao_list_all(evidence_dao, &error);
evidence_dao_free(evidence_dao);
entity_dao_free(
entity_dao
);
if (entities == NULL)
if (entities == NULL || evidences == NULL || project == NULL)
{
application_present_error(
application,
@ -5220,6 +5340,9 @@ static void application_on_add_relation_requested(
&error
);
g_clear_pointer(&entities, g_ptr_array_unref);
g_clear_pointer(&evidences, g_ptr_array_unref);
return;
}
@ -5229,6 +5352,8 @@ static void application_on_add_relation_requested(
),
source_entity_identifier,
entities,
evidences,
investigation_project_get_root_path(project),
application_on_create_relation_completed,
application,
&error
@ -5250,6 +5375,7 @@ static void application_on_add_relation_requested(
g_ptr_array_unref(
entities
);
g_ptr_array_unref(evidences);
}
/**
@ -6544,6 +6670,8 @@ static void application_on_activate(
main_window_set_edit_relation_callback(application->main_window,
application_on_edit_relation_requested, application);
main_window_set_relation_selected_callback(application->main_window,
application_on_relation_selected, application);
main_window_set_person_role_callback(application->main_window,
application_on_person_role_changed, application);

View file

@ -6,6 +6,7 @@
#include "views/create_relation_dialog.h"
#include "models/entity_record.h"
#include "models/evidence_record.h"
#include "models/relation_record.h"
#include <glib.h>
@ -21,10 +22,32 @@ struct CreateRelationDialogResult
char *relation_type;
char *label;
char *justification;
GPtrArray *evidence_identifiers;
gint confidence;
};
/** @brief Copie privée des données nécessaires à la sélection d'une preuve. */
typedef struct
{
char *identifier;
char *name;
char *type;
char *date;
char *sha256;
char *absolute_path;
} CreateRelationEvidenceOption;
/** @brief Libère une option de preuve. */
static void create_relation_evidence_option_free(gpointer data)
{
CreateRelationEvidenceOption *option = data;
if (option == NULL) return;
g_free(option->identifier); g_free(option->name); g_free(option->type);
g_free(option->date); g_free(option->sha256); g_free(option->absolute_path);
g_free(option);
}
/**
* @brief État privé conservé pendant l'affichage du dialogue.
*/
@ -41,6 +64,13 @@ typedef struct
char *source_entity_identifier;
GPtrArray *target_identifiers;
GPtrArray *evidence_options;
GPtrArray *evidence_check_buttons;
GtkWidget *evidence_preview_stack;
GtkWidget *evidence_preview_status;
GtkWidget *evidence_preview_picture;
GtkWidget *evidence_preview_video;
GtkWidget *evidence_metadata_label;
CreateRelationDialogCallback callback;
gpointer callback_data;
@ -263,6 +293,75 @@ static void create_relation_dialog_show_error(
);
}
/** @brief Arrête la vidéo utilisée par l'aperçu des preuves. */
static void create_relation_dialog_clear_preview(
CreateRelationDialogState *state)
{
GtkMediaStream *stream = NULL;
if (state == NULL || state->evidence_preview_video == NULL ||
state->evidence_preview_picture == NULL) return;
stream = gtk_video_get_media_stream(GTK_VIDEO(
state->evidence_preview_video));
if (stream != NULL) gtk_media_stream_pause(stream);
gtk_video_set_media_stream(GTK_VIDEO(state->evidence_preview_video), NULL);
gtk_picture_set_paintable(GTK_PICTURE(
state->evidence_preview_picture), NULL);
}
/** @brief Affiche la preuve dont la case vient d'être manipulée. */
static void create_relation_dialog_on_evidence_toggled(GtkCheckButton *button,
gpointer user_data)
{
CreateRelationDialogState *state = user_data;
CreateRelationEvidenceOption *option = g_object_get_data(
G_OBJECT(button), "relation-evidence-option");
GFile *file = NULL;
GFileInfo *info = NULL;
const char *content_type = NULL;
char *mime = NULL;
char *metadata = NULL;
GError *error = NULL;
if (state == NULL || option == NULL) return;
create_relation_dialog_clear_preview(state);
metadata = g_strdup_printf("%s\nType : %s\nDate : %s\nSHA-256 : %s",
option->name, option->type, option->date, option->sha256);
gtk_label_set_text(GTK_LABEL(state->evidence_metadata_label), metadata);
g_free(metadata);
if (option->absolute_path == NULL) goto unsupported;
file = g_file_new_for_path(option->absolute_path);
info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
G_FILE_QUERY_INFO_NONE, NULL, &error);
if (info != NULL) content_type = g_file_info_get_content_type(info);
if (content_type != NULL) mime = g_content_type_get_mime_type(content_type);
if (mime != NULL && g_str_has_prefix(mime, "image/"))
{
gtk_picture_set_filename(GTK_PICTURE(state->evidence_preview_picture),
option->absolute_path);
gtk_stack_set_visible_child_name(GTK_STACK(
state->evidence_preview_stack), "image");
}
else if (mime != NULL && g_str_has_prefix(mime, "video/"))
{
GtkMediaStream *stream = gtk_media_file_new_for_filename(
option->absolute_path);
gtk_video_set_media_stream(GTK_VIDEO(state->evidence_preview_video),
stream);
g_object_unref(stream);
gtk_stack_set_visible_child_name(GTK_STACK(
state->evidence_preview_stack), "video");
}
else
{
unsupported:
gtk_label_set_text(GTK_LABEL(state->evidence_preview_status),
"Aperçu indisponible pour ce format.");
gtk_stack_set_visible_child_name(GTK_STACK(
state->evidence_preview_stack), "status");
}
g_clear_error(&error); g_free(mime);
g_clear_object(&info); g_clear_object(&file);
}
/**
* @brief Libère l'état privé du dialogue.
*/
@ -282,6 +381,9 @@ static void create_relation_dialog_state_free(
&state->target_identifiers,
g_ptr_array_unref
);
create_relation_dialog_clear_preview(state);
g_clear_pointer(&state->evidence_options, g_ptr_array_unref);
g_clear_pointer(&state->evidence_check_buttons, g_ptr_array_unref);
g_free(
state->source_entity_identifier
@ -529,13 +631,32 @@ static void create_relation_dialog_on_create_clicked(
result->confidence =
confidence;
result->evidence_identifiers = g_ptr_array_new_with_free_func(g_free);
if (result->evidence_identifiers != NULL)
{
guint evidence_index = 0;
for (evidence_index = 0;
evidence_index < state->evidence_check_buttons->len;
evidence_index++)
{
GtkCheckButton *check_button = g_ptr_array_index(
state->evidence_check_buttons, evidence_index);
CreateRelationEvidenceOption *option = g_object_get_data(
G_OBJECT(check_button), "relation-evidence-option");
if (gtk_check_button_get_active(check_button) && option != NULL)
g_ptr_array_add(result->evidence_identifiers,
g_strdup(option->identifier));
}
}
g_free(
justification_text
);
if (result->source_entity_identifier == NULL ||
result->target_entity_identifier == NULL ||
result->relation_type == NULL)
result->relation_type == NULL ||
result->evidence_identifiers == NULL)
{
create_relation_dialog_result_free(
result
@ -689,6 +810,9 @@ static gboolean create_relation_dialog_present_internal(
const char *source_entity_identifier,
const RelationRecord *existing_relation,
const GPtrArray *entities,
const GPtrArray *evidence_records,
const GPtrArray *selected_evidence_identifiers,
const char *investigation_root_path,
CreateRelationDialogCallback callback,
gpointer user_data,
GError **error
@ -733,6 +857,12 @@ static gboolean create_relation_dialog_present_internal(
GtkWidget *justification_scrolled_window =
NULL;
GtkWidget *evidence_box = NULL;
GtkWidget *evidence_list = NULL;
GtkWidget *evidence_list_scrolled = NULL;
GtkWidget *evidence_preview_box = NULL;
GtkWidget *evidence_title = NULL;
GtkWidget *button_box =
NULL;
@ -764,7 +894,8 @@ static gboolean create_relation_dialog_present_internal(
!g_uuid_string_is_valid(
source_entity_identifier
) ||
entities == NULL ||
entities == NULL || evidence_records == NULL ||
investigation_root_path == NULL ||
callback == NULL)
{
g_set_error_literal(
@ -804,6 +935,10 @@ static gboolean create_relation_dialog_present_internal(
g_ptr_array_new_with_free_func(
g_free
);
state->evidence_options = g_ptr_array_new_with_free_func(
create_relation_evidence_option_free);
state->evidence_check_buttons = g_ptr_array_new_with_free_func(
g_object_unref);
state->callback =
callback;
@ -817,7 +952,8 @@ static gboolean create_relation_dialog_present_internal(
);
if (state->source_entity_identifier == NULL ||
state->target_identifiers == NULL ||
state->target_identifiers == NULL || state->evidence_options == NULL ||
state->evidence_check_buttons == NULL ||
target_labels == NULL)
{
g_clear_object(
@ -976,6 +1112,43 @@ static gboolean create_relation_dialog_present_internal(
return FALSE;
}
for (guint evidence_index = 0; evidence_index < evidence_records->len;
evidence_index++)
{
const EvidenceRecord *record = g_ptr_array_index(evidence_records,
evidence_index);
CreateRelationEvidenceOption *option = NULL;
char *candidate = NULL;
char *canonical_root = NULL;
char *canonical_path = NULL;
const char *date = NULL;
if (record == NULL) continue;
option = g_try_new0(CreateRelationEvidenceOption, 1);
if (option == NULL) continue;
option->identifier = g_strdup(evidence_record_get_identifier(record));
option->name = g_strdup(evidence_record_get_original_name(record));
option->type = g_strdup(evidence_record_get_type_identifier(record));
date = evidence_record_get_collected_at(record);
if (date == NULL) date = evidence_record_get_imported_at(record);
option->date = g_strdup(date != NULL ? date : "Date inconnue");
option->sha256 = g_strdup(evidence_record_get_sha256(record));
candidate = g_build_filename(investigation_root_path,
evidence_record_get_relative_path(record), NULL);
canonical_root = g_canonicalize_filename(investigation_root_path, NULL);
canonical_path = g_canonicalize_filename(candidate, NULL);
if (canonical_root != NULL && canonical_path != NULL &&
g_str_has_prefix(canonical_path, canonical_root) &&
(canonical_path[strlen(canonical_root)] == G_DIR_SEPARATOR ||
canonical_path[strlen(canonical_root)] == '\0'))
option->absolute_path = g_strdup(canonical_path);
g_free(canonical_path); g_free(canonical_root); g_free(candidate);
if (option->identifier == NULL || option->name == NULL ||
option->type == NULL || option->sha256 == NULL)
create_relation_evidence_option_free(option);
else
g_ptr_array_add(state->evidence_options, option);
}
state->window =
GTK_WINDOW(
gtk_window_new()
@ -1005,8 +1178,8 @@ static gboolean create_relation_dialog_present_internal(
gtk_window_set_default_size(
state->window,
640,
560
920,
780
);
root_box =
@ -1299,6 +1472,73 @@ static gboolean create_relation_dialog_present_internal(
1
);
evidence_title = gtk_label_new("Pièces jointes à la relation");
gtk_label_set_xalign(GTK_LABEL(evidence_title), 0.0F);
evidence_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
evidence_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
evidence_list_scrolled = gtk_scrolled_window_new();
gtk_widget_set_size_request(evidence_list_scrolled, 360, 240);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(evidence_list_scrolled),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(evidence_list_scrolled),
evidence_list);
for (guint evidence_index = 0;
evidence_index < state->evidence_options->len; evidence_index++)
{
CreateRelationEvidenceOption *option = g_ptr_array_index(
state->evidence_options, evidence_index);
char *display = g_strdup_printf("%s — %s — %s", option->name,
option->type, option->date);
GtkWidget *check = gtk_check_button_new_with_label(display);
gboolean active = FALSE;
g_free(display);
if (selected_evidence_identifiers != NULL)
for (guint selected_index = 0;
selected_index < selected_evidence_identifiers->len;
selected_index++)
if (g_strcmp0(option->identifier, g_ptr_array_index(
selected_evidence_identifiers, selected_index)) == 0)
active = TRUE;
gtk_check_button_set_active(GTK_CHECK_BUTTON(check), active);
g_object_set_data(G_OBJECT(check), "relation-evidence-option", option);
g_signal_connect(check, "toggled", G_CALLBACK(
create_relation_dialog_on_evidence_toggled), state);
gtk_box_append(GTK_BOX(evidence_list), check);
g_ptr_array_add(state->evidence_check_buttons, g_object_ref(check));
}
if (state->evidence_options->len == 0)
gtk_box_append(GTK_BOX(evidence_list), gtk_label_new(
"Aucune preuve enregistrée dans lenquête."));
evidence_preview_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_widget_set_hexpand(evidence_preview_box, TRUE);
state->evidence_metadata_label = gtk_label_new(
"Sélectionnez une preuve pour afficher son aperçu.");
gtk_label_set_xalign(GTK_LABEL(state->evidence_metadata_label), 0.0F);
gtk_label_set_wrap(GTK_LABEL(state->evidence_metadata_label), TRUE);
gtk_label_set_selectable(GTK_LABEL(state->evidence_metadata_label), TRUE);
state->evidence_preview_stack = gtk_stack_new();
gtk_widget_set_size_request(state->evidence_preview_stack, 420, 220);
state->evidence_preview_status = gtk_label_new("Aucun aperçu.");
state->evidence_preview_picture = gtk_picture_new();
gtk_picture_set_content_fit(GTK_PICTURE(state->evidence_preview_picture),
GTK_CONTENT_FIT_CONTAIN);
state->evidence_preview_video = gtk_video_new();
gtk_video_set_autoplay(GTK_VIDEO(state->evidence_preview_video), FALSE);
gtk_stack_add_named(GTK_STACK(state->evidence_preview_stack),
state->evidence_preview_status, "status");
gtk_stack_add_named(GTK_STACK(state->evidence_preview_stack),
state->evidence_preview_picture, "image");
gtk_stack_add_named(GTK_STACK(state->evidence_preview_stack),
state->evidence_preview_video, "video");
gtk_box_append(GTK_BOX(evidence_preview_box),
state->evidence_metadata_label);
gtk_box_append(GTK_BOX(evidence_preview_box), state->evidence_preview_stack);
gtk_box_append(GTK_BOX(evidence_box), evidence_list_scrolled);
gtk_box_append(GTK_BOX(evidence_box), evidence_preview_box);
gtk_grid_attach(GTK_GRID(grid), evidence_title, 0, 5, 2, 1);
gtk_grid_attach(GTK_GRID(grid), evidence_box, 0, 6, 2, 1);
gtk_grid_attach(
GTK_GRID(
grid
@ -1582,14 +1822,19 @@ static gboolean create_relation_dialog_present_internal(
gboolean create_relation_dialog_present(GtkWindow *parent,
const char *source_entity_identifier, const GPtrArray *entities,
const GPtrArray *evidence_records, const char *investigation_root_path,
CreateRelationDialogCallback callback, gpointer user_data, GError **error)
{
return create_relation_dialog_present_internal(parent,
source_entity_identifier, NULL, entities, callback, user_data, error);
source_entity_identifier, NULL, entities, evidence_records, NULL,
investigation_root_path, callback, user_data, error);
}
gboolean edit_relation_dialog_present(GtkWindow *parent,
const RelationRecord *relation_record, const GPtrArray *entities,
const GPtrArray *evidence_records,
const GPtrArray *selected_evidence_identifiers,
const char *investigation_root_path,
CreateRelationDialogCallback callback, gpointer user_data, GError **error)
{
if (relation_record == NULL)
@ -1601,7 +1846,9 @@ gboolean edit_relation_dialog_present(GtkWindow *parent,
}
return create_relation_dialog_present_internal(parent,
relation_record_get_source_entity_identifier(relation_record),
relation_record, entities, callback, user_data, error);
relation_record, entities, evidence_records,
selected_evidence_identifiers, investigation_root_path,
callback, user_data, error);
}
void create_relation_dialog_result_free(
@ -1633,11 +1880,19 @@ void create_relation_dialog_result_free(
result->source_entity_identifier
);
g_clear_pointer(&result->evidence_identifiers, g_ptr_array_unref);
g_free(
result
);
}
const GPtrArray *create_relation_dialog_result_get_evidence_identifiers(
const CreateRelationDialogResult *result)
{
return result != NULL ? result->evidence_identifiers : NULL;
}
const char *create_relation_dialog_result_get_source_identifier(
const CreateRelationDialogResult *result
)

View file

@ -1672,6 +1672,21 @@ void main_window_set_edit_relation_callback(MainWindow *main_window,
main_window->edit_relation_user_data = user_data;
}
void main_window_set_relation_selected_callback(MainWindow *main_window,
MainWindowRelationSelectedCallback callback, gpointer user_data)
{
if (main_window == NULL) return;
workspace_set_relation_selected_callback(main_window->workspace,
(WorkspaceRelationSelectedCallback) callback, user_data);
}
void main_window_set_relation_evidences(MainWindow *main_window,
const GPtrArray *evidence_records)
{
if (main_window == NULL) return;
workspace_set_relation_evidences(main_window->workspace, evidence_records);
}
void main_window_set_person_role_callback(MainWindow *main_window,
MainWindowPersonRoleCallback callback, gpointer user_data)
{

View file

@ -67,6 +67,7 @@ struct Workspace
GtkWidget *relation_details_panel;
GtkWidget *relation_details_title_label;
GtkWidget *relation_details_summary_label;
GtkWidget *relation_evidences_label;
GtkWidget *edit_relation_button;
GtkWidget *close_relation_details_button;
char *selected_relation_identifier;
@ -130,6 +131,8 @@ struct Workspace
add_relation_user_data;
WorkspaceEditRelationCallback edit_relation_callback;
WorkspaceRelationSelectedCallback relation_selected_callback;
gpointer relation_selected_user_data;
gpointer edit_relation_user_data;
WorkspacePersonRoleCallback person_role_callback;
gpointer person_role_user_data;
@ -804,6 +807,11 @@ static void workspace_on_graph_node_selected(
summary != NULL ? summary : "Informations indisponibles"
);
g_free(summary);
if (workspace->relation_selected_callback != NULL)
workspace->relation_selected_callback(
workspace->selected_relation_identifier,
workspace->relation_selected_user_data);
}
workspace_update_osint_tools_menu(
@ -1677,6 +1685,8 @@ Workspace *workspace_new(void)
);
workspace->relation_details_title_label = gtk_label_new(NULL);
workspace->relation_details_summary_label = gtk_label_new(NULL);
workspace->relation_evidences_label = gtk_label_new(
"Pièces jointes : chargement…");
workspace->edit_relation_button = gtk_button_new_with_label("Modifier");
workspace->close_relation_details_button =
gtk_button_new_from_icon_name("window-close-symbolic");
@ -1686,6 +1696,7 @@ Workspace *workspace_new(void)
workspace->relation_details_panel == NULL ||
workspace->relation_details_title_label == NULL ||
workspace->relation_details_summary_label == NULL ||
workspace->relation_evidences_label == NULL ||
workspace->edit_relation_button == NULL ||
workspace->close_relation_details_button == NULL)
{
@ -1732,6 +1743,13 @@ Workspace *workspace_new(void)
GTK_LABEL(workspace->relation_details_summary_label),
TRUE
);
gtk_widget_set_margin_start(workspace->relation_evidences_label, 16);
gtk_widget_set_margin_end(workspace->relation_evidences_label, 16);
gtk_widget_set_margin_bottom(workspace->relation_evidences_label, 12);
gtk_label_set_xalign(GTK_LABEL(workspace->relation_evidences_label), 0.0F);
gtk_label_set_wrap(GTK_LABEL(workspace->relation_evidences_label), TRUE);
gtk_label_set_selectable(GTK_LABEL(workspace->relation_evidences_label),
TRUE);
gtk_box_append(
GTK_BOX(workspace->relation_details_panel),
workspace->relation_details_title_label
@ -1745,6 +1763,8 @@ Workspace *workspace_new(void)
GTK_BOX(workspace->relation_details_panel),
workspace->relation_details_summary_label
);
gtk_box_append(GTK_BOX(workspace->relation_details_panel),
workspace->relation_evidences_label);
gtk_box_append(GTK_BOX(workspace->relation_details_panel),
workspace->edit_relation_button);
g_signal_connect(workspace->edit_relation_button, "clicked",
@ -3195,6 +3215,41 @@ void workspace_set_edit_relation_callback(Workspace *workspace,
workspace->edit_relation_user_data = user_data;
}
void workspace_set_relation_selected_callback(Workspace *workspace,
WorkspaceRelationSelectedCallback callback, gpointer user_data)
{
if (workspace == NULL) return;
workspace->relation_selected_callback = callback;
workspace->relation_selected_user_data = user_data;
}
void workspace_set_relation_evidences(Workspace *workspace,
const GPtrArray *evidence_records)
{
GString *text = NULL;
if (workspace == NULL || workspace->relation_evidences_label == NULL)
return;
text = g_string_new("Pièces jointes");
if (evidence_records == NULL || evidence_records->len == 0)
g_string_append(text, " : aucune");
else
{
g_string_append_printf(text, " (%u) :", evidence_records->len);
for (guint index = 0; index < evidence_records->len; index++)
{
const EvidenceRecord *record = g_ptr_array_index(evidence_records,
index);
if (record == NULL) continue;
g_string_append_printf(text, "\n• %s — %s",
evidence_record_get_original_name(record),
evidence_record_get_type_identifier(record));
}
}
gtk_label_set_text(GTK_LABEL(workspace->relation_evidences_label),
text->str);
g_string_free(text, TRUE);
}
void workspace_set_person_role_callback(Workspace *workspace,
WorkspacePersonRoleCallback callback, gpointer user_data)
{