summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2016-09-21 19:29:39 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2016-09-21 19:29:39 (GMT)
commitb6afbe8c699ae76443628badae33beee9934c6bc (patch)
tree69878624d3ae529314a9824f0c0ceca8bca617b7 /src/gui
parent291968f4f4a5e85f6963813a43f2176320fb8d49 (diff)
Provided a real welcome panel at startup, with tricks and actions.
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/core/panels.c4
-rw-r--r--src/gui/editor.c109
-rw-r--r--src/gui/panels/Makefile.am19
-rw-r--r--src/gui/panels/gresource.xml6
-rw-r--r--src/gui/panels/panel-int.h10
-rw-r--r--src/gui/panels/panel.c6
-rw-r--r--src/gui/panels/regedit.c2
-rw-r--r--src/gui/panels/welcome.c865
-rw-r--r--src/gui/panels/welcome.h68
-rw-r--r--src/gui/panels/welcome.ui245
10 files changed, 1306 insertions, 28 deletions
diff --git a/src/gui/core/panels.c b/src/gui/core/panels.c
index d8565f4..9dcb199 100644
--- a/src/gui/core/panels.c
+++ b/src/gui/core/panels.c
@@ -33,6 +33,7 @@
#include "../panels/regedit.h"
#include "../panels/strings.h"
#include "../panels/symbols.h"
+#include "../panels/welcome.h"
#include "../../core/params.h"
#include "../../gtkext/gtkdockable.h"
@@ -62,6 +63,9 @@ void load_main_panels(GObject *ref)
config = get_main_configuration();
+ item = g_welcome_panel_new();
+ register_panel_item(item, ref, config);
+
item = g_log_panel_new();
register_panel_item(item, ref, config);
diff --git a/src/gui/editor.c b/src/gui/editor.c
index 926201a..3723bd8 100644
--- a/src/gui/editor.c
+++ b/src/gui/editor.c
@@ -39,6 +39,7 @@
#include "core/core.h"
#include "core/panels.h"
#include "panels/panel.h"
+#include "panels/welcome.h"
#include "tb/portions.h"
#include "tb/source.h"
#include "../analysis/project.h"
@@ -158,7 +159,7 @@ static void update_path_of_paned_nodes(panel_node *, const char *, const char *)
static size_t compute_path_common_length(const panel_node *, const char *);
/* Place au bon endroit un panneau donné. */
-static void insert_item_as_panel_node(GPanelItem *, panel_node *, const char *, size_t);
+static panel_node *insert_item_as_panel_node(GPanelItem *, panel_node *, const char *, size_t);
/* Tente de mettre la main sur une station d'accueil. */
static panel_node *find_node_for_station(panel_node *, GtkWidget *);
@@ -788,18 +789,18 @@ static size_t compute_path_common_length(const panel_node *node, const char *tar
* *
* Description : Place au bon endroit un panneau donné. *
* *
-* Retour : - *
+* Retour : Noeud final inséré dans la liste. *
* *
* Remarques : - *
* *
******************************************************************************/
-static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const char *path, size_t consumed)
+static panel_node *insert_item_as_panel_node(GPanelItem *item, panel_node *node, const char *path, size_t consumed)
{
+ panel_node *result; /* Noeud ultime à renvoyer */
char div; /* Division demandée */
bool horiz; /* Traduction en composant */
bool first; /* Point d'insertion */
- panel_node *new; /* Nouveau noeud créé */
size_t common1; /* Tron common avec le côté #1 */
size_t common2; /* Tron common avec le côté #2 */
@@ -813,7 +814,10 @@ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const
{
/* Le parcours s'arrête ici ! */
if (strcmp(node->path, path) == 0)
+ {
gtk_dock_station_add_dockable(GTK_DOCK_STATION(node->station), GTK_DOCKABLE(item));
+ result = node;
+ }
/* On ne peut aller plus loin, on doit diviser... */
else
@@ -828,9 +832,9 @@ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const
switch_panel_node_into_paned(node, horiz, !first);
- new = create_simple_panel_node_for_item(item, path);
+ result = create_simple_panel_node_for_item(item, path);
- attach_panel_node_to_paned(node, new, first);
+ attach_panel_node_to_paned(node, result, first);
fprintf(stderr, "1# [%p] widget = %p --- split :: %p // %p\n",
@@ -857,10 +861,10 @@ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const
if (common1 > 0 || common2 > 0)
{
if (common1 > common2)
- insert_item_as_panel_node(item, node->first, path, common1);
+ result = insert_item_as_panel_node(item, node->first, path, common1);
else
- insert_item_as_panel_node(item, node->second, path, common2);
+ result = insert_item_as_panel_node(item, node->second, path, common2);
}
@@ -875,9 +879,9 @@ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const
switch_panel_node_into_paned(node, horiz, !first);
- new = create_simple_panel_node_for_item(item, path);
+ result = create_simple_panel_node_for_item(item, path);
- attach_panel_node_to_paned(node, new, first);
+ attach_panel_node_to_paned(node, result, first);
fprintf(stderr, "2# [%p] split :: %p-%p // %p-%p\n", node,
@@ -888,6 +892,8 @@ static void insert_item_as_panel_node(GPanelItem *item, panel_node *node, const
}
+ return result;
+
}
@@ -1047,7 +1053,7 @@ static void delete_panel_node(panel_node *node)
/******************************************************************************
* *
-* Paramètres : item = composant à retirer de l'affichage. *
+* Paramètres : panel = composant à placer dans l'affichage. *
* unused = adresse non utilisée ici. *
* *
* Description : Réagit à une demande de placement d'un panneau d'affichage. *
@@ -1058,28 +1064,53 @@ static void delete_panel_node(panel_node *node)
* *
******************************************************************************/
-void on_panel_item_dock_request(GPanelItem *item, void *unused)
+void on_panel_item_dock_request(GPanelItem *panel, void *unused)
{
const char *path; /* Chemin d'accès */
+ panel_node *node; /* Noeud à supprimer */
+ const char *name; /* Nom du dernier panneau */
+ GPanelItem *welcome; /* Panneau d'accueil */
- path = gtk_panel_item_get_path(item);
+ path = gtk_panel_item_get_path(panel);
/* Tout est à faire... */
if (_nodes == NULL)
{
- _nodes = create_simple_panel_node_for_item(item, path);
+ _nodes = create_simple_panel_node_for_item(panel, path);
gtk_container_add(GTK_CONTAINER(_support), _nodes->widget);
}
- else insert_item_as_panel_node(item, _nodes, path, 0);
+ else
+ {
+ node = insert_item_as_panel_node(panel, _nodes, path, 0);
+
+ if (strncmp(node->path, "N", 1) == 0)
+ {
+ name = g_editor_item_get_name(G_EDITOR_ITEM(panel));
+
+ if (strcmp(name, PANEL_WELCOME_ID) != 0)
+ {
+ welcome = get_panel_item_by_name(PANEL_WELCOME_ID);
+
+ if (!g_welcome_panel_get_user_origin(G_WELCOME_PANEL(welcome)))
+ {
+ /* Equivalent d'un appel "g_panel_item_undock(welcome)", avec effet immédiat */
+ on_panel_item_undock_request(welcome, NULL);
+ }
+
+ }
+
+ }
+
+ }
- g_panel_item_set_dock_status(item, true);
+ g_panel_item_set_dock_status(panel, true);
}
/******************************************************************************
* *
-* Paramètres : item = composant à retirer de l'affichage. *
+* Paramètres : panel = composant à retirer de l'affichage. *
* unused = adresse non utilisée ici. *
* *
* Description : Réagit à une demande de suppression d'un panneau d'affichage.*
@@ -1090,25 +1121,53 @@ void on_panel_item_dock_request(GPanelItem *item, void *unused)
* *
******************************************************************************/
-void on_panel_item_undock_request(GPanelItem *item, void *unused)
+void on_panel_item_undock_request(GPanelItem *panel, void *unused)
{
GtkWidget *station; /* Support courant */
+ GtkNotebook *notebook; /* Version parente de station */
panel_node *node; /* Noeud à supprimer */
+ bool allowed; /* Fermture permise ? */
+ const char *name; /* Nom du dernier panneau */
+ GPanelItem *welcome; /* Panneau d'accueil */
+
+ gtk_dockable_decompose(GTK_DOCKABLE(panel), &station);
+
+ notebook = GTK_NOTEBOOK(station);
- gtk_dockable_decompose(GTK_DOCKABLE(item), &station);
+ node = find_node_for_station(_nodes, station);
+ assert(node != NULL);
- gtk_dock_station_remove_dockable(GTK_DOCK_STATION(station), GTK_DOCKABLE(item));
+ allowed = true;
- if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(station)) == 0)
+ if (strcmp(node->path, "N") == 0 && gtk_notebook_get_n_pages(notebook) == 1)
{
- node = find_node_for_station(_nodes, station);
- assert(node != NULL);
+ name = g_editor_item_get_name(G_EDITOR_ITEM(panel));
+
+ if (strcmp(name, PANEL_WELCOME_ID) == 0)
+ allowed = false;
+
+ else
+ {
+ welcome = get_panel_item_by_name(PANEL_WELCOME_ID);
+ g_welcome_panel_set_user_origin(G_WELCOME_PANEL(welcome), false);
+
+ /* Equivalent d'un appel "g_panel_item_dock(welcome)", avec effet immédiat */
+ on_panel_item_dock_request(welcome, NULL);
- delete_panel_node(node);
+ }
}
- g_panel_item_set_dock_status(item, false);
+ if (allowed)
+ {
+ gtk_dock_station_remove_dockable(GTK_DOCK_STATION(station), GTK_DOCKABLE(panel));
+
+ if (gtk_notebook_get_n_pages(notebook) == 0)
+ delete_panel_node(node);
+
+ g_panel_item_set_dock_status(panel, false);
+
+ }
}
diff --git a/src/gui/panels/Makefile.am b/src/gui/panels/Makefile.am
index 4746210..929aef1 100644
--- a/src/gui/panels/Makefile.am
+++ b/src/gui/panels/Makefile.am
@@ -1,6 +1,11 @@
+BUILT_SOURCES = resources.h resources.c
+
noinst_LTLIBRARIES = libguipanels.la
+UI_FILES = \
+ welcome.ui
+
libguipanels_la_SOURCES = \
bookmarks.h bookmarks.c \
glance.h glance.c \
@@ -8,8 +13,10 @@ libguipanels_la_SOURCES = \
log.h log.c \
panel.h panel.c \
regedit.h regedit.c \
+ resources.h resources.c \
strings.h strings.c \
- symbols.h symbols.c
+ symbols.h symbols.c \
+ welcome.h welcome.c
libguipanels_la_LDFLAGS =
@@ -19,3 +26,13 @@ AM_CPPFLAGS = $(LIBGTK_CFLAGS) $(LIBXML_CFLAGS)
AM_CFLAGS = $(DEBUG_CFLAGS) $(WARNING_FLAGS) $(COMPLIANCE_FLAGS)
SUBDIRS =
+
+
+resources.c: gresource.xml $(UI_FILES)
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-source --c-name gui_panels gresource.xml
+
+resources.h: gresource.xml
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name gui_panels gresource.xml
+
+
+CLEANFILES = resources.h resources.c
diff --git a/src/gui/panels/gresource.xml b/src/gui/panels/gresource.xml
new file mode 100644
index 0000000..58d47e2
--- /dev/null
+++ b/src/gui/panels/gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/chrysalide/gui/panels">
+ <file compressed="true">welcome.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/gui/panels/panel-int.h b/src/gui/panels/panel-int.h
index 7e44275..4b0ef0c 100644
--- a/src/gui/panels/panel-int.h
+++ b/src/gui/panels/panel-int.h
@@ -37,6 +37,13 @@
+/* Place un panneau dans l'ensemble affiché. */
+typedef void (* ack_dock_process_fc) (GPanelItem *item);
+
+/* Supprime un panneau de l'ensemble affiché. */
+typedef void (* ack_undock_process_fc) (GPanelItem *item);
+
+
/* Elément réactif pour panneaux de l'éditeur (instance) */
struct _GPanelItem
{
@@ -63,7 +70,8 @@ struct _GPanelItemClass
bool unique; /* Panneau instanciable ? */
const char *bindings; /* Raccourci clavier éventuel */
- //GtkBin *first; /* Elément racine */
+ ack_dock_process_fc ack_dock; /* Prise en compte d'accroche */
+ ack_undock_process_fc ack_undock; /* Prise en compte de décroche */
/* Signaux */
diff --git a/src/gui/panels/panel.c b/src/gui/panels/panel.c
index ee0c102..edf57d3 100644
--- a/src/gui/panels/panel.c
+++ b/src/gui/panels/panel.c
@@ -474,6 +474,9 @@ void g_panel_item_dock(GPanelItem *item)
g_signal_emit_by_name(item, "dock-request");
+ if (G_PANEL_ITEM_GET_CLASS(item)->ack_dock != NULL)
+ G_PANEL_ITEM_GET_CLASS(item)->ack_dock(item);
+
}
@@ -543,4 +546,7 @@ void g_panel_item_undock(GPanelItem *item)
g_signal_emit_by_name(item, "undock-request");
+ if (G_PANEL_ITEM_GET_CLASS(item)->ack_undock != NULL)
+ G_PANEL_ITEM_GET_CLASS(item)->ack_undock(item);
+
}
diff --git a/src/gui/panels/regedit.c b/src/gui/panels/regedit.c
index 5e224ec..66f0330 100644
--- a/src/gui/panels/regedit.c
+++ b/src/gui/panels/regedit.c
@@ -225,7 +225,7 @@ static void g_regedit_panel_init(GRegeditPanel *panel)
pitem->personality = PIP_SINGLETON;
pitem->lname = _("Configuration parameters");
- pitem->dock_at_startup = true;
+ pitem->dock_at_startup = false;
pitem->path = strdup("N");
/* Représentation graphique */
diff --git a/src/gui/panels/welcome.c b/src/gui/panels/welcome.c
new file mode 100644
index 0000000..209419f
--- /dev/null
+++ b/src/gui/panels/welcome.c
@@ -0,0 +1,865 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * welcome.c - panneau d'accueil par défaut
+ *
+ * Copyright (C) 2016 Cyrille Bagard
+ *
+ * This file is part of Chrysalide.
+ *
+ * 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 "welcome.h"
+
+
+#include <assert.h>
+#include <malloc.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <config.h>
+#include <i18n.h>
+
+
+#include "panel-int.h"
+#include "../../common/cpp.h"
+#include "../../common/io.h"
+#include "../../common/net.h"
+#include "../../common/shuffle.h"
+#include "../../core/params.h"
+#include "../../gtkext/support.h"
+
+
+
+/* Panneau d'accueil par défaut (instance) */
+struct _GWelcomePanel
+{
+ GPanelItem parent; /* A laisser en premier */
+
+ GtkBuilder *builder; /* Constructeur utilisé */
+ cairo_surface_t *background; /* Fond pour astuces */
+
+ char **tips; /* Liste de toutes les astuces */
+ size_t count; /* Quantité d'astuces */
+ size_t current; /* Indice de l'astuce courante */
+
+ bool uorigin; /* Origine de l'affichage */
+
+};
+
+/* Panneau d'accueil par défaut (classe) */
+struct _GWelcomePanelClass
+{
+ GPanelItemClass parent; /* A laisser en premier */
+
+};
+
+
+/* Colonnes de la liste des messages */
+typedef enum _RecentProjectColumn
+{
+ RPC_VALID, /* Validité de l'entrée */
+ RPC_FULLPATH, /* Chemin d'accès à un projet */
+
+ RPC_COUNT /* Nombre de colonnes */
+
+} RecentProjectColumn;
+
+
+/* Initialise la classe des panneaux d'accueil par défaut. */
+static void g_welcome_panel_class_init(GWelcomePanelClass *);
+
+/* Initialise une instance de panneau d'accueil par défaut. */
+static void g_welcome_panel_init(GWelcomePanel *);
+
+/* Supprime toutes les références externes. */
+static void g_welcome_panel_dispose(GWelcomePanel *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_welcome_panel_finalize(GWelcomePanel *);
+
+/* Place un panneau dans l'ensemble affiché. */
+static void g_welcome_panel_dock(GWelcomePanel *);
+
+/* Charge l'ensemble des astuces. */
+static void g_welcome_panel_load_tips(GWelcomePanel *);
+
+/* Assure le dessin du fond de la bulle d'astuce. */
+static gboolean on_tip_background_draw(GtkWidget *, cairo_t *, GWelcomePanel *);
+
+/* Réagit à la demande d'étude d'un nouveau binaire. */
+static void on_new_binary_clicked(GtkButton *, GWelcomePanel *);
+
+/* Actualise au besoin la liste des projets récents. */
+static void on_recent_list_changed(GtkRecentManager *, GWelcomePanel *);
+
+/* Recharge une liste à jour des projets récents. */
+static void g_welcome_panel_reload_project_list(GWelcomePanel *, GtkRecentManager *);
+
+/* Réagit à une sélection décidée d'un projet particulier. */
+static void on_row_activated_for_projects(GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *, GWelcomePanel *);
+
+/* Enregistre les conditions d'affichage du panneau d'accueil. */
+static void on_startup_toggled(GtkToggleButton *, GWelcomePanel *);
+
+/* Consulte les versions existantes et affiche une conclusion. */
+static void g_welcome_panel_check_version(GWelcomePanel *);
+
+/* Affiche l'astuce précédente dans la liste globale. */
+static void on_tip_previous_clicked(GtkButton *, GWelcomePanel *);
+
+/* Affiche l'astuce suivante dans la liste globale. */
+static void on_tip_next_clicked(GtkButton *, GWelcomePanel *);
+
+/* Actualise l'affichage des astuces. */
+static void g_welcome_panel_refresh_tip(GWelcomePanel *);
+
+
+/* Indique le type défini pour un panneau d'accueil. */
+G_DEFINE_TYPE(GWelcomePanel, g_welcome_panel, G_TYPE_PANEL_ITEM);
+
+
+/******************************************************************************
+* *
+* Paramètres : klass = classe à initialiser. *
+* *
+* Description : Initialise la classe des panneaux d'accueil par défaut. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_class_init(GWelcomePanelClass *klass)
+{
+ GObjectClass *object; /* Autre version de la classe */
+ GPanelItemClass *parent; /* Version parente de classe */
+
+ object = G_OBJECT_CLASS(klass);
+
+ object->dispose = (GObjectFinalizeFunc/* ! */)g_welcome_panel_dispose;
+ object->finalize = (GObjectFinalizeFunc)g_welcome_panel_finalize;
+
+ parent = G_PANEL_ITEM_CLASS(klass);
+
+ parent->ack_dock = (ack_undock_process_fc)g_welcome_panel_dock;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = instance à initialiser. *
+* *
+* Description : Initialise une instance de panneau d'accueil par défaut. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_init(GWelcomePanel *panel)
+{
+ GEditorItem *base; /* Version basique d'instance */
+ GPanelItem *pitem; /* Version parente du panneau */
+ GtkTreeView *treeview; /* Affichage de la liste */
+ GtkCellRenderer *renderer; /* Moteur de rendu de colonne */
+ GtkTreeViewColumn *column; /* Colonne de la liste */
+ GtkToggleButton *button; /* Bouton à bascule à traiter */
+ bool state; /* Etat de la coche à définir */
+ gchar *filename; /* Chemin d'accès à une image */
+
+ /* Eléments de base */
+
+ base = G_EDITOR_ITEM(panel);
+
+ base->name = PANEL_WELCOME_ID;
+
+ pitem = G_PANEL_ITEM(panel);
+
+ pitem->personality = PIP_SINGLETON;
+ pitem->lname = _("Welcome");
+ pitem->dock_at_startup = true;
+ pitem->path = strdup("N");
+
+ panel->uorigin = !pitem->dock_at_startup;
+
+ /* Représentation graphique */
+
+ panel->builder = gtk_builder_new_from_resource("/org/chrysalide/gui/panels/welcome.ui");
+
+ base->widget = GTK_WIDGET(gtk_builder_get_object(panel->builder, "box"));
+ g_object_ref(G_OBJECT(base->widget));
+ gtk_widget_unparent(base->widget);
+
+ /* Liste des projets récents */
+
+ treeview = GTK_TREE_VIEW(gtk_builder_get_object(panel->builder, "treeview"));
+
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_append_column(treeview, column);
+ gtk_tree_view_set_expander_column(treeview, column);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_add_attribute(column, renderer, "markup", RPC_FULLPATH);
+
+ /* Affichage au démarrage ? */
+
+ button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(panel->builder, "startup"));
+
+ g_generic_config_get_value(get_main_configuration(), MPK_WELCOME_STARTUP, &state);
+
+ gtk_toggle_button_set_active(button, state);
+
+ /* Chargement de l'image de fond */
+
+ filename = find_pixmap_file("tipoftheday.png");
+
+ panel->background = cairo_image_surface_create_from_png(filename);
+
+ g_free(filename);
+
+ /* Connexion des signaux */
+
+ gtk_builder_add_callback_symbols(panel->builder,
+ "on_tip_background_draw", G_CALLBACK(on_tip_background_draw),
+ "on_new_binary_clicked", G_CALLBACK(on_new_binary_clicked),
+ "on_row_activated_for_projects", G_CALLBACK(on_row_activated_for_projects),
+ "on_startup_toggled", G_CALLBACK(on_startup_toggled),
+ "on_tip_previous_clicked", G_CALLBACK(on_tip_previous_clicked),
+ "on_tip_next_clicked", G_CALLBACK(on_tip_next_clicked),
+ NULL);
+
+ gtk_builder_connect_signals(panel->builder, panel);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = instance d'objet GLib à traiter. *
+* *
+* Description : Supprime toutes les références externes. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_dispose(GWelcomePanel *panel)
+{
+ g_object_unref(G_OBJECT(panel->builder));
+
+ free(panel->tips);
+
+ G_OBJECT_CLASS(g_welcome_panel_parent_class)->dispose(G_OBJECT(panel));
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = instance d'objet GLib à traiter. *
+* *
+* Description : Procède à la libération totale de la mémoire. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_finalize(GWelcomePanel *panel)
+{
+ cairo_surface_destroy(panel->background);
+
+ G_OBJECT_CLASS(g_welcome_panel_parent_class)->finalize(G_OBJECT(panel));
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : - *
+* *
+* Description : Crée un panneau d'accueil par défaut. *
+* *
+* Retour : Adresse de la structure mise en place. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+GPanelItem *g_welcome_panel_new(void)
+{
+ GWelcomePanel *result; /* Structure à retourner */
+ GtkRecentManager *manager; /* Gestionnaire global */
+
+ result = g_object_new(G_TYPE_WELCOME_PANEL, NULL);
+
+ manager = get_projects_manager();
+
+ g_signal_connect(manager, "changed", G_CALLBACK(on_recent_list_changed), result);
+
+ g_welcome_panel_reload_project_list(result, manager);
+
+ g_welcome_panel_load_tips(result);
+
+ g_welcome_panel_check_version(result);
+
+ return G_PANEL_ITEM(result);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = composant à présenter à l'affichage. *
+* *
+* Description : Place un panneau dans l'ensemble affiché. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_dock(GWelcomePanel *panel)
+{
+ g_welcome_panel_set_user_origin(panel, true);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau d'accueil à mettre à jour. *
+* *
+* Description : Charge l'ensemble des astuces. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_load_tips(GWelcomePanel *panel)
+{
+ size_t i; /* Boucle de parcours */
+
+ char *tips[] = {
+
+ _("There is no need to install Chrysalide on your system if you only want to give it a try.\n\n"
+ "Just compile the source code and run the program from there."),
+
+ _("Chrysalide can be used in external Python scripts by setting PYTHONPATH to the directory "
+ "containing the 'pychrysalide.so' file. For instance:\n\n"
+ " cd plugins/pychrysa/.libs/\n"
+ " export PYTHONPATH=$PWD\n\n"
+ "Then run the interpreter suitable to your configuration (debug or release):\n\n"
+ " python3-dbg -c 'import pychrysalide ; print(pychrysalide.mod_version())'"),
+
+ _("All the configuration files for chrysalide are located in $HOME/.config/chrysalide/.")
+
+ };
+
+ panel->count = ARRAY_SIZE(tips);
+
+ panel->tips = (char **)calloc(panel->count, sizeof(char *));
+
+ for (i = 0; i < panel->count; i++)
+ panel->tips[i] = tips[i];
+
+ shuffle(panel->tips, panel->count, sizeof(char *));
+
+ panel->current = 0;
+
+ g_welcome_panel_refresh_tip(panel);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : widget = composant graphique à redessiner. *
+* cr = contexte graphique à utiliser. *
+* panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Assure le dessin du fond de la bulle d'astuce. *
+* *
+* Retour : FALSE pour poursuivre la propagation de l'événement. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static gboolean on_tip_background_draw(GtkWidget *widget, cairo_t *cr, GWelcomePanel *panel)
+{
+ int wgt_width; /* Largeur disponible totale */
+ int wgt_height; /* Hauteur disponible totale */
+ int img_width; /* Largeur de l'image de fond */
+ int img_height; /* Hauteur de l'image de fond */
+ double scale; /* Echelle à appliquer */
+
+ if (cairo_surface_status(panel->background) == CAIRO_STATUS_SUCCESS)
+ {
+ wgt_width = gtk_widget_get_allocated_width(widget);
+ wgt_height = gtk_widget_get_allocated_height(widget);
+
+ img_width = cairo_image_surface_get_width(panel->background);
+ img_height = cairo_image_surface_get_height(panel->background);
+
+ scale = wgt_height / (2.0 * img_height);
+
+ cairo_scale(cr, scale, scale);
+
+ cairo_set_source_surface(cr, panel->background,
+ (wgt_width / scale) - img_width,
+ ((wgt_height / scale) - img_height) / 2);
+
+ cairo_paint(cr);
+
+ }
+
+ return FALSE;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : button = bouton impliqué dans la procédure. *
+* panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Réagit à la demande d'étude d'un nouveau binaire. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_new_binary_clicked(GtkButton *button, GWelcomePanel *panel)
+{
+ GObject *ref; /* Espace de référencements */
+ GtkMenuItem *item; /* Elément de menu simulé */
+
+ ref = g_editor_item_get_global_ref(G_EDITOR_ITEM(panel));
+
+ item = GTK_MENU_ITEM(g_object_get_data(ref, "mnu_project_add_binary"));
+
+ gtk_menu_item_activate(item);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : manager = gestion de fichiers récemment utilisés. *
+* panel = panneau associé comportant des informations utiles.*
+* *
+* Description : Actualise au besoin la liste des projets récents. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_recent_list_changed(GtkRecentManager *manager, GWelcomePanel *panel)
+{
+ g_welcome_panel_reload_project_list(panel, manager);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau comportant des informations utiles. *
+* manager = gestion de fichiers récemment utilisés. *
+* *
+* Description : Recharge une liste à jour des projets récents. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_reload_project_list(GWelcomePanel *panel, GtkRecentManager *manager)
+{
+ GtkListStore *store; /* Modèle de gestion */
+ bool empty; /* Liste vide ? */
+ GList *recents; /* Liste des fichiers récents */
+ GList *recent; /* Elément à traiter */
+ GtkRecentInfo *info; /* Informations sur l'élément */
+ GtkTreeIter iter; /* Point d'insertion */
+
+ /* Réinitialisation */
+
+ store = GTK_LIST_STORE(gtk_builder_get_object(panel->builder, "store"));
+
+ gtk_list_store_clear(store);
+
+ empty = true;
+
+ /* Chargement */
+
+ recents = gtk_recent_manager_get_items(manager);
+
+ if (recents != NULL)
+ {
+ for (recent = g_list_first(recents); recent != NULL; recent = g_list_next(recent))
+ {
+ info = recent->data;
+
+ if (strcmp(gtk_recent_info_get_mime_type(info), "application/chrysalide.project") == 0)
+ {
+ gtk_list_store_append(store, &iter);
+
+ gtk_list_store_set(store, &iter,
+ RPC_VALID, true,
+ RPC_FULLPATH, gtk_recent_info_get_uri_display(info),
+ -1);
+
+ empty = false;
+
+ }
+
+ gtk_recent_info_unref(info);
+
+ }
+
+ g_list_free(recents);
+
+ }
+
+ /* Indication par défaut */
+ if (empty)
+ {
+ gtk_list_store_append(store, &iter);
+
+ gtk_list_store_set(store, &iter,
+ RPC_VALID, false,
+ RPC_FULLPATH, _("<i>(No recent project)</i>"),
+ -1);
+
+ }
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : treeview = liste graphique concernée par la procédure. *
+* path = chemin d'accès à la ligne sélectionnée. *
+* column = colonne concernée par la sélection. *
+* panel = panneau associé avec des informations utiles. *
+* *
+* Description : Réagit à une sélection décidée d'un projet particulier. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_row_activated_for_projects(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, GWelcomePanel *panel)
+{
+ GtkTreeModel *model; /* Modèle de gestion */
+ GtkTreeIter iter; /* Point de la consultation */
+ gboolean valid; /* Validité de l'entrée */
+ gchar *filename; /* Chemin d'accès au projet */
+ GObject *ref; /* Espace de référencements */
+ GStudyProject *project; /* Nouveau projet à ouvrir */
+
+ model = gtk_tree_view_get_model(treeview);
+
+ if (gtk_tree_model_get_iter(model, &iter, path))
+ {
+ gtk_tree_model_get(model, &iter, RPC_VALID, &valid, RPC_FULLPATH, &filename, -1);
+
+ if (valid)
+ {
+ ref = g_editor_item_get_global_ref(G_EDITOR_ITEM(panel));
+
+ project = g_study_project_open(ref, filename);
+
+ if (project != NULL)
+ {
+ set_current_project(project);
+
+ push_project_into_recent_list(project);
+
+ }
+
+ g_free(filename);
+
+ }
+
+ }
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : button = bouton de défilement des astuces activé; *
+* panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Enregistre les conditions d'affichage du panneau d'accueil. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_startup_toggled(GtkToggleButton *button, GWelcomePanel *panel)
+{
+ g_generic_config_set_value(get_main_configuration(),
+ MPK_WELCOME_STARTUP, gtk_toggle_button_get_active(button));
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau d'accueil à mettre à jour. *
+* *
+* Description : Consulte les versions existantes et affiche une conclusion. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_check_version(GWelcomePanel *panel)
+{
+ bool skip; /* Saut de la vérification */
+ bool unknown; /* Impossibilité de comparaison*/
+ int current; /* Version courante */
+ int sock; /* Canal de communication */
+ bool status; /* Bilan d'une communication */
+ char buffer[1024]; /* Tampon de réception */
+ size_t got; /* Quantité de données reçues */
+ char *version; /* Version récupérée */
+ int available; /* Version disponible */
+ GtkLabel *label; /* Etiquette à éditer */
+ char *msg; /* Message à faire paraître */
+
+ g_generic_config_get_value(get_main_configuration(), MPK_WELCOME_CHECK, &skip);
+ skip = !skip;
+
+ unknown = true;
+
+ current = atoi(VERSION);
+
+ if (skip) goto check_process;
+
+ /* Recherche en ligne */
+
+ sock = connect_via_tcp("www.chrysalide.re", "80", NULL);
+ if (sock == -1) goto check_process;
+
+#define REQUEST "GET /version.last HTTP/1.1\r\nHost: www.chrysalide.re\r\n\r\n"
+
+ status = safe_send(sock, REQUEST, strlen(REQUEST), 0);
+ if (!status) goto check_done;
+
+ status = recv_all(sock, buffer, sizeof(buffer), &got);
+ if (!status) goto check_done;
+
+ version = strstr(buffer, "\r\n\r\n");
+
+ if (version != NULL)
+ {
+ available = atoi(version + 4);
+
+ unknown = false;
+
+ }
+
+ check_done:
+
+ close(sock);
+
+ check_process:
+
+ /* Affichage */
+
+ label = GTK_LABEL(gtk_builder_get_object(panel->builder, "version"));
+
+ if (skip)
+ asprintf(&msg,
+ "Your version is: <b>%d</b>\n\n" \
+ "Automatic version check is disabled.",
+ current);
+
+ else
+ {
+ if (unknown)
+ asprintf(&msg,
+ "Your version is: <b>%d</b>\n\n" \
+ "Lastest available version is unknown.",
+ current);
+
+ else
+ {
+ if (current >= available)
+ asprintf(&msg,
+ "Your version is: <b>%d</b>\n\n" \
+ "Lastest version is: <b>%d</b>\n\n" \
+ "Your software is <span color='green'><b>up-to-date</b></span>.",
+ current, available);
+
+ else
+ asprintf(&msg,
+ "Your version is: <b>%d</b>\n\n" \
+ "Lastest version is: <b>%d</b>\n\n" \
+ "Your software is <span color='red'><b>outdated</b></span>.",
+ current, available);
+
+ }
+
+ }
+
+ gtk_label_set_markup(label, msg);
+
+ free(msg);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : button = bouton de défilement des astuces activé; *
+* panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Affiche l'astuce précédente dans la liste globale. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_tip_previous_clicked(GtkButton *button, GWelcomePanel *panel)
+{
+ if (panel->current > 0)
+ panel->current--;
+ else
+ panel->current = panel->count - 1;
+
+ g_welcome_panel_refresh_tip(panel);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : button = bouton de défilement des astuces activé; *
+* panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Affiche l'astuce suivante dans la liste globale. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void on_tip_next_clicked(GtkButton *button, GWelcomePanel *panel)
+{
+ if ((panel->current + 1) < panel->count)
+ panel->current++;
+ else
+ panel->current = 0;
+
+ g_welcome_panel_refresh_tip(panel);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Actualise l'affichage des astuces. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void g_welcome_panel_refresh_tip(GWelcomePanel *panel)
+{
+ GtkLabel *label; /* Etiquette de présentation */
+
+ assert(panel->current < panel->count);
+
+ label = GTK_LABEL(gtk_builder_get_object(panel->builder, "tip"));
+
+ gtk_label_set_markup(label, panel->tips[panel->current]);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau associé comportant des informations utiles. *
+* *
+* Description : Indique l'origine de l'affichage du panneau d'accueil. *
+* *
+* Retour : true si l'affichage est le fait de l'utilisateur. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+bool g_welcome_panel_get_user_origin(const GWelcomePanel *panel)
+{
+ return panel->uorigin;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : panel = panneau associé comportant des informations utiles.*
+* uorigin = true si l'affichage est le fait de l'utilisateur. *
+* *
+* Description : Détermine l'origine de l'affichage du panneau d'accueil. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+void g_welcome_panel_set_user_origin(GWelcomePanel *panel, bool uorigin)
+{
+ panel->uorigin = uorigin;
+
+}
diff --git a/src/gui/panels/welcome.h b/src/gui/panels/welcome.h
new file mode 100644
index 0000000..c75d6f5
--- /dev/null
+++ b/src/gui/panels/welcome.h
@@ -0,0 +1,68 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * welcome.h - prototypes pour le panneau d'accueil par défaut
+ *
+ * Copyright (C) 2016 Cyrille Bagard
+ *
+ * This file is part of Chrysalide.
+ *
+ * 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
+ */
+
+
+#ifndef _GUI_PANELS_WELCOME_H
+#define _GUI_PANELS_WELCOME_H
+
+
+#include <i18n.h>
+
+
+#include "panel.h"
+
+
+
+#define PANEL_WELCOME_ID _("Welcome")
+
+
+#define G_TYPE_WELCOME_PANEL g_welcome_panel_get_type()
+#define G_WELCOME_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), g_welcome_panel_get_type(), GWelcomePanel))
+#define G_IS_WELCOME_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_welcome_panel_get_type()))
+#define G_WELCOME_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WELCOME_PANEL, GWelcomePanelClass))
+#define G_IS_WELCOME_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WELCOME_PANEL))
+#define G_WELCOME_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WELCOME_PANEL, GWelcomePanelClass))
+
+
+/* Panneau d'accueil par défaut (instance) */
+typedef struct _GWelcomePanel GWelcomePanel;
+
+/* Panneau d'accueil par défaut (classe) */
+typedef struct _GWelcomePanelClass GWelcomePanelClass;
+
+
+/* Indique le type défini pour un panneau d'accueil. */
+GType g_welcome_panel_get_type(void);
+
+/* Crée un panneau d'accueil par défaut. */
+GPanelItem *g_welcome_panel_new(void);
+
+/* Indique l'origine de l'affichage du panneau d'accueil. */
+bool g_welcome_panel_get_user_origin(const GWelcomePanel *);
+
+/* Détermine l'origine de l'affichage du panneau d'accueil. */
+void g_welcome_panel_set_user_origin(GWelcomePanel *, bool);
+
+
+
+#endif /* _GUI_PANELS_WELCOME_H */
diff --git a/src/gui/panels/welcome.ui b/src/gui/panels/welcome.ui
new file mode 100644
index 0000000..d016907
--- /dev/null
+++ b/src/gui/panels/welcome.ui
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">6</property>
+ </object>
+ <object class="GtkListStore" id="store">
+ <columns>
+ <!-- column-name valid -->
+ <column type="gboolean"/>
+ <!-- column-name fullpath -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkOffscreenWindow" id="offscreenwindow1">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">8</property>
+ <property name="margin_right">8</property>
+ <property name="margin_top">8</property>
+ <property name="margin_bottom">8</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="top_padding">180</property>
+ <property name="bottom_padding">30</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">New binary...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="image">image1</property>
+ <property name="image_position">top</property>
+ <property name="always_show_image">True</property>
+ <signal name="clicked" handler="on_new_binary_clicked" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Last projects:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTreeView" id="treeview">
+ <property name="height_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_left">8</property>
+ <property name="hexpand">True</property>
+ <property name="model">store</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">True</property>
+ <signal name="row-activated" handler="on_row_activated_for_projects" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="startup">
+ <property name="label" translatable="yes">Show this panel at startup.</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="valign">end</property>
+ <property name="vexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_startup_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">8</property>
+ <property name="margin_right">8</property>
+ <property name="margin_top">8</property>
+ <property name="margin_bottom">8</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkBox" id="box4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="version">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="opacity">0.81999999977648264</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="opacity">0.81999999999999995</property>
+ <property name="label" translatable="yes">Get access to the online documentation and stay tuned by visiting the official website : &lt;a href="http://chrysalide.re"&gt;chrysalide.re&lt;/a&gt;
+
+You can also follow Chrysalide on Twitter : &lt;a href="http://twitter.com/chrysalide_ref"&gt;@chrysalide_ref&lt;/a&gt;</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ <property name="track_visited_links">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tip">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">1</property>
+ <property name="xpad">8</property>
+ <property name="ypad">8</property>
+ <property name="label" translatable="yes">label</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <signal name="draw" handler="on_tip_background_draw" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="buttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">8</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="label" translatable="yes">Previous</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_tip_previous_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label" translatable="yes">Next</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_tip_next_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>