/* Chrysalide - Outil d'analyse de fichiers binaires
* gtkstatusstack.c - empilement d'informations de statut
*
* Copyright (C) 2015 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 Foobar. If not, see .
*/
#include "gtkstatusstack.h"
#include
#include
#include
#include
#include
#include "easygtk.h"
#include "../common/extstr.h"
/* ------------------------- GESTION EXTERIEURE DE LA BARRE ------------------------- */
/* Navigation au sein d'assemblage */
typedef struct _assembly_info assembly_info;
/* Mémorisation des progressions */
typedef struct _progress_info progress_info;
/* Abstration d'une gestion de barre de statut (instance) */
struct _GtkStatusStack
{
GtkBin parent; /* A laisser en premier */
GtkWidget *asm_status; /* Barre de status d'assemblage*/
assembly_info *asm_info; /* Informations courantes */
GtkWidget *prog_status; /* Barre de status d'activité */
progress_info *prog_info; /* Informations courantes */
};
/* Abstration d'une gestion de barre de statut (classe) */
struct _GtkStatusStackClass
{
GtkBinClass parent; /* A laisser en premier */
};
/* 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 *);
/* S'assure de l'affichage du niveau de pile attendu. */
static void gtk_status_stack_switch(GtkStatusStack *, GtkWidget *);
/* -------------------- STATUT DES INFORMATIONS DE DESASSEMBLAGE -------------------- */
/* Navigation au sein d'assemblage */
struct _assembly_info
{
bool reset; /* Réinitialisation */
mrange_t current; /* Emplacement correspondant */
char *segment; /* Segment d'appartenance */
VMPA_BUFFER(phys); /* Localisation physique */
VMPA_BUFFER(virt); /* Localisation virtuelle */
char *symbol; /* Eventuel symbole concerné */
const char *encoding; /* Encodage de l'instruction */
phys_t size; /* Taille de l'instruction */
};
/* Supprime l'empreinte mémoire d'informations d'assemblage. */
static void reset_assembly_info(assembly_info *);
/* Construit une barre d'état pour language d'assemblage. */
static GtkWidget *build_assembly_status_stack(GtkStatusStack *);
/* Réagit à un redimensionnement de la barre de désassemblage. */
static void on_size_allocate_for_asm_status(GtkWidget *, GdkRectangle *, GObject *);
/* Réagit à un clic sur l'icône de zoom. */
static void on_zoom_icon_press(GtkEntry *, GtkEntryIconPosition, GdkEventButton *, GtkStatusStack *);
/* S'assure de l'affichage à jour de la partie "assemblage". */
static gboolean gtk_status_stack_show_current_instruction(GtkStatusStack *);
/* -------------------------- STATUT DES SUIVIS D'ACTIVITE -------------------------- */
/* Informations de progression */
typedef struct _progress_status
{
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 */
} progress_status;
/* Mémorisation des progressions */
struct _progress_info
{
activity_id_t generator; /* Générateur de séquence */
progress_status *statuses; /* Statuts de progression */
size_t count; /* Nombre de ces statuts */
GMutex access; /* Accès à la pile */
guint tag; /* Identifiant de mise à jour */
};
/* Supprime l'empreinte mémoire d'informations d'activité. */
static void reset_progress_info(progress_info *);
/* Construit une barre d'état pour un suivi d'activité. */
static GtkWidget *build_progress_status_stack(GtkStatusStack *);
/* S'assure de l'affichage à jour de la partie "activité". */
static gboolean gtk_status_stack_show_current_activity(GtkStatusStack *);
/* ---------------------------------------------------------------------------------- */
/* GESTION EXTERIEURE DE LA BARRE */
/* ---------------------------------------------------------------------------------- */
/* Détermine le type de la barre de statut améliorée. */
G_DEFINE_TYPE(GtkStatusStack, gtk_status_stack, GTK_TYPE_BIN)
/******************************************************************************
* *
* Paramètres : klass = classe GTK à initialiser. *
* *
* Description : Initialise la classe des barres de statut améliorées. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_status_stack_class_init(GtkStatusStackClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)gtk_status_stack_dispose;
object->finalize = (GObjectFinalizeFunc)gtk_status_stack_finalize;
}
/******************************************************************************
* *
* Paramètres : stack = instance GTK à initialiser. *
* *
* Description : Initialise une instance de barre de statut améliorée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_status_stack_init(GtkStatusStack *stack)
{
stack->asm_status = build_assembly_status_stack(stack);
stack->asm_info = (assembly_info *)calloc(1, sizeof(assembly_info));
reset_assembly_info(stack->asm_info);
stack->prog_status = build_progress_status_stack(stack);
stack->prog_info = (progress_info *)calloc(1, sizeof(progress_info));
reset_progress_info(stack->prog_info);
gtk_status_stack_reset_current_instruction(stack);
}
/******************************************************************************
* *
* Paramètres : view = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_status_stack_dispose(GtkStatusStack *stack)
{
g_object_unref(G_OBJECT(stack->asm_status));
G_OBJECT_CLASS(gtk_status_stack_parent_class)->dispose(G_OBJECT(stack));
}
/******************************************************************************
* *
* Paramètres : view = 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)
{
reset_assembly_info(stack->asm_info);
free(stack->asm_info);
reset_progress_info(stack->prog_info);
free(stack->prog_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 : - *
* *
******************************************************************************/
GtkWidget *gtk_status_stack_new(void)
{
return g_object_new(GTK_TYPE_STATUS_STACK, NULL);
}
/******************************************************************************
* *
* Paramètres : stack = pile de statuts à considérer. *
* next = niveau de pile à afficher désormais. *
* *
* Description : S'assure de l'affichage du niveau de pile attendu. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_status_stack_switch(GtkStatusStack *stack, GtkWidget *next)
{
GtkWidget *child; /* Support courant */
child = gtk_bin_get_child(GTK_BIN(stack));
if (child != next)
{
if (child != NULL)
gtk_container_remove(GTK_CONTAINER(stack), child);
g_object_ref(G_OBJECT(next));
gtk_container_add(GTK_CONTAINER(stack), next);
}
}
/* ---------------------------------------------------------------------------------- */
/* STATUT DES INFORMATIONS DE DESASSEMBLAGE */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : info = informations à réinitialiser. *
* *
* Description : Supprime l'empreinte mémoire d'informations d'assemblage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void reset_assembly_info(assembly_info *info)
{
info->reset = true;
if (info->segment != NULL)
{
free(info->segment);
info->segment = NULL;
}
if (info->symbol != NULL)
{
free(info->symbol);
info->symbol = NULL;
}
}
/******************************************************************************
* *
* Paramètres : stack = composant global en cours de construction. *
* *
* Description : Construit une barre d'état pour language d'assemblage. *
* *
* Retour : Composant GTK mis en place. *
* *
* Remarques : - *
* *
******************************************************************************/
static GtkWidget *build_assembly_status_stack(GtkStatusStack *stack)
{
GtkWidget *result; /* Support à retourner */
GObject *ref; /* Espace de référencements */
GtkWidget *hbox; /* Sous-division horizontale */
GtkWidget *label; /* Etiquette pour impression */
GtkWidget *zoom; /* Sélection du zoom courant */
result = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_show(result);
ref = G_OBJECT(result);
g_signal_connect(result, "size-allocate", G_CALLBACK(on_size_allocate_for_asm_status), ref);
/* Première partie : navigation */
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 16);
gtk_widget_show(hbox);
gtk_box_pack_start(GTK_BOX(result), hbox, TRUE, TRUE, 8);
label = qck_create_label(ref, "segment", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
label = qck_create_label(ref, "phys", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
label = qck_create_label(ref, "virt", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
label = qck_create_label(ref, "offset", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
/* Seconde partie : architecture */
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
g_object_set_data(ref, "arch_box", hbox);
gtk_widget_show(hbox);
gtk_box_pack_start(GTK_BOX(result), hbox, FALSE, TRUE, 8);
label = qck_create_label(ref, "arch", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
label = qck_create_label(ref, "size", NULL);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
/* Troisième partie : affichage */
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_show(hbox);
gtk_box_pack_start(GTK_BOX(result), hbox, FALSE, FALSE, 8);
zoom = qck_create_entry(ref, "zoom", "100%");
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(zoom), GTK_ENTRY_ICON_SECONDARY, "go-up-symbolic");
g_signal_connect(zoom, "icon-press", G_CALLBACK(on_zoom_icon_press), stack);
gtk_box_pack_start(GTK_BOX(hbox), zoom, FALSE, TRUE, 0);
return result;
}
/******************************************************************************
* *
* Paramètres : widget = composant graphique qui vient d'évoluer. *
* allocation = espace réservé pour le composant visé. *
* ref = espace de référencement global. *
* *
* Description : Réagit à un redimensionnement de la barre de désassemblage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_size_allocate_for_asm_status(GtkWidget *widget, GdkRectangle *allocation, GObject *ref)
{
GtkWidget *hbox; /* Sous-division horizontale */
hbox = GTK_WIDGET(g_object_get_data(ref, "arch_box"));
gtk_widget_set_size_request(hbox, (allocation->width * 40) / 100, -1);
/**
* On intervient après que le containeur soit passer collecter les tailles
* de ses enfants lors de son redimensionnement.
*
* Donc on force un prise en compte des changements.
*/
gtk_container_check_resize(GTK_CONTAINER(widget));
}
/******************************************************************************
* *
* Paramètres : entry = zone de texte visée par la procédure. *
* icon_pos = position de l'image associée à l'entrée. *
* event = informations liées à l'événement. *
* stack = composant graphique de gestion des statuts. *
* *
* Description : Réagit à un clic sur l'icône de zoom. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_zoom_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEventButton *event, GtkStatusStack *stack)
{
GtkWidget *popup; /* Popup à faire surgir */
GdkRectangle rect; /* Zone précise à cibler */
if (event->button != GDK_BUTTON_PRIMARY)
return;
popup = gtk_popover_new(GTK_WIDGET(entry));
gtk_entry_get_icon_area(entry, GTK_ENTRY_ICON_SECONDARY, &rect);
gtk_popover_set_pointing_to(GTK_POPOVER(popup), &rect);
gtk_widget_show(popup);
}
/******************************************************************************
* *
* Paramètres : stack = barre de statut à actualiser. *
* binary = binaire chargé rassemblant l'ensemble des infos. *
* instr = instruction désassemblée ciblée graphiquement. *
* *
* Description : Actualise les informations liées une position d'assemblage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_status_stack_update_current_instruction(GtkStatusStack *stack, const GLoadedBinary *binary, const GArchInstruction *instr)
{
assembly_info *info; /* Informations à constituer */
GExeFormat *format; /* Format de binaire à traiter */
const mrange_t *range; /* Emplacement d'instruction */
const vmpa2t *addr; /* Localisation de départ */
GPortionLayer *layer; /* Couche première de portions */
GBinPortion *portion; /* Zone mémoire d'appartenance */
const char *text; /* Texte au contenu à copier */
GBinSymbol *symbol; /* Symbole présent à l'adresse */
phys_t diff; /* Décallage de l'adresse */
const char *label; /* Description d'un symbole */
vmpa2t tmp; /* Zone de construction temp. */
VMPA_BUFFER(offset); /* Décalage physique */
info = stack->asm_info;
format = g_loaded_binary_get_format(binary);
/* Bascule vers une zone courante nouvelle ? */
range = g_arch_instruction_get_range(instr);
addr = get_mrange_addr(range);
if (cmp_mrange(&info->current, range) == 0)
goto gssuci_useless;
/* Réinitialisation */
reset_assembly_info(info);
copy_mrange(&info->current, range);
/* Zone d'appartenance */
layer = g_exe_format_get_main_layer(format);
portion = g_portion_layer_find_portion_at_addr(layer, addr, (GdkRectangle []) { });
text = g_binary_portion_get_desc(portion);
if (text != NULL)
info->segment = strdup(text);
else
info->segment = strdup(_("Binary"));
g_object_unref(G_OBJECT(layer));
/* Adresses de base */
vmpa2_phys_to_string(addr, MDS_UNDEFINED, info->phys, NULL);
vmpa2_virt_to_string(addr, MDS_UNDEFINED, info->virt, NULL);
info->encoding = g_arch_instruction_get_encoding(instr);
info->size = get_mrange_length(range);
/* Symbole concerné */
if (g_binary_format_resolve_symbol(G_BIN_FORMAT(format), addr, false, &symbol, &diff))
{
label = g_binary_symbol_get_label(symbol);
if (label != NULL)
{
info->symbol = strdup(label);
info->symbol = stradd(info->symbol, "+");
init_vmpa(&tmp, diff, VMPA_NO_VIRTUAL);
vmpa2_phys_to_string(&tmp, MDS_UNDEFINED, offset, NULL);
info->symbol = stradd(info->symbol, offset);
}
g_object_unref(G_OBJECT(symbol));
}
/* Nettoyage et conclusion */
info->reset = false;
gtk_status_stack_show_current_instruction(stack);
gssuci_useless:
g_object_unref(G_OBJECT(format));
}
/******************************************************************************
* *
* Paramètres : stack = barre de statut à actualiser. *
* *
* Description : Réinitialise les informations associées une position. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_status_stack_reset_current_instruction(GtkStatusStack *stack)
{
assembly_info *info; /* Informations à constituer */
info = stack->asm_info;
reset_assembly_info(info);
gtk_status_stack_show_current_instruction(stack);
}
/******************************************************************************
* *
* Paramètres : stack = pile de statuts à manipuler. *
* *
* Description : S'assure de l'affichage à jour de la partie "assemblage". *
* *
* Retour : G_SOURCE_REMOVE pour une exécution unique. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_status_stack_show_current_instruction(GtkStatusStack *stack)
{
GObject *ref; /* Espace de référencements */
assembly_info *info; /* Informations à consulter */
GtkLabel *label; /* Etiquette à actualiser */
char raw_pos[6 + VMPA_MAX_LEN + 1]; /* Formatage final en direct */
char *content; /* Contenu dynamique */
gtk_status_stack_switch(stack, stack->asm_status);
ref = G_OBJECT(stack->asm_status);
info = stack->asm_info;
/* Première partie : navigation */
if (info->reset)
{
label = GTK_LABEL(g_object_get_data(ref, "segment"));
gtk_label_set_text(label, NULL);
label = GTK_LABEL(g_object_get_data(ref, "phys"));
gtk_label_set_text(label, NULL);
label = GTK_LABEL(g_object_get_data(ref, "virt"));
gtk_label_set_text(label, NULL);
label = GTK_LABEL(g_object_get_data(ref, "offset"));
gtk_label_set_text(label, NULL);
}
else
{
label = GTK_LABEL(g_object_get_data(ref, "segment"));
gtk_label_set_text(label, info->segment);
snprintf(raw_pos, sizeof(raw_pos), "phys: %s", info->phys);
label = GTK_LABEL(g_object_get_data(ref, "phys"));
gtk_label_set_text(label, raw_pos);
snprintf(raw_pos, sizeof(raw_pos), "virt: %s", info->virt);
label = GTK_LABEL(g_object_get_data(ref, "virt"));
gtk_label_set_text(label, raw_pos);
label = GTK_LABEL(g_object_get_data(ref, "offset"));
gtk_label_set_text(label, info->symbol != NULL ? info->symbol : "");
}
/* Seconde partie : architecture */
if (info->reset)
{
label = GTK_LABEL(g_object_get_data(ref, "arch"));
gtk_label_set_text(label, NULL);
label = GTK_LABEL(g_object_get_data(ref, "size"));
gtk_label_set_text(label, NULL);
}
else
{
label = GTK_LABEL(g_object_get_data(ref, "arch"));
gtk_label_set_text(label, info->encoding);
if (info->size > 1)
asprintf(&content, "%" PRIu64 " %s", (uint64_t)info->size, _("bytes"));
else
asprintf(&content, "%" PRIu64 " %s", (uint64_t)info->size, _("byte"));
label = GTK_LABEL(g_object_get_data(ref, "size"));
gtk_label_set_text(label, content);
free(content);
}
return G_SOURCE_REMOVE;
}
/* ---------------------------------------------------------------------------------- */
/* STATUT DES SUIVIS D'ACTIVITE */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : info = informations à réinitialiser. *
* *
* Description : Supprime l'empreinte mémoire d'informations d'activité. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void reset_progress_info(progress_info *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_init(&info->access);
}
/******************************************************************************
* *
* Paramètres : stack = composant global en cours de construction. *
* *
* Description : Construit une barre d'état pour un suivi d'activité. *
* *
* Retour : Composant GTK mis en place. *
* *
* Remarques : - *
* *
******************************************************************************/
static GtkWidget *build_progress_status_stack(GtkStatusStack *stack)
{
GtkWidget *result; /* Support à retourner */
GObject *ref; /* Espace de référencements */
GtkWidget *progress; /* Barre de progression */
GtkWidget *label; /* Désignation de l'activité */
result = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_show(result);
ref = G_OBJECT(result);
progress = gtk_progress_bar_new();
g_object_set_data(ref, "progress", progress);
gtk_widget_set_size_request(progress, 200, -1);
gtk_widget_set_valign(progress, GTK_ALIGN_CENTER);
gtk_widget_show(progress);
gtk_box_pack_start(GTK_BOX(result), progress, FALSE, TRUE, 8);
label = qck_create_label(ref, "message", NULL);
gtk_box_pack_start(GTK_BOX(result), label, TRUE, TRUE, 0);
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 */
progress_info *info; /* Informations à consulter */
size_t new; /* Indice de l'activité créée */
info = stack->prog_info;
g_mutex_lock(&info->access);
result = ++info->generator;
new = info->count++;
info->statuses = (progress_status *)realloc(info->statuses,
info->count * sizeof(progress_status));
info->statuses[new].id = result;
/* Intitulé */
if (msg == NULL)
info->statuses[new].message = NULL;
else
info->statuses[new].message = strdup(msg);
/* Valeur */
info->statuses[new].current = 0;
info->statuses[new].max = max;
info->statuses[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. *
* 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(GtkStatusStack *stack, activity_id_t id, unsigned long extra)
{
progress_info *info; /* Informations à consulter */
size_t i; /* Boucle de parcours */
info = stack->prog_info;
g_mutex_lock(&info->access);
for (i = 0; i < info->count; i++)
if (info->statuses[i].id == id)
break;
assert(i < info->count);
info->statuses[i].max += extra;
g_mutex_unlock(&info->access);
}
/******************************************************************************
* *
* 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(GtkStatusStack *stack, activity_id_t id, const char *msg)
{
progress_info *info; /* Informations à consulter */
size_t i; /* Boucle de parcours */
bool msg_changed; /* Changement d'intitulé */
info = stack->prog_info;
g_mutex_lock(&info->access);
for (i = 0; i < info->count; i++)
if (info->statuses[i].id == id)
break;
assert(i < info->count);
/* Intitulé */
if (info->statuses[i].message != NULL)
{
if (msg == NULL)
msg_changed = true;
else
msg_changed = (strcmp(info->statuses[i].message, msg) != 0);
free(info->statuses[i].message);
}
else
msg_changed = (msg != NULL);
if (msg == NULL)
info->statuses[i].message = NULL;
else
info->statuses[i].message = strdup(msg);
/* On n'actualise que le sommet de la pile */
if ((i + 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);
}
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)
{
progress_info *info; /* Informations à consulter */
size_t i; /* Boucle de parcours */
progress_status *status; /* Raccourci de confort */
double new; /* Nouvelle progression */
info = stack->prog_info;
g_mutex_lock(&info->access);
for (i = 0; i < info->count; i++)
if (info->statuses[i].id == id)
break;
assert(i < info->count);
status = &info->statuses[i];
/* Valeur */
status->current += inc;
new = (status->current * 1.0) / status->max;
/* On n'actualise que le sommet de la pile */
if ((i + 1) == info->count && (new - status->last_updated) > 0.1)
{
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);
}
/******************************************************************************
* *
* 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)
{
progress_info *info; /* Informations à consulter */
size_t i; /* Boucle de parcours */
info = stack->prog_info;
g_mutex_lock(&info->access);
for (i = 0; i < info->count; i++)
if (info->statuses[i].id == id)
break;
assert(i < info->count);
if (info->tag != 0)
g_source_remove(info->tag);
if (info->statuses[i].message != NULL)
free(info->statuses[i].message);
if (info->count == 1)
{
free(info->statuses);
info->statuses = NULL;
}
else
{
memmove(&info->statuses[i], &info->statuses[i + 1],
(info->count - i - 1) * sizeof(progress_status));
info->statuses = (progress_status *)realloc(info->statuses,
(info->count - 1) * sizeof(progress_status));
}
info->count--;
if (info->count == 0)
{
info->tag = 0;
g_idle_add((GSourceFunc)gtk_status_stack_show_current_instruction, stack);
}
else
info->tag = g_idle_add((GSourceFunc)gtk_status_stack_show_current_activity, stack);
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)
{
GObject *ref; /* Espace de référencements */
progress_info *info; /* Informations à consulter */
progress_status *last; /* Dernier statut à traiter */
GtkProgressBar *progress; /* Barre de progression */
GtkLabel *label; /* Désignation de l'activité */
if (!g_source_is_destroyed(g_main_current_source()))
{
gtk_status_stack_switch(stack, stack->prog_status);
ref = G_OBJECT(stack->prog_status);
info = stack->prog_info;
g_mutex_lock(&info->access);
info->tag = 0;
if (info->count > 0)
{
last = &info->statuses[info->count - 1];
progress = GTK_PROGRESS_BAR(g_object_get_data(ref, "progress"));
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), (last->current * 1.0) / last->max);
label = GTK_LABEL(g_object_get_data(ref, "message"));
gtk_label_set_text(label, last->message);
}
g_mutex_unlock(&info->access);
}
return G_SOURCE_REMOVE;
}