/* 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 . */ /* * 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" /** * GtkDropTargetAsync: * * `GtkDropTargetAsync` is an event controller to receive Drag-and-Drop * operations, asynchronously. * * It is the more complete but also more complex method of handling drop * operations compared to [class@Gtk.DropTarget], 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 [method@Gtk.Widget.add_controller]. * * During a drag operation, the first signal that a `GtkDropTargetAsync` * emits is [signal@Gtk.DropTargetAsync::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 * [signal@Gtk.DropTargetAsync::drag-enter] signal, followed by * [signal@Gtk.DropTargetAsync::drag-motion] signals as the pointer moves, * optionally a [signal@Gtk.DropTargetAsync::drop] signal when a drop happens, * and finally a [signal@Gtk.DropTargetAsync::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 * [method@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_dnd_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_dnd_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: (attributes org.gtk.Property.get=gtk_drop_target_async_get_actions org.gtk.Property.set=gtk_drop_target_async_set_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: (attributes org.gtk.Property.get=gtk_drop_target_async_get_formats org.gtk.Property.set=gtk_drop_target_async_set_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); /** * GtkDropTargetAsync::accept: * @self: the `GtkDropTargetAsync` * @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.DropTargetAsync.reject_drop] * or ultimately by returning %FALSE from a [signal@Gtk.DropTargetAsync::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 needs * further processing, 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 [method@Gtk.DropTargetAsync.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 * * 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 * * 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` * * 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 * * 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 [method@Gdk.Drop.finish] * is called to let the source know that the drop is done. The call to * [method@Gdk.Drop.finish] must only be done when all data has been received. * * To receive the data, use one of the read functions provided by * [class@Gdk.Drop] such as [method@Gdk.Drop.read_async] or * [method@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: (attributes org.gtk.Method.set_property=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: (attributes org.gtk.Method.get_property=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: (attributes org.gtk.Method.set_property=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: (attributes org.gtk.Method.get_property=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); }