/* Chrysalide - Outil d'analyse de fichiers binaires
 * statusstack.c - empilement d'informations de statut
 *
 * Copyright (C) 2015-2024 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chrysalide is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "statusstack.h"


#include <assert.h>
#include <malloc.h>
#include <string.h>


#include <i18n.h>


#include "helpers.h"
#include "statusstack-int.h"
#include "../core/global.h"



/* -------------------------- GESTION GENERALE DES STATUTS -------------------------- */


/* Initialise la classe des barres de statut améliorées. */
static void gtk_status_stack_class_init(GtkStatusStackClass *);

/* Initialise une instance de barre de statut améliorée. */
static void gtk_status_stack_init(GtkStatusStack *);

/* Supprime toutes les références externes. */
static void gtk_status_stack_dispose(GtkStatusStack *);

/* Procède à la libération totale de la mémoire. */
static void gtk_status_stack_finalize(GtkStatusStack *);

/* Met à jour dans la barre les débits réseau observés. */
static gboolean gtk_status_stack_update_network_stats(GtkStatusStack *);



/* -------------------- STATUT DES INFORMATIONS DE DESASSEMBLAGE -------------------- */


/* Navigation au sein d'assemblage */
struct _navigation_info_t
{
    char *segment;                          /* Segment d'appartenance      */

    mrange_t current;                       /* Emplacement correspondant   */

    VMPA_BUFFER(phys);                      /* Localisation physique       */
    VMPA_BUFFER(virt);                      /* Localisation virtuelle      */

    char *symbol;                           /* Eventuel symbole concerné   */

    char *format;                           /* Architecture et format      */
    char *details;                          /* Encodage de l'instruction   */

};


/* Met en place le suivi d'informations de navigation. */
static void init_navigation_info(navigation_info_t *);

/* Supprime l'empreinte mémoire d'informations de navigation. */
static void fini_navigation_info(navigation_info_t *);

/* S'assure de l'affichage à jour de la partie "navigation". */
static gboolean gtk_status_stack_show_current_location(GtkStatusStack *);

/* Réagit à un clic sur l'icône de zoom. */
static void gtk_status_stack_on_zoom_icon_press(GtkEntry *, GtkEntryIconPosition, GtkStatusStack *);



/* -------------------------- STATUT DES SUIVIS D'ACTIVITE -------------------------- */


/* Informations de progression */
typedef struct _activity_status_t
{
    activity_id_t id;                       /* Identifiant unique          */

    char *message;                          /* Indication à faire valoir   */

    unsigned long current;                  /* Position courante           */
    unsigned long max;                      /* Couverture à parcourir      */

    double last_updated;                    /* Dernière valeur poussée     */

} activity_status_t;


/* Mémorisation des progressions au sein d'activités */
struct _activity_info_t
{
    GMutex access;                          /* Accès à la pile             */

    activity_id_t generator;                /* Générateur de séquence      */

    activity_status_t *statuses;            /* Statuts de progression      */
    size_t count;                           /* Nombre de ces statuts       */

    guint tag;                              /* Identifiant de mise à jour  */

};


/* Met en place le suivi d'informations d'activité. */
static void init_activity_info(activity_info_t *);

/* Supprime l'empreinte mémoire d'informations d'activité. */
static void fini_activity_info(activity_info_t *);

/* Recherche les indications de statut d'une activité donnée. */
static activity_status_t *find_activity_status_by_id(activity_info_t *, activity_id_t);

/* S'assure de l'affichage à jour de la partie "activité". */
static gboolean gtk_status_stack_show_current_activity(GtkStatusStack *);



/* ---------------------------------------------------------------------------------- */
/*                            GESTION GENERALE DES STATUTS                            */
/* ---------------------------------------------------------------------------------- */


