summaryrefslogtreecommitdiff
path: root/gtk/gtkdroptargetasync.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/gtkdroptargetasync.c')
-rw-r--r--gtk/gtkdroptargetasync.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/gtk/gtkdroptargetasync.c b/gtk/gtkdroptargetasync.c
new file mode 100644
index 0000000000..0c2061927a
--- /dev/null
+++ b/gtk/gtkdroptargetasync.c
@@ -0,0 +1,673 @@
+/* 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 "gtkdroptargetasync.h"
+
+#include "gtkdropprivate.h"
+#include "gtkeventcontrollerprivate.h"
+#include "gtkintl.h"
+#include "gtkmarshalers.h"
+#include "gtknative.h"
+#include "gtktypebuiltins.h"
+
+
+/**
+ * SECTION:gtkdroptargetasync
+ * @Short_description: Event controller to receive DND drops
+ * @Title: GtkDropTargetAsync
+ * @See_also: #GtkDropTarget
+ *
+ * GtkDropTargetAsync is an auxiliary object that can be used to receive
+ * Drag-and-Drop operations.
+ * It is the more complete but also more complex method of handling drop
+ * operations compared to #GtkDropTarget and you should only use it if
+ * #GtkDropTarget doesn't provide all the features you need.
+ *
+ * To use a #GtkDropTargetAsync to receive drops on a widget, you create
+ * a #GtkDropTargetAsync object, configure which data formats and actions
+ * you support, connect to its signals, and then attach
+ * it to the widget with gtk_widget_add_controller().
+ *
+ * During a drag operation, the first signal that a GtkDropTargetAsync
+ * emits is #GtkDropTargetAsync::accept, which is meant to determine
+ * whether the target is a possible drop site for the ongoing drop.
+ * The default handler for the ::accept signal accepts the drop
+ * if it finds a compatible data format and an action that is supported
+ * on both sides.
+ *
+ * If it is, and the widget becomes a target, you will receive a
+ * #GtkDropTargetAsync::drag-enter signal, followed by
+ * #GtkDropTargetAsync::drag-motion signals as the pointer moves,
+ * optionally a #GtkDropTargetAsync::drop signal when a drop happens,
+ * and finally a #GtkDropTargetAsync::drag-leave signal when the pointer
+ * moves off the widget.
+ *
+ * The ::drag-enter and ::drag-motion handler return a #GdkDragAction
+ * to update the status of the ongoing operation. The ::drop handler
+ * should decide if it ultimately accepts the drop and if it does, it
+ * should initiate the data transfer and finish the operation by calling
+ * gdk_drop_finish().
+ *
+ * Between the ::drag-enter and ::drag-leave signals the widget is a
+ * current drop target, and will receive the %GTK_STATE_FLAG_DROP_ACTIVE
+ * state, which can be used by themes to style the widget as a drop target.
+ */
+
+struct _GtkDropTargetAsync
+{
+ GtkEventController parent_object;
+
+ GdkContentFormats *formats;
+ GdkDragAction actions;
+
+ GdkDrop *drop;
+ gboolean rejected;
+};
+
+struct _GtkDropTargetAsyncClass
+{
+ GtkEventControllerClass parent_class;
+
+ gboolean (* accept) (GtkDropTargetAsync *self,
+ GdkDrop *drop);
+ GdkDragAction (* drag_enter) (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y);
+ GdkDragAction (* drag_motion) (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y);
+ void (* drag_leave) (GtkDropTargetAsync *self,
+ GdkDrop *drop);
+ gboolean (* drop) (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y);
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIONS,
+ PROP_FORMATS,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+enum {
+ ACCEPT,
+ DRAG_ENTER,
+ DRAG_MOTION,
+ DRAG_LEAVE,
+ DROP,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+G_DEFINE_TYPE (GtkDropTargetAsync, gtk_drop_target_async, GTK_TYPE_EVENT_CONTROLLER);
+
+static gboolean
+gtk_drop_target_async_accept (GtkDropTargetAsync *self,
+ GdkDrop *drop)
+{
+ if ((gdk_drop_get_actions (drop) & self->actions) == 0)
+ return FALSE;
+
+ if (self->formats == NULL)
+ return TRUE;
+
+ return gdk_content_formats_match (self->formats, gdk_drop_get_formats (drop));
+}
+
+static GdkDragAction
+make_action_unique (GdkDragAction actions)
+{
+ if (actions & GDK_ACTION_COPY)
+ return GDK_ACTION_COPY;
+
+ if (actions & GDK_ACTION_MOVE)
+ return GDK_ACTION_MOVE;
+
+ if (actions & GDK_ACTION_LINK)
+ return GDK_ACTION_LINK;
+
+ return 0;
+}
+
+static GdkDragAction
+gtk_drop_target_async_drag_enter (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y)
+{
+ return make_action_unique (self->actions & gdk_drop_get_actions (drop));
+}
+
+static GdkDragAction
+gtk_drop_target_async_drag_motion (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y)
+{
+ return make_action_unique (self->actions & gdk_drop_get_actions (drop));
+}
+
+static gboolean
+gtk_drop_target_async_drop (GtkDropTargetAsync *self,
+ GdkDrop *drop,
+ double x,
+ double y)
+{
+ return FALSE;
+}
+
+static gboolean
+gtk_drop_target_async_filter_event (GtkEventController *controller,
+ GdkEvent *event)
+{
+ switch ((int)gdk_event_get_event_type (event))
+ {
+ case GDK_DRAG_ENTER:
+ case GDK_DRAG_LEAVE:
+ case GDK_DRAG_MOTION:
+ case GDK_DROP_START:
+ return GTK_EVENT_CONTROLLER_CLASS (gtk_drop_target_async_parent_class)->filter_event (controller, event);
+
+ default:;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gtk_drop_target_async_handle_event (GtkEventController *controller,
+ GdkEvent *event,
+ double x,
+ double y)
+{
+ GtkDropTargetAsync *self = GTK_DROP_TARGET_ASYNC (controller);
+ GdkDrop *drop;
+
+ switch ((int) gdk_event_get_event_type (event))
+ {
+ case GDK_DRAG_MOTION:
+ {
+ GtkWidget *widget = gtk_event_controller_get_widget (controller);
+ GdkDragAction preferred_action;
+
+ drop = gdk_drag_event_get_drop (event);
+ /* sanity check */
+ g_return_val_if_fail (self->drop == drop, FALSE);
+ if (self->rejected)
+ return FALSE;
+
+ g_signal_emit (self, signals[DRAG_MOTION], 0, drop, x, y, &preferred_action);
+ if (preferred_action &&
+ gtk_drop_status (self->drop, self->actions, preferred_action))
+ {
+ gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
+ }
+ else
+ {
+ gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
+ }
+ }
+ return FALSE;
+
+ case GDK_DROP_START:
+ {
+ gboolean handled;
+
+ drop = gdk_drag_event_get_drop (event);
+ /* sanity check */
+ g_return_val_if_fail (self->drop == drop, FALSE);
+ if (self->rejected)
+ return FALSE;
+
+ g_signal_emit (self, signals[DROP], 0, self->drop, x, y, &handled);
+ return handled;
+ }
+
+ default:
+ return FALSE;
+ }
+}
+
+static void
+gtk_drop_target_async_handle_crossing (GtkEventController *controller,
+ const GtkCrossingData *crossing,
+ double x,
+ double y)
+{
+ GtkDropTargetAsync *self = GTK_DROP_TARGET_ASYNC (controller);
+ GtkWidget *widget = gtk_event_controller_get_widget (controller);
+
+ if (crossing->type != GTK_CROSSING_DROP)
+ return;
+
+ /* sanity check */
+ g_warn_if_fail (self->drop == NULL || self->drop == crossing->drop);
+
+ if (crossing->direction == GTK_CROSSING_IN)
+ {
+ gboolean accept = FALSE;
+ GdkDragAction preferred_action;
+
+ if (self->drop != NULL)
+ return;
+
+ self->drop = g_object_ref (crossing->drop);
+
+ g_signal_emit (self, signals[ACCEPT], 0, self->drop, &accept);
+ self->rejected = !accept;
+ if (self->rejected)
+ return;
+
+ g_signal_emit (self, signals[DRAG_ENTER], 0, self->drop, x, y, &preferred_action);
+ if (preferred_action &&
+ gtk_drop_status (self->drop, self->actions, preferred_action))
+ {
+ gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
+ }
+ }
+ else
+ {
+ if (crossing->new_descendent != NULL ||
+ crossing->new_target == widget)
+ return;
+
+ g_signal_emit (self, signals[DRAG_LEAVE], 0, self->drop);
+ g_clear_object (&self->drop);
+ gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
+ }
+}
+
+static void
+gtk_drop_target_async_finalize (GObject *object)
+{
+ GtkDropTargetAsync *self = GTK_DROP_TARGET_ASYNC (object);
+
+ g_clear_pointer (&self->formats, gdk_content_formats_unref);
+
+ G_OBJECT_CLASS (gtk_drop_target_async_parent_class)->finalize (object);
+}
+
+static void
+gtk_drop_target_async_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkDropTargetAsync *self = GTK_DROP_TARGET_ASYNC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIONS:
+ gtk_drop_target_async_set_actions (self, g_value_get_flags (value));
+ break;
+
+ case PROP_FORMATS:
+ gtk_drop_target_async_set_formats (self, g_value_get_boxed (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_drop_target_async_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkDropTargetAsync *self = GTK_DROP_TARGET_ASYNC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIONS:
+ g_value_set_flags (value, gtk_drop_target_async_get_actions (self));
+ break;
+
+ case PROP_FORMATS:
+ g_value_set_boxed (value, gtk_drop_target_async_get_formats (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_drop_target_async_class_init (GtkDropTargetAsyncClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (class);
+
+ object_class->finalize = gtk_drop_target_async_finalize;
+ object_class->set_property = gtk_drop_target_async_set_property;
+ object_class->get_property = gtk_drop_target_async_get_property;
+
+ controller_class->handle_event = gtk_drop_target_async_handle_event;
+ controller_class->filter_event = gtk_drop_target_async_filter_event;
+ controller_class->handle_crossing = gtk_drop_target_async_handle_crossing;
+
+ class->accept = gtk_drop_target_async_accept;
+ class->drag_enter = gtk_drop_target_async_drag_enter;
+ class->drag_motion = gtk_drop_target_async_drag_motion;
+ class->drop = gtk_drop_target_async_drop;
+
+ /**
+ * GtkDropTargetAsync:actions:
+ *
+ * The #GdkDragActions that this drop target supports
+ */
+ properties[PROP_ACTIONS] =
+ g_param_spec_flags ("actions", P_("Actions"), P_("Actions"),
+ GDK_TYPE_DRAG_ACTION, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDropTargetAsync:formats:
+ *
+ * The #GdkContentFormats that determines the supported data formats
+ */
+ properties[PROP_FORMATS] =
+ g_param_spec_boxed ("formats", P_("Formats"), P_("Formats"),
+ GDK_TYPE_CONTENT_FORMATS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+
+ /**
+ * GtkWidget::accept:
+ * @self: the #GtkDropTargetAsync
+ * @drop: the #GdkDrop
+ *
+ * The ::accept signal is emitted on the drop site when a drop operation
+ * is about to begin.
+ * If the drop is not accepted, %FALSE will be returned and the drop target
+ * will ignore the drop. If %TRUE is returned, the drop is accepted for now
+ * but may be rejected later via a call to gtk_drop_target_reject() or
+ * ultimately by returning %FALSE from GtkDropTarget::drop
+ *
+ * The default handler for this signal decides whether to accept the drop
+ * based on the formats provided by the @drop.
+ *
+ * If the decision whether the drop will be accepted or rejected needs
+ * further procesing, such as inspecting the data, this function should
+ * return %TRUE and proceed as is @drop was accepted and if it decides to
+ * reject the drop later, it should call gtk_drop_target_reject_drop().
+ *
+ * Returns: %TRUE if @drop is accepted
+ */
+ signals[ACCEPT] =
+ g_signal_new (I_("accept"),
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkDropTargetAsyncClass, accept),
+ g_signal_accumulator_first_wins, NULL,
+ NULL,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_DROP);
+
+ /**
+ * GtkDropTargetAsync::drag-enter:
+ * @self: the #GtkDropTargetAsync
+ * @drop: the #GdkDrop
+ * @x: the x coordinate of the current pointer position
+ * @y: the y coordinate of the current pointer position
+ *
+ * The ::drag-enter signal is emitted on the drop site when the pointer
+ * enters the widget. It can be used to set up custom highlighting.
+ *
+ * Returns: Preferred action for this drag operation.
+ */
+ signals[DRAG_ENTER] =
+ g_signal_new (I_("drag-enter"),
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkDropTargetAsyncClass, drag_enter),
+ g_signal_accumulator_first_wins, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 3,
+ GDK_TYPE_DROP, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+ /**
+ * GtkDropTargetAsync::drag-motion:
+ * @self: the #GtkDropTargetAsync
+ * @drop: the #GdkDrop
+ * @x: the x coordinate of the current pointer position
+ * @y: the y coordinate of the current pointer position
+ *
+ * The ::drag-motion signal is emitted while the pointer is moving
+ * over the drop target.
+ *
+ * Returns: Preferred action for this drag operation.
+ */
+ signals[DRAG_MOTION] =
+ g_signal_new (I_("drag-motion"),
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkDropTargetAsyncClass, drag_motion),
+ g_signal_accumulator_first_wins, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 3,
+ GDK_TYPE_DROP, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+ /**
+ * GtkDropTargetAsync::drag-leave:
+ * @self: the #GtkDropTargetAsync
+ * @drop: the #GdkDrop
+ *
+ * The ::drag-leave signal is emitted on the drop site when the pointer
+ * leaves the widget. Its main purpose it to undo things done in
+ * #GtkDropTargetAsync::drag-enter.
+ */
+ signals[DRAG_LEAVE] =
+ g_signal_new (I_("drag-leave"),
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkDropTargetAsyncClass, drag_leave),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ GDK_TYPE_DROP);
+
+ /**
+ * GtkDropTargetAsync::drop:
+ * @self: the #GtkDropTargetAsync
+ * @drop: the #GdkDrop
+ * @x: the x coordinate of the current pointer position
+ * @y: the y coordinate of the current pointer position
+ *
+ * The ::drop signal is emitted on the drop site when the user drops
+ * the data onto the widget. The signal handler must determine whether
+ * the pointer position is in a drop zone or not. If it is not in a drop
+ * zone, it returns %FALSE and no further processing is necessary.
+ *
+ * Otherwise, the handler returns %TRUE. In this case, this handler will
+ * accept the drop. The handler must ensure that gdk_drop_finish() is
+ * called to let the source know that the drop is done. The call to
+ * gtk_drag_finish() must only be done when all data has been received.
+ *
+ * To receive the data, use one of the read functions provides by #GdkDrop
+ * such as gdk_drop_read_async() or gdk_drop_read_value_async().
+ *
+ * Returns: whether the drop is accepted at the given pointer position
+ */
+ signals[DROP] =
+ g_signal_new (I_("drop"),
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_first_wins, NULL,
+ NULL,
+ G_TYPE_BOOLEAN, 3,
+ GDK_TYPE_DROP, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+}
+
+static void
+gtk_drop_target_async_init (GtkDropTargetAsync *self)
+{
+}
+
+/**
+ * gtk_drop_target_async_new:
+ * @formats: (nullable) (transfer full): the supported data formats
+ * @actions: the supported actions
+ *
+ * Creates a new #GtkDropTargetAsync object.
+ *
+ * Returns: the new #GtkDropTargetAsync
+ */
+GtkDropTargetAsync *
+gtk_drop_target_async_new (GdkContentFormats *formats,
+ GdkDragAction actions)
+{
+ GtkDropTargetAsync *result;
+
+ result = g_object_new (GTK_TYPE_DROP_TARGET_ASYNC,
+ "formats", formats,
+ "actions", actions,
+ NULL);
+
+ g_clear_pointer (&formats, gdk_content_formats_unref);
+
+ return result;
+}
+
+/**
+ * gtk_drop_target_async_set_formats:
+ * @self: a #GtkDropTargetAsync
+ * @formats: (nullable): the supported data formats or %NULL for
+ * any format.
+ *
+ * Sets the data formats that this drop target will accept.
+ */
+void
+gtk_drop_target_async_set_formats (GtkDropTargetAsync *self,
+ GdkContentFormats *formats)
+{
+ g_return_if_fail (GTK_IS_DROP_TARGET_ASYNC (self));
+
+ if (self->formats == formats)
+ return;
+
+ if (self->formats)
+ gdk_content_formats_unref (self->formats);
+
+ self->formats = formats;
+
+ if (self->formats)
+ gdk_content_formats_ref (self->formats);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FORMATS]);
+}
+
+/**
+ * gtk_drop_target_async_get_formats:
+ * @self: a #GtkDropTargetAsync
+ *
+ * Gets the data formats that this drop target accepts.
+ *
+ * If the result is %NULL, all formats are expected to be supported.
+ *
+ * Returns: (nullable): the supported data formats
+ */
+GdkContentFormats *
+gtk_drop_target_async_get_formats (GtkDropTargetAsync *self)
+{
+ g_return_val_if_fail (GTK_IS_DROP_TARGET_ASYNC (self), NULL);
+
+ return self->formats;
+}
+
+/**
+ * gtk_drop_target_async_set_actions:
+ * @self: a #GtkDropTargetAsync
+ * @actions: the supported actions
+ *
+ * Sets the actions that this drop target supports.
+ */
+void
+gtk_drop_target_async_set_actions (GtkDropTargetAsync *self,
+ GdkDragAction actions)
+{
+ g_return_if_fail (GTK_IS_DROP_TARGET_ASYNC (self));
+
+ if (self->actions == actions)
+ return;
+
+ self->actions = actions;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]);
+}
+
+/**
+ * gtk_drop_target_async_get_actions:
+ * @self: a #GtkDropTargetAsync
+ *
+ * Gets the actions that this drop target supports.
+ *
+ * Returns: the actions that this drop target supports
+ */
+GdkDragAction
+gtk_drop_target_async_get_actions (GtkDropTargetAsync *self)
+{
+ g_return_val_if_fail (GTK_IS_DROP_TARGET_ASYNC (self), 0);
+
+ return self->actions;
+}
+
+/**
+ * gtk_drop_target_async_reject_drop:
+ * @self: a #GtkDropTargetAsync
+ * @drop: the #GdkDrop of an ongoing drag operation
+ *
+ * Sets the @drop as not accepted on this drag site.
+ *
+ * This function should be used when delaying the decision
+ * on whether to accept a drag or not until after reading
+ * the data.
+ */
+void
+gtk_drop_target_async_reject_drop (GtkDropTargetAsync *self,
+ GdkDrop *drop)
+{
+ g_return_if_fail (GTK_IS_DROP_TARGET_ASYNC (self));
+ g_return_if_fail (GDK_IS_DROP (drop));
+ g_return_if_fail (self->drop == drop);
+
+ if (self->rejected)
+ return;
+
+ self->rejected = TRUE;
+ gtk_widget_unset_state_flags (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)),
+ GTK_STATE_FLAG_DROP_ACTIVE);
+}