gtk2/gtk/gtkdragsource.c
Matthias Clasen 303c9becf8 Add a GtkDragSource object
Add an explicit GtkDragSource object, and move the source-side
DND signals here. The object can be used on the spot, by calling
gtk_drag_source_drag_begin() yourself, or for static drag-source
configuration, by calling gtk_drag_source_attach(). In the latter
case, we set up and conect a drag gesture to trigger the drag_begin
call.
2020-01-08 18:48:19 -05:00

1141 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gtkdragsource.h"
#include "gtkdnd.h"
#include "gtkdndprivate.h"
#include "gtkgesturedrag.h"
#include "gtkgesturesingleprivate.h"
#include "gtkimagedefinitionprivate.h"
#include "gtknative.h"
#include "gtkwidgetprivate.h"
#include "gtkintl.h"
#include "gtkstylecontext.h"
#include "gtkimageprivate.h"
#include "gtkdragiconprivate.h"
#include "gtkprivate.h"
#include "gtkmarshalers.h"
typedef struct _GtkDragSourceSite GtkDragSourceSite;
struct _GtkDragSourceSite
{
GdkModifierType start_button_mask;
GdkContentFormats *target_list; /* Targets for drag data */
GdkDragAction actions; /* Possible actions */
GtkImageDefinition *image_def;
GtkGesture *drag_gesture;
};
static void
gtk_drag_source_gesture_begin (GtkGesture *gesture,
GdkEventSequence *sequence,
gpointer data)
{
GtkDragSourceSite *site = data;
guint button;
if (gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)))
button = 1;
else
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
g_assert (button >= 1);
if (!site->start_button_mask ||
!(site->start_button_mask & (GDK_BUTTON1_MASK << (button - 1))))
gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
}
static void
gtk_drag_source_gesture_update (GtkGesture *gesture,
GdkEventSequence *sequence,
gpointer data)
{
gdouble start_x, start_y, offset_x, offset_y;
GtkDragSourceSite *site = data;
GtkWidget *widget;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
if (gtk_gesture_is_recognized (site->drag_gesture))
{
gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (site->drag_gesture),
&start_x, &start_y);
gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (site->drag_gesture),
&offset_x, &offset_y);
if (gtk_drag_check_threshold (widget, start_x, start_y,
start_x + offset_x, start_y + offset_y))
{
GdkDevice *device = gtk_gesture_get_device (site->drag_gesture);
gtk_event_controller_reset (GTK_EVENT_CONTROLLER (site->drag_gesture));
gtk_drag_begin_internal (widget,
device,
site->image_def, site->target_list,
site->actions,
start_x, start_y);
}
}
}
static void
gtk_drag_source_site_destroy (gpointer data)
{
GtkDragSourceSite *site = data;
if (site->target_list)
gdk_content_formats_unref (site->target_list);
gtk_image_definition_unref (site->image_def);
/* This gets called only during widget finalization.
* And widget finalization takes care of gestures. */
g_slice_free (GtkDragSourceSite, site);
}
/**
* gtk_drag_source_set: (method)
* @widget: a #GtkWidget
* @start_button_mask: the bitmask of buttons that can start the drag
* @formats: (allow-none): the formats that the drag will support,
* may be %NULL
* @actions: the bitmask of possible actions for a drag from this widget
*
* Sets up a widget so that GTK+ will start a drag operation when the user
* clicks and drags on the widget. The widget must have a window.
*/
void
gtk_drag_source_set (GtkWidget *widget,
GdkModifierType start_button_mask,
GdkContentFormats *targets,
GdkDragAction actions)
{
GtkDragSourceSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
if (site)
{
if (site->target_list)
gdk_content_formats_unref (site->target_list);
}
else
{
site = g_slice_new0 (GtkDragSourceSite);
site->image_def = gtk_image_definition_new_empty ();
site->drag_gesture = gtk_gesture_drag_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (site->drag_gesture),
GTK_PHASE_CAPTURE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (site->drag_gesture), 0);
g_signal_connect (site->drag_gesture, "begin",
G_CALLBACK (gtk_drag_source_gesture_begin),
site);
g_signal_connect (site->drag_gesture, "update",
G_CALLBACK (gtk_drag_source_gesture_update),
site);
gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (site->drag_gesture));
g_object_set_data_full (G_OBJECT (widget),
I_("gtk-site-data"),
site, gtk_drag_source_site_destroy);
}
site->start_button_mask = start_button_mask;
if (targets)
site->target_list = gdk_content_formats_ref (targets);
else
site->target_list = NULL;
site->actions = actions;
}
/**
* gtk_drag_source_unset: (method)
* @widget: a #GtkWidget
*
* Undoes the effects of gtk_drag_source_set().
*/
void
gtk_drag_source_unset (GtkWidget *widget)
{
g_return_if_fail (GTK_IS_WIDGET (widget));
g_object_set_data (G_OBJECT (widget), I_("gtk-site-data"), NULL);
}
/**
* gtk_drag_source_get_target_list: (method)
* @widget: a #GtkWidget
*
* Gets the list of targets this widget can provide for
* drag-and-drop.
*
* Returns: (nullable) (transfer none): the #GdkContentFormats, or %NULL if none
*/
GdkContentFormats *
gtk_drag_source_get_target_list (GtkWidget *widget)
{
GtkDragSourceSite *site;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
return site ? site->target_list : NULL;
}
/**
* gtk_drag_source_set_target_list: (method)
* @widget: a #GtkWidget thats a drag source
* @target_list: (allow-none): list of draggable targets, or %NULL for none
*
* Changes the target types that this widget offers for drag-and-drop.
* The widget must first be made into a drag source with
* gtk_drag_source_set().
*/
void
gtk_drag_source_set_target_list (GtkWidget *widget,
GdkContentFormats *target_list)
{
GtkDragSourceSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
if (site == NULL)
{
g_warning ("gtk_drag_source_set_target_list() requires the widget "
"to already be a drag source.");
return;
}
if (target_list)
gdk_content_formats_ref (target_list);
if (site->target_list)
gdk_content_formats_unref (site->target_list);
site->target_list = target_list;
}
/**
* gtk_drag_source_add_text_targets: (method)
* @widget: a #GtkWidget thats is a drag source
*
* Add the text targets supported by #GtkSelectionData to
* the target list of the drag source. The targets
* are added with @info = 0. If you need another value,
* use gtk_content_formats_add_text_targets() and
* gtk_drag_source_set_target_list().
*/
void
gtk_drag_source_add_text_targets (GtkWidget *widget)
{
GdkContentFormats *target_list;
target_list = gtk_drag_source_get_target_list (widget);
if (target_list)
gdk_content_formats_ref (target_list);
else
target_list = gdk_content_formats_new (NULL, 0);
target_list = gtk_content_formats_add_text_targets (target_list);
gtk_drag_source_set_target_list (widget, target_list);
gdk_content_formats_unref (target_list);
}
/**
* gtk_drag_source_add_image_targets: (method)
* @widget: a #GtkWidget thats is a drag source
*
* Add the writable image targets supported by #GtkSelectionData to
* the target list of the drag source. The targets
* are added with @info = 0. If you need another value,
* use gtk_target_list_add_image_targets() and
* gtk_drag_source_set_target_list().
*/
void
gtk_drag_source_add_image_targets (GtkWidget *widget)
{
GdkContentFormats *target_list;
target_list = gtk_drag_source_get_target_list (widget);
if (target_list)
gdk_content_formats_ref (target_list);
else
target_list = gdk_content_formats_new (NULL, 0);
target_list = gtk_content_formats_add_image_targets (target_list, TRUE);
gtk_drag_source_set_target_list (widget, target_list);
gdk_content_formats_unref (target_list);
}
/**
* gtk_drag_source_add_uri_targets: (method)
* @widget: a #GtkWidget thats is a drag source
*
* Add the URI targets supported by #GtkSelectionData to
* the target list of the drag source. The targets
* are added with @info = 0. If you need another value,
* use gtk_content_formats_add_uri_targets() and
* gtk_drag_source_set_target_list().
*/
void
gtk_drag_source_add_uri_targets (GtkWidget *widget)
{
GdkContentFormats *target_list;
target_list = gtk_drag_source_get_target_list (widget);
if (target_list)
gdk_content_formats_ref (target_list);
else
target_list = gdk_content_formats_new (NULL, 0);
target_list = gtk_content_formats_add_uri_targets (target_list);
gtk_drag_source_set_target_list (widget, target_list);
gdk_content_formats_unref (target_list);
}
/**
* gtk_drag_source_set_icon_name: (method)
* @widget: a #GtkWidget
* @icon_name: name of icon to use
*
* Sets the icon that will be used for drags from a particular source
* to a themed icon. See the docs for #GtkIconTheme for more details.
*/
void
gtk_drag_source_set_icon_name (GtkWidget *widget,
const gchar *icon_name)
{
GtkDragSourceSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (icon_name != NULL);
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
g_return_if_fail (site != NULL);
gtk_image_definition_unref (site->image_def);
site->image_def = gtk_image_definition_new_icon_name (icon_name);
}
/**
* gtk_drag_source_set_icon_gicon: (method)
* @widget: a #GtkWidget
* @icon: A #GIcon
*
* Sets the icon that will be used for drags from a particular source
* to @icon. See the docs for #GtkIconTheme for more details.
*/
void
gtk_drag_source_set_icon_gicon (GtkWidget *widget,
GIcon *icon)
{
GtkDragSourceSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (icon != NULL);
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
g_return_if_fail (site != NULL);
gtk_image_definition_unref (site->image_def);
site->image_def = gtk_image_definition_new_gicon (icon);
}
/**
* gtk_drag_source_set_icon_paintable: (method)
* @widget: a #GtkWidget
* @paintable: A #GdkPaintable
*
* Sets the icon that will be used for drags from a particular source
* to @paintable.
*/
void
gtk_drag_source_set_icon_paintable (GtkWidget *widget,
GdkPaintable *paintable)
{
GtkDragSourceSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GDK_IS_PAINTABLE (paintable));
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
g_return_if_fail (site != NULL);
gtk_image_definition_unref (site->image_def);
site->image_def = gtk_image_definition_new_paintable (paintable);
}
/**
* SECTION:gtkdragsource
* @Short_description: An object to initiate DND operations
* @Title: GtkDragSource
*
* GtkDragSource is an auxiliary object that is used to initiate
* Drag-And-Drop operations. It can be set up with the necessary
* ingredients for a DND operation ahead of time. This includes
* the source for the data that is being transferred, in the form
* of a #GdkContentProvider, the desired action, and the icon to
* use during the drag operation.
*
* GtkDragSource can be used in two ways:
* - for static drag-source configuration
* - for one-off drag operations
*
* To configure a widget as a permanent source for DND operations,
* set up the GtkDragSource, then call gtk_drag_source_attach().
* This sets up a drag gesture on the widget that will trigger
* DND actions.
*
* To initiate a on-off drag operation, set up the GtkDragSource,
* then call gtk_drag_source_drag_begin(). GTK keeps a reference
* on the drag source until the DND operation is done, so you
* can unref the source after calling drag_being().
*
* During the DND operation, GtkDragSource emits signals that
* can be used to obtain updates about the status of the operation,
* but it is not normally necessary to connect to any signals,
* except for one case: when the supported actions include
* %GDK_DRAG_MOVE, you need to listen for the
* #GtkDragSource::drag-data-deleted signal and delete the
* drag data after it has been transferred.
*/
struct _GtkDragSource
{
GObject parent_instance;
GdkContentProvider *content;
GdkDragAction actions;
GtkWidget *icon_window;
GdkPaintable *paintable;
int hot_x;
int hot_y;
GtkGesture *gesture;
int start_button_mask;
GdkDrag *drag;
GtkWidget *widget;
};
struct _GtkDragSourceClass
{
GObjectClass parent_class;
};
enum {
PROP_CONTENT = 1,
PROP_ACTIONS,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
enum {
DRAG_BEGIN,
DRAG_END,
DRAG_FAILED,
DRAG_DATA_DELETE,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS];
G_DEFINE_TYPE (GtkDragSource, gtk_drag_source, G_TYPE_OBJECT);
static void
gtk_drag_source_init (GtkDragSource *source)
{
}
static void
gtk_drag_source_finalize (GObject *object)
{
GtkDragSource *source = GTK_DRAG_SOURCE (object);
gtk_drag_source_detach (source);
g_clear_object (&source->content);
g_clear_object (&source->paintable);
g_clear_object (&source->icon_window);
G_OBJECT_CLASS (gtk_drag_source_parent_class)->finalize (object);
}
static void
gtk_drag_source_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkDragSource *source = GTK_DRAG_SOURCE (object);
switch (prop_id)
{
case PROP_CONTENT:
gtk_drag_source_set_content (source, g_value_get_object (value));
break;
case PROP_ACTIONS:
gtk_drag_source_set_actions (source, g_value_get_flags (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gtk_drag_source_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkDragSource *source = GTK_DRAG_SOURCE (object);
switch (prop_id)
{
case PROP_CONTENT:
g_value_set_object (value, gtk_drag_source_get_content (source));
break;
case PROP_ACTIONS:
g_value_set_flags (value, gtk_drag_source_get_actions (source));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gtk_drag_source_class_init (GtkDragSourceClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = gtk_drag_source_finalize;
object_class->set_property = gtk_drag_source_set_property;
object_class->get_property = gtk_drag_source_get_property;
/**
* GtkDragSource:content:
*
* The data that is offered by drag operations from this source,
* in the form of a #GdkContentProvider.
*/
properties[PROP_CONTENT] =
g_param_spec_object ("content",
P_("Content"),
P_("The content provider for the dragged data"),
GDK_TYPE_CONTENT_PROVIDER,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDragSource:actions:
*
* The actions that are supported by drag operations from the source.
*
* Note that you must handle the #GtkDragSource::drag-data-deleted signal
* if the actions include %GDK_ACTION_MOVE.
*/
properties[PROP_ACTIONS] =
g_param_spec_flags ("actions",
P_("Actions"),
P_("Supported actions"),
GDK_TYPE_DRAG_ACTION, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
/**
* GtkDragSource::drag-begin:
* @source: the #GtkDragSource
*
* The ::drag-begin signal is emitted on the drag source when a drag
* is started. It can be used to e.g. set a custom drag icon with
* gtk_drag_source_set_icon(). But all of the setup can also be
* done before calling gtk_drag_source_drag_begin(), so this is not
* really necessary.
*/
signals[DRAG_BEGIN] =
g_signal_new (I_("drag-begin"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
/**
* GtkDragSource::drag-end:
* @source: the #GtkDragSource
*
* The ::drag-end signal is emitted on the drag source when a drag is
* finished. A typical reason to connect to this signal is to undo
* things done in #GtkDragSource::drag-begin.
*/
signals[DRAG_END] =
g_signal_new (I_("drag-end"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
/**
* GtkDragSource::drag-failed:
* @source: the #GtkDragSource
* @reason: information on why the drag failed
*
* The ::drag-failed signal is emitted on the drag source when a drag has
* failed. The signal handler may handle a failed drag operation based on
* the type of error. It should return %TRUE if the failure has been handled
* and the default "drag operation failed" should not be shown.
*
* Returns: %TRUE if the failed drag operation has been already handled
*/
signals[DRAG_FAILED] =
g_signal_new (I_("drag-failed"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
_gtk_boolean_handled_accumulator, NULL,
_gtk_marshal_BOOLEAN__ENUM,
G_TYPE_BOOLEAN, 1,
GDK_TYPE_DRAG_CANCEL_REASON);
/**
* GtkDragSource::drag-data-delete:
* @source: the #GtkDragSource
*
* The ::drag-data-delete signal is emitted on the drag source when a drag
* with the action %GDK_ACTION_MOVE is successfully completed. The signal
* handler is responsible for deleting the data that has been dropped. What
* "delete" means depends on the context of the drag operation.
*/
signals[DRAG_DATA_DELETE] =
g_signal_new (I_("drag-data-delete"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
static void gtk_drag_source_dnd_finished_cb (GdkDrag *drag,
GtkDragSource *source);
static void gtk_drag_source_drop_performed_cb (GdkDrag *drag,
GtkDragSource *source);
static void gtk_drag_source_cancel_cb (GdkDrag *drag,
GdkDragCancelReason reason,
GtkDragSource *source);
static void
drag_end (GtkDragSource *source)
{
g_signal_handlers_disconnect_by_func (source->drag, gtk_drag_source_drop_performed_cb, source);
g_signal_handlers_disconnect_by_func (source->drag, gtk_drag_source_dnd_finished_cb, source);
g_signal_handlers_disconnect_by_func (source->drag, gtk_drag_source_cancel_cb, source);
g_signal_emit (source, signals[DRAG_END], 0);
g_object_set_data (G_OBJECT (source->drag), I_("gtk-drag-source"), NULL);
g_clear_object (&source->drag);
source->widget = NULL;
g_object_unref (source);
}
static void
gtk_drag_source_dnd_finished_cb (GdkDrag *drag,
GtkDragSource *source)
{
if (gdk_drag_get_selected_action (drag) == GDK_ACTION_MOVE)
g_signal_emit (source, signals[DRAG_DATA_DELETE], 0);
drag_end (source);
}
static void
gtk_drag_source_cancel_cb (GdkDrag *drag,
GdkDragCancelReason reason,
GtkDragSource *source)
{
gboolean success = FALSE;
g_signal_emit (source, signals[DRAG_FAILED], 0, reason, &success);
drag_end (source);
}
static void
gtk_drag_source_drop_performed_cb (GdkDrag *drag,
GtkDragSource *source)
{
if (source->icon_window)
gtk_widget_hide (source->icon_window);
}
/**
* gtk_drag_source_drag_begin:
* @source: a #GtkDragSource
* @widget: the widget where the drag operation originates
* @device: the device that is driving the drag operation
* @x: start point X coordinate
* @y: start point Y xoordinate
*
* Starts a DND operation with @source.
*
* The start point coordinates are relative to @widget.
*
* GTK keeps a reference on @source for the duration of
* the DND operation, so it is safe to unref @source
* after this call.
*/
void
gtk_drag_source_drag_begin (GtkDragSource *source,
GtkWidget *widget,
GdkDevice *device,
int x,
int y)
{
GtkNative *native;
GdkSurface *surface;
double px, py;
int dx, dy;
GtkWidget *icon;
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GDK_IS_DEVICE (device));
g_return_if_fail (source->content != NULL);
g_return_if_fail (source->actions != 0);
if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
device = gdk_device_get_associated_device (device);
native = gtk_widget_get_native (widget);
surface = gtk_native_get_surface (native);
gtk_widget_translate_coordinates (widget, GTK_WIDGET (native), x, y, &x, &y);
gdk_surface_get_device_position (surface, device, &px, &py, NULL);
dx = round (px) - x;
dy = round (py) - y;
source->drag = gdk_drag_begin (surface, device, source->content, source->actions, dx, dy);
if (source->drag == NULL)
{
g_print ("no drag :(\n");
return;
}
g_object_set_data (G_OBJECT (source->drag), I_("gtk-drag-source"), source);
source->widget = widget;
gtk_widget_reset_controllers (widget);
/* We hold a ref until ::drag-end is emitted */
g_object_ref (source);
g_signal_emit (source, signals[DRAG_BEGIN], 0);
if (!source->paintable)
{
GtkIconTheme *theme;
theme = gtk_icon_theme_get_for_display (gtk_widget_get_display (widget));
source->paintable = gtk_icon_theme_load_icon (theme, "text-x-generic", 32, 0, NULL);
source->hot_x = 0;
source->hot_y = 0;
}
gdk_drag_set_hotspot (source->drag, source->hot_x, source->hot_y);
source->icon_window = gtk_drag_icon_new ();
g_object_ref_sink (source->icon_window);
gtk_drag_icon_set_surface (GTK_DRAG_ICON (source->icon_window),
gdk_drag_get_drag_surface (source->drag));
icon = gtk_picture_new_for_paintable (source->paintable);
gtk_picture_set_can_shrink (GTK_PICTURE (icon), FALSE);
gtk_drag_icon_set_widget (GTK_DRAG_ICON (source->icon_window), icon);
gtk_widget_show (source->icon_window);
g_signal_connect (source->drag, "drop-performed",
G_CALLBACK (gtk_drag_source_drop_performed_cb), source);
g_signal_connect (source->drag, "dnd-finished",
G_CALLBACK (gtk_drag_source_dnd_finished_cb), source);
g_signal_connect (source->drag, "cancel",
G_CALLBACK (gtk_drag_source_cancel_cb), source);
}
/**
* gtk_drag_source_new:
* @content: (nullable): the #GdkContentProvider to use, or %NULL
* @actions: the actions to offer
*
* Creates a new #GtkDragSource object.
*
* Returns: the new #GtkDragSource
*/
GtkDragSource *
gtk_drag_source_new (GdkContentProvider *content,
GdkDragAction actions)
{
return g_object_new (GTK_TYPE_DRAG_SOURCE,
"content", content,
"actions", actions,
NULL);
}
/**
* gtk_drag_source_get_content:
* @source: a #GtkDragSource
*
* Gets the current content provider of a #GtkDragSource.
*
* Returns: the #GtkContentProvider of @source
*/
GdkContentProvider *
gtk_drag_source_get_content (GtkDragSource *source)
{
g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), NULL);
return source->content;
}
/**
* gtk_drag_source_set_content:
* @source: a #GtkDragSource
* @content: (nullable): a #GtkContntProvider, or %NULL
*
* Sets a content provider on a #GtkDragSource.
*
* When the data is requested in the cause of a
* DND operation, it will be obtained from the
* content provider.
*/
void
gtk_drag_source_set_content (GtkDragSource *source,
GdkContentProvider *content)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
if (!g_set_object (&source->content, content))
return;
g_object_notify_by_pspec (G_OBJECT (source), properties[PROP_CONTENT]);
}
/**
* gtk_drag_source_get_actions:
* @source: a #GtkDragSource
*
* Gets the actions that are currently set on the #GtkDragSource.
*
* Returns: the actions set on @source
*/
GdkDragAction
gtk_drag_source_get_actions (GtkDragSource *source)
{
g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), 0);
return source->actions;
}
/**
* gtk_drag_source_set_actions:
* @source: a #GtkDragSource
* @actions: the actions to offer
*
* Sets the actions on the #GtkDragSource.
*
* During a DND operation, the actions are offered
* to potential drop targets.
*/
void
gtk_drag_source_set_actions (GtkDragSource *source,
GdkDragAction actions)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
if (source->actions == actions)
return;
source->actions = actions;
g_object_notify_by_pspec (G_OBJECT (source), properties[PROP_ACTIONS]);
}
/**
* gtk_drag_source_set_icon:
* @source: a #GtkDragSource
* @paintable: (nullable): the #GtkPaintable to use as icon, or %NULL
* @hot_x: the hotspot X coordinate on the icon
* @hot_y: the hotspot Y coordinate on the icon
*
* Sets a paintable to use as icon during DND operations.
*
* The hotspot coordinates determine the point on the icon
* that gets aligned with the hotspot of the cursor.
*
* If @paintable is %NULL, a default icon is used.
*/
void
gtk_drag_source_set_icon (GtkDragSource *source,
GdkPaintable *paintable,
int hot_x,
int hot_y)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
g_set_object (&source->paintable, paintable);
source->hot_x = hot_x;
source->hot_y = hot_y;
}
static void
source_gesture_begin (GtkGesture *gesture,
GdkEventSequence *sequence,
GtkDragSource *source)
{
guint button;
if (gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)))
button = 1;
else
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
g_assert (button >= 1);
if ((source->start_button_mask & (GDK_BUTTON1_MASK << (button - 1))) == 0)
gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
}
static void
source_gesture_update (GtkGesture *gesture,
GdkEventSequence *sequence,
GtkDragSource *source)
{
gdouble start_x, start_y, offset_x, offset_y;
GtkWidget *widget;
if (!gtk_gesture_is_recognized (gesture))
return;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture), &start_x, &start_y);
gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (gesture), &offset_x, &offset_y);
if (gtk_drag_check_threshold (widget,
start_x, start_y,
start_x + offset_x, start_y + offset_y))
{
GdkDevice *device = gtk_gesture_get_device (gesture);
gtk_drag_source_drag_begin (source, widget, device, start_x, start_y);
}
}
static void
gesture_removed (gpointer data)
{
GtkDragSource *source = data;
/* if we get here, the widget we are attached to was destroyed,
* and removed our gesture.
*
* Clean up, and drop the ref we held for being attached.
*/
source->gesture = NULL;
g_object_unref (source);
}
/**
* gtk_drag_source_attach:
* @source: (transfer full): a #GtkDragSource
* @widget: the widget to attach @source to
* @start_button_mask: mask determining which mouse buttons trigger
*
* Attaches the @source to a @widget by creating a drag gesture
* on @widget that will trigger DND operations with @source.
*
* The @start_button_mask determines which mouse buttons trigger
* a DND operation.
*
* To undo the effect of this call, use gtk_drag_source_detach().
*/
void
gtk_drag_source_attach (GtkDragSource *source,
GtkWidget *widget,
GdkModifierType start_button_mask)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (source->gesture == NULL);
g_return_if_fail (start_button_mask != 0);
g_return_if_fail ((start_button_mask & ~(GDK_BUTTON1_MASK |
GDK_BUTTON2_MASK |
GDK_BUTTON3_MASK |
GDK_BUTTON4_MASK |
GDK_BUTTON5_MASK)) == 0);
source->gesture = gtk_gesture_drag_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (source->gesture),
GTK_PHASE_CAPTURE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (source->gesture), 0);
g_signal_connect (source->gesture, "begin",
G_CALLBACK (source_gesture_begin), source);
g_signal_connect (source->gesture, "update",
G_CALLBACK (source_gesture_update), source);
gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (source->gesture));
source->start_button_mask = start_button_mask;
g_object_set_data_full (G_OBJECT (source->gesture), "gtk-drag-source",
source, gesture_removed);
}
/**
* gtk_drag_source_detach:
* @source: a #GtkDragSource
*
* Undoes the effect of a prior gtk_drag_source_attach() call.
*/
void
gtk_drag_source_detach (GtkDragSource *source)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
if (source->gesture)
{
GtkWidget *widget;
g_object_ref (source);
g_object_set_data (G_OBJECT (source->gesture), "gtk-drag-source", NULL);
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source->gesture));
gtk_widget_remove_controller (widget, GTK_EVENT_CONTROLLER (source->gesture));
g_object_unref (source);
}
}
/**
* gtk_drag_get_source:
* @drag: a #GdkDrag
*
* Obtains the #GtkDragSource from which a #GdkDrag originates.
*
* This function should rarely be needed. Once case where it can
* be used is together with gtk_drop_get_drag(), to determine
* whether a 'local' drag is coming from the same widget.
*
* Returns: (transfer none) (nullable): a #GtkDragSource, or %NULL
*/
GtkDragSource *
gtk_drag_get_source (GdkDrag *drag)
{
gpointer data;
g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
data = g_object_get_data (G_OBJECT (drag), I_("gtk-drag-source"));
if (data)
return GTK_DRAG_SOURCE (data);
return NULL;
}
/**
* gtk_drag_source_get_origin:
* @source: a #GtkDragSource
*
* Returns the widget that an ongoing drag is started from.
*
* Returns: (nullable): the origin of the current drag operation, or %NULL
*/
GtkWidget *
gtk_drag_source_get_origin (GtkDragSource *source)
{
g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), NULL);
return source->widget;
}
/**
* gtk_drag_source_get_drag:
* @source: a #GtkDragSource
*
* Returns the underlying #GtkDrag object for an ongoing drag.
*
* Returns: (nullable): the #GdkDrag of the current drag operation, or %NULL
*/
GdkDrag *
gtk_drag_source_get_drag (GtkDragSource *source)
{
g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), NULL);
return source->drag;
}
/**
* gtk_drag_source_drag_cancel:
* @source: a #GtkDragSource
*
* Cancels a currently ongoing drag operation.
*/
void
gtk_drag_source_drag_cancel (GtkDragSource *source)
{
g_return_if_fail (GTK_IS_DRAG_SOURCE (source));
if (source->drag)
{
gboolean success = FALSE;
g_signal_emit (source, signals[DRAG_FAILED], 0, GDK_DRAG_CANCEL_ERROR, &success);
gdk_drag_drop_done (source->drag, success);
}
}