gtk2/tests/testdnd2.c
Matthias Clasen e8b830a3dd dragsource: Reshuffle api a bit
Remove arguments from the constructor.

For actions, we now default to COPY, which is the most common one
that we should enable by default (MOVE requires handling deletion
on the the source side, and ASK only makes sense if we have
multiple actions).

For the content provider, we add a new ::prepare signal where
it should be provided just-in-time.
2020-01-08 18:48:21 -05:00

396 lines
11 KiB
C

#include <unistd.h>
#include <gtk/gtk.h>
static GdkPaintable *
get_image_paintable (GtkImage *image,
int *out_size)
{
GtkIconTheme *icon_theme;
const char *icon_name;
int width = 48;
GdkPaintable *paintable;
GtkIconInfo *icon_info;
switch (gtk_image_get_storage_type (image))
{
case GTK_IMAGE_PAINTABLE:
paintable = gtk_image_get_paintable (image);
*out_size = gdk_paintable_get_intrinsic_width (paintable);
return g_object_ref (paintable);
case GTK_IMAGE_ICON_NAME:
icon_name = gtk_image_get_icon_name (image);
icon_theme = gtk_icon_theme_get_for_display (gtk_widget_get_display (GTK_WIDGET (image)));
*out_size = width;
icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, width, GTK_ICON_LOOKUP_GENERIC_FALLBACK);
paintable = gtk_icon_info_load_icon (icon_info, NULL);
g_object_unref (icon_info);
return paintable;
default:
g_warning ("Image storage type %d not handled",
gtk_image_get_storage_type (image));
return NULL;
}
}
enum {
TOP_LEFT,
CENTER,
BOTTOM_RIGHT
};
void
image_drag_data_get (GtkWidget *widget,
GdkDrag *drag,
GtkSelectionData *selection_data,
gpointer data)
{
GdkPaintable *paintable;
const gchar *name;
int size;
if (gtk_selection_data_targets_include_image (selection_data, TRUE))
{
paintable = get_image_paintable (GTK_IMAGE (data), &size);
if (GDK_IS_TEXTURE (paintable))
gtk_selection_data_set_texture (selection_data, GDK_TEXTURE (paintable));
if (paintable)
g_object_unref (paintable);
}
else if (gtk_selection_data_targets_include_text (selection_data))
{
if (gtk_image_get_storage_type (GTK_IMAGE (data)) == GTK_IMAGE_ICON_NAME)
name = gtk_image_get_icon_name (GTK_IMAGE (data));
else
name = "Boo!";
gtk_selection_data_set_text (selection_data, name, -1);
}
else
{
g_assert_not_reached ();
}
}
static void
got_texture (GObject *source,
GAsyncResult *result,
gpointer data)
{
GdkDrop *drop = GDK_DROP (source);
GtkWidget *image = data;
const GValue *value;
GError *error = NULL;
value = gdk_drop_read_value_finish (drop, result, &error);
if (value)
{
GdkTexture *texture = g_value_get_object (value);
gtk_image_set_from_paintable (GTK_IMAGE (image), GDK_PAINTABLE (texture));
gdk_drop_finish (drop, GDK_ACTION_COPY);
}
else
{
g_error_free (error);
gdk_drop_finish (drop, 0);
}
g_object_set_data (G_OBJECT (image), "drop", NULL);
}
static void
perform_drop (GdkDrop *drop,
GtkWidget *image)
{
if (gdk_drop_has_value (drop, GDK_TYPE_TEXTURE))
gdk_drop_read_value_async (drop, GDK_TYPE_TEXTURE, G_PRIORITY_DEFAULT, NULL, got_texture, image);
else
{
gdk_drop_finish (drop, 0);
g_object_set_data (G_OBJECT (image), "drop", NULL);
}
}
static void
do_copy (GtkWidget *button)
{
GtkWidget *popover = gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER);
GtkWidget *image = gtk_popover_get_relative_to (GTK_POPOVER (popover));
GdkDrop *drop = GDK_DROP (g_object_get_data (G_OBJECT (image), "drop"));
gtk_popover_popdown (GTK_POPOVER (popover));
perform_drop (drop, image);
}
static void
do_cancel (GtkWidget *button)
{
GtkWidget *popover = gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER);
GtkWidget *image = gtk_popover_get_relative_to (GTK_POPOVER (popover));
GdkDrop *drop = GDK_DROP (g_object_get_data (G_OBJECT (image), "drop"));
gtk_popover_popdown (GTK_POPOVER (popover));
gdk_drop_finish (drop, 0);
g_object_set_data (G_OBJECT (image), "drop", NULL);
}
static void
ask_actions (GdkDrop *drop,
GtkWidget *image)
{
GtkWidget *popover, *box, *button;
popover = g_object_get_data (G_OBJECT (image), "popover");
if (!popover)
{
popover = gtk_popover_new (image);
g_object_set_data (G_OBJECT (image), "popover", popover);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (popover), box);
button = gtk_button_new_with_label ("Copy");
g_signal_connect (button, "clicked", G_CALLBACK (do_copy), NULL);
gtk_container_add (GTK_CONTAINER (box), button);
button = gtk_button_new_with_label ("Move");
g_signal_connect (button, "clicked", G_CALLBACK (do_copy), NULL);
gtk_container_add (GTK_CONTAINER (box), button);
button = gtk_button_new_with_label ("Cancel");
g_signal_connect (button, "clicked", G_CALLBACK (do_cancel), NULL);
gtk_container_add (GTK_CONTAINER (box), button);
}
gtk_popover_popup (GTK_POPOVER (popover));
}
static gboolean
image_drag_drop (GtkDropTarget *dest,
int x,
int y,
gpointer data)
{
GtkWidget *image = data;
GdkDrop *drop = gtk_drop_target_get_drop (dest);
GdkDragAction action = gdk_drop_get_actions (drop);
g_object_set_data_full (G_OBJECT (image), "drop", g_object_ref (drop), g_object_unref);
g_print ("drop, actions %d\n", action);
if (!gdk_drag_action_is_unique (action))
ask_actions (drop, image);
else
perform_drop (drop, image);
return TRUE;
}
static void
update_source_icon (GtkDragSource *source,
const char *icon_name,
int hotspot)
{
GdkPaintable *paintable;
int hot_x, hot_y;
int size = 48;
paintable = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
icon_name, size, 0, NULL);
switch (hotspot)
{
default:
case TOP_LEFT:
hot_x = 0;
hot_y = 0;
break;
case CENTER:
hot_x = size / 2;
hot_y = size / 2;
break;
case BOTTOM_RIGHT:
hot_x = size;
hot_y = size;
break;
}
gtk_drag_source_set_icon (source, paintable, hot_x, hot_y);
g_object_unref (paintable);
}
static GBytes *
get_data (const char *mimetype,
gpointer data)
{
GtkWidget *image = data;
GdkContentFormats *formats;
gboolean want_text;
formats = gdk_content_formats_new (NULL, 0);
formats = gtk_content_formats_add_text_targets (formats);
want_text = gdk_content_formats_contain_mime_type (formats, mimetype);
gdk_content_formats_unref (formats);
if (want_text)
{
const char *text = gtk_image_get_icon_name (GTK_IMAGE (image));
return g_bytes_new (text, strlen (text) + 1);
}
else if (strcmp (mimetype, "image/png") == 0)
{
int size;
GdkPaintable *paintable = get_image_paintable (GTK_IMAGE (image), &size);
if (GDK_IS_TEXTURE (paintable))
{
char *name = g_strdup ("drag-data-XXXXXX");
int fd;
char *data;
gsize size;
// FIXME: this is horrible
fd = g_mkstemp (name);
close (fd);
gdk_texture_save_to_png (GDK_TEXTURE (paintable), name);
g_file_get_contents (name, &data, &size, NULL);
g_free (name);
return g_bytes_new_take (data, size);
}
g_clear_object (&paintable);
}
return NULL;
}
static void
drag_begin (GtkDragSource *source)
{
g_print ("drag begin\n");
}
static void
drag_end (GtkDragSource *source)
{
g_print ("drag end\n");
}
static gboolean
drag_failed (GtkDragSource *source,
GdkDrag *drag,
GdkDragCancelReason reason)
{
g_print ("drag failed: %d\n", reason);
return FALSE;
}
GtkWidget *
make_image (const gchar *icon_name, int hotspot)
{
GtkWidget *image;
GtkDragSource *source;
GtkDropTarget *dest;
GdkContentFormats *formats;
GdkContentProvider *content;
image = gtk_image_new_from_icon_name (icon_name);
gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
formats = gdk_content_formats_new (NULL, 0);
formats = gtk_content_formats_add_image_targets (formats, FALSE);
formats = gtk_content_formats_add_text_targets (formats);
content = gdk_content_provider_new_with_formats (formats, get_data, image);
source = gtk_drag_source_new ();
gtk_drag_source_set_content (source, content);
gtk_drag_source_set_actions (source, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_ASK);
g_object_unref (content);
update_source_icon (source, icon_name, hotspot);
g_signal_connect (source, "drag-begin", G_CALLBACK (drag_begin), NULL);
g_signal_connect (source, "drag-end", G_CALLBACK (drag_end), NULL);
g_signal_connect (source, "drag-failed", G_CALLBACK (drag_failed), NULL);
gtk_drag_source_attach (source, image, GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK);
dest = gtk_drop_target_new (formats, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_ASK);
g_signal_connect (dest, "drag-drop", G_CALLBACK (image_drag_drop), image);
gtk_widget_add_controller (image, GTK_EVENT_CONTROLLER (dest));
gdk_content_formats_unref (formats);
return image;
}
static void
spinner_drag_begin (GtkDragSource *source,
GdkDrag *drag,
GtkWidget *widget)
{
GdkPaintable *paintable;
paintable = gtk_widget_paintable_new (widget);
gtk_drag_source_set_icon (source, paintable, 0, 0);
g_object_unref (paintable);
}
static GtkWidget *
make_spinner (void)
{
GtkWidget *spinner;
GtkDragSource *source;
GdkContentProvider *content;
GValue value = G_VALUE_INIT;
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
g_value_init (&value, G_TYPE_STRING);
g_value_set_string (&value, "ACTIVE");
content = gdk_content_provider_new_for_value (&value);
source = gtk_drag_source_new ();
gtk_drag_source_set_content (source, content);
g_signal_connect (source, "drag-begin", G_CALLBACK (spinner_drag_begin), spinner);
gtk_drag_source_attach (source, spinner, GDK_BUTTON1_MASK);
g_object_unref (content);
g_value_unset (&value);
return spinner;
}
int
main (int argc, char *Argv[])
{
GtkWidget *window;
GtkWidget *grid;
GtkWidget *entry;
gtk_init ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Drag And Drop");
gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
grid = gtk_grid_new ();
g_object_set (grid,
"margin", 20,
"row-spacing", 20,
"column-spacing", 20,
NULL);
gtk_container_add (GTK_CONTAINER (window), grid);
gtk_grid_attach (GTK_GRID (grid), make_image ("dialog-warning", TOP_LEFT), 0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), make_image ("process-stop", BOTTOM_RIGHT), 1, 0, 1, 1);
entry = gtk_entry_new ();
gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 2, 1);
gtk_grid_attach (GTK_GRID (grid), make_spinner (), 0, 2, 1, 1);
gtk_grid_attach (GTK_GRID (grid), make_image ("weather-clear", CENTER), 1, 2, 1, 1);
gtk_grid_attach (GTK_GRID (grid), make_image ("dialog-question", TOP_LEFT), 0, 3, 1, 1);
gtk_grid_attach (GTK_GRID (grid), make_image ("dialog-information", CENTER), 1, 3, 1, 1);
gtk_widget_show (window);
gtk_main ();
return 0;
}