/* Chrysalide - Outil d'analyse de fichiers binaires * gwidthtracker.c - suivi des largeurs associées à un ensemble de lignes * * Copyright (C) 2016 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 "gwidthtracker.h" #include #include #include #include #include "gbuffercache.h" /* Portions de largeurs communes */ typedef struct _common_metrics { size_t first; /* Premier indice de portion */ size_t last; /* Dernier indice de portion */ line_width_summary summary; /* Compilation de largeurs */ bool cached; /* Mise en cache des calculs */ } common_metrics; /* Gestionnaire de largeurs associées aux lignes (instance) */ struct _GWidthTracker { GObject parent; /* A laisser en premier */ GBufferCache *cache; /* Ensemble complet de lignes */ common_metrics *portions; /* Portions représentées */ size_t count; /* Quantité de ces portions */ line_width_summary summary; /* Largeurs requises suivies */ bool cached; /* Mise en cache des calculs */ }; /* Gestionnaire de largeurs associées aux lignes (classe) */ struct _GWidthTrackerClass { GObjectClass parent; /* A laisser en premier */ }; /* Procède à l'initialisation d'une classe de suivi de largeurs. */ static void g_width_tracker_class_init(GWidthTrackerClass *); /* Procède à l'initialisation d'un suivi de largeurs de lignes. */ static void g_width_tracker_init(GWidthTracker *); /* Supprime toutes les références externes. */ static void g_width_tracker_dispose(GWidthTracker *); /* Procède à la libération totale de la mémoire. */ static void g_width_tracker_finalize(GWidthTracker *); /* Recherche la portion contenant un indice de ligne donné. */ static size_t g_width_tracker_find_metrics(const GWidthTracker *, size_t); /* Prend en compte une évolution du volume de lignes. */ static void g_width_tracker_update_ranges(GWidthTracker *, size_t, size_t); /* Réinitialise les largeurs requises par une portion de lignes.* */ static void g_width_tracker_reset_widths(GWidthTracker *, size_t); /* Recalcule les largeurs requises par une portion de lignes. */ static const line_width_summary *g_width_tracker_get_up_to_date_widths(GWidthTracker *, size_t); /* Calcule les largeurs requises par un ensemble de lignes. */ static void g_width_tracker_ensure_valid_required_widths(GWidthTracker *); /* Détermine le type du gestionnaire de largeurs associées aux lignes. */ G_DEFINE_TYPE(GWidthTracker, g_width_tracker, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : class = classe de composant GTK à initialiser. * * * * Description : Procède à l'initialisation d'une classe de suivi de largeurs.* * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_class_init(GWidthTrackerClass *class) { GObjectClass *object; /* Autre version de la classe */ object = G_OBJECT_CLASS(class); object->dispose = (GObjectFinalizeFunc/* ! */)g_width_tracker_dispose; object->finalize = (GObjectFinalizeFunc)g_width_tracker_finalize; } /****************************************************************************** * * * Paramètres : tracker = composant GLib à initialiser. * * * * Description : Procède à l'initialisation d'un suivi de largeurs de lignes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_init(GWidthTracker *tracker) { tracker->portions = NULL; tracker->count = 0; memset(&tracker->summary, 0, sizeof(line_width_summary)); tracker->cached = false; } /****************************************************************************** * * * Paramètres : tracker = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_dispose(GWidthTracker *tracker) { g_object_unref(G_OBJECT(tracker->cache)); G_OBJECT_CLASS(g_width_tracker_parent_class)->dispose(G_OBJECT(tracker)); } /****************************************************************************** * * * Paramètres : tracker = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_finalize(GWidthTracker *tracker) { G_OBJECT_CLASS(g_width_tracker_parent_class)->finalize(G_OBJECT(tracker)); } /****************************************************************************** * * * Paramètres : buffer = tampon contenant les lignes à surveiller. * * first = adresse contenant l'indice de la première ligne. * * last = adresse contenant l'indice de la dernière ligne. * * * * Description : Crée un nouveau suivi de largeurs au sein de lignes. * * * * Retour : Composant GLib créé. * * * * Remarques : - * * * ******************************************************************************/ GWidthTracker *g_width_tracker_new(GBufferCache *cache) { GWidthTracker *result; /* Composant à retourner */ result = g_object_new(G_TYPE_WIDTH_TRACKER, NULL); g_object_ref(G_OBJECT(cache)); result->cache = cache; return result; } /****************************************************************************** * * * Paramètres : buffer = tampon contenant les lignes à surveiller. * * first = indice de la première ligne d'une zone réduite. * * last = indice de la dernière ligne d'une zone réduite. * * * * Description : Crée un nouveau suivi de largeurs au sein de lignes. * * * * Retour : Composant GLib créé. * * * * Remarques : - * * * ******************************************************************************/ GWidthTracker *g_width_tracker_new_restricted(const GWidthTracker *template, size_t first, size_t last) { GWidthTracker *result; /* Composant à retourner */ size_t start; /* Début de la zone à copier */ size_t end; /* Fin de cette même zone */ size_t i; /* Boucle de parcours */ result = g_object_new(G_TYPE_WIDTH_TRACKER, NULL); g_object_ref(G_OBJECT(template->cache)); result->cache = template->cache; start = g_width_tracker_find_metrics(template, first); assert(start < template->count); end = g_width_tracker_find_metrics(template, last); assert(end < template->count); result->count = end - start + 1; result->portions = (common_metrics *)calloc(result->count, sizeof(common_metrics)); for (i = 0; i < result->count; i++) memcpy(&result->portions[i], &template->portions[start + i], sizeof(common_metrics)); if (result->portions[0].first != first) { result->portions[0].first = first; g_width_tracker_reset_widths(result, 0); } if (result->portions[result->count - 1].last != last) { result->portions[result->count - 1].last = last; g_width_tracker_reset_widths(result, result->count - 1); } return result; } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de suivi à consulter. * * index = indice d'une ligne dont la portion est inconnue. * * * * Description : Recherche la portion contenant un indice de ligne donné. * * * * Retour : Indice de portion trouvée ou le nombre de portions sinon. * * * * Remarques : - * * * ******************************************************************************/ static size_t g_width_tracker_find_metrics(const GWidthTracker *tracker, size_t index) { size_t result; /* Indice trouvé à retourner */ common_metrics *found; /* Portion trouvée ou NULL */ int look_for_metrics(const size_t *idx, const common_metrics *m) { int status; if (*idx < m->first) status = -1; else if (*idx > m->last) status = 1; else status = 0; return status; } found = bsearch(&index, tracker->portions, tracker->count, sizeof(common_metrics), (__compar_fn_t)look_for_metrics); if (found == NULL) result = tracker->count; else result = found - tracker->portions; return result; } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * start = première ligne à traiter. * * diff = nombre de lignes ajoutées ou supprimées. * * * * Description : Prend en compte une évolution du volume de lignes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_update_ranges(GWidthTracker *tracker, size_t start, size_t diff) { size_t i; /* Boucle de parcours */ for (i = start; i < tracker->count; i++) { tracker->portions[i].first += diff; tracker->portions[i].last += diff; } } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * index = indice de portion à marquer pour réinitialisation. * * * * Description : Réinitialise les largeurs requises par une portion de lignes.* * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_reset_widths(GWidthTracker *tracker, size_t index) { common_metrics *portion; /* Portion à actualiser */ BufferLineColumn k; /* Boucle de parcours */ assert(index < tracker->count); portion = &tracker->portions[index]; /* Réinitialisation globale ? */ if (portion->cached) { for (k = 0; k < BLC_COUNT && tracker->cached; k++) tracker->cached &= (tracker->summary.max_widths[k] != portion->summary.max_widths[k]); tracker->cached &= (tracker->summary.merged_width != portion->summary.merged_width); } /* Réinitialisation locale */ portion->cached = false; } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * index = indice de la portion à rafraîchir. * * * * Description : Recalcule les largeurs requises par une portion de lignes. * * * * Retour : Accès en lecture seule au résumé à jour. * * * * Remarques : - * * * ******************************************************************************/ static const line_width_summary *g_width_tracker_get_up_to_date_widths(GWidthTracker *tracker, size_t index) { common_metrics *portion; /* Portion à actualiser */ size_t i; /* Boucle de parcours */ assert(index < tracker->count); portion = &tracker->portions[index]; if (!portion->cached) { /* Réinitialisation locale */ memset(&portion->summary, 0, sizeof(line_width_summary)); /* Collecte */ for (i = portion->first; i <= portion->last; i++) g_buffer_cache_collect_widths(tracker->cache, i, &portion->summary); /* Marquage pour mémoire */ portion->cached = true; } return &portion->summary; } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * index = position de la première des lignes à ajouter. * * * * Description : Prend acte d'un changement sur une ligne pour les largeurs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_width_tracker_update(GWidthTracker *tracker, size_t index) { size_t current; /* Indice de portion visée */ current = g_width_tracker_find_metrics(tracker, index); g_width_tracker_reset_widths(tracker, current); } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * index = position de la première des lignes à ajouter. * * count = quantité de lignes devant être ajoutées. * * * * Description : Prend acte de l'ajout de lignes pour les largeurs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_width_tracker_update_added(GWidthTracker *tracker, size_t index, size_t count) { size_t current; /* Indice de portion visée */ common_metrics *portion; /* Portion sélectionnée */ size_t next; /* Prochaine portion à décaller*/ size_t i; /* Boucle de parcours */ size_t dest; /* Destination d'une recopie */ size_t src; /* Source d'une recopie */ /* Cas particulier du premier ajout */ if (tracker->count == 0) { assert(index == 0); tracker->portions = (common_metrics *)calloc(1, sizeof(common_metrics)); tracker->count = 1; tracker->portions[0].first = 0; tracker->portions[0].last = count - 1; g_width_tracker_reset_widths(tracker, 0); return; } current = g_width_tracker_find_metrics(tracker, index); /* Si la ligne est rajoutée en fin d'ensemble */ if (current == tracker->count) { current = tracker->count - 1; portion = &tracker->portions[current]; assert(index == (portion->last + 1)); } else portion = &tracker->portions[current]; portion->last += count; g_width_tracker_reset_widths(tracker, current); next = current + 1; /* Un découpage s'impose-t-il quelque part ? */ for (i = index + count - 1; i >= index; i--) { if (g_buffer_cache_get_line_flags(tracker->cache, i) & BLF_WIDTH_MANAGER) { /* Insertion d'une nouvelle place */ tracker->count++; tracker->portions = (common_metrics *)realloc(tracker->portions, tracker->count * sizeof(common_metrics)); portion = &tracker->portions[current]; dest = current + 2; src = current + 1; if ((tracker->count - src) > 0) memmove(&tracker->portions[dest], &tracker->portions[src], (tracker->count - src - 1) * sizeof(common_metrics)); next++; /* Insertion au début */ if (i == portion->first) { assert(i == index); tracker->portions[current + 1].first = portion->first + 1; tracker->portions[current + 1].last = portion->last; tracker->portions[current + 1].cached = false; portion->first = i; portion->last = i; } /* Insertion au sein de la portion ou à la fin */ else { tracker->portions[current + 1].first = i; tracker->portions[current + 1].last = portion->last; tracker->portions[current + 1].cached = false; portion->last = i - 1; } /* Mise à jour des largeurs */ g_width_tracker_reset_widths(tracker, current); g_width_tracker_reset_widths(tracker, current + 1); } } /* Suite impérative : accroître les indices ! */ g_width_tracker_update_ranges(tracker, next, 1); } /****************************************************************************** * * * Paramètres : tracker = gestionnaire de largeurs de lignes à mettre jour. * * start = première ligne devant être supprimée. * * end = dernière ligne devant être supprimée. * * * * Description : Prend acte de la suppression de lignes pour les largeurs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_width_tracker_update_deleted(GWidthTracker *tracker, size_t start, size_t end) { size_t first; /* Première portion concernée */ size_t last; /* Dernière portion concernée */ size_t diff; /* Nombre de lignes supprimées */ bool keep_first; /* Conservation de portion #1 */ size_t dest; /* Destination du transfert */ bool keep_last; /* Conservation de portion #2 */ size_t src; /* Source du transfert */ first = g_width_tracker_find_metrics(tracker, start); assert(first < tracker->count); last = g_width_tracker_find_metrics(tracker, end); assert(last < tracker->count); diff = end - start + 1; /* Suppression de portions inutiles ? */ keep_first = (tracker->portions[first].first < start || end < tracker->portions[first].last); dest = (keep_first ? first + 1 : first); keep_last = (tracker->portions[last].first < start || end < tracker->portions[last].last); src = (keep_last ? last : last + 1); if (src > dest) { memmove(&tracker->portions[dest], &tracker->portions[src], (tracker->count - src) * sizeof(common_metrics)); tracker->count -= (src - dest); tracker->portions = (common_metrics *)realloc(tracker->portions, tracker->count * sizeof(common_metrics)); } /* Si une fusion s'impose */ if (keep_first && keep_last && last != first) { tracker->portions[first].last = tracker->portions[dest].last; memmove(&tracker->portions[first + 1], &tracker->portions[first + 2], (tracker->count - first - 2) * sizeof(common_metrics)); tracker->count--; tracker->portions = (common_metrics *)realloc(tracker->portions, tracker->count * sizeof(common_metrics)); keep_last = false; } /* Avant toute chose : faire décroître les indices ! */ if (keep_first) { if (end < tracker->portions[first].last) tracker->portions[first].last -= diff; else tracker->portions[first].last = start - 1; } if (keep_last && last != first) { tracker->portions[dest].first = end + 1; tracker->portions[dest].last -= diff; } g_width_tracker_update_ranges(tracker, keep_last ? dest + 1 : dest, -diff); /* Mise à jour des largeurs aux extrémités */ if (keep_first) g_width_tracker_reset_widths(tracker, first); if (keep_last && last != first) g_width_tracker_reset_widths(tracker, dest); } /****************************************************************************** * * * Paramètres : tracker = suivi de largeurs à mettre à jour si besoin est. * * * * Description : Calcule les largeurs requises par un ensemble de lignes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_width_tracker_ensure_valid_required_widths(GWidthTracker *tracker) { line_width_summary *global; /* Valeurs collectées */ size_t i; /* Boucle de parcours #1 */ const line_width_summary *summary; /* Valeurs à intégrer */ BufferLineColumn k; /* Boucle de parcours #2 */ if (!tracker->cached) { /* Réinitialisation */ memset(&tracker->summary, 0, sizeof(line_width_summary)); /* Collecte */ global = &tracker->summary; for (i = 0; i < tracker->count; i++) { summary = g_width_tracker_get_up_to_date_widths(tracker, i); for (k = 0; k < BLC_COUNT; k++) global->max_widths[k] = MAX(global->max_widths[k], summary->max_widths[k]); global->merged_width = MAX(global->merged_width, summary->merged_width); } tracker->cached = true; } } /****************************************************************************** * * * Paramètres : tracker = suivi de largeurs à consulter. * * * * Description : Fournit un bon résumé des largeurs en vigueur. * * * * Retour : Ensemble des largeurs collectées. * * * * Remarques : - * * * ******************************************************************************/ const line_width_summary *g_width_tracker_get_width_summary(GWidthTracker *tracker) { g_width_tracker_ensure_valid_required_widths(tracker); return &tracker->summary; } /****************************************************************************** * * * Paramètres : tracker = suivi de largeurs à consulter. * * index = indice de la ligne dont la portion est recherchée. * * summary = ensemble ciblé de largeurs collectées. [OUT] * * * * Description : Fournit un résumé local des largeurs en vigueur. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_width_tracker_get_local_width_summary(GWidthTracker *tracker, size_t index, line_width_summary *summary) { size_t current; /* Indice de portion visée */ const line_width_summary *local; /* Valeurs à intégrer */ BufferLineColumn i; /* Boucle de parcours */ g_width_tracker_ensure_valid_required_widths(tracker); current = g_width_tracker_find_metrics(tracker, index); assert(current < tracker->count); local = g_width_tracker_get_up_to_date_widths(tracker, current); for (i = BLC_FIRST; i < BLC_DISPLAY; i++) summary->max_widths[i] = tracker->summary.max_widths[i]; for (i = BLC_DISPLAY; i < BLC_COUNT; i++) summary->max_widths[i] = local->max_widths[i]; summary->merged_width = local->merged_width; } /****************************************************************************** * * * Paramètres : tracker = suivi de largeurs à consulter. * * display = règles d'affichage des colonnes modulables. * * * * Description : Fournit la largeur requise par une visualisation. * * * * Retour : Dimension calculée. * * * * Remarques : - * * * ******************************************************************************/ gint g_width_tracker_get_width(GWidthTracker *tracker, const bool *display) { gint result; /* Taille à retourner */ const line_width_summary *summary; /* Accès rapide aux mesures */ gint col_width; /* Calcul selon les colonnes */ gint full_width; /* Calcul selon les fusions */ BufferLineColumn i; /* Boucle de parcours */ g_width_tracker_ensure_valid_required_widths(tracker); result = 0; summary = &tracker->summary; col_width = 0; full_width = 0; /* Première méthode */ for (i = 0; i < BLC_COUNT; i++) { if (i < BLC_DISPLAY && !display[i]) continue; col_width += summary->max_widths[i]; if ((i + 1) < BLC_COUNT) col_width += COL_MARGIN; } /* Seconde méthode */ for (i = 0; i < BLC_DISPLAY; i++) { if (!display[i]) continue; full_width += summary->max_widths[i] + COL_MARGIN; } full_width += summary->merged_width; /* Mise en concurrence et poursuite... */ result += + MAX(col_width, full_width); return result; } /****************************************************************************** * * * Paramètres : tracker = suivi de largeurs à consulter. * * display = règles d'affichage des colonnes modulables. * * * * Description : Fournit la largeur requise pour dépasser les marges gauches. * * * * Retour : Dimension calculée. * * * * Remarques : - * * * ******************************************************************************/ gint g_width_tracker_get_margin(GWidthTracker *tracker, const bool *display) { gint result; /* Taille à retourner */ const line_width_summary *summary; /* Accès rapide aux mesures */ BufferLineColumn i; /* Boucle de parcours */ g_width_tracker_ensure_valid_required_widths(tracker); result = 0; summary = &tracker->summary; for (i = 0; i < BLC_DISPLAY; i++) { if (!display[i]) continue; result += summary->max_widths[i] + COL_MARGIN; } return result; }