forked from AuroraMiddleware/gtk
1a3eeb1233
Redo highlight handling slightly. GtkDropTarget now has a ::armed property that can be tracked to do custom highlighting, and we always add the dnd style class to armed drop sites.
434 lines
13 KiB
C
434 lines
13 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 "gtkdndprivate.h"
|
|
|
|
#include "gtkdragdestprivate.h"
|
|
#include "gtkimageprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkmain.h"
|
|
#include "gtkpicture.h"
|
|
#include "gtkselectionprivate.h"
|
|
#include "gtksettingsprivate.h"
|
|
#include "gtkstylecontext.h"
|
|
#include "gtktooltipprivate.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gtkwindowgroup.h"
|
|
#include "gtkwindowprivate.h"
|
|
#include "gtknative.h"
|
|
#include "gtkdragiconprivate.h"
|
|
|
|
#include "gdk/gdkcontentformatsprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
/**
|
|
* SECTION:gtkdnd
|
|
* @Short_description: Functions for controlling drag and drop handling
|
|
* @Title: Drag and Drop
|
|
*
|
|
* GTK+ has a rich set of functions for doing inter-process communication
|
|
* via the drag-and-drop metaphor.
|
|
*
|
|
* As well as the functions listed here, applications may need to use some
|
|
* facilities provided for [Selections][gtk3-Selections]. Also, the Drag and
|
|
* Drop API makes use of signals in the #GtkWidget class.
|
|
*/
|
|
|
|
|
|
typedef struct _GtkDragDestInfo GtkDragDestInfo;
|
|
|
|
struct _GtkDragDestInfo
|
|
{
|
|
GtkWidget *widget; /* Widget in which drag is in */
|
|
GdkDrop *drop; /* drop */
|
|
};
|
|
|
|
#define DROP_ABORT_TIME 300000
|
|
|
|
typedef gboolean (* GtkDragDestCallback) (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
gint x,
|
|
gint y,
|
|
guint32 time);
|
|
|
|
/* Forward declarations */
|
|
static gboolean gtk_drop_find_widget (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
GtkDragDestInfo *info,
|
|
gint x,
|
|
gint y,
|
|
guint32 time,
|
|
GtkDragDestCallback callback);
|
|
static void gtk_drag_dest_leave (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
guint time);
|
|
static gboolean gtk_drag_dest_motion (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
gint x,
|
|
gint y,
|
|
guint time);
|
|
static gboolean gtk_drag_dest_drop (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
gint x,
|
|
gint y,
|
|
guint time);
|
|
static void gtk_drag_dest_set_widget (GtkDragDestInfo *info,
|
|
GtkWidget *widget);
|
|
|
|
static GtkDragDestInfo * gtk_drag_get_dest_info (GdkDrop *drop,
|
|
gboolean create);
|
|
|
|
|
|
/*
|
|
* _gtk_drag_dest_handle_event:
|
|
* @toplevel: Toplevel widget that received the event
|
|
* @event: the event to handle
|
|
*
|
|
* Called from widget event handling code on Drag events
|
|
* for destinations.
|
|
*/
|
|
void
|
|
_gtk_drag_dest_handle_event (GtkWidget *toplevel,
|
|
GdkEvent *event)
|
|
{
|
|
GtkDragDestInfo *info;
|
|
GdkDrop *drop;
|
|
guint32 time;
|
|
GdkEventType event_type;
|
|
|
|
g_return_if_fail (toplevel != NULL);
|
|
g_return_if_fail (event != NULL);
|
|
|
|
event_type = gdk_event_get_event_type (event);
|
|
drop = gdk_event_get_drop (event);
|
|
time = gdk_event_get_time (event);
|
|
|
|
info = gtk_drag_get_dest_info (drop, TRUE);
|
|
|
|
/* Find the widget for the event */
|
|
switch ((guint) event_type)
|
|
{
|
|
case GDK_DRAG_ENTER:
|
|
break;
|
|
|
|
case GDK_DRAG_LEAVE:
|
|
if (info->widget)
|
|
{
|
|
gtk_drag_dest_leave (info->widget, drop, time);
|
|
gtk_drag_dest_set_widget (info, NULL);
|
|
}
|
|
break;
|
|
|
|
case GDK_DRAG_MOTION:
|
|
case GDK_DROP_START:
|
|
{
|
|
double x, y;
|
|
gboolean found;
|
|
|
|
if (event_type == GDK_DROP_START)
|
|
{
|
|
/* We send a leave here so that the widget unhighlights
|
|
* properly.
|
|
*/
|
|
if (info->widget)
|
|
{
|
|
gtk_drag_dest_leave (info->widget, drop, time);
|
|
gtk_drag_dest_set_widget (info, NULL);
|
|
}
|
|
}
|
|
|
|
gdk_event_get_coords (event, &x, &y);
|
|
|
|
found = gtk_drop_find_widget (toplevel,
|
|
drop,
|
|
info,
|
|
x,
|
|
y,
|
|
time,
|
|
(event_type == GDK_DRAG_MOTION) ?
|
|
gtk_drag_dest_motion :
|
|
gtk_drag_dest_drop);
|
|
|
|
if (info->widget && !found)
|
|
{
|
|
gtk_drag_dest_leave (info->widget, drop, time);
|
|
gtk_drag_dest_set_widget (info, NULL);
|
|
}
|
|
|
|
/* Send a reply.
|
|
*/
|
|
if (!found)
|
|
gdk_drop_status (drop, 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drop_find_widget (GtkWidget *event_widget,
|
|
GdkDrop *drop,
|
|
GtkDragDestInfo *info,
|
|
gint x,
|
|
gint y,
|
|
guint32 time,
|
|
GtkDragDestCallback callback)
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
if (!gtk_widget_get_mapped (event_widget) ||
|
|
!gtk_widget_get_sensitive (event_widget))
|
|
return FALSE;
|
|
|
|
widget = gtk_widget_pick (event_widget, x, y, GTK_PICK_DEFAULT);
|
|
|
|
if (!widget)
|
|
return FALSE;
|
|
|
|
gtk_widget_translate_coordinates (event_widget, widget, x, y, &x, &y);
|
|
|
|
while (widget)
|
|
{
|
|
GtkWidget *parent;
|
|
GList *hierarchy = NULL;
|
|
gboolean found = FALSE;
|
|
|
|
if (!gtk_widget_get_mapped (widget))
|
|
return FALSE;
|
|
|
|
if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_INSENSITIVE)
|
|
{
|
|
widget = gtk_widget_get_parent (widget);
|
|
continue;
|
|
}
|
|
|
|
/* need to reference the entire hierarchy temporarily in case the
|
|
* ::drag-motion/::drag-drop callbacks change the widget hierarchy.
|
|
*/
|
|
for (parent = widget;
|
|
parent;
|
|
parent = gtk_widget_get_parent (parent))
|
|
{
|
|
hierarchy = g_list_prepend (hierarchy, g_object_ref (parent));
|
|
}
|
|
|
|
/* If the current widget is registered as a drop site, check to
|
|
* emit "drag-motion" to check if we are actually in a drop
|
|
* site.
|
|
*/
|
|
if (g_object_get_data (G_OBJECT (widget), "gtk-drag-dest"))
|
|
{
|
|
found = callback (widget, drop, x, y, time);
|
|
|
|
/* If so, send a "drag-leave" to the last widget */
|
|
if (found && info->widget != widget)
|
|
{
|
|
if (info->widget)
|
|
gtk_drag_dest_leave (info->widget, drop, time);
|
|
|
|
gtk_drag_dest_set_widget (info, widget);
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
/* Get the parent before unreffing the hierarchy because
|
|
* invoking the callback might have destroyed the widget
|
|
*/
|
|
parent = gtk_widget_get_parent (widget);
|
|
|
|
/* The parent might be going away when unreffing the
|
|
* hierarchy, so also protect againt that
|
|
*/
|
|
if (parent)
|
|
g_object_add_weak_pointer (G_OBJECT (parent), (gpointer *) &parent);
|
|
}
|
|
|
|
g_list_free_full (hierarchy, g_object_unref);
|
|
|
|
if (found)
|
|
return TRUE;
|
|
|
|
if (parent)
|
|
g_object_remove_weak_pointer (G_OBJECT (parent), (gpointer *) &parent);
|
|
else
|
|
return FALSE;
|
|
|
|
if (!gtk_widget_translate_coordinates (widget, parent, x, y, &x, &y))
|
|
return FALSE;
|
|
|
|
widget = parent;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_drag_dest_set_widget (GtkDragDestInfo *info,
|
|
GtkWidget *widget)
|
|
{
|
|
if (info->widget)
|
|
g_object_remove_weak_pointer (G_OBJECT (info->widget), (gpointer *) &info->widget);
|
|
|
|
info->widget = widget;
|
|
|
|
if (info->widget)
|
|
g_object_add_weak_pointer (G_OBJECT (info->widget), (gpointer *) &info->widget);
|
|
}
|
|
|
|
static void
|
|
gtk_drag_dest_info_destroy (gpointer data)
|
|
{
|
|
GtkDragDestInfo *info = (GtkDragDestInfo *)data;
|
|
|
|
gtk_drag_dest_set_widget (info, NULL);
|
|
|
|
g_slice_free (GtkDragDestInfo, data);
|
|
}
|
|
|
|
static GtkDragDestInfo *
|
|
gtk_drag_get_dest_info (GdkDrop *drop,
|
|
gboolean create)
|
|
{
|
|
GtkDragDestInfo *info;
|
|
static GQuark info_quark = 0;
|
|
if (!info_quark)
|
|
info_quark = g_quark_from_static_string ("gtk-dest-info");
|
|
|
|
info = g_object_get_qdata (G_OBJECT (drop), info_quark);
|
|
if (!info && create)
|
|
{
|
|
info = g_slice_new0 (GtkDragDestInfo);
|
|
info->drop = drop;
|
|
g_object_set_qdata_full (G_OBJECT (drop), info_quark,
|
|
info, gtk_drag_dest_info_destroy);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/*
|
|
* Default drag handlers
|
|
*/
|
|
static void
|
|
gtk_drag_dest_leave (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
guint time)
|
|
{
|
|
GtkDropTarget *dest;
|
|
GtkDestDefaults flags;
|
|
gboolean track_motion;
|
|
gboolean armed;
|
|
|
|
dest = gtk_drop_target_get (widget);
|
|
g_return_if_fail (dest != NULL);
|
|
|
|
flags = gtk_drop_target_get_defaults (dest);
|
|
track_motion = gtk_drop_target_get_track_motion (dest);
|
|
armed = gtk_drop_target_get_armed (dest);
|
|
|
|
if (!(flags & GTK_DEST_DEFAULT_MOTION) || armed || track_motion)
|
|
gtk_drop_target_emit_drag_leave (dest, drop, time);
|
|
|
|
gtk_drop_target_set_armed (dest, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drag_dest_motion (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
gint x,
|
|
gint y,
|
|
guint time)
|
|
{
|
|
GtkDropTarget *dest;
|
|
GdkDragAction dest_actions;
|
|
GtkDestDefaults flags;
|
|
gboolean track_motion;
|
|
gboolean retval;
|
|
|
|
dest = gtk_drop_target_get (widget);
|
|
g_return_val_if_fail (dest != NULL, FALSE);
|
|
|
|
dest_actions = gtk_drop_target_get_actions (dest);
|
|
flags = gtk_drop_target_get_defaults (dest);
|
|
track_motion = gtk_drop_target_get_track_motion (dest);
|
|
|
|
if (track_motion || flags & GTK_DEST_DEFAULT_MOTION)
|
|
{
|
|
GdkDragAction actions;
|
|
GdkAtom target;
|
|
|
|
actions = gdk_drop_get_actions (drop);
|
|
|
|
if ((dest_actions & actions) == 0)
|
|
actions = 0;
|
|
|
|
target = gtk_drop_target_match (dest, drop);
|
|
|
|
if (actions && target)
|
|
{
|
|
gtk_drop_target_set_armed (dest, TRUE);
|
|
gdk_drop_status (drop, dest_actions);
|
|
}
|
|
else
|
|
{
|
|
gdk_drop_status (drop, 0);
|
|
if (!track_motion)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
retval = gtk_drop_target_emit_drag_motion (dest, drop, x, y);
|
|
|
|
return (flags & GTK_DEST_DEFAULT_MOTION) ? TRUE : retval;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_drag_dest_drop (GtkWidget *widget,
|
|
GdkDrop *drop,
|
|
gint x,
|
|
gint y,
|
|
guint time)
|
|
{
|
|
GtkDropTarget *dest;
|
|
GtkDragDestInfo *info;
|
|
|
|
dest = gtk_drop_target_get (widget);
|
|
g_return_val_if_fail (dest != NULL, FALSE);
|
|
|
|
info = gtk_drag_get_dest_info (drop, FALSE);
|
|
g_return_val_if_fail (info != NULL, FALSE);
|
|
|
|
return gtk_drop_target_emit_drag_drop (dest, drop, x, y);
|
|
}
|