/****************************************************************************** * @file entity_details_panel.c * @brief Implémentation du volet latéral de détails d'une entité. ******************************************************************************/ #include "widgets/entity_details_panel.h" #include "models/entity_record.h" #include /** * @struct EntityDetailsPanel * @brief État privé du volet latéral. */ struct EntityDetailsPanel { GtkWidget *root_revealer; GtkWidget *frame; GtkWidget *add_relation_button; GtkWidget *close_button; GtkWidget *entity_title_label; GtkWidget *entity_value_label; GtkWidget *entity_type_label; GtkWidget *entity_description_label; GtkWidget *entity_confidence_label; GtkWidget *entity_status_label; GtkWidget *entity_created_at_label; GtkWidget *entity_updated_at_label; GtkWidget *entity_identifier_label; GtkWidget *person_role_box; GtkDropDown *person_role_dropdown; GtkSpinButton *person_confidence_spin; GtkWidget *person_confidence_button; char *selected_entity_identifier; EntityDetailsPanelCloseCallback close_callback; gpointer close_user_data; EntityDetailsPanelAddRelationCallback add_relation_callback; gpointer add_relation_user_data; 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) { EntityDetailsPanel *details_panel = user_data; guint selected = GTK_INVALID_LIST_POSITION; (void) object; (void) parameter; if (details_panel == NULL || details_panel->updating_person_role || details_panel->person_role_callback == NULL || details_panel->selected_entity_identifier == NULL) return; selected = gtk_drop_down_get_selected(details_panel->person_role_dropdown); if (selected > PERSON_ROLE_RELATED_PERSON) return; details_panel->person_role_callback( details_panel->selected_entity_identifier, (PersonRole) selected, details_panel->person_role_user_data); } /** * @brief Définit une valeur avec un texte de remplacement. */ static void entity_details_panel_set_field_text( GtkWidget *label, const char *value, const char *fallback ) { if (label == NULL) { return; } gtk_label_set_text( GTK_LABEL(label), value != NULL && value[0] != '\0' ? value : fallback ); } /** * @brief Ajoute une ligne de métadonnée dans la grille. * * @return Label GTK destiné à recevoir la valeur. */ static GtkWidget *entity_details_panel_add_field( GtkGrid *grid, gint row, const char *title ) { GtkWidget *title_label = NULL; GtkWidget *value_label = NULL; if (grid == NULL || title == NULL || title[0] == '\0') { return NULL; } title_label = gtk_label_new( title ); value_label = gtk_label_new( NULL ); if (title_label == NULL || value_label == NULL) { return NULL; } gtk_label_set_xalign( GTK_LABEL(title_label), 0.0F ); gtk_widget_set_valign( title_label, GTK_ALIGN_START ); gtk_widget_add_css_class( title_label, "dim-label" ); gtk_label_set_xalign( GTK_LABEL(value_label), 0.0F ); gtk_label_set_wrap( GTK_LABEL(value_label), TRUE ); gtk_label_set_wrap_mode( GTK_LABEL(value_label), PANGO_WRAP_WORD_CHAR ); gtk_label_set_selectable( GTK_LABEL(value_label), TRUE ); gtk_widget_set_hexpand( value_label, TRUE ); gtk_widget_set_halign( value_label, GTK_ALIGN_FILL ); gtk_widget_set_valign( value_label, GTK_ALIGN_START ); gtk_grid_attach( grid, title_label, 0, row, 1, 1 ); gtk_grid_attach( grid, value_label, 1, row, 1, 1 ); return value_label; } /** * @brief Convertit le statut métier en texte utilisateur. */ static const char *entity_details_panel_get_status_text( EntityStatus status ) { switch (status) { case ENTITY_STATUS_ACTIVE: return "Active"; case ENTITY_STATUS_ARCHIVED: return "Archivée"; case ENTITY_STATUS_DELETED: return "Supprimée"; case ENTITY_STATUS_UNKNOWN: default: return "Inconnu"; } } /** * @brief Met à jour l'état du bouton d'ajout d'une relation. */ static void entity_details_panel_update_add_relation_button( EntityDetailsPanel *details_panel ) { gboolean enabled = FALSE; if (details_panel == NULL || details_panel->add_relation_button == NULL) { return; } enabled = details_panel->add_relation_callback != NULL && details_panel->selected_entity_identifier != NULL && g_uuid_string_is_valid( details_panel->selected_entity_identifier ); gtk_widget_set_sensitive( details_panel->add_relation_button, enabled ); } /** * @brief Transmet la demande d'ajout d'une relation. */ static void entity_details_panel_on_add_relation_clicked( GtkButton *button, gpointer user_data ) { EntityDetailsPanel *details_panel = user_data; (void) button; if (details_panel == NULL || details_panel->add_relation_callback == NULL || details_panel->selected_entity_identifier == NULL || !g_uuid_string_is_valid( details_panel->selected_entity_identifier )) { return; } details_panel->add_relation_callback( details_panel->selected_entity_identifier, details_panel->add_relation_user_data ); } /** * @brief Ferme le volet depuis le bouton de l'interface. */ static void entity_details_panel_on_close_clicked( GtkButton *button, gpointer user_data ) { EntityDetailsPanel *details_panel = user_data; (void) button; if (details_panel == NULL) { return; } entity_details_panel_clear( details_panel ); if (details_panel->close_callback != NULL) { details_panel->close_callback( details_panel->close_user_data ); } } EntityDetailsPanel *entity_details_panel_new(void) { EntityDetailsPanel *details_panel = NULL; GtkWidget *content_box = NULL; GtkWidget *header_box = NULL; GtkWidget *header_label = NULL; GtkWidget *separator = NULL; GtkWidget *scrolled_window = NULL; GtkWidget *details_box = NULL; GtkWidget *details_grid = NULL; GtkStringList *person_role_labels = NULL; details_panel = g_try_new0( EntityDetailsPanel, 1 ); if (details_panel == NULL) { return NULL; } details_panel->root_revealer = gtk_revealer_new(); details_panel->frame = gtk_frame_new( NULL ); details_panel->add_relation_button = gtk_button_new_from_icon_name( "list-add-symbolic" ); details_panel->close_button = gtk_button_new_from_icon_name( "window-close-symbolic" ); content_box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); header_box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 8 ); header_label = gtk_label_new( "Détails de l’entité" ); separator = gtk_separator_new( GTK_ORIENTATION_HORIZONTAL ); scrolled_window = gtk_scrolled_window_new(); details_box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 16 ); details_grid = gtk_grid_new(); if (details_panel->root_revealer == NULL || details_panel->frame == NULL || details_panel->add_relation_button == NULL || details_panel->close_button == NULL || content_box == NULL || header_box == NULL || header_label == NULL || separator == NULL || scrolled_window == NULL || details_box == NULL || details_grid == NULL) { entity_details_panel_free( details_panel ); return NULL; } gtk_widget_set_size_request( details_panel->root_revealer, 360, -1 ); gtk_widget_set_halign( details_panel->root_revealer, GTK_ALIGN_END ); gtk_widget_set_valign( details_panel->root_revealer, GTK_ALIGN_FILL ); gtk_widget_set_vexpand( details_panel->root_revealer, TRUE ); gtk_revealer_set_transition_type( GTK_REVEALER( details_panel->root_revealer ), GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT ); gtk_revealer_set_transition_duration( GTK_REVEALER( details_panel->root_revealer ), 180 ); gtk_revealer_set_reveal_child( GTK_REVEALER( details_panel->root_revealer ), FALSE ); gtk_widget_set_margin_top( details_panel->frame, 12 ); gtk_widget_set_margin_end( details_panel->frame, 12 ); gtk_widget_set_margin_bottom( details_panel->frame, 12 ); gtk_widget_add_css_class( details_panel->frame, "view" ); gtk_widget_set_margin_start( header_box, 16 ); gtk_widget_set_margin_end( header_box, 8 ); gtk_widget_set_margin_top( header_box, 8 ); gtk_widget_set_margin_bottom( header_box, 8 ); gtk_label_set_xalign( GTK_LABEL(header_label), 0.0F ); gtk_widget_set_hexpand( header_label, TRUE ); gtk_widget_set_halign( header_label, GTK_ALIGN_FILL ); gtk_widget_add_css_class( header_label, "heading" ); gtk_widget_set_tooltip_text( details_panel->add_relation_button, "Ajouter une relation depuis cette entité" ); gtk_widget_add_css_class( details_panel->add_relation_button, "flat" ); gtk_widget_set_sensitive( details_panel->add_relation_button, FALSE ); gtk_widget_set_tooltip_text( details_panel->close_button, "Fermer le volet" ); gtk_widget_add_css_class( details_panel->close_button, "flat" ); gtk_box_append( GTK_BOX(header_box), header_label ); gtk_box_append( GTK_BOX(header_box), details_panel->add_relation_button ); gtk_box_append( GTK_BOX(header_box), details_panel->close_button ); gtk_box_append( GTK_BOX(content_box), header_box ); gtk_box_append( GTK_BOX(content_box), separator ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); gtk_widget_set_vexpand( scrolled_window, TRUE ); gtk_widget_set_margin_start( details_box, 20 ); gtk_widget_set_margin_end( details_box, 20 ); gtk_widget_set_margin_top( details_box, 20 ); gtk_widget_set_margin_bottom( details_box, 20 ); details_panel->entity_title_label = gtk_label_new( NULL ); if (details_panel->entity_title_label == NULL) { entity_details_panel_free( details_panel ); return NULL; } gtk_label_set_xalign( GTK_LABEL( details_panel->entity_title_label ), 0.0F ); gtk_label_set_wrap( GTK_LABEL( details_panel->entity_title_label ), TRUE ); gtk_label_set_selectable( GTK_LABEL( details_panel->entity_title_label ), TRUE ); gtk_widget_add_css_class( details_panel->entity_title_label, "title-3" ); gtk_box_append( GTK_BOX(details_box), details_panel->entity_title_label ); gtk_grid_set_column_spacing( GTK_GRID(details_grid), 16 ); gtk_grid_set_row_spacing( GTK_GRID(details_grid), 12 ); gtk_widget_set_hexpand( details_grid, TRUE ); details_panel->entity_value_label = entity_details_panel_add_field( GTK_GRID(details_grid), 0, "Valeur" ); details_panel->entity_type_label = entity_details_panel_add_field( GTK_GRID(details_grid), 1, "Type" ); details_panel->entity_description_label = entity_details_panel_add_field( GTK_GRID(details_grid), 2, "Description" ); details_panel->entity_confidence_label = entity_details_panel_add_field( GTK_GRID(details_grid), 3, "Confiance" ); details_panel->entity_status_label = entity_details_panel_add_field( GTK_GRID(details_grid), 4, "Statut" ); details_panel->entity_created_at_label = entity_details_panel_add_field( GTK_GRID(details_grid), 5, "Créée le" ); details_panel->entity_updated_at_label = entity_details_panel_add_field( GTK_GRID(details_grid), 6, "Modifiée le" ); details_panel->entity_identifier_label = entity_details_panel_add_field( GTK_GRID(details_grid), 7, "Identifiant" ); details_panel->person_role_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); person_role_labels = gtk_string_list_new(NULL); for (guint role = PERSON_ROLE_UNCATEGORIZED; role <= PERSON_ROLE_RELATED_PERSON; role++) gtk_string_list_append(person_role_labels, person_role_get_label((PersonRole) role)); details_panel->person_role_dropdown = GTK_DROP_DOWN( gtk_drop_down_new(G_LIST_MODEL(person_role_labels), NULL)); person_role_labels = NULL; gtk_box_append(GTK_BOX(details_panel->person_role_box), 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 || details_panel->entity_description_label == NULL || details_panel->entity_confidence_label == NULL || details_panel->entity_status_label == NULL || details_panel->entity_created_at_label == NULL || details_panel->entity_updated_at_label == NULL || details_panel->entity_identifier_label == NULL) { entity_details_panel_free( details_panel ); return NULL; } gtk_box_append( GTK_BOX(details_box), details_grid ); gtk_scrolled_window_set_child( GTK_SCROLLED_WINDOW(scrolled_window), details_box ); gtk_box_append( GTK_BOX(content_box), scrolled_window ); gtk_frame_set_child( GTK_FRAME(details_panel->frame), content_box ); gtk_revealer_set_child( GTK_REVEALER( details_panel->root_revealer ), details_panel->frame ); g_signal_connect( details_panel->add_relation_button, "clicked", G_CALLBACK( entity_details_panel_on_add_relation_clicked ), details_panel ); g_signal_connect( details_panel->close_button, "clicked", G_CALLBACK( entity_details_panel_on_close_clicked ), details_panel ); entity_details_panel_clear( details_panel ); return details_panel; } GtkWidget *entity_details_panel_get_widget( const EntityDetailsPanel *details_panel ) { if (details_panel == NULL) { return NULL; } return details_panel->root_revealer; } void entity_details_panel_set_entity( EntityDetailsPanel *details_panel, const EntityRecord *entity_record ) { const char *title = NULL; char *confidence_text = NULL; if (details_panel == NULL) { return; } g_clear_pointer( &details_panel->selected_entity_identifier, g_free ); if (entity_record == NULL) { entity_details_panel_clear( details_panel ); return; } details_panel->selected_entity_identifier = g_strdup( entity_record_get_identifier( entity_record ) ); gboolean is_person = g_strcmp0(entity_record_get_type_identifier( entity_record), "person") == 0; gtk_widget_set_visible(details_panel->person_role_box, is_person); 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( details_panel ); title = entity_record_get_label( entity_record ); if (title == NULL || title[0] == '\0') { title = entity_record_get_value( entity_record ); } if (title == NULL || title[0] == '\0') { title = entity_record_get_identifier( entity_record ); } confidence_text = g_strdup_printf( "%d %%", entity_record_get_confidence( entity_record ) ); entity_details_panel_set_field_text( details_panel->entity_title_label, title, "(entité sans libellé)" ); entity_details_panel_set_field_text( details_panel->entity_value_label, entity_record_get_value( entity_record ), "(valeur inconnue)" ); entity_details_panel_set_field_text( details_panel->entity_type_label, entity_record_get_type_identifier( entity_record ), "(type inconnu)" ); entity_details_panel_set_field_text( details_panel->entity_description_label, entity_record_get_description( entity_record ), "Non renseignée" ); entity_details_panel_set_field_text( details_panel->entity_confidence_label, confidence_text, "Inconnue" ); entity_details_panel_set_field_text( details_panel->entity_status_label, entity_details_panel_get_status_text( entity_record_get_status( entity_record ) ), "Inconnu" ); entity_details_panel_set_field_text( details_panel->entity_created_at_label, entity_record_get_created_at( entity_record ), "(date inconnue)" ); entity_details_panel_set_field_text( details_panel->entity_updated_at_label, entity_record_get_updated_at( entity_record ), "(date inconnue)" ); entity_details_panel_set_field_text( details_panel->entity_identifier_label, entity_record_get_identifier( entity_record ), "(identifiant inconnu)" ); gtk_revealer_set_reveal_child( GTK_REVEALER( details_panel->root_revealer ), TRUE ); gtk_widget_set_can_target(details_panel->root_revealer, TRUE); g_free( confidence_text ); } void entity_details_panel_clear( EntityDetailsPanel *details_panel ) { if (details_panel == NULL) { return; } g_clear_pointer( &details_panel->selected_entity_identifier, g_free ); entity_details_panel_update_add_relation_button( details_panel ); entity_details_panel_set_field_text( details_panel->entity_title_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_value_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_type_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_description_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_confidence_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_status_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_created_at_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_updated_at_label, NULL, "" ); entity_details_panel_set_field_text( details_panel->entity_identifier_label, NULL, "" ); if (details_panel->root_revealer != NULL) { gtk_revealer_set_reveal_child( GTK_REVEALER( details_panel->root_revealer ), FALSE ); /* Le revealer fermé ne doit pas bloquer le canvas sous l'overlay. */ gtk_widget_set_can_target(details_panel->root_revealer, FALSE); } if (details_panel->person_role_box != NULL) gtk_widget_set_visible(details_panel->person_role_box, FALSE); } void entity_details_panel_set_close_callback( EntityDetailsPanel *details_panel, EntityDetailsPanelCloseCallback callback, gpointer user_data ) { if (details_panel == NULL) { return; } details_panel->close_callback = callback; details_panel->close_user_data = user_data; } void entity_details_panel_set_add_relation_callback( EntityDetailsPanel *details_panel, EntityDetailsPanelAddRelationCallback callback, gpointer user_data ) { if (details_panel == NULL) { return; } details_panel->add_relation_callback = callback; details_panel->add_relation_user_data = user_data; entity_details_panel_update_add_relation_button( details_panel ); } void entity_details_panel_set_person_role_callback( EntityDetailsPanel *details_panel, EntityDetailsPanelPersonRoleCallback callback, gpointer user_data) { if (details_panel == NULL) return; details_panel->person_role_callback = 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 ) { if (details_panel == NULL || details_panel->root_revealer == NULL) { return FALSE; } return gtk_revealer_get_reveal_child( GTK_REVEALER( details_panel->root_revealer ) ); } void entity_details_panel_free( EntityDetailsPanel *details_panel ) { if (details_panel == NULL) { return; } if (details_panel->add_relation_button != NULL) { g_signal_handlers_disconnect_by_data( details_panel->add_relation_button, details_panel ); } if (details_panel->close_button != NULL) { g_signal_handlers_disconnect_by_data( details_panel->close_button, details_panel ); } details_panel->close_callback = NULL; details_panel->close_user_data = NULL; details_panel->add_relation_callback = NULL; details_panel->add_relation_user_data = NULL; g_clear_pointer( &details_panel->selected_entity_identifier, g_free ); details_panel->root_revealer = NULL; details_panel->frame = NULL; details_panel->add_relation_button = NULL; details_panel->close_button = NULL; details_panel->entity_title_label = NULL; details_panel->entity_value_label = NULL; details_panel->entity_type_label = NULL; details_panel->entity_description_label = NULL; details_panel->entity_confidence_label = NULL; details_panel->entity_status_label = NULL; details_panel->entity_created_at_label = NULL; details_panel->entity_updated_at_label = NULL; details_panel->entity_identifier_label = NULL; g_free( details_panel ); }