/* OpenIDA - Outil d'analyse de fichiers binaires * panel.c - prototypes pour la gestion des éléments réactifs spécifiques aux panneaux * * Copyright (C) 2009-2012 Cyrille Bagard * * This file is part of OpenIDA. * * OpenIDA 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. * * OpenIDA 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "panel.h" #include #include #include #include "log.h" #include "panel-int.h" #include "symbols.h" #include "welcome.h" #include "../../gtkext/easygtk.h" #include "../../gtkext/gtkdockstation.h" /* Support de fond pour les composants. */ static GtkWidget *_support; static panel_node *_nodes = NULL; /* Procédure à appeler lors des changements de panneaux. */ static GCallback _handler; static gpointer _data; /* Liste des panneaux en place. */ static GPanelItem *_panels_list = NULL; /* Initialise la classe des éléments réactifs de l'éditeur. */ static void g_panel_item_class_init(GPanelItemClass *); /* Initialise une instance d'élément réactif pour l'éditeur. */ static void g_panel_item_init(GPanelItem *); /* ---------------------- MECANISMES DE PLACEMENT DES PANNEAUX ---------------------- */ /* Crée un nouveau noeud pour un panneau particulier. */ static panel_node *create_simple_panel_node_for_item(GPanelItem *, const char *, size_t); /* Prépare une nouvelle sous-division pour deux panneaux. */ static void switch_panel_node_into_paned(panel_node *, bool, bool); /* Met en place un nouveau noeud dans une division. */ static void attach_panel_node_to_paned(panel_node *, panel_node *, bool); /* Indique si un noeud correspond à la branche recherchée. */ static bool is_panel_node_matching(const panel_node *, char); /* Place au bon endroit un panneau donné. */ static void insert_item_as_panel_node(GPanelItem *, panel_node *, const char *, size_t); /* Met à jour l'affichage suite à un changement hiérarchique. */ static void rebuild_panels_interface(const panel_node *); /* ---------------------- REAJUSTEMENT AUTOMATIQUE DE L'ESPACE ---------------------- */ /* Part réservée aux parties principales (en %) */ #define MAIN_PART_PERCENT 70 /* Met à jour l'affichage suite à un changement hiérarchique. */ static void auto_resize_panels(GtkWidget *, GdkRectangle *, gpointer); /* S'enquiert de la taille idéale pour un noeud. */ static void get_panel_node_size_request(const panel_node *, GtkRequisition *); /* Impose une taille accordée à un noeud. */ static void set_panel_node_size_request(const panel_node *, const GtkRequisition *); /* Indique le type défini pour un élément destiné à un panneau. */ G_DEFINE_TYPE(GPanelItem, g_panel_item, G_TYPE_EDITOR_ITEM); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des éléments réactifs de l'éditeur. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_panel_item_class_init(GPanelItemClass *klass) { } /****************************************************************************** * * * Paramètres : item = instance à initialiser. * * * * Description : Initialise une instance d'élément réactif pour l'éditeur. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_panel_item_init(GPanelItem *item) { DL_LIST_ITEM_INIT(&item->link); } /****************************************************************************** * * * Paramètres : item = composant à présenter à l'affichage. * * ref = espace de référencement global. * * name = nom associé à l'élément. * * lname = description longue du panneau. * * widget = composant à présenter à l'affichage. * * path = chemin vers la place idéale pour le futur panneau. * * * * Description : Initialise dynamique les propriétés de l'instance. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_panel_item_init_ext(GPanelItem *item, GObject *ref, const char *name, const char *lname, GtkWidget *widget, const char *path) { GEditorItem *parent; /* Autre version de l'élément */ parent = G_EDITOR_ITEM(item); g_object_ref(ref); parent->ref = ref; parent->name = name; item->lname = lname; g_object_ref(widget); parent->widget = widget; g_object_set_data(G_OBJECT(widget), "pitem", item); item->path = path; panels_list_add_tail(item, &_panels_list); } /****************************************************************************** * * * Paramètres : ref = espace de référencement global. * * name = nom associé à l'élément. * * lname = description longue du panneau. * * widget = composant à présenter à l'affichage. * * path = chemin vers la place idéale pour le futur panneau. * * * * Description : Crée un élément de panneau réactif. * * * * Retour : Adresse de la structure mise en place. * * * * Remarques : - * * * ******************************************************************************/ GEditorItem *g_panel_item_new(GObject *ref, const char *name, const char *lname, GtkWidget *widget, const char *path) { GPanelItem *result; /* Structure à retourner */ result = g_object_new(G_TYPE_PANEL_ITEM, NULL); g_panel_item_init_ext(result, ref, name, lname, widget, path); return G_EDITOR_ITEM(result); } /****************************************************************************** * * * Paramètres : name = désignation courte servant de clef. * * * * Description : Recherche un panneau à partir de son nom court. * * * * Retour : Panneau trouvé ou NULL si aucun. * * * * Remarques : Le parcours peut se faire aussi depuis la classe parente, * * mais il est plus rapide par ici. * * * ******************************************************************************/ GPanelItem *g_panel_item_get(const char *name) { GPanelItem *iter; /* Boucle de parcours */ panels_list_for_each(iter, _panels_list) { if (strcmp(G_EDITOR_ITEM(iter)->name, name) == 0) return iter; } return NULL; } /****************************************************************************** * * * Paramètres : item = composant à présenter à l'affichage. * * * * Description : Place un panneau dans l'ensemble affiché. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_panel_item_dock(GPanelItem *item) { /* Tout est à faire... */ if (_nodes == NULL) { _nodes = create_simple_panel_node_for_item(item, item->path, 0); gtk_container_add(GTK_CONTAINER(_support), _nodes->station); } else insert_item_as_panel_node(item, _nodes, item->path, 0); } /****************************************************************************** * * * Paramètres : item = composant à retirer de l'affichage. * * * * Description : Supprime un panneau de l'ensemble affiché. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_panel_item_undock(GPanelItem *item) { GtkWidget *station; /* Base du remplacement */ station = gtk_widget_get_parent(G_EDITOR_ITEM(item)->widget); /* NoteBook */ station = gtk_widget_get_parent(station); /* DockStation */ gtk_dock_panel_remove_widget(GTK_DOCK_STATION(station), G_EDITOR_ITEM(item)->widget); } /* ---------------------------------------------------------------------------------- */ /* PLACEMENTS DES DIFFERENTS PANNEAUX */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : handler = procédure à réveiller en cas de changements. * * data = donnée à faire suivre. * * * * Description : Prépare le terrain pour l'affichage central. * * * * Retour : Composant de support sur lequel tout va se placer. * * * * Remarques : - * * * ******************************************************************************/ GtkWidget *init_panels2(GCallback handler, gpointer data) { GtkWidget *result; /* Support à retourner */ result = qck_create_padded_alignment(0, 0, 0, 0); gtk_widget_show(result); g_signal_connect(result, "size-allocate", G_CALLBACK(auto_resize_panels), NULL); _support = result; _handler = handler; _data = data; return result; } /****************************************************************************** * * * Paramètres : ref = espace de référencement global. * * * * Description : Charge les principaux panneaux de l'éditeur. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void load_main_panels(GObject *ref) { GPanelItem *item; /* Panneau de base à charger */ item = create_welcome_panel(ref); g_panel_item_dock(item); item = create_log_panel(ref); g_panel_item_dock(item); item = create_symbols_panel(ref); g_panel_item_dock(item); } /* ---------------------------------------------------------------------------------- */ /* MECANISMES DE PLACEMENT DES PANNEAUX */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : item = composant à présenter à l'affichage. * * path = partie du chemin représentée ici. * * depth = profondeur du chemin utilisé. * * * * Description : Crée un nouveau noeud pour un panneau particulier. * * * * Retour : Structure d'accueil mise en place. * * * * Remarques : - * * * ******************************************************************************/ static panel_node *create_simple_panel_node_for_item(GPanelItem *item, const char *path, size_t depth) { panel_node *result; /* Structure à retourner */ GtkWidget *station; /* Premier support concentré */ GEditorItem *editem; /* Autre vision des choses */ result = (panel_node *)calloc(1, sizeof(panel_node)); result->path = path[depth]; result->depth = depth; result->simple = true; station = gtk_dock_station_new(); g_signal_connect(station, "switch-widget", _handler, _data); gtk_widget_show(station); result->station = station; editem = G_EDITOR_ITEM(item); gtk_dock_panel_add_widget(GTK_DOCK_STATION(station), editem->widget, editem->name); return result; } /****************************************************************************** * * * Paramètres : item = composant à présenter à l'affichage. * * horiz = indique le type d'orientation désiré. * * first = indication sur l'emplacement à utiliser. * * * * Description : Prépare une nouvelle sous-division pour deux panneaux. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void switch_panel_node_into_paned(panel_node *current, bool horiz, bool first) { panel_node *moved; /* Noeud descendu d'un étage */ GtkWidget *widget; /* Composant à traiter */ /* Descend l'élément actuel */ moved = (panel_node *)calloc(1, sizeof(panel_node)); memcpy(moved, current, sizeof(panel_node)); moved->parent = current; widget = GET_PANEL_NODE_WIDGET(current); g_object_ref(G_OBJECT(widget)); gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget); if (first) current->first = moved; else current->second = moved; /* Achève la transformation */ current->path = '\0'; current->simple = false; current->station = NULL; if (horiz) current->paned = gtk_hpaned_new(); else current->paned = gtk_vpaned_new(); gtk_widget_show(current->paned); /* Replace le composant d'origine */ if (first) gtk_paned_add1(GTK_PANED(current->paned), widget); else gtk_paned_add2(GTK_PANED(current->paned), widget); g_object_unref(G_OBJECT(widget)); } /****************************************************************************** * * * Paramètres : parent = noeud d'accueil pour le nouveau noeud. * * node = nouveau noeud à intégrer dans l'ensemble. * * first = indication sur l'emplacement à utiliser. * * * * Description : Met en place un nouveau noeud dans une division. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void attach_panel_node_to_paned(panel_node *parent, panel_node *node, bool first) { GtkWidget *widget; /* Composant à traiter */ /* Raccordement hiérarchique */ if (first) parent->first = node; else parent->second = node; node->parent = parent; /* Raccordement graphique */ widget = GET_PANEL_NODE_WIDGET(node); if (first) gtk_paned_add1(GTK_PANED(parent->paned), widget); else gtk_paned_add2(GTK_PANED(parent->paned), widget); } /****************************************************************************** * * * Paramètres : node = noeud d'où lancer les recherches. * * target = identifiant de la position visée. * * * * Description : Indique si un noeud correspond à la branche recherchée. * * * * Retour : Bilan de l'évaluation. * * * * Remarques : - * * * ******************************************************************************/ static bool is_panel_node_matching(const panel_node *node, char target) { if (node->path == target) return true; if (node->simple) return false; return is_panel_node_matching(node->first, target) || is_panel_node_matching(node->second, target); } /****************************************************************************** * * * Paramètres : item = composant à présenter à l'affichage. * * node = point d'insertion courant. * * path = partie du chemin représentée ici. * * depth = profondeur du chemin utilisé. * * * * Description : Place au bon endroit un panneau donné. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const char *path, size_t depth) { char div; /* Division demandée */ bool horiz; /* Traduction en composant */ bool first; /* Point d'insertion */ panel_node *new; /* Nouveau noeud créé */ panel_node *support; /* Noeud d'accueil désigné */ if (node->simple) { /* Si on est sur la bonne voie... */ if (path[depth] == '\0' || path[depth] == node->path) { /* Le parcours s'arrête ici ! */ if (path[depth] == '\0' || path[depth + 1] == '\0') gtk_dock_panel_add_widget(GTK_DOCK_STATION(node->station), G_EDITOR_ITEM(item)->widget, G_EDITOR_ITEM(item)->name); /* On ne peut aller plus loin, on doit diviser... */ else { div = toupper(path[depth + 1]); first = (div == 'E' || div == 'S'); horiz = (div == 'W' || div == 'E'); switch_panel_node_into_paned(node, horiz, first); new = create_simple_panel_node_for_item(item, path, depth + 1); attach_panel_node_to_paned(node, new, !first); rebuild_panels_interface(node); } } /* On ne peut aller plus loin, on doit diviser... */ else { div = toupper(path[depth]); first = (div == 'E' || div == 'S'); horiz = (div == 'W' || div == 'E'); switch_panel_node_into_paned(node, horiz, first); new = create_simple_panel_node_for_item(item, path, depth); attach_panel_node_to_paned(node, new, !first); rebuild_panels_interface(node); } } /* On regarde des autres côtés... */ else if (is_panel_node_matching(node->first, path[depth])) insert_item_as_panel_node(item, node->first, path, depth + (node->first->simple ? 1 : 0)); else if (is_panel_node_matching(node->second, path[depth])) insert_item_as_panel_node(item, node->second, path, depth + (node->second->simple ? 1 : 0)); /* On doit diviser qqch... */ else { /* Si l'élément doit passer en force */ if (isupper(path[depth])) { div = path[depth]; first = (div == 'E' || div == 'S'); horiz = (div == 'W' || div == 'E'); switch_panel_node_into_paned(node, horiz, first); new = create_simple_panel_node_for_item(item, path, depth); attach_panel_node_to_paned(node, new, !first); rebuild_panels_interface(node); } else { if (is_panel_node_matching(node->second, 'M')) support = node->second; else support = node->first; div = toupper(path[depth]); first = (div == 'E' || div == 'S'); horiz = (div == 'W' || div == 'E'); switch_panel_node_into_paned(support, horiz, first); new = create_simple_panel_node_for_item(item, path, depth); attach_panel_node_to_paned(support, new, !first); rebuild_panels_interface(support); } } } /****************************************************************************** * * * Paramètres : current = point de départ de la réorganisation graphique. * * * * Description : Met à jour l'affichage suite à un changement hiérarchique. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void rebuild_panels_interface(const panel_node *current) { GtkWidget *widget; /* Composant à traiter */ panel_node *parent; /* Raccourci confortable */ widget = GET_PANEL_NODE_WIDGET(current); /* On se trouve à la racine... */ if (current->parent == NULL) gtk_container_add(GTK_CONTAINER(_support), widget); /* Sinon, une sous-division ne peut venir que d'une division... */ else { /* BUG_ON(parent->simple) */ parent = current->parent; if (current == parent->first) gtk_paned_add1(GTK_PANED(parent->paned), widget); else gtk_paned_add2(GTK_PANED(parent->paned), widget); } } /* ---------------------------------------------------------------------------------- */ /* REAJUSTEMENT AUTOMATIQUE DE L'ESPACE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : current = point de départ de la réorganisation graphique. * * * * Description : Met à jour l'affichage suite à un changement hiérarchique. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void auto_resize_panels(GtkWidget *support, GdkRectangle *alloc, gpointer data) { GtkRequisition available; /* Taille disponible */ //g_signal_handlers_disconnect_by_func(support, G_CALLBACK(auto_resize_panels), NULL); available.width = alloc->width; available.height = alloc->height; set_panel_node_size_request(_nodes, &available); //g_signal_connect(support, "size-allocate", G_CALLBACK(auto_resize_panels), NULL); } /****************************************************************************** * * * Paramètres : node = noeud à consulter. * * req = taille demandée par le noeud. [OUT] * * * * Description : S'enquiert de la taille idéale pour un noeud. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void get_panel_node_size_request(const panel_node *node, GtkRequisition *req) { gint handle_size; /* Taille de la séparation */ GtkRequisition tmp; /* Stockage temporaire */ if (node->simple) gtk_widget_size_request(node->station, req); else { gtk_widget_style_get(node->paned, "handle-size", &handle_size, NULL); if (GTK_IS_HPANED(node->paned)) { req->width = handle_size; req->height = 0; } else { req->width = 0; req->height = handle_size; } get_panel_node_size_request(node->first, &tmp); req->width += tmp.width; req->height += tmp.height; get_panel_node_size_request(node->second, &tmp); req->width += tmp.width; req->height += tmp.height; } } /****************************************************************************** * * * Paramètres : node = noeud à consulter. * * space = taille disponible pour le noeud. * * * * Description : Impose une taille accordée à un noeud. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void set_panel_node_size_request(const panel_node *node, const GtkRequisition *space) { GtkRequisition first_req; /* Taille demandée par n°1 */ GtkRequisition second_req; /* Taille demandée par n°2 */ gint handle_size; /* Taille de la séparation */ gint position; /* Position de la séparation */ bool can_lower; /* Diminution possible ? */ bool can_upper; /* Augmentation possible ? */ GtkAllocation allocation; /* Taille allouée */ /* Pour les cas simple, GTK gère très bien... */ if (node->simple) return; get_panel_node_size_request(node->first, &first_req); get_panel_node_size_request(node->second, &second_req); gtk_widget_style_get(node->paned, "handle-size", &handle_size, NULL); /** * Définitions des bornes dans chacun des cas. */ /* Le premier noeud est le principal... */ if (is_panel_node_matching(node->first, 'M')) { if (GTK_IS_HPANED(node->paned)) position = (space->width * MAIN_PART_PERCENT) / 100; else position = (space->height * MAIN_PART_PERCENT) / 100; can_lower = false; can_upper = true; } /* Le second noeud est le principal... */ else if (is_panel_node_matching(node->second, 'M')) { if (GTK_IS_HPANED(node->paned)) position = space->width - (space->width * MAIN_PART_PERCENT) / 100; else position = space->height - (space->height * MAIN_PART_PERCENT) / 100; can_lower = true; can_upper = false; } /* Les éléments sont quelconques... */ else { if (GTK_IS_HPANED(node->paned)) position = space->width / 2; else position = space->height / 2; can_lower = true; can_upper = true; } /** * Calcul des valeurs applicables et mise en application. */ /* Une partie principale arrive en premier */ if (!can_lower && can_upper) { if (GTK_IS_HPANED(node->paned)) position = MAX(position, space->width - second_req.width - handle_size); else position = MAX(position, space->height - second_req.height - handle_size); } /* Une partie principale arrive en second */ else if (can_lower && !can_upper) { if (GTK_IS_HPANED(node->paned)) position = MIN(position, second_req.width + handle_size); else position = MIN(position, second_req.height + handle_size); } /* Chacun pour soit ! */ else { /* TODO */; } gtk_paned_set_position(GTK_PANED(node->paned), position); gtk_widget_get_allocation(GET_PANEL_NODE_WIDGET(node->first), &allocation); set_panel_node_size_request(node->first, ALLOC_2_REQ(&allocation)); gtk_widget_get_allocation(GET_PANEL_NODE_WIDGET(node->second), &allocation); set_panel_node_size_request(node->second, ALLOC_2_REQ(&allocation)); }