/* Détermine le type du composant d'affichage générique. */
G_DEFINE_TYPE(GtkStatusStack, gtk_status_stack, GTK_TYPE_BOX);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe GTK à initialiser.                            *
*                                                                             *
*  Description : Initialise la classe des barres de statut améliorées.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_status_stack_class_init(GtkStatusStackClass *class)
{
    GObjectClass *object;                   /* Plus haut niveau équivalent */
    GtkWidgetClass *widget;                 /* Classe de haut niveau       */

    object = G_OBJECT_CLASS(class);

    object->dispose = (GObjectFinalizeFunc/* ! */)gtk_status_stack_dispose;
    object->finalize = (GObjectFinalizeFunc)gtk_status_stack_finalize;

    widget = GTK_WIDGET_CLASS(class);

    gtk_widget_class_set_template_from_resource(widget, "/re/chrysalide/framework/gtkext/statusstack.ui");

    gtk_widget_class_bind_template_callback_full(widget, BUILDER_CB(gtk_status_stack_on_zoom_icon_press));

    gtk_widget_class_bind_template_child(widget, GtkStatusStack, main);

    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_segment);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_phys);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_virt);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_offset);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_format);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, nav_details);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, zoom);

    gtk_widget_class_bind_template_child(widget, GtkStatusStack, activity_message);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, activity_progress);

    gtk_widget_class_bind_template_child(widget, GtkStatusStack, net_recv_speed);
    gtk_widget_class_bind_template_child(widget, GtkStatusStack, net_send_speed);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = composant GTK à initialiser.                         *
