forked from AuroraMiddleware/gtk
1064 lines
31 KiB
C
1064 lines
31 KiB
C
/* 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 "gtkdroptarget.h"
|
|
|
|
#include "gtkdropprivate.h"
|
|
#include "gtkeventcontrollerprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkmarshalers.h"
|
|
#include "gtknative.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtktypebuiltins.h"
|
|
|
|
|
|
/**
|
|
* GtkDropTarget:
|
|
*
|
|
* `GtkDropTarget` is an event controller to receive Drag-and-Drop operations.
|
|
*
|
|
* The most basic way to use a `GtkDropTarget` to receive drops on a
|
|
* widget is to create it via [ctor@Gtk.DropTarget.new], passing in the
|
|
* `GType` of the data you want to receive and connect to the
|
|
* [signal@Gtk.DropTarget::drop] signal to receive the data:
|
|
*
|
|
* ```c
|
|
* static gboolean
|
|
* on_drop (GtkDropTarget *target,
|
|
* const GValue *value,
|
|
* double x,
|
|
* double y,
|
|
* gpointer data)
|
|
* {
|
|
* MyWidget *self = data;
|
|
*
|
|
* // Call the appropriate setter depending on the type of data
|
|
* // that we received
|
|
* if (G_VALUE_HOLDS (value, G_TYPE_FILE))
|
|
* my_widget_set_file (self, g_value_get_object (value));
|
|
* else if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF))
|
|
* my_widget_set_pixbuf (self, g_value_get_object (value));
|
|
* else
|
|
* return FALSE;
|
|
*
|
|
* return TRUE;
|
|
* }
|
|
*
|
|
* static void
|
|
* my_widget_init (MyWidget *self)
|
|
* {
|
|
* GtkDropTarget *target =
|
|
* gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_COPY);
|
|
*
|
|
* // This widget accepts two types of drop types: GFile objects
|
|
* // and GdkPixbuf objects
|
|
* gtk_drop_target_set_gtypes (target, (GTypes [2]) {
|
|
* G_TYPE_FILE,
|
|
* GDK_TYPE_PIXBUF,
|
|
* }, 2);
|
|
*
|
|
* gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (target));
|
|
* }
|
|
* ```
|
|
*
|
|
* `GtkDropTarget` supports more options, such as:
|
|
*
|
|
* * rejecting potential drops via the [signal@Gtk.DropTarget::accept] signal
|
|
* and the [method@Gtk.DropTarget.reject] function to let other drop
|
|
* targets handle the drop
|
|
* * tracking an ongoing drag operation before the drop via the
|
|
* [signal@Gtk.DropTarget::enter], [signal@Gtk.DropTarget::motion] and
|
|
* [signal@Gtk.DropTarget::leave] signals
|
|
* * configuring how to receive data by setting the
|
|
* [property@Gtk.DropTarget:preload] property and listening for its
|
|
* availability via the [property@Gtk.DropTarget:value] property
|
|
*
|
|
* However, `GtkDropTarget` is ultimately modeled in a synchronous way
|
|
* and only supports data transferred via `GType`. If you want full control
|
|
* over an ongoing drop, the [class@Gtk.DropTargetAsync] object gives you
|
|
* this ability.
|
|
*
|
|
* While a pointer is dragged over the drop target's widget and the drop
|
|
* has not been rejected, that widget will receive the
|
|
* %GTK_STATE_FLAG_DROP_ACTIVE state, which can be used to style the widget.
|
|
*
|
|
* If you are not interested in receiving the drop, but just want to update
|
|
* UI state during a Drag-and-Drop operation (e.g. switching tabs), you can
|
|
* use [class@Gtk.DropControllerMotion].
|
|
*/
|
|
|
|
struct _GtkDropTarget
|
|
{
|
|
GtkEventController parent_object;
|
|
|
|
GdkContentFormats *formats;
|
|
GdkDragAction actions;
|
|
guint preload : 1;
|
|
|
|
guint dropping : 1;
|
|
graphene_point_t coords;
|
|
GdkDrop *drop;
|
|
GCancellable *cancellable; /* NULL unless doing a read of value */
|
|
GValue value;
|
|
};
|
|
|
|
struct _GtkDropTargetClass
|
|
{
|
|
GtkEventControllerClass parent_class;
|
|
|
|
gboolean (* accept) (GtkDropTarget *self,
|
|
GdkDrop *drop);
|
|
GdkDragAction (* enter) (GtkDropTarget *self,
|
|
double x,
|
|
double y);
|
|
GdkDragAction (* motion) (GtkDropTarget *self,
|
|
double x,
|
|
double y);
|
|
void (* leave) (GtkDropTarget *self,
|
|
GdkDrop *drop);
|
|
gboolean (* drop) (GtkDropTarget *self,
|
|
const GValue *value,
|
|
double x,
|
|
double y);
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_ACTIONS,
|
|
PROP_DROP,
|
|
PROP_FORMATS,
|
|
PROP_PRELOAD,
|
|
PROP_VALUE,
|
|
NUM_PROPERTIES
|
|
};
|
|
|
|
static GParamSpec *properties[NUM_PROPERTIES];
|
|
|
|
enum {
|
|
ACCEPT,
|
|
ENTER,
|
|
MOTION,
|
|
LEAVE,
|
|
DROP,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint signals[NUM_SIGNALS];
|
|
|
|
G_DEFINE_TYPE (GtkDropTarget, gtk_drop_target, GTK_TYPE_EVENT_CONTROLLER);
|
|
|
|
static void
|
|
gtk_drop_target_end_drop (GtkDropTarget *self)
|
|
{
|
|
if (self->drop == NULL)
|
|
return;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
if (self->dropping)
|
|
{
|
|
gdk_drop_finish (self->drop, 0);
|
|
self->dropping = FALSE;
|
|
}
|
|
|
|
g_clear_object (&self->drop);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DROP]);
|
|
|
|
if (G_IS_VALUE (&self->value))
|
|
{
|
|
g_value_unset (&self->value);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE]);
|
|
}
|
|
|
|
if (self->cancellable)
|
|
{
|
|
g_cancellable_cancel (self->cancellable);
|
|
g_clear_object (&self->cancellable);
|
|
}
|
|
|
|
gtk_widget_unset_state_flags (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)),
|
|
GTK_STATE_FLAG_DROP_ACTIVE);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_do_drop (GtkDropTarget *self)
|
|
{
|
|
gboolean success;
|
|
|
|
g_assert (self->dropping);
|
|
g_assert (G_IS_VALUE (&self->value));
|
|
|
|
g_signal_emit (self, signals[DROP], 0, &self->value, self->coords.x, self->coords.y, &success);
|
|
|
|
if (success)
|
|
gdk_drop_finish (self->drop, gdk_drop_get_actions (self->drop));
|
|
else
|
|
gdk_drop_finish (self->drop, 0);
|
|
|
|
self->dropping = FALSE;
|
|
|
|
gtk_drop_target_end_drop (self);
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_load_done (GObject *source,
|
|
GAsyncResult *res,
|
|
gpointer data)
|
|
{
|
|
GtkDropTarget *self = data;
|
|
const GValue *value;
|
|
GError *error = NULL;
|
|
|
|
value = gdk_drop_read_value_finish (GDK_DROP (source), res, &error);
|
|
if (value == NULL)
|
|
{
|
|
/* If this happens, data/self is invalid */
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
g_clear_error (&error);
|
|
return;
|
|
}
|
|
|
|
g_clear_object (&self->cancellable);
|
|
/* XXX: Should this be a warning? */
|
|
g_warning ("Failed to receive drop data: %s", error->message);
|
|
g_clear_error (&error);
|
|
gtk_drop_target_end_drop (self);
|
|
return;
|
|
}
|
|
|
|
g_clear_object (&self->cancellable);
|
|
g_value_init (&self->value, G_VALUE_TYPE (value));
|
|
g_value_copy (value, &self->value);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE]);
|
|
|
|
if (self->dropping)
|
|
gtk_drop_target_do_drop (self);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_load_local (GtkDropTarget *self,
|
|
GType type)
|
|
{
|
|
GdkDrag *drag;
|
|
|
|
drag = gdk_drop_get_drag (self->drop);
|
|
if (drag == NULL)
|
|
return FALSE;
|
|
|
|
g_value_init (&self->value, type);
|
|
if (gdk_content_provider_get_value (gdk_drag_get_content (drag),
|
|
&self->value,
|
|
NULL))
|
|
return TRUE;
|
|
|
|
g_value_unset (&self->value);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_load (GtkDropTarget *self)
|
|
{
|
|
GType type;
|
|
|
|
g_assert (self->drop);
|
|
|
|
if (G_IS_VALUE (&self->value))
|
|
return TRUE;
|
|
|
|
if (self->cancellable)
|
|
return FALSE;
|
|
|
|
type = gdk_content_formats_match_gtype (self->formats, gdk_drop_get_formats (self->drop));
|
|
|
|
if (gtk_drop_target_load_local (self, type))
|
|
return TRUE;
|
|
|
|
self->cancellable = g_cancellable_new ();
|
|
|
|
gdk_drop_read_value_async (self->drop,
|
|
type,
|
|
G_PRIORITY_DEFAULT,
|
|
self->cancellable,
|
|
gtk_drop_target_load_done,
|
|
g_object_ref (self));
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_start_drop (GtkDropTarget *self,
|
|
GdkDrop *drop)
|
|
{
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
gtk_drop_target_end_drop (self);
|
|
|
|
self->drop = g_object_ref (drop);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DROP]);
|
|
|
|
if (self->preload)
|
|
gtk_drop_target_load (self);
|
|
|
|
gtk_widget_set_state_flags (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)),
|
|
GTK_STATE_FLAG_DROP_ACTIVE,
|
|
FALSE);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_accept (GtkDropTarget *self,
|
|
GdkDrop *drop)
|
|
{
|
|
if ((gdk_drop_get_actions (drop) & gtk_drop_target_get_actions (self)) == 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_enter (GtkDropTarget *self,
|
|
double x,
|
|
double y)
|
|
{
|
|
return make_action_unique (self->actions & gdk_drop_get_actions (self->drop));
|
|
}
|
|
|
|
static GdkDragAction
|
|
gtk_drop_target_motion (GtkDropTarget *self,
|
|
double x,
|
|
double y)
|
|
{
|
|
return make_action_unique (self->actions & gdk_drop_get_actions (self->drop));
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_drop (GtkDropTarget *self,
|
|
const GValue *value,
|
|
double x,
|
|
double y)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_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_parent_class)->filter_event (controller, event);
|
|
|
|
default:;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_target_handle_event (GtkEventController *controller,
|
|
GdkEvent *event,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkDropTarget *self = GTK_DROP_TARGET (controller);
|
|
|
|
/* All drops have been rejected. New drops only arrive via crossing
|
|
* events, so we can: */
|
|
if (self->drop == NULL)
|
|
return FALSE;
|
|
|
|
switch ((int) gdk_event_get_event_type (event))
|
|
{
|
|
case GDK_DRAG_MOTION:
|
|
{
|
|
GtkWidget *widget = gtk_event_controller_get_widget (controller);
|
|
GdkDragAction preferred;
|
|
|
|
/* sanity check */
|
|
g_return_val_if_fail (self->drop == gdk_dnd_event_get_drop (event), FALSE);
|
|
|
|
graphene_point_init (&self->coords, x, y);
|
|
g_signal_emit (self, signals[MOTION], 0, x, y, &preferred);
|
|
if (preferred &&
|
|
gtk_drop_status (self->drop, self->actions, preferred))
|
|
{
|
|
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:
|
|
{
|
|
/* sanity check */
|
|
g_return_val_if_fail (self->drop == gdk_dnd_event_get_drop (event), FALSE);
|
|
|
|
graphene_point_init (&self->coords, x, y);
|
|
self->dropping = TRUE;
|
|
if (gtk_drop_target_load (self))
|
|
gtk_drop_target_do_drop (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_handle_crossing (GtkEventController *controller,
|
|
const GtkCrossingData *crossing,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkDropTarget *self = GTK_DROP_TARGET (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;
|
|
|
|
if (self->drop != NULL)
|
|
return;
|
|
|
|
/* if we were a target already but self->drop == NULL, the drop
|
|
* was rejected already */
|
|
if (crossing->old_descendent != NULL ||
|
|
crossing->old_target == widget)
|
|
return;
|
|
|
|
g_signal_emit (self, signals[ACCEPT], 0, crossing->drop, &accept);
|
|
if (!accept)
|
|
return;
|
|
|
|
graphene_point_init (&self->coords, x, y);
|
|
gtk_drop_target_start_drop (self, crossing->drop);
|
|
|
|
g_signal_emit (self, signals[ENTER], 0, x, y, &preferred);
|
|
if (preferred &&
|
|
gtk_drop_status (self->drop, self->actions, preferred))
|
|
{
|
|
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (crossing->new_descendent != NULL ||
|
|
crossing->new_target == widget)
|
|
return;
|
|
|
|
g_signal_emit (self, signals[LEAVE], 0);
|
|
if (!self->dropping)
|
|
gtk_drop_target_end_drop (self);
|
|
gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_finalize (GObject *object)
|
|
{
|
|
GtkDropTarget *self = GTK_DROP_TARGET (object);
|
|
|
|
g_clear_pointer (&self->formats, gdk_content_formats_unref);
|
|
|
|
G_OBJECT_CLASS (gtk_drop_target_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkDropTarget *self = GTK_DROP_TARGET (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ACTIONS:
|
|
gtk_drop_target_set_actions (self, g_value_get_flags (value));
|
|
break;
|
|
|
|
case PROP_PRELOAD:
|
|
gtk_drop_target_set_preload (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkDropTarget *self = GTK_DROP_TARGET (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ACTIONS:
|
|
g_value_set_flags (value, self->actions);
|
|
break;
|
|
|
|
case PROP_DROP:
|
|
g_value_set_object (value, self->drop);
|
|
break;
|
|
|
|
case PROP_FORMATS:
|
|
g_value_set_boxed (value, self->formats);
|
|
break;
|
|
|
|
case PROP_PRELOAD:
|
|
g_value_set_boolean (value, self->preload);
|
|
break;
|
|
|
|
case PROP_VALUE:
|
|
if (G_IS_VALUE (&self->value))
|
|
g_value_set_boxed (value, &self->value);
|
|
else
|
|
g_value_set_boxed (value, NULL);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_class_init (GtkDropTargetClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (class);
|
|
|
|
object_class->finalize = gtk_drop_target_finalize;
|
|
object_class->set_property = gtk_drop_target_set_property;
|
|
object_class->get_property = gtk_drop_target_get_property;
|
|
|
|
controller_class->handle_event = gtk_drop_target_handle_event;
|
|
controller_class->filter_event = gtk_drop_target_filter_event;
|
|
controller_class->handle_crossing = gtk_drop_target_handle_crossing;
|
|
|
|
class->accept = gtk_drop_target_accept;
|
|
class->enter = gtk_drop_target_enter;
|
|
class->motion = gtk_drop_target_motion;
|
|
class->drop = gtk_drop_target_drop;
|
|
|
|
/**
|
|
* GtkDropTarget:actions: (attributes org.gtk.Property.get=gtk_drop_target_get_actions org.gtk.Property.set=gtk_drop_target_set_actions)
|
|
*
|
|
* The `GdkDragActions` that this drop target supports.
|
|
*/
|
|
properties[PROP_ACTIONS] =
|
|
g_param_spec_flags ("actions",
|
|
P_("Actions"),
|
|
P_("The actions supported by this drop target"),
|
|
GDK_TYPE_DRAG_ACTION, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkDropTarget:drop: (attributes org.gtk.Property.get=gtk_drop_target_get_drop)
|
|
*
|
|
* The `GdkDrop` that is currently being performed.
|
|
*/
|
|
properties[PROP_DROP] =
|
|
g_param_spec_object ("drop",
|
|
P_("Drop"),
|
|
P_("Current drop"),
|
|
GDK_TYPE_DROP,
|
|
GTK_PARAM_READABLE);
|
|
|
|
/**
|
|
* GtkDropTarget:formats: (attributes org.gtk.Property.get=gtk_drop_target_get_formats)
|
|
*
|
|
* The `GdkContentFormats` that determine the supported data formats.
|
|
*/
|
|
properties[PROP_FORMATS] =
|
|
g_param_spec_boxed ("formats",
|
|
P_("Formats"),
|
|
P_("The supported formats"),
|
|
GDK_TYPE_CONTENT_FORMATS,
|
|
GTK_PARAM_READABLE);
|
|
|
|
/**
|
|
* GtkDropTarget:preload: (attributes org.gtk.Property.get=gtk_drop_target_get_preload org.gtk.Property.set=gtk_drop_target_set_preload)
|
|
*
|
|
* Whether the drop data should be preloaded when the pointer is only
|
|
* hovering over the widget but has not been released.
|
|
*
|
|
* Setting this property allows finer grained reaction to an ongoing
|
|
* drop at the cost of loading more data.
|
|
*
|
|
* The default value for this property is %FALSE to avoid downloading
|
|
* huge amounts of data by accident.
|
|
*
|
|
* For example, if somebody drags a full document of gigabytes of text
|
|
* from a text editor across a widget with a preloading drop target,
|
|
* this data will be downloaded, even if the data is ultimately dropped
|
|
* elsewhere.
|
|
*
|
|
* For a lot of data formats, the amount of data is very small (like
|
|
* %GDK_TYPE_RGBA), so enabling this property does not hurt at all.
|
|
* And for local-only Drag-and-Drop operations, no data transfer is done,
|
|
* so enabling it there is free.
|
|
*/
|
|
properties[PROP_PRELOAD] =
|
|
g_param_spec_boolean ("preload",
|
|
P_("Preload"),
|
|
P_("Whether drop data should be preloaded while hovering"),
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkDropTarget:value: (attributes org.gtk.Property.get=gtk_drop_target_get_value)
|
|
*
|
|
* The value for this drop operation.
|
|
*
|
|
* This is %NULL if the data has not been loaded yet or no drop
|
|
* operation is going on.
|
|
*
|
|
* Data may be available before the [signal@Gtk.DropTarget::drop]
|
|
* signal gets emitted - for example when the [property@Gtk.DropTarget:preload]
|
|
* property is set. You can use the ::notify signal to be notified
|
|
* of available data.
|
|
*/
|
|
properties[PROP_VALUE] =
|
|
g_param_spec_boxed ("value",
|
|
P_("Value"),
|
|
P_("The value for this drop operation"),
|
|
G_TYPE_VALUE,
|
|
GTK_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
|
|
|
|
/**
|
|
* GtkDropTarget::accept:
|
|
* @self: the `GtkDropTarget`
|
|
* @drop: the `GdkDrop`
|
|
*
|
|
* 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 [method@Gtk.DropTarget.reject]
|
|
* or ultimately by returning %FALSE from a [signal@Gtk.DropTarget::drop]
|
|
* handler.
|
|
*
|
|
* 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 depends
|
|
* on the data, this function should return %TRUE, the
|
|
* [property@Gtk.DropTarget:preload] property should be set and the value
|
|
* should be inspected via the ::notify:value signal, calling
|
|
* [method@Gtk.DropTarget.reject] if required.
|
|
*
|
|
* 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 (GtkDropTargetClass, accept),
|
|
g_signal_accumulator_first_wins, NULL,
|
|
NULL,
|
|
G_TYPE_BOOLEAN, 1,
|
|
GDK_TYPE_DROP);
|
|
|
|
/**
|
|
* GtkDropTarget::enter:
|
|
* @self: the `GtkDropTarget`
|
|
* @x: the x coordinate of the current pointer position
|
|
* @y: the y coordinate of the current pointer position
|
|
*
|
|
* 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 or 0 if
|
|
* dropping is not supported at the current @x,@y location.
|
|
*/
|
|
signals[ENTER] =
|
|
g_signal_new (I_("enter"),
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkDropTargetClass, enter),
|
|
g_signal_accumulator_first_wins, NULL,
|
|
NULL,
|
|
GDK_TYPE_DRAG_ACTION, 2,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
|
|
/**
|
|
* GtkDropTarget::motion:
|
|
* @self: the `GtkDropTarget`
|
|
* @x: the x coordinate of the current pointer position
|
|
* @y: the y coordinate of the current pointer position
|
|
*
|
|
* Emitted while the pointer is moving over the drop target.
|
|
*
|
|
* Returns: Preferred action for this drag operation or 0 if
|
|
* dropping is not supported at the current @x,@y location.
|
|
*/
|
|
signals[MOTION] =
|
|
g_signal_new (I_("motion"),
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkDropTargetClass, motion),
|
|
g_signal_accumulator_first_wins, NULL,
|
|
NULL,
|
|
GDK_TYPE_DRAG_ACTION, 2,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
|
|
/**
|
|
* GtkDropTarget::leave:
|
|
* @self: the `GtkDropTarget`
|
|
*
|
|
* Emitted on the drop site when the pointer leaves the widget.
|
|
*
|
|
* Its main purpose it to undo things done in
|
|
* [signal@Gtk.DropTarget::enter].
|
|
*/
|
|
signals[LEAVE] =
|
|
g_signal_new (I_("leave"),
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkDropTargetClass, leave),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* GtkDropTarget::drop:
|
|
* @self: the `GtkDropTarget`
|
|
* @value: the #GValue being dropped
|
|
* @x: the x coordinate of the current pointer position
|
|
* @y: the y coordinate of the current pointer position
|
|
*
|
|
* 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 is responsible for rading the given @value
|
|
* and performing the drop operation.
|
|
*
|
|
* Returns: whether the drop was 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,
|
|
G_TYPE_VALUE, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
}
|
|
|
|
static void
|
|
gtk_drop_target_init (GtkDropTarget *self)
|
|
{
|
|
self->formats = gdk_content_formats_new (NULL, 0);
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_new:
|
|
* @type: The supported type or %G_TYPE_INVALID
|
|
* @actions: the supported actions
|
|
*
|
|
* Creates a new `GtkDropTarget` object.
|
|
*
|
|
* If the drop target should support more than 1 type, pass
|
|
* %G_TYPE_INVALID for @type and then call
|
|
* [method@Gtk.DropTarget.set_gtypes].
|
|
*
|
|
* Returns: the new `GtkDropTarget`
|
|
*/
|
|
GtkDropTarget *
|
|
gtk_drop_target_new (GType type,
|
|
GdkDragAction actions)
|
|
{
|
|
GtkDropTarget *result;
|
|
|
|
result = g_object_new (GTK_TYPE_DROP_TARGET,
|
|
"actions", actions,
|
|
NULL);
|
|
|
|
if (type != G_TYPE_INVALID)
|
|
gtk_drop_target_set_gtypes (result, &type, 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_formats: (attributes org.gtk.Method.get_property=formats)
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* 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_get_formats (GtkDropTarget *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL);
|
|
|
|
return self->formats;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_set_gtypes:
|
|
* @self: a `GtkDropTarget`
|
|
* @types: (nullable) (transfer none) (array length=n_types):
|
|
* all supported #GTypes that can be dropped
|
|
* @n_types: number of @types
|
|
*
|
|
* Sets the supported `GTypes` for this drop target.
|
|
*/
|
|
void
|
|
gtk_drop_target_set_gtypes (GtkDropTarget *self,
|
|
GType *types,
|
|
gsize n_types)
|
|
{
|
|
GdkContentFormatsBuilder *builder;
|
|
gsize i;
|
|
|
|
g_return_if_fail (GTK_IS_DROP_TARGET (self));
|
|
g_return_if_fail (n_types == 0 || types != NULL);
|
|
|
|
gdk_content_formats_unref (self->formats);
|
|
|
|
builder = gdk_content_formats_builder_new ();
|
|
for (i = 0; i < n_types; i++)
|
|
gdk_content_formats_builder_add_gtype (builder, types[i]);
|
|
|
|
self->formats = gdk_content_formats_builder_free_to_formats (builder);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FORMATS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_gtypes:
|
|
* @self: a `GtkDropTarget`
|
|
* @n_types: (out) (optional): optional pointer to take the
|
|
* number of #GTypes contained in the return value
|
|
*
|
|
* Gets the list of supported `GTypes` for @self.
|
|
*
|
|
* If no type have been set, %NULL will be returned.
|
|
*
|
|
* Returns: (transfer none) (nullable) (array length=n_types):
|
|
* %G_TYPE_INVALID-terminated array of types included in
|
|
* @formats or %NULL if none.
|
|
*/
|
|
const GType *
|
|
gtk_drop_target_get_gtypes (GtkDropTarget *self,
|
|
gsize *n_types)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL);
|
|
|
|
return gdk_content_formats_get_gtypes (self->formats, n_types);
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_set_actions: (attributes org.gtk.Method.set_property=actions)
|
|
* @self: a `GtkDropTarget`
|
|
* @actions: the supported actions
|
|
*
|
|
* Sets the actions that this drop target supports.
|
|
*/
|
|
void
|
|
gtk_drop_target_set_actions (GtkDropTarget *self,
|
|
GdkDragAction actions)
|
|
{
|
|
g_return_if_fail (GTK_IS_DROP_TARGET (self));
|
|
|
|
if (self->actions == actions)
|
|
return;
|
|
|
|
self->actions = actions;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_actions: (attributes org.gtk.Method.get_property=actions)
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* Gets the actions that this drop target supports.
|
|
*
|
|
* Returns: the actions that this drop target supports
|
|
*/
|
|
GdkDragAction
|
|
gtk_drop_target_get_actions (GtkDropTarget *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), 0);
|
|
|
|
return self->actions;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_set_preload: (attributes org.gtk.Method.set_property=preload)
|
|
* @self: a `GtkDropTarget`
|
|
* @preload: %TRUE to preload drop data
|
|
*
|
|
* Sets whether data should be preloaded on hover.
|
|
*/
|
|
void
|
|
gtk_drop_target_set_preload (GtkDropTarget *self,
|
|
gboolean preload)
|
|
{
|
|
g_return_if_fail (GTK_IS_DROP_TARGET (self));
|
|
|
|
if (self->preload == preload)
|
|
return;
|
|
|
|
self->preload = preload;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRELOAD]);
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_preload: (attributes org.gtk.Method.get_property=preload)
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* Gets whether data should be preloaded on hover.
|
|
*
|
|
* Returns: %TRUE if drop data should be preloaded
|
|
*/
|
|
gboolean
|
|
gtk_drop_target_get_preload (GtkDropTarget *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), 0);
|
|
|
|
return self->preload;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_drop: (attributes org.gtk.Method.get_property=drop)
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* Gets the currently handled drop operation.
|
|
*
|
|
* If no drop operation is going on, %NULL is returned.
|
|
*
|
|
* Returns: (nullable) (transfer none): The current drop
|
|
*/
|
|
GdkDrop *
|
|
gtk_drop_target_get_drop (GtkDropTarget *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL);
|
|
|
|
return self->drop;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_get_value: (attributes org.gtk.Method.get_property=value)
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* Gets the current drop data, as a `GValue`.
|
|
*
|
|
* Returns: (nullable) (transfer none): The current drop data
|
|
*/
|
|
const GValue *
|
|
gtk_drop_target_get_value (GtkDropTarget *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL);
|
|
|
|
if (!G_IS_VALUE (&self->value))
|
|
return NULL;
|
|
|
|
return &self->value;
|
|
}
|
|
|
|
/**
|
|
* gtk_drop_target_reject:
|
|
* @self: a `GtkDropTarget`
|
|
*
|
|
* Rejects the ongoing drop operation.
|
|
*
|
|
* If no drop operation is ongoing, i.e when [property@Gtk.DropTarget:drop]
|
|
* is %NULL, this function does nothing.
|
|
*
|
|
* 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_reject (GtkDropTarget *self)
|
|
{
|
|
g_return_if_fail (GTK_IS_DROP_TARGET (self));
|
|
|
|
if (self->drop == NULL)
|
|
return;
|
|
|
|
gtk_drop_target_end_drop (self);
|
|
}
|