summaryrefslogtreecommitdiff
path: root/gdk/gdkdrag.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdk/gdkdrag.c')
-rw-r--r--gdk/gdkdrag.c767
1 files changed, 767 insertions, 0 deletions
diff --git a/gdk/gdkdrag.c b/gdk/gdkdrag.c
new file mode 100644
index 0000000000..2e8efb8e71
--- /dev/null
+++ b/gdk/gdkdrag.c
@@ -0,0 +1,767 @@
+/* GDK - The GIMP Drawing Kit
+ * 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 "gdkdragprivate.h"
+#include "gdkdisplay.h"
+#include "gdksurface.h"
+#include "gdkintl.h"
+#include "gdkcontentformats.h"
+#include "gdkcontentprovider.h"
+#include "gdkcontentserializer.h"
+#include "gdkcursor.h"
+#include "gdkenumtypes.h"
+#include "gdkeventsprivate.h"
+
+typedef struct _GdkDragPrivate GdkDragPrivate;
+
+struct _GdkDragPrivate
+{
+ GdkDisplay *display;
+ GdkDevice *device;
+ GdkContentFormats *formats;
+ GdkDragAction actions;
+ GdkDragAction suggested_action;
+};
+
+static struct {
+ GdkDragAction action;
+ const gchar *name;
+ GdkCursor *cursor;
+} drag_cursors[] = {
+ { GDK_ACTION_ASK, "dnd-ask", NULL },
+ { GDK_ACTION_COPY, "dnd-copy", NULL },
+ { GDK_ACTION_MOVE, "dnd-move", NULL },
+ { GDK_ACTION_LINK, "dnd-link", NULL },
+ { 0, "dnd-none", NULL },
+};
+
+enum {
+ PROP_0,
+ PROP_CONTENT,
+ PROP_DEVICE,
+ PROP_DISPLAY,
+ PROP_FORMATS,
+ N_PROPERTIES
+};
+
+enum {
+ CANCEL,
+ DROP_PERFORMED,
+ DND_FINISHED,
+ ACTION_CHANGED,
+ N_SIGNALS
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+static guint signals[N_SIGNALS] = { 0 };
+static GList *drags = NULL;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrag, gdk_drag, G_TYPE_OBJECT)
+
+/**
+ * SECTION:dnd
+ * @title: Drag And Drop
+ * @short_description: Functions for controlling drag and drop handling
+ *
+ * These functions provide a low level interface for drag and drop.
+ * The X backend of GDK supports both the Xdnd and Motif drag and drop
+ * protocols transparently, the Win32 backend supports the WM_DROPFILES
+ * protocol.
+ *
+ * GTK+ provides a higher level abstraction based on top of these functions,
+ * and so they are not normally needed in GTK+ applications.
+ * See the [Drag and Drop][gtk3-Drag-and-Drop] section of
+ * the GTK+ documentation for more information.
+ */
+
+/**
+ * GdkDrag:
+ *
+ * The GdkDrag struct contains only private fields and
+ * should not be accessed directly.
+ */
+
+/**
+ * gdk_drag_get_display:
+ * @drag: a #GdkDrag
+ *
+ * Gets the #GdkDisplay that the drag object was created for.
+ *
+ * Returns: (transfer none): a #GdkDisplay
+ **/
+GdkDisplay *
+gdk_drag_get_display (GdkDrag *drag)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
+
+ return priv->display;
+}
+
+/**
+ * gdk_drag_get_formats:
+ * @drag: a #GdkDrag
+ *
+ * Retrieves the formats supported by this GdkDrag object.
+ *
+ * Returns: (transfer none): a #GdkContentFormats
+ **/
+GdkContentFormats *
+gdk_drag_get_formats (GdkDrag *drag)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
+
+ return priv->formats;
+}
+
+/**
+ * gdk_drag_get_actions:
+ * @drag: a #GdkDrag
+ *
+ * Determines the bitmask of actions proposed by the source if
+ * gdk_drag_get_suggested_action() returns %GDK_ACTION_ASK.
+ *
+ * Returns: the #GdkDragAction flags
+ **/
+GdkDragAction
+gdk_drag_get_actions (GdkDrag *drag)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ g_return_val_if_fail (GDK_IS_DRAG (drag), 0);
+
+ return priv->actions;
+}
+
+/**
+ * gdk_drag_get_suggested_action:
+ * @drag: a #GdkDrag
+ *
+ * Determines the suggested drag action of the GdkDrag object.
+ *
+ * Returns: a #GdkDragAction value
+ **/
+GdkDragAction
+gdk_drag_get_suggested_action (GdkDrag *drag)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ g_return_val_if_fail (GDK_IS_DRAG (drag), 0);
+
+ return priv->suggested_action;
+}
+
+/**
+ * gdk_drag_get_selected_action:
+ * @drag: a #GdkDrag
+ *
+ * Determines the action chosen by the drag destination.
+ *
+ * Returns: a #GdkDragAction value
+ **/
+GdkDragAction
+gdk_drag_get_selected_action (GdkDrag *drag)
+{
+ g_return_val_if_fail (GDK_IS_DRAG (drag), 0);
+
+ return drag->action;
+}
+
+/**
+ * gdk_drag_get_device:
+ * @drag: a #GdkDrag
+ *
+ * Returns the #GdkDevice associated to the GdkDrag object.
+ *
+ * Returns: (transfer none): The #GdkDevice associated to @drag.
+ **/
+GdkDevice *
+gdk_drag_get_device (GdkDrag *drag)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
+
+ return priv->device;
+}
+
+static void
+gdk_drag_init (GdkDrag *drag)
+{
+ drags = g_list_prepend (drags, drag);
+}
+
+static void
+gdk_drag_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkDrag *drag = GDK_DRAG (gobject);
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ switch (prop_id)
+ {
+ case PROP_CONTENT:
+ drag->content = g_value_dup_object (value);
+ if (drag->content)
+ {
+ g_assert (priv->formats == NULL);
+ priv->formats = gdk_content_provider_ref_formats (drag->content);
+ }
+ break;
+
+ case PROP_DEVICE:
+ priv->device = g_value_dup_object (value);
+ g_assert (priv->device != NULL);
+ priv->display = gdk_device_get_display (priv->device);
+ break;
+
+ case PROP_FORMATS:
+ if (priv->formats)
+ {
+ GdkContentFormats *override = g_value_dup_boxed (value);
+ if (override)
+ {
+ gdk_content_formats_unref (priv->formats);
+ priv->formats = override;
+ }
+ }
+ else
+ {
+ priv->formats = g_value_dup_boxed (value);
+ g_assert (priv->formats != NULL);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdk_drag_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkDrag *drag = GDK_DRAG (gobject);
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ switch (prop_id)
+ {
+ case PROP_CONTENT:
+ g_value_set_object (value, drag->content);
+ break;
+
+ case PROP_DEVICE:
+ g_value_set_object (value, priv->device);
+ break;
+
+ case PROP_DISPLAY:
+ g_value_set_object (value, priv->display);
+ break;
+
+ case PROP_FORMATS:
+ g_value_set_boxed (value, priv->formats);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdk_drag_finalize (GObject *object)
+{
+ GdkDrag *drag = GDK_DRAG (object);
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ drags = g_list_remove (drags, drag);
+
+ g_clear_object (&drag->content);
+ g_clear_pointer (&priv->formats, gdk_content_formats_unref);
+
+ if (drag->source_surface)
+ g_object_unref (drag->source_surface);
+
+ G_OBJECT_CLASS (gdk_drag_parent_class)->finalize (object);
+}
+
+static void
+gdk_drag_class_init (GdkDragClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gdk_drag_get_property;
+ object_class->set_property = gdk_drag_set_property;
+ object_class->finalize = gdk_drag_finalize;
+
+ /**
+ * GdkDrag:content:
+ *
+ * The #GdkContentProvider.
+ */
+ properties[PROP_CONTENT] =
+ g_param_spec_object ("content",
+ "Content",
+ "The content being dragged",
+ GDK_TYPE_CONTENT_PROVIDER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GdkDrag:device:
+ *
+ * The #GdkDevice that is performing the drag.
+ */
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The device performing the drag",
+ GDK_TYPE_DEVICE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GdkDrag:display:
+ *
+ * The #GdkDisplay that the drag belongs to.
+ */
+ properties[PROP_DISPLAY] =
+ g_param_spec_object ("display",
+ "Display",
+ "Display this drag belongs to",
+ GDK_TYPE_DISPLAY,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GdkDrag:formats:
+ *
+ * The possible formats that the drag can provide its data in.
+ */
+ properties[PROP_FORMATS] =
+ g_param_spec_boxed ("formats",
+ "Formats",
+ "The possible formats for data",
+ GDK_TYPE_CONTENT_FORMATS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GdkDrag::cancel:
+ * @drag: The object on which the signal is emitted
+ * @reason: The reason the drag was cancelled
+ *
+ * The drag operation was cancelled.
+ */
+ signals[CANCEL] =
+ g_signal_new (g_intern_static_string ("cancel"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdkDragClass, cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1, GDK_TYPE_DRAG_CANCEL_REASON);
+
+ /**
+ * GdkDrag::drop-performed:
+ * @drag: The object on which the signal is emitted
+ *
+ * The drag operation was performed on an accepting client.
+ */
+ signals[DROP_PERFORMED] =
+ g_signal_new (g_intern_static_string ("drop-performed"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdkDragClass, drop_performed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GdkDrag::dnd-finished:
+ * @drag: The object on which the signal is emitted
+ *
+ * The drag operation was finished, the destination
+ * finished reading all data. The drag object can now
+ * free all miscellaneous data.
+ */
+ signals[DND_FINISHED] =
+ g_signal_new (g_intern_static_string ("dnd-finished"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdkDragClass, dnd_finished),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GdkDrag::action-changed:
+ * @drag: The object on which the signal is emitted
+ * @action: The action currently chosen
+ *
+ * A new action is being chosen for the drag operation.
+ */
+ signals[ACTION_CHANGED] =
+ g_signal_new (g_intern_static_string ("action-changed"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdkDragClass, action_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLAGS,
+ G_TYPE_NONE, 1, GDK_TYPE_DRAG_ACTION);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/*
+ * gdk_drag_abort:
+ * @drag: a #GdkDrag
+ * @time_: the timestamp for this operation
+ *
+ * Aborts a drag without dropping.
+ *
+ * This function is called by the drag source.
+ */
+void
+gdk_drag_abort (GdkDrag *drag,
+ guint32 time_)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ GDK_DRAG_GET_CLASS (drag)->drag_abort (drag, time_);
+}
+
+/*
+ * gdk_drag_drop:
+ * @drag: a #GdkDrag
+ * @time_: the timestamp for this operation
+ *
+ * Drops on the current destination.
+ *
+ * This function is called by the drag source.
+ */
+void
+gdk_drag_drop (GdkDrag *drag,
+ guint32 time_)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ GDK_DRAG_GET_CLASS (drag)->drag_drop (drag, time_);
+}
+
+static void
+gdk_drag_write_done (GObject *content,
+ GAsyncResult *result,
+ gpointer task)
+{
+ GError *error = NULL;
+
+ if (gdk_content_provider_write_mime_type_finish (GDK_CONTENT_PROVIDER (content), result, &error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, error);
+
+ g_object_unref (task);
+}
+
+static void
+gdk_drag_write_serialize_done (GObject *content,
+ GAsyncResult *result,
+ gpointer task)
+{
+ GError *error = NULL;
+
+ if (gdk_content_serialize_finish (result, &error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, error);
+
+ g_object_unref (task);
+}
+
+void
+gdk_drag_write_async (GdkDrag *drag,
+ const char *mime_type,
+ GOutputStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GdkContentFormats *formats, *mime_formats;
+ GTask *task;
+ GType gtype;
+
+ g_return_if_fail (GDK_IS_DRAG (drag));
+ g_return_if_fail (drag->content);
+ g_return_if_fail (mime_type != NULL);
+ g_return_if_fail (mime_type == g_intern_string (mime_type));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ task = g_task_new (drag, cancellable, callback, user_data);
+ g_task_set_priority (task, io_priority);
+ g_task_set_source_tag (task, gdk_drag_write_async);
+
+ formats = gdk_content_provider_ref_formats (drag->content);
+ if (gdk_content_formats_contain_mime_type (formats, mime_type))
+ {
+ gdk_content_provider_write_mime_type_async (drag->content,
+ mime_type,
+ stream,
+ io_priority,
+ cancellable,
+ gdk_drag_write_done,
+ task);
+ gdk_content_formats_unref (formats);
+ return;
+ }
+
+ mime_formats = gdk_content_formats_new ((const gchar *[2]) { mime_type, NULL }, 1);
+ mime_formats = gdk_content_formats_union_serialize_gtypes (mime_formats);
+ gtype = gdk_content_formats_match_gtype (formats, mime_formats);
+ if (gtype != G_TYPE_INVALID)
+ {
+ GValue value = G_VALUE_INIT;
+ GError *error = NULL;
+
+ g_assert (gtype != G_TYPE_INVALID);
+
+ g_value_init (&value, gtype);
+ if (gdk_content_provider_get_value (drag->content, &value, &error))
+ {
+ gdk_content_serialize_async (stream,
+ mime_type,
+ &value,
+ io_priority,
+ cancellable,
+ gdk_drag_write_serialize_done,
+ g_object_ref (task));
+ }
+ else
+ {
+ g_task_return_error (task, error);
+ }
+
+ g_value_unset (&value);
+ }
+ else
+ {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("No compatible formats to transfer clipboard contents."));
+ }
+
+ gdk_content_formats_unref (mime_formats);
+ gdk_content_formats_unref (formats);
+ g_object_unref (task);
+}
+
+gboolean
+gdk_drag_write_finish (GdkDrag *drag,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, drag), FALSE);
+ g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drag_write_async, FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+gdk_drag_set_actions (GdkDrag *drag,
+ GdkDragAction actions,
+ GdkDragAction suggested_action)
+{
+ GdkDragPrivate *priv = gdk_drag_get_instance_private (drag);
+
+ priv->actions = actions;
+ priv->suggested_action = suggested_action;
+}
+
+/**
+ * gdk_drag_get_drag_surface:
+ * @drag: a #GdkDrag
+ *
+ * Returns the surface on which the drag icon should be rendered
+ * during the drag operation. Note that the surface may not be
+ * available until the drag operation has begun. GDK will move
+ * the surface in accordance with the ongoing drag operation.
+ * The surface is owned by @drag and will be destroyed when
+ * the drag operation is over.
+ *
+ * Returns: (nullable) (transfer none): the drag surface, or %NULL
+ */
+GdkSurface *
+gdk_drag_get_drag_surface (GdkDrag *drag)
+{
+ g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
+
+ if (GDK_DRAG_GET_CLASS (drag)->get_drag_surface)
+ return GDK_DRAG_GET_CLASS (drag)->get_drag_surface (drag);
+
+ return NULL;
+}
+
+/**
+ * gdk_drag_set_hotspot:
+ * @drag: a #GdkDrag
+ * @hot_x: x coordinate of the drag surface hotspot
+ * @hot_y: y coordinate of the drag surface hotspot
+ *
+ * Sets the position of the drag surface that will be kept
+ * under the cursor hotspot. Initially, the hotspot is at the
+ * top left corner of the drag surface.
+ */
+void
+gdk_drag_set_hotspot (GdkDrag *drag,
+ gint hot_x,
+ gint hot_y)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ if (GDK_DRAG_GET_CLASS (drag)->set_hotspot)
+ GDK_DRAG_GET_CLASS (drag)->set_hotspot (drag, hot_x, hot_y);
+}
+
+/**
+ * gdk_drag_drop_done:
+ * @drag: a #GdkDrag
+ * @success: whether the drag was ultimatively successful
+ *
+ * Inform GDK if the drop ended successfully. Passing %FALSE
+ * for @success may trigger a drag cancellation animation.
+ *
+ * This function is called by the drag source, and should
+ * be the last call before dropping the reference to the
+ * @drag.
+ *
+ * The #GdkDrag will only take the first gdk_drag_drop_done()
+ * call as effective, if this function is called multiple times,
+ * all subsequent calls will be ignored.
+ */
+void
+gdk_drag_drop_done (GdkDrag *drag,
+ gboolean success)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ if (drag->drop_done)
+ return;
+
+ drag->drop_done = TRUE;
+
+ if (GDK_DRAG_GET_CLASS (drag)->drop_done)
+ GDK_DRAG_GET_CLASS (drag)->drop_done (drag, success);
+}
+
+void
+gdk_drag_set_cursor (GdkDrag *drag,
+ GdkCursor *cursor)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ if (GDK_DRAG_GET_CLASS (drag)->set_cursor)
+ GDK_DRAG_GET_CLASS (drag)->set_cursor (drag, cursor);
+}
+
+void
+gdk_drag_cancel (GdkDrag *drag,
+ GdkDragCancelReason reason)
+{
+ g_return_if_fail (GDK_IS_DRAG (drag));
+
+ g_signal_emit (drag, signals[CANCEL], 0, reason);
+}
+
+gboolean
+gdk_drag_handle_source_event (GdkEvent *event)
+{
+ GdkDrag *drag;
+ GList *l;
+
+ for (l = drags; l; l = l->next)
+ {
+ drag = l->data;
+
+ if (!GDK_DRAG_GET_CLASS (drag)->handle_event)
+ continue;
+
+ if (GDK_DRAG_GET_CLASS (drag)->handle_event (drag, event))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GdkCursor *
+gdk_drag_get_cursor (GdkDrag *drag,
+ GdkDragAction action)
+{
+ gint i;
+
+ for (i = 0 ; i < G_N_ELEMENTS (drag_cursors) - 1; i++)
+ if (drag_cursors[i].action == action)
+ break;
+
+ if (drag_cursors[i].cursor == NULL)
+ drag_cursors[i].cursor = gdk_cursor_new_from_name (drag_cursors[i].name, NULL);
+
+ return drag_cursors[i].cursor;
+}
+
+/**
+ * gdk_drag_action_is_unique:
+ * @action: a #GdkDragAction
+ *
+ * Checks if @action represents a single action or if it
+ * includes multiple flags that can be selected from.
+ *
+ * When @action is 0 - ie no action was given, %TRUE
+ * is returned.
+ *
+ * Returns: %TRUE if exactly one action was given
+ **/
+gboolean
+gdk_drag_action_is_unique (GdkDragAction action)
+{
+ return (action & (action - 1)) == 0;
+}