*                                                                             *
*  Description : Initialise une instance de barre de statut améliorée.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_status_stack_init(GtkStatusStack *stack)
{
    gtk_widget_init_template(GTK_WIDGET(stack));

    stack->def_source = NULL;

    stack->nav_info = calloc(1, sizeof(navigation_info_t));
    init_navigation_info(stack->nav_info);

    stack->activity_info = calloc(1, sizeof(activity_info_t));
    init_activity_info(stack->activity_info);

    stack->next_index = 0;

    stack->network_update_tag = g_timeout_add(NETWORK_UPDATE_INTERVAL,
                                              G_SOURCE_FUNC(gtk_status_stack_update_network_stats), stack);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_status_stack_dispose(GtkStatusStack *stack)
{
    g_source_remove(stack->network_update_tag);

    gtk_widget_dispose_template(GTK_WIDGET(stack), GTK_TYPE_STATUS_STACK);

    G_OBJECT_CLASS(gtk_status_stack_parent_class)->dispose(G_OBJECT(stack));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_status_stack_finalize(GtkStatusStack *stack)
{
    fini_navigation_info(stack->nav_info);
    free(stack->nav_info);

    fini_activity_info(stack->activity_info);
    free(stack->activity_info);

    G_OBJECT_CLASS(gtk_status_stack_parent_class)->finalize(G_OBJECT(stack));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Crée une nouvelle instance de barre de statut.               *
*                                                                             *
*  Retour      : Composant GTK mis en place.                                  *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GtkStatusStack *gtk_status_stack_new(void)
{
    GtkStatusStack *result;                 /* Instance à retourner        */

    result = g_object_new(GTK_TYPE_STATUS_STACK, NULL);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                                                                             *
*  Description : Réinitialise la barre de statut à son stade par défaut.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_reset(GtkStatusStack *stack)
{
    gtk_stack_set_visible_child_name(stack->main, "default");

    stack->def_source = NULL;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                                                                             *
*  Description : Met à jour dans la barre les débits réseau observés.         *
*                                                                             *
*  Retour      : G_SOURCE_CONTINUE pour poursuivre les mises à jour.          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_status_stack_update_network_stats(GtkStatusStack *stack)
{
    gboolean result;                        /* Indication à retourner      */
    gint64 timestamp;                       /* Position temporelle         */
    size_t received;                        /* Quantité d'octets reçus     */
    size_t sent;                            /* Quantité d'octets émis      */
    gint64 diff_time;                       /* Différentiel de temps       */
    size_t diff_bytes;                      /* Différentiel de volume      */
    double speed;                           /* Débit de transfert constaté */
    size_t i;                               /* Boucle de parcours          */
    char *value;                            /* Valeur à afficher           */

    const char *units[] = { _("b/s"), _("kb/s"), _("Mb/s"), _("Gb/s"), _("Tb/s") };

    result = G_SOURCE_CONTINUE;

    /* Mémorisation des données */

    timestamp = g_get_monotonic_time();

    get_network_stats(&received, &sent);

    if (stack->next_index < NETWORK_UPDATE_COUNT)
    {
        stack->last_bytes_received[stack->next_index] = received;
        stack->last_bytes_sent[stack->next_index] = sent;
        stack->last_timestamps[stack->next_index] = timestamp;

        stack->next_index++;

    }
    else
    {
        memcpy(stack->last_bytes_received, stack->last_bytes_received + 1,
               (NETWORK_UPDATE_COUNT - 1) * sizeof(size_t));

        memcpy(stack->last_bytes_sent, stack->last_bytes_sent + 1,
               (NETWORK_UPDATE_COUNT - 1) * sizeof(size_t));

        memcpy(stack->last_timestamps, stack->last_timestamps + 1,
               (NETWORK_UPDATE_COUNT - 1) * sizeof(gint64));

        stack->last_bytes_received[NETWORK_UPDATE_COUNT - 1] = received;
        stack->last_bytes_sent[NETWORK_UPDATE_COUNT - 1] = sent;
        stack->last_timestamps[NETWORK_UPDATE_COUNT - 1] = timestamp;

    }

    if (stack->next_index < NETWORK_UPDATE_COUNT)
        goto done;

    diff_time = stack->last_timestamps[NETWORK_UPDATE_COUNT - 1] - stack->last_timestamps[0];

    /* Débit de réception */

    diff_bytes = stack->last_bytes_received[NETWORK_UPDATE_COUNT - 1] - stack->last_bytes_received[0];

    speed = (diff_bytes * 1000000) / diff_time;

    for (i = 0; i < G_N_ELEMENTS(units); i++)
    {
        if (speed < 1024)
            break;

        speed /= 1024;

    }

    if (i == 0)
        asprintf(&value, "%d %s", (int)speed, units[i]);
    else
        asprintf(&value, "%.1f %s", speed, units[i]);

    gtk_label_set_label(stack->net_recv_speed, value);

    free(value);

    /* Débit de émission */

    diff_bytes = stack->last_bytes_sent[NETWORK_UPDATE_COUNT - 1] - stack->last_bytes_sent[0];

    speed = (diff_bytes * 1000000) / diff_time;

    for (i = 0; i < G_N_ELEMENTS(units); i++)
    {
        if (speed < 1024)
            break;

        speed /= 1024;

    }

    if (i == 0)
        asprintf(&value, "%d %s", (int)speed, units[i]);
    else
        asprintf(&value, "%.1f %s", speed, units[i]);

    gtk_label_set_label(stack->net_send_speed, value);

    free(value);

 done:

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                      STATUT DES INFORMATIONS DE DESASSEMBLAGE                      */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : info = informations à initialiser.                           *
*                                                                             *
*  Description : Met en place le suivi d'informations de navigation.          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void init_navigation_info(navigation_info_t *info)
{
    info->segment = NULL;

    info->symbol = NULL;

    info->format = NULL;
    info->details = NULL;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : info = informations à libérer de la mémoire.                 *
*                                                                             *
*  Description : Supprime l'empreinte mémoire d'informations de navigation.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void fini_navigation_info(navigation_info_t *info)
{
    if (info->segment != NULL)
        free(info->segment);

    if (info->symbol != NULL)
        free(info->symbol);

    if (info->format != NULL)
        free(info->format);

    if (info->details != NULL)
        free(info->details);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack   = barre de statut à actualiser.                      *
*                range   = emplacement à mettre en valeur.                    *
*                segment = zone de binaire d'appartenance.                    *
*                symbol  = éventuelle position par rapport à un symbole.      *
*                format  = format du binaire manipulé                         *
*                details = détails supplémentaires (liés à l'encodage ?)      *
*                                                                             *
*  Description : Actualise les informations liées une position d'assemblage.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_update_current_location(GtkStatusStack *stack, const mrange_t *range, const char *segment, const char *symbol, const char *format, const char *details)
{
    navigation_info_t *info;                /* Informations à constituer   */
    const vmpa2t *addr;                     /* Localisation de départ      */

    info = stack->nav_info;

    if (cmp_mrange(&info->current, range) == 0)
        goto useless;

    /* Zone d'appartenance */

    if (segment != NULL)
        info->segment = strdup(segment);
    else
        info->segment = NULL;

    /* Adresses de base */

    copy_mrange(&info->current, range);

    addr = get_mrange_addr(range);

    vmpa2_phys_to_string(addr, MDS_UNDEFINED, info->phys, NULL);

    vmpa2_virt_to_string(addr, MDS_UNDEFINED, info->virt, NULL);

    /* Symbole concerné */

    if (symbol != NULL)
        info->symbol = strdup(symbol);
    else
        info->symbol = NULL;

    /* Architecture & format */

    info->format = strdup(format);

    if (details != NULL)
        info->details = strdup(details);
    else
        info->details = NULL;

    /* Nettoyage et conclusion */

    gtk_status_stack_show_current_location(stack);

 useless:

    ;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = pile de statuts à manipuler.                         *
*                                                                             *
*  Description : S'assure de l'affichage à jour de la partie "navigation".    *
*                                                                             *
*  Retour      : G_SOURCE_REMOVE pour une exécution unique.                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_status_stack_show_current_location(GtkStatusStack *stack)
{
    navigation_info_t *info;                /* Informations à constituer   */
    char raw_pos[6 + VMPA_MAX_LEN + 1];     /* Formatage final en direct   */

    stack->def_source = (GSourceFunc)gtk_status_stack_show_current_location;

    gtk_stack_set_visible_child_name(stack->main, "navigation");

    info = stack->nav_info;

    /* Première partie : navigation */

    gtk_label_set_text(stack->nav_segment, info->segment != NULL ? info->segment : "");

    snprintf(raw_pos, sizeof(raw_pos), "phys: %s", info->phys);

    gtk_label_set_text(stack->nav_phys, raw_pos);

    snprintf(raw_pos, sizeof(raw_pos), "virt: %s", info->virt);

    gtk_label_set_text(stack->nav_virt, raw_pos);

    gtk_label_set_text(stack->nav_offset, info->symbol != NULL ? info->symbol : "");

    /* Seconde partie : format & architecture */

    gtk_label_set_text(stack->nav_format, info->format);

    gtk_label_set_text(stack->nav_details, info->details != NULL ? info->details : "");

    return G_SOURCE_REMOVE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : entry    = zone de texte visée par la procédure.             *
*                icon_pos = position de l'image associée à l'entrée.          *
*                stack    = composant graphique de gestion des statuts.       *
*                                                                             *
*  Description : Réagit à un clic sur l'icône de zoom.                        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void gtk_status_stack_on_zoom_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GtkStatusStack *stack)
{
#if 0
    GtkWidget *popup;                       /* Popup à faire surgir        */
    GdkRectangle rect;                      /* Zone précise à cibler       */

    popup = gtk_popover_new();

    gtk_entry_get_icon_area(entry, GTK_ENTRY_ICON_SECONDARY, &rect);
    gtk_popover_set_pointing_to(GTK_POPOVER(popup), &rect);

    gtk_widget_show(popup);
#endif
}


/* ---------------------------------------------------------------------------------- */
/*                            STATUT DES SUIVIS D'ACTIVITE                            */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : info = informations à initialiser.                           *
*                                                                             *
*  Description : Met en place le suivi d'informations d'activité.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void init_activity_info(activity_info_t *info)
{
    g_mutex_init(&info->access);

    info->generator = NO_ACTIVITY_ID;

    info->statuses = NULL;
    info->count = 0;

    info->tag = 0;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : info = informations à libérer de la mémoire.                 *
*                                                                             *
*  Description : Supprime l'empreinte mémoire d'informations d'activité.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void fini_activity_info(activity_info_t *info)
{
    size_t i;                               /* Boucle de parcours          */

    if (info->tag != 0)
        g_source_remove(info->tag);

    info->tag = 0;

    for (i = 0; i < info->count; i++)
    {
        if (info->statuses[i].message != NULL)
            free(info->statuses[i].message);
    }

    if (info->statuses != NULL)
    {
        free(info->statuses);
        info->statuses = NULL;
    }

    info->count = 0;

    g_mutex_clear(&info->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : info = informations relatives aux activités à consulter.     *
*                id   = identifiant de l'activité à cibler.                   *
*                                                                             *
*  Description : Recherche les indications de statut d'une activité donnée.   *
*                                                                             *
*  Retour      : Structure d'encadrement trouvée ou NULL.                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static activity_status_t *find_activity_status_by_id(activity_info_t *info, activity_id_t id)
{
    activity_status_t *result;              /* Statut trouvé à renvoyer    */
    size_t i;                               /* Boucle de parcours          */

    result = NULL;

    assert(!g_mutex_trylock(&info->access));

    for (i = 0; i < info->count; i++)
        if (info->statuses[i].id == id)
        {
            result = info->statuses + i;
            break;
        }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                msg   = nouveau message de statut à copier.                  *
*                max   = taille de la plage à parcourir.                      *
*                                                                             *
*  Description : Démarre le suivi d'une nouvelle activité.                    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

activity_id_t gtk_status_stack_add_activity(GtkStatusStack *stack, const char *msg, unsigned long max)
{
    activity_id_t result;                   /* Numéro unique à renvoyer    */
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *new;                 /* Nouveau suivi d'activité    */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    while (1)
    {
        result = ++info->generator;

        if (find_activity_status_by_id(info, result) == NULL)
            break;

    }
    while (0);

    info->statuses = realloc(info->statuses, ++info->count * sizeof(activity_status_t));

    new = info->statuses + info->count - 1;

    /* Identifiant */

    new->id = result;

    /* Intitulé */

    if (msg == NULL)
        new->message = NULL;
    else
        new->message = strdup(msg);

    /* Valeur */

    new->current = 0;
    new->max = max;
    new->last_updated = 0;

    /* Actualisation */

    if (info->tag != 0)
        g_source_remove(info->tag);

    info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);

    g_mutex_unlock(&info->access);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                id    = identifiant de l'activité à cibler.                  *
*                msg   = nouveau message de statut à copier.                  *
*                                                                             *
*  Description : Actualise les informations concernant une activité.          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_update_activity_message(GtkStatusStack *stack, activity_id_t id, const char *msg)
{
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *status;              /* Suivi d'activité à traiter  */
    bool msg_changed;                       /* Changement d'intitulé       */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    status = find_activity_status_by_id(info, id);

    assert(status != NULL);

    if (status == NULL)
        goto exit;

    /* Intitulé */

    if (status->message != NULL)
    {
        if (msg == NULL)
            msg_changed = true;
        else
            msg_changed = (strcmp(status->message, msg) != 0);

        free(status->message);

    }
    else
        msg_changed = (msg != NULL);

    if (msg == NULL)
        status->message = NULL;
    else
        status->message = strdup(msg);

    /* On n'actualise que le sommet de la pile */

    if ((status - info->statuses + 1) == info->count && msg_changed)
    {
        if (info->tag != 0)
            g_source_remove(info->tag);

        info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);

    }

 exit:

    g_mutex_unlock(&info->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                id    = identifiant de l'activité à cibler.                  *
*                inc   = nouvelle valeur pour une progression donnée.         *
*                                                                             *
*  Description : Actualise la progression d'une activité.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_update_activity_value(GtkStatusStack *stack, activity_id_t id, unsigned long inc)
{
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *status;              /* Suivi d'activité à traiter  */
    double new;                             /* Nouvelle progression        */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    status = find_activity_status_by_id(info, id);

    assert(status != NULL);

    if (status == NULL)
        goto exit;

    /* Valeur */

    status->current += inc;

    new = (status->current * 1.0) / status->max;

    /* On n'actualise que le sommet de la pile */

    if ((status - info->statuses + 1) == info->count && (new - status->last_updated) > 0.1)
    {
        status->last_updated = new;

        if (info->tag != 0)
            g_source_remove(info->tag);

        info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);

    }

 exit:

    g_mutex_unlock(&info->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                id    = identifiant de l'activité à cibler.                  *
*                extra = nouvelle échéance supplémentaire des traitements.    *
*                                                                             *
*  Description : Etend la portée des travaux d'une nouvelle activité.         *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_extend_activity_max(GtkStatusStack *stack, activity_id_t id, unsigned long extra)
{
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *status;              /* Suivi d'activité à traiter  */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    status = find_activity_status_by_id(info, id);

    assert(status != NULL);

    if (status == NULL)
        goto exit;

    /* Valeur */

    status->max += extra;

    /* On n'actualise que le sommet de la pile */

    if ((status - info->statuses + 1) == info->count)
    {
        if (info->tag != 0)
            g_source_remove(info->tag);

        info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);

    }

 exit:

    g_mutex_unlock(&info->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = barre de statut à actualiser.                        *
*                                                                             *
*  Description : Met fin au suivi d'une activité donnée.                      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void gtk_status_stack_remove_activity(GtkStatusStack *stack, activity_id_t id)
{
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *status;              /* Suivi d'activité à traiter  */
    bool is_last;                           /* Dernière position ?         */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    status = find_activity_status_by_id(info, id);

    assert(status != NULL);

    if (status == NULL)
        goto exit;

    is_last = ((status - info->statuses + 1) == info->count);

    /* Suppression des données */

    if (is_last)
    {
        if (info->tag != 0)
            g_source_remove(info->tag);
    }

    if (status->message != NULL)
        free(status->message);

    /* Réajustement des enregistrements */

    if (!is_last)
        memmove(status, status + 1,
                (((info->statuses + info->count) - status) - 1) * sizeof(activity_status_t));

    info->statuses = realloc(info->statuses, --info->count * sizeof(activity_status_t));

    /* Bascule vers un autre affichage ou actualisation ? */

    if (info->count == 0)
    {
        info->tag = 0;
        g_idle_add(stack->def_source, stack);
    }
    else if (is_last)
        info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);

 exit:

    g_mutex_unlock(&info->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : stack = pile de statuts à manipuler.                         *
*                                                                             *
*  Description : S'assure de l'affichage à jour de la partie "activité".      *
*                                                                             *
*  Retour      : G_SOURCE_REMOVE pour une exécution unique.                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gboolean gtk_status_stack_show_current_activity(GtkStatusStack *stack)
{
    activity_info_t *info;                  /* Informations à consulter    */
    activity_status_t *last;                /* Dernier statut à traiter    */

    info = stack->activity_info;

    g_mutex_lock(&info->access);

    if (!g_source_is_destroyed(g_main_current_source()))
    {
        if (info->count > 0)
        {
            gtk_stack_set_visible_child_name(stack->main, "activity");

            last = &info->statuses[info->count - 1];

            gtk_label_set_text(stack->activity_message, last->message);

            gtk_progress_bar_set_fraction(stack->activity_progress, (last->current * 1.0) / last->max);

        }

        info->tag = 0;

    }

    g_mutex_unlock(&info->access);

    return G_SOURCE_REMOVE;

}