/* Chrysalide - Outil d'analyse de fichiers binaires
* gtkdockable.c - éléments acceptés dans les composants de rassemblement
*
* Copyright (C) 2015-2017 Cyrille Bagard
*
* This file is part of Chrysalide.
*
* Chrysalide is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Chrysalide is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chrysalide. If not, see .
*/
#include "gtkdockable.h"
#include
#include
#include
#include "easygtk.h"
#include "gtkdockable-int.h"
#include "tmgt.h"
#include "../gui/agroup.h"
/* --------------------- DEFINITIONS PRINCIPALES DE L'INTERFACE --------------------- */
/* Procède à l'initialisation de l'interface de rassemblement. */
static void gtk_dockable_default_init(GtkDockableInterface *);
/* ------------------------ FONCTIONS DE RECHERCHE INTEGREES ------------------------ */
/* Construit une zone de recherches vouée à être intégrée. */
static GtkWidget *build_search_area(GtkDockable *, GtkWidget **);
/* Met à jour l'expression de filtrage de la zone intégrée. */
static void on_dockable_search_changed(GtkSearchEntry *, GtkDockable *);
/* ----------------------- PROCEDURES POUR LE GLISSER-DEPOSER ----------------------- */
/* Enumération des types de données à déplacer */
enum
{
TARGET_ROOTWIN
};
static GtkTargetEntry target_list[] = {
{ "application/x-rootwindow-drop", 0, TARGET_ROOTWIN }
};
/* Fenêtre d'aperçu de destination */
static GtkWidget *_drag_window = NULL;
/* Dimensions maximales de l'aperçu du contenu déplacé */
#define DND_WND_MAX_WIDTH 250
#define DND_WND_MAX_HEIGHT 150
/* Emplacements de la fenêtre de DragAndDrop */
typedef enum _DNDWindowPosition
{
DWP_NONE, /* Position non définie */
DWP_CENTER, /* Position centrale */
DWP_LEFT, /* Position à gauche */
DWP_TOP, /* Position supérieure */
DWP_RIGHT, /* Position à droite */
DWP_BOTTOM /* Position inférieure */
} DNDWindowPosition;
static DNDWindowPosition _dnd_position = DWP_NONE;
/* Seuil de basculement entre zones */
#define BORDER_THRESHOLD 0.20
/* Amorce un démarrage de DragAndDrop. */
static void on_dockable_drag_begin(GtkWidget *, GdkDragContext *, void *);
/* Adapte le dessin de fond à de nouvelles dimensions. */
static void update_drag_window_background(GtkWidget *, gint, gint);
/* Suit le déplacement d'un contenu déposable. */
static gboolean on_dockable_drag_motion(GtkWidget *, GdkDragContext *, gint, gint, guint, void *);
/* Accommpagne la sortie de la souris d'un composant graphique. */
static void on_dockable_drag_leave(GtkWidget *, GdkDragContext *, guint, void *);
/* ---------------------------------------------------------------------------------- */
/* DEFINITIONS PRINCIPALES DE L'INTERFACE */
/* ---------------------------------------------------------------------------------- */
/* Détermine le type d'une interface pour rassemblement. */
G_DEFINE_INTERFACE(GtkDockable, gtk_dockable, G_TYPE_OBJECT)
/******************************************************************************
* *
* Paramètres : iface = interface GTK à initialiser. *
* *
* Description : Procède à l'initialisation de l'interface de rassemblement. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_dockable_default_init(GtkDockableInterface *iface)
{
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* *
* Description : Fournit le nom court du composant encapsulable. *
* *
* Retour : Désignation humaine pour titre d'onglet ou de fenêtre. *
* *
* Remarques : - *
* *
******************************************************************************/
const char *gtk_dockable_get_name(const GtkDockable *dockable)
{
GtkDockableIface *iface; /* Interface utilisée */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
return iface->get_name(dockable);
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* *
* Description : Fournit le nom long du composant encapsulable. *
* *
* Retour : Désignation humaine pour titre d'onglet ou de fenêtre. *
* *
* Remarques : - *
* *
******************************************************************************/
const char *gtk_dockable_get_desc(const GtkDockable *dockable)
{
GtkDockableIface *iface; /* Interface utilisée */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
return iface->get_desc(dockable);
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* *
* Description : Indique si le composant représenté à du contenu à fouiller. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
bool gtk_dockable_can_search(const GtkDockable *dockable)
{
bool result; /* Indication à retourner */
GtkDockableIface *iface; /* Interface utilisée */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
result = iface->can_search(dockable);
return result;
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* *
* Description : Fournit le composant graphique intégrable dans un ensemble. *
* *
* Retour : Composant graphique prêt à emploi. *
* *
* Remarques : - *
* *
******************************************************************************/
GtkWidget *gtk_dockable_build_widget(GtkDockable *dockable)
{
GtkWidget *result; /* Composant à retourner */
GtkDockableIface *iface; /* Interface utilisée */
GtkWidget *widget; /* Composant graphique interne */
GtkWidget *revealer; /* Révélateur à intégrer ? */
GtkWidget *search; /* Zone de recherche */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
widget = iface->get_widget(dockable);
g_object_ref(G_OBJECT(widget));
/* Encapsulation avec un panneau coulissant ? */
if (gtk_dockable_can_search(dockable))
{
revealer = gtk_revealer_new();
gtk_widget_show(revealer);
gtk_container_add(GTK_CONTAINER(revealer), build_search_area(dockable, &search));
result = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_show(result);
gtk_box_pack_start(GTK_BOX(result), revealer, FALSE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(result), widget, TRUE, TRUE, 0);
g_object_set_data(G_OBJECT(result), "revealer", revealer);
g_object_set_data(G_OBJECT(result), "search", search);
}
/* Ou bien non ! */
else
result = widget;
return result;
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* support = composant à partir duquel décrocher ou NULL. [OUT]*
* *
* Description : Fournit tous les éléments pour un retrait graphique. *
* *
* Retour : Composant graphique à décrocher. *
* *
* Remarques : - *
* *
******************************************************************************/
GtkWidget *gtk_dockable_decompose(GtkDockable *dockable, GtkWidget **support)
{
GtkWidget *result; /* Composant à retourner */
GtkDockableIface *iface; /* Interface utilisée */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
result = iface->get_widget(dockable);
if (gtk_dockable_can_search(dockable))
result = gtk_widget_get_parent(result); /* GtkBox */
if (support != NULL)
*support = gtk_widget_get_parent(result);
return result;
}
/******************************************************************************
* *
* Paramètres : dockable = instance GTK dont l'interface est à consulter. *
* built = composant graphique d'encapsulation mis en place. *
* reveal = détermine l'action à mener. *
* *
* Description : Révèle ou cache la zone de recherches. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_dockable_toggle_revealer(GtkDockable *dockable, GtkWidget *built, gboolean reveal)
{
GtkRevealer *revealer; /* Révélateur à actionner */
GtkWidget *entry; /* Zone de recherche à activer */
revealer = GTK_REVEALER(g_object_get_data(G_OBJECT(built), "revealer"));
assert(revealer != NULL);
gtk_revealer_set_reveal_child(revealer, reveal);
if (reveal)
{
entry = GTK_WIDGET(g_object_get_data(G_OBJECT(built), "search"));
gtk_widget_grab_focus(entry);
}
}
/* ---------------------------------------------------------------------------------- */
/* FONCTIONS DE RECHERCHE INTEGREES */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : dockable = élément encapsulable à avertir des changements. *
* search = zone de saisie pour lancer les recherches. *
* *
* Description : Construit une zone de recherches vouée à être intégrée. *
* *
* Retour : Composant GTK prêt à être intégré. *
* *
* Remarques : - *
* *
******************************************************************************/
static GtkWidget *build_search_area(GtkDockable *dockable, GtkWidget **search)
{
GtkWidget *result; /* Support à retourner */
GtkWidget *label; /* Etiquette à utiliser */
result = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(result), 8);
gtk_widget_show(result);
label = qck_create_label(NULL, NULL, _("Look for:"));
g_object_set(label, "margin", 8, NULL);
gtk_grid_attach(GTK_GRID(result), label, 0, 0, 1, 1);
*search = gtk_search_entry_new();
g_signal_connect(*search, "search-changed", G_CALLBACK(on_dockable_search_changed), dockable);
g_signal_connect(*search, "focus-in-event", G_CALLBACK(track_focus_change_in_text_area), NULL);
g_signal_connect(*search, "focus-out-event", G_CALLBACK(track_focus_change_in_text_area), NULL);
gtk_widget_set_hexpand(*search, TRUE);
gtk_widget_show(*search);
gtk_grid_attach_next_to(GTK_GRID(result), *search, label, GTK_POS_RIGHT, 1, 1);
return result;
}
/******************************************************************************
* *
* Paramètres : entry = entrée de texte contenant le filtre brut. *
* Paramètres : dockable = élément encapsulable à avertir des changements. *
* *
* Description : Met à jour l'expression de filtrage de la zone intégrée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_dockable_search_changed(GtkSearchEntry *entry, GtkDockable *dockable)
{
char *filter; /* Nouveau filtre à considérer */
GtkDockableIface *iface; /* Interface utilisée */
filter = NULL;
update_regex_on_search_entry_changed(entry, &filter);
iface = GTK_DOCKABLE_GET_IFACE(dockable);
iface->update_filtered(dockable, filter);
}
/* ---------------------------------------------------------------------------------- */
/* PROCEDURES POUR LE GLISSER-DEPOSER */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
* *
* Paramètres : - *
* *
* Description : Prépare en sous-main la fenêtre de prédiction du déposer. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void prepare_drag_and_drop_window(void)
{
GdkScreen *screen; /* Ecran d'affichage */
GdkVisual *rgba_visual; /* Configuration visuelle */
gboolean has_rgba; /* Support de la transparence ?*/
screen = gdk_screen_get_default();
rgba_visual = gdk_screen_get_rgba_visual(screen);
has_rgba = (rgba_visual != NULL && gdk_screen_is_composited(screen));
_drag_window = gtk_window_new(GTK_WINDOW_POPUP);
if (has_rgba)
gtk_widget_set_visual(_drag_window, rgba_visual);
gtk_window_set_type_hint(GTK_WINDOW(_drag_window), GDK_WINDOW_TYPE_HINT_DND);
gtk_window_set_screen(GTK_WINDOW(_drag_window), screen);
gtk_widget_set_app_paintable(_drag_window, TRUE);
gtk_widget_set_size_request(_drag_window, 1, 1);
gtk_widget_realize(_drag_window);
/**
* Cf. commentaires de on_dockable_drag_leave().
*/
gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
gtk_widget_show(_drag_window);
}
/******************************************************************************
* *
* Paramètres : dockable = instance de composant à intégrer pleinement. *
* *
* Description : Initialise les fonctions de glisser/déposer pour un élément. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_dockable_setup_dnd(GtkDockable *dockable)
{
GtkDockableIface *iface; /* Interface utilisée */
GtkWidget *widget; /* Composant graphique interne */
iface = GTK_DOCKABLE_GET_IFACE(dockable);
widget = iface->get_widget(dockable);
#if 0
gtk_drag_source_set(widget, GDK_BUTTON1_MASK,
target_list, G_N_ELEMENTS(target_list),
GDK_ACTION_MOVE);
#endif
g_signal_connect_after(widget, "drag-begin", G_CALLBACK(on_dockable_drag_begin), NULL);
gtk_drag_dest_set(widget, 0, target_list, G_N_ELEMENTS(target_list), GDK_ACTION_MOVE);
g_signal_connect(widget, "drag-motion", G_CALLBACK(on_dockable_drag_motion), NULL);
g_signal_connect(widget, "drag-leave", G_CALLBACK(on_dockable_drag_leave), NULL);
}
/******************************************************************************
* *
* Paramètres : widget = composant graphique à l'origine de l'opération. *
* context = contexte assurant la gestion de l'opération. *
* unused = adresse non utilisée ici. *
* *
* Description : Amorce un démarrage de DragAndDrop. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_dockable_drag_begin(GtkWidget *widget, GdkDragContext *context, void *unused)
{
GtkAllocation alloc; /* Espace graphique occupé */
gint width; /* Largeur de l'aperçu */
gint height; /* Hauteur de l'aperçu */
cairo_surface_t *surface; /* Représentation du contenu */
cairo_t *cr; /* Pinceau de dessin */
GtkStyleContext *style; /* Contexte du thème actuel */
gtk_widget_get_allocation(widget, &alloc);
if (alloc.width > alloc.height)
{
width = DND_WND_MAX_WIDTH;
height = alloc.height * ((1.0 * DND_WND_MAX_WIDTH) / alloc.width);
}
else
{
height = DND_WND_MAX_HEIGHT;
width = alloc.width * ((1.0 * DND_WND_MAX_HEIGHT) / alloc.height);
}
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create(surface);
/* Fond */
style = gtk_widget_get_style_context(widget);
gtk_style_context_save(style);
gtk_style_context_add_class(style, GTK_STYLE_CLASS_VIEW);
gtk_render_background(style, cr, 0, 0, width, height);
gtk_render_frame(style, cr, 0, 0, width, height);
gtk_style_context_restore(style);
/* Aperçu */
cairo_save(cr);
cairo_scale(cr, (1.0 * width) / alloc.width, (1.0 * height) / alloc.height);
gtk_widget_draw(widget, cr);
cairo_restore(cr);
/* Application */
gtk_drag_set_icon_surface(context, surface);
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
/******************************************************************************
* *
* Paramètres : dnd_widget = composant GTK assurant l'affichage du fond. *
* width = nouvelle largeur à remplir. *
* height = nouvelle hauteur à remplir. *
* *
* Description : Adapte le dessin de fond à de nouvelles dimensions. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void update_drag_window_background(GtkWidget *dnd_window, gint width, gint height)
{
GdkWindow *window; /* Représentation de bas niveau*/
cairo_surface_t *surface; /* Surface à appliquer au fond */
cairo_t *cr; /* Pinceau de dessin */
GdkScreen *screen; /* Ecran d'affichage */
gboolean has_rgba; /* Support de la transparence ?*/
cairo_surface_t *saturated; /* Surface de remplacement */
cairo_region_t *region; /* Limites du dessin */
cairo_pattern_t *pattern; /* Modèle de dessin final */
cairo_matrix_t matrix; /* Matrice de transformation */
window = gtk_widget_get_window(dnd_window);
/**
* Code inspiré de celui de la fonction gtk_drag_set_icon_surface().
*/
/* Création d'un nouveau modèle */
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create(surface);
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.05);
cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.05);
cairo_rectangle(cr, 0, 0, width, height);
cairo_stroke(cr);
cairo_destroy(cr);
/* Application */
screen = gdk_window_get_screen(window);
has_rgba = (gdk_screen_get_rgba_visual(screen) != NULL
&& gdk_screen_is_composited(screen));
if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR && !has_rgba)
{
region = gdk_cairo_region_create_from_surface(surface);
gtk_widget_shape_combine_region(dnd_window, region);
cairo_region_destroy(region);
saturated = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR, width, height);
cr = cairo_create(saturated);
cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR_ALPHA);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SATURATE);
cairo_paint(cr);
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_destroy(cr);
pattern = cairo_pattern_create_for_surface(saturated);
cairo_surface_destroy(saturated);
}
else
{
pattern = cairo_pattern_create_for_surface(surface);
cairo_matrix_init_translate(&matrix, 0, 0);
cairo_pattern_set_matrix(pattern, &matrix);
}
cairo_surface_destroy(surface);
// Deprecated
// gdk_window_set_background_pattern(window, pattern);
cairo_pattern_destroy(pattern);
}
/******************************************************************************
* *
* Paramètres : widget = composant graphique à l'origine de l'opération. *
* context = contexte assurant la gestion de l'opération. *
* x = abscisse courante du pointeur de la souris. *
* y = ordonnée courante du pointeur de la souris. *
* time = horodatage associé à l'événement. *
* unused = adresse non utilisée ici. *
* *
* Description : Suit le déplacement d'un contenu déposable. *
* *
* Retour : TRUE pour un contenu acceptable, FALSE sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean on_dockable_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, void *unused)
{
GtkAllocation alloc; /* Espace graphique occupé */
DNDWindowPosition wpos; /* Direction actuelle */
gint border_width; /* Largeur d'une bordure */
gint border_height; /* Hauteur d'une bordure */
gint corner_left; /* Bordure à gauche */
gint corner_top; /* Bordure en haut */
gint corner_right; /* Bordure à droite */
gint corner_bottom; /* Bordure en bas */
GtkAllocation update; /* Nouvelle position */
/* Détermination de la position idéale */
gtk_widget_get_allocation(widget, &alloc);
wpos = DWP_NONE;
border_width = alloc.width * BORDER_THRESHOLD;
border_height = alloc.height * BORDER_THRESHOLD;
corner_left = border_width;
corner_top = border_height;
corner_right = alloc.width - border_width;
corner_bottom = alloc.height - border_height;
if (alloc.width > alloc.height)
{
if (x < corner_left)
wpos = DWP_LEFT;
else if (x > corner_right)
wpos = DWP_RIGHT;
else if (y < corner_top)
wpos = DWP_TOP;
else if (y > corner_bottom)
wpos = DWP_BOTTOM;
}
else
{
if (y < corner_top)
wpos = DWP_TOP;
else if (y > corner_bottom)
wpos = DWP_BOTTOM;
else if (x < corner_left)
wpos = DWP_LEFT;
else if (x > corner_right)
wpos = DWP_RIGHT;
}
if (x >= corner_left && x <= corner_right && y >= corner_top && y <= corner_bottom)
{
wpos = DWP_CENTER;
}
/* Mise à jour de la fenêtre de portée ? */
if (_dnd_position != wpos)
{
if (wpos != DWP_NONE)
gdk_window_get_origin(gtk_widget_get_window(widget), &update.x, &update.y);
update.x += alloc.x;
update.y += alloc.y;
switch (wpos)
{
case DWP_NONE:
break;
case DWP_CENTER:
update.x += border_width;
update.y += border_height;
update.width = alloc.width - 2 * border_width;
update.height = alloc.height - 2 * border_height;
break;
case DWP_LEFT:
update.width = border_width;
update.height = alloc.height;
break;
case DWP_TOP:
update.width = alloc.width;
update.height = border_height;
break;
case DWP_RIGHT:
update.x += corner_right;
update.width = border_width;
update.height = alloc.height;
break;
case DWP_BOTTOM:
update.y += corner_bottom;
update.width = alloc.width;
update.height = border_height;
break;
}
if (wpos == DWP_NONE)
{
/**
* Cf. commentaires de on_dockable_drag_leave().
*/
gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
}
else
{
update_drag_window_background(_drag_window, update.width, update.height);
gtk_window_move(GTK_WINDOW(_drag_window), update.x, update.y);
gtk_window_resize(GTK_WINDOW(_drag_window), update.width, update.height);
}
_dnd_position = wpos;
}
gdk_drag_status(context, GDK_ACTION_MOVE, time);
return TRUE;
}
/******************************************************************************
* *
* Paramètres : widget = composant graphique qui était survolé. *
* context = contexte assurant la gestion de l'opération. *
* time = horodatage associé à l'événement. *
* unused = adresse non utilisée ici. *
* *
* Description : Accommpagne la sortie de la souris d'un composant graphique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_dockable_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, void *unused)
{
/**
* Visiblement, cacher et réafficher la fenêtre d'aperçu de la zone de
* destination provoque un enchaînement ininterrompu d'événements
* "motion" & "leave".
*
* Conséquence : la fenêtre scintille.
*
* On contourne le bogue visuel en ne cachant plus la fenêtre.
*/
_dnd_position = DWP_NONE;
gtk_window_resize(GTK_WINDOW(_drag_window), 1, 1);
gtk_window_move(GTK_WINDOW(_drag_window), -1, -1);
}