/* 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
#include
#include "gdk/gdk.h"
#include "gtkdnd.h"
#include "gtkdndprivate.h"
#include "deprecated/gtkiconfactory.h"
#include "gtkicontheme.h"
#include "gtkimageprivate.h"
#include "gtkinvisible.h"
#include "gtkmain.h"
#include "gtkoffscreenwindow.h"
#include "deprecated/gtkstock.h"
#include "gtkwindow.h"
#include "gtkintl.h"
#include "gtkquartz.h"
#include "gdk/quartz/gdkquartz.h"
#include "gdk/quartz/gdkquartz-cocoa-access.h"
#include "gdk/quartz/gdkquartz-gtk-only.h"
#include "gdk/quartz/gdkquartzdnd.h"
#include "gtkselectionprivate.h"
#include "gtksettings.h"
#include "gtkiconhelperprivate.h"
typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
typedef struct _GtkDragDestInfo GtkDragDestInfo;
typedef struct _GtkDragFindData GtkDragFindData;
static void gtk_drag_find_widget (GtkWidget *widget,
GtkDragFindData *data);
static void gtk_drag_dest_site_destroy (gpointer data);
static void gtk_drag_dest_leave (GtkWidget *widget,
GdkDragContext *context,
guint time);
static GtkDragDestInfo *gtk_drag_get_dest_info (GdkDragContext *context,
gboolean create);
static void gtk_drag_source_site_destroy (gpointer data);
static GtkDragSourceInfo *gtk_drag_get_source_info (GdkDragContext *context,
gboolean create);
static void gtk_drag_drop_finished (GtkDragSourceInfo *info,
GtkDragResult result);
struct _GtkDragSourceInfo
{
GtkWidget *source_widget;
GtkWidget *widget;
GtkTargetList *target_list; /* Targets for drag data */
GdkDragAction possible_actions; /* Actions allowed by source */
GdkDragContext *context; /* drag context */
NSEvent *nsevent; /* what started it */
gint hot_x, hot_y; /* Hot spot for drag */
cairo_surface_t *icon_surface;
gboolean success;
gboolean delete;
};
struct _GtkDragDestInfo
{
GtkWidget *widget; /* Widget in which drag is in */
GdkDragContext *context; /* Drag context */
guint dropped : 1; /* Set after we receive a drop */
gint drop_x, drop_y; /* Position of drop */
};
struct _GtkDragFindData
{
gint x;
gint y;
GdkDragContext *context;
GtkDragDestInfo *info;
gboolean found;
gboolean toplevel;
gboolean (*callback) (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, guint32 time);
guint32 time;
};
@interface GtkDragSourceOwner : NSObject {
GtkDragSourceInfo *info;
}
@end
@implementation GtkDragSourceOwner
-(void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
{
guint target_info;
GtkSelectionData selection_data;
selection_data.selection = GDK_NONE;
selection_data.data = NULL;
selection_data.length = -1;
selection_data.target = gdk_quartz_pasteboard_type_to_atom_libgtk_only (type);
selection_data.display = gdk_display_get_default ();
if (gtk_target_list_find (info->target_list,
selection_data.target,
&target_info))
{
g_signal_emit_by_name (info->widget, "drag-data-get",
info->context,
&selection_data,
target_info,
time);
if (selection_data.length >= 0)
_gtk_quartz_set_selection_data_for_pasteboard (sender, &selection_data);
g_free (selection_data.data);
}
}
- (id)initWithInfo:(GtkDragSourceInfo *)anInfo
{
self = [super init];
if (self)
{
info = anInfo;
}
return self;
}
@end
/**
* gtk_drag_get_data: (method)
* @widget: the widget that will receive the
* #GtkWidget::drag-data-received signal.
* @context: the drag context
* @target: the target (form of the data) to retrieve.
* @time_: a timestamp for retrieving the data. This will
* generally be the time received in a #GtkWidget::drag-motion"
* or #GtkWidget::drag-drop" signal.
*/
void
gtk_drag_get_data (GtkWidget *widget,
GdkDragContext *context,
GdkAtom target,
guint32 time)
{
id dragging_info;
NSPasteboard *pasteboard;
GtkSelectionData *selection_data;
GtkDragDestInfo *info;
GtkDragDestSite *site;
dragging_info = gdk_quartz_drag_context_get_dragging_info_libgtk_only (context);
pasteboard = [dragging_info draggingPasteboard];
info = gtk_drag_get_dest_info (context, FALSE);
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
selection_data = _gtk_quartz_get_selection_data_from_pasteboard (pasteboard,
target, 0);
if (site && site->target_list)
{
guint target_info;
if (gtk_target_list_find (site->target_list,
selection_data->target,
&target_info))
{
if (!(site->flags & GTK_DEST_DEFAULT_DROP) ||
selection_data->length >= 0)
g_signal_emit_by_name (widget,
"drag-data-received",
context, info->drop_x, info->drop_y,
selection_data,
target_info, time);
}
}
else
{
g_signal_emit_by_name (widget,
"drag-data-received",
context, info->drop_x, info->drop_y,
selection_data,
0, time);
}
if (site && site->flags & GTK_DEST_DEFAULT_DROP)
{
gtk_drag_finish (context,
(selection_data->length >= 0),
(gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE),
time);
}
}
/**
* gtk_drag_finish: (method)
* @context: the drag context.
* @success: a flag indicating whether the drop was successful
* @del: a flag indicating whether the source should delete the
* original data. (This should be %TRUE for a move)
* @time_: the timestamp from the #GtkWidget::drag-drop signal.
*/
void
gtk_drag_finish (GdkDragContext *context,
gboolean success,
gboolean del,
guint32 time)
{
GtkDragSourceInfo *info;
GdkDragContext* source_context = gdk_quartz_drag_source_context_libgtk_only ();
if (source_context)
{
info = gtk_drag_get_source_info (source_context, FALSE);
if (info)
{
info->success = success;
info->delete = del;
}
}
}
static void
gtk_drag_dest_info_destroy (gpointer data)
{
GtkDragDestInfo *info = data;
g_free (info);
}
static GtkDragDestInfo *
gtk_drag_get_dest_info (GdkDragContext *context,
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 (context), info_quark);
if (!info && create)
{
info = g_new (GtkDragDestInfo, 1);
info->widget = NULL;
info->context = context;
info->dropped = FALSE;
g_object_set_qdata_full (G_OBJECT (context), info_quark,
info, gtk_drag_dest_info_destroy);
}
return info;
}
static GQuark dest_info_quark = 0;
static GtkDragSourceInfo *
gtk_drag_get_source_info (GdkDragContext *context,
gboolean create)
{
GtkDragSourceInfo *info;
if (!dest_info_quark)
dest_info_quark = g_quark_from_static_string ("gtk-source-info");
info = g_object_get_qdata (G_OBJECT (context), dest_info_quark);
if (!info && create)
{
info = g_new0 (GtkDragSourceInfo, 1);
info->context = context;
g_object_set_qdata (G_OBJECT (context), dest_info_quark, info);
}
return info;
}
static void
gtk_drag_clear_source_info (GdkDragContext *context)
{
g_object_set_qdata (G_OBJECT (context), dest_info_quark, NULL);
}
/**
* gtk_drag_get_source_widget: (method)
* @context: a (destination side) drag context
*
* Returns: (transfer none):
*/
GtkWidget *
gtk_drag_get_source_widget (GdkDragContext *context)
{
GtkDragSourceInfo *info;
GdkDragContext* real_source_context = gdk_quartz_drag_source_context_libgtk_only ();
if (!real_source_context)
return NULL;
info = gtk_drag_get_source_info (real_source_context, FALSE);
if (!info)
return NULL;
return info->source_widget;
}
/**
* gtk_drag_highlight: (method)
* @widget: a widget to highlight
*/
void
gtk_drag_highlight (GtkWidget *widget)
{
g_return_if_fail (GTK_IS_WIDGET (widget));
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
}
/**
* gtk_drag_unhighlight: (method)
* @widget: a widget to remove the highlight from.
*/
void
gtk_drag_unhighlight (GtkWidget *widget)
{
g_return_if_fail (GTK_IS_WIDGET (widget));
gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
}
static NSWindow *
get_toplevel_nswindow (GtkWidget *widget)
{
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
GdkWindow *window = gtk_widget_get_window (toplevel);
/* Offscreen windows don't support drag and drop */
if (GTK_IS_OFFSCREEN_WINDOW (toplevel))
return NULL;
if (gtk_widget_is_toplevel (toplevel) && window)
return [gdk_quartz_window_get_nsview (window) window];
else
return NULL;
}
static void
register_types (GtkWidget *widget, GtkDragDestSite *site)
{
if (site->target_list)
{
NSWindow *nswindow = get_toplevel_nswindow (widget);
NSSet *types;
NSAutoreleasePool *pool;
if (!nswindow)
return;
pool = [[NSAutoreleasePool alloc] init];
types = _gtk_quartz_target_list_to_pasteboard_types (site->target_list);
[nswindow registerForDraggedTypes:[types allObjects]];
[types release];
[pool release];
}
}
static void
gtk_drag_dest_realized (GtkWidget *widget,
gpointer user_data)
{
GtkDragDestSite *site = user_data;
register_types (widget, site);
}
static void
gtk_drag_dest_hierarchy_changed (GtkWidget *widget,
GtkWidget *previous_toplevel,
gpointer user_data)
{
GtkDragDestSite *site = user_data;
register_types (widget, site);
}
static void
gtk_drag_dest_site_destroy (gpointer data)
{
GtkDragDestSite *site = data;
if (site->target_list)
gtk_target_list_unref (site->target_list);
g_free (site);
}
/**
* gtk_drag_dest_set: (method)
* @widget: a #GtkWidget
* @flags: which types of default drag behavior to use
* @targets: (allow-none) (array length=n_targets): a pointer to an array of #GtkTargetEntrys
* indicating the drop types that this @widget will accept, or %NULL.
* Later you can access the list with gtk_drag_dest_get_target_list()
* and gtk_drag_dest_find_target().
* @n_targets: the number of entries in @targets
* @actions: a bitmask of possible actions for a drop onto this @widget.
*/
void
gtk_drag_dest_set (GtkWidget *widget,
GtkDestDefaults flags,
const GtkTargetEntry *targets,
gint n_targets,
GdkDragAction actions)
{
GtkDragDestSite *old_site, *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
old_site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
site = g_new0 (GtkDragDestSite, 1);
site->flags = flags;
site->have_drag = FALSE;
if (targets)
site->target_list = gtk_target_list_new (targets, n_targets);
else
site->target_list = NULL;
site->actions = actions;
if (old_site)
site->track_motion = old_site->track_motion;
else
site->track_motion = FALSE;
gtk_drag_dest_unset (widget);
if (gtk_widget_get_realized (widget))
gtk_drag_dest_realized (widget, site);
g_signal_connect (widget, "realize",
G_CALLBACK (gtk_drag_dest_realized), site);
g_signal_connect (widget, "hierarchy-changed",
G_CALLBACK (gtk_drag_dest_hierarchy_changed), site);
g_object_set_data_full (G_OBJECT (widget), I_("gtk-drag-dest"),
site, gtk_drag_dest_site_destroy);
}
/**
* gtk_drag_dest_set_proxy: (method)
* @widget: a #GtkWidget
* @proxy_window: the window to which to forward drag events
* @protocol: the drag protocol which the @proxy_window accepts
* (You can use gdk_drag_get_protocol() to determine this)
* @use_coordinates: If %TRUE, send the same coordinates to the
* destination, because it is an embedded
* subwindow.
*/
void
gtk_drag_dest_set_proxy (GtkWidget *widget,
GdkWindow *proxy_window,
GdkDragProtocol protocol,
gboolean use_coordinates)
{
g_warning ("gtk_drag_dest_set_proxy is not supported on Mac OS X.");
}
/**
* gtk_drag_dest_unset: (method)
* @widget: a #GtkWidget
*/
void
gtk_drag_dest_unset (GtkWidget *widget)
{
GtkDragDestSite *old_site;
g_return_if_fail (GTK_IS_WIDGET (widget));
old_site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
if (old_site)
{
g_signal_handlers_disconnect_by_func (widget,
gtk_drag_dest_realized,
old_site);
g_signal_handlers_disconnect_by_func (widget,
gtk_drag_dest_hierarchy_changed,
old_site);
}
g_object_set_data (G_OBJECT (widget), I_("gtk-drag-dest"), NULL);
}
/**
* gtk_drag_dest_get_target_list: (method)
* @widget: a #GtkWidget
* Returns: (nullable) (transfer none): the #GtkTargetList, or %NULL if none
*/
GtkTargetList*
gtk_drag_dest_get_target_list (GtkWidget *widget)
{
GtkDragDestSite *site;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
return site ? site->target_list : NULL;
}
/**
* gtk_drag_dest_set_target_list: (method)
* @widget: a #GtkWidget that’s a drag destination
* @target_list: (allow-none): list of droppable targets, or %NULL for none
*/
void
gtk_drag_dest_set_target_list (GtkWidget *widget,
GtkTargetList *target_list)
{
GtkDragDestSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
if (!site)
{
g_warning ("Can't set a target list on a widget until you've called gtk_drag_dest_set() "
"to make the widget into a drag destination");
return;
}
if (target_list)
gtk_target_list_ref (target_list);
if (site->target_list)
gtk_target_list_unref (site->target_list);
site->target_list = target_list;
register_types (widget, site);
}
/**
* gtk_drag_dest_add_text_targets: (method)
* @widget: a #GtkWidget that’s a drag destination
*/
void
gtk_drag_dest_add_text_targets (GtkWidget *widget)
{
GtkTargetList *target_list;
target_list = gtk_drag_dest_get_target_list (widget);
if (target_list)
gtk_target_list_ref (target_list);
else
target_list = gtk_target_list_new (NULL, 0);
gtk_target_list_add_text_targets (target_list, 0);
gtk_drag_dest_set_target_list (widget, target_list);
gtk_target_list_unref (target_list);
}
/**
* gtk_drag_dest_add_image_targets: (method)
* @widget: a #GtkWidget that’s a drag destination
*/
void
gtk_drag_dest_add_image_targets (GtkWidget *widget)
{
GtkTargetList *target_list;
target_list = gtk_drag_dest_get_target_list (widget);
if (target_list)
gtk_target_list_ref (target_list);
else
target_list = gtk_target_list_new (NULL, 0);
gtk_target_list_add_image_targets (target_list, 0, FALSE);
gtk_drag_dest_set_target_list (widget, target_list);
gtk_target_list_unref (target_list);
}
/**
* gtk_drag_dest_add_uri_targets: (method)
* @widget: a #GtkWidget that’s a drag destination
*/
void
gtk_drag_dest_add_uri_targets (GtkWidget *widget)
{
GtkTargetList *target_list;
target_list = gtk_drag_dest_get_target_list (widget);
if (target_list)
gtk_target_list_ref (target_list);
else
target_list = gtk_target_list_new (NULL, 0);
gtk_target_list_add_uri_targets (target_list, 0);
gtk_drag_dest_set_target_list (widget, target_list);
gtk_target_list_unref (target_list);
}
static void
prepend_and_ref_widget (GtkWidget *widget,
gpointer data)
{
GSList **slist_p = data;
*slist_p = g_slist_prepend (*slist_p, g_object_ref (widget));
}
static void
gtk_drag_find_widget (GtkWidget *widget,
GtkDragFindData *data)
{
GtkAllocation new_allocation;
gint allocation_to_window_x = 0;
gint allocation_to_window_y = 0;
gint x_offset = 0;
gint y_offset = 0;
if (data->found || !gtk_widget_get_mapped (widget) || !gtk_widget_get_sensitive (widget))
return;
/* Note that in the following code, we only count the
* position as being inside a WINDOW widget if it is inside
* widget->window; points that are outside of widget->window
* but within the allocation are not counted. This is consistent
* with the way we highlight drag targets.
*
* data->x,y are relative to widget->parent->window (if
* widget is not a toplevel, widget->window otherwise).
* We compute the allocation of widget in the same coordinates,
* clipping to widget->window, and all intermediate
* windows. If data->x,y is inside that, then we translate
* our coordinates to be relative to widget->window and
* recurse.
*/
gtk_widget_get_allocation (widget, &new_allocation);
if (gtk_widget_get_parent (widget))
{
gint tx, ty;
GdkWindow *window = gtk_widget_get_window (widget);
GdkWindow *parent_window;
GtkAllocation allocation;
parent_window = gtk_widget_get_window (gtk_widget_get_parent (widget));
/* Compute the offset from allocation-relative to
* window-relative coordinates.
*/
gtk_widget_get_allocation (widget, &allocation);
allocation_to_window_x = allocation.x;
allocation_to_window_y = allocation.y;
if (gtk_widget_get_has_window (widget))
{
/* The allocation is relative to the parent window for
* window widgets, not to widget->window.
*/
gdk_window_get_position (window, &tx, &ty);
allocation_to_window_x -= tx;
allocation_to_window_y -= ty;
}
new_allocation.x = 0 + allocation_to_window_x;
new_allocation.y = 0 + allocation_to_window_y;
while (window && window != parent_window)
{
GdkRectangle window_rect = { 0, 0, 0, 0 };
window_rect.width = gdk_window_get_width (window);
window_rect.height = gdk_window_get_height (window);
gdk_rectangle_intersect (&new_allocation, &window_rect, &new_allocation);
gdk_window_get_position (window, &tx, &ty);
new_allocation.x += tx;
x_offset += tx;
new_allocation.y += ty;
y_offset += ty;
window = gdk_window_get_parent (window);
}
if (!window) /* Window and widget heirarchies didn't match. */
return;
}
if (data->toplevel ||
((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
(data->x < new_allocation.x + new_allocation.width) &&
(data->y < new_allocation.y + new_allocation.height)))
{
/* First, check if the drag is in a valid drop site in
* one of our children
*/
if (GTK_IS_CONTAINER (widget))
{
GtkDragFindData new_data = *data;
GSList *children = NULL;
GSList *tmp_list;
new_data.x -= x_offset;
new_data.y -= y_offset;
new_data.found = FALSE;
new_data.toplevel = FALSE;
/* need to reference children temporarily in case the
* ::drag-motion/::drag-drop callbacks change the widget hierarchy.
*/
gtk_container_forall (GTK_CONTAINER (widget), prepend_and_ref_widget, &children);
for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
{
if (!new_data.found && gtk_widget_is_drawable (tmp_list->data))
gtk_drag_find_widget (tmp_list->data, &new_data);
g_object_unref (tmp_list->data);
}
g_slist_free (children);
data->found = new_data.found;
}
/* If not, and this widget is registered as a drop site, check to
* emit "drag-motion" to check if we are actually in
* a drop site.
*/
if (!data->found &&
g_object_get_data (G_OBJECT (widget), "gtk-drag-dest"))
{
data->found = data->callback (widget,
data->context,
data->x - x_offset - allocation_to_window_x,
data->y - y_offset - allocation_to_window_y,
data->time);
/* If so, send a "drag-leave" to the last widget */
if (data->found)
{
if (data->info->widget && data->info->widget != widget)
{
gtk_drag_dest_leave (data->info->widget, data->context, data->time);
}
data->info->widget = widget;
}
}
}
}
static void
gtk_drag_dest_leave (GtkWidget *widget,
GdkDragContext *context,
guint time)
{
GtkDragDestSite *site;
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
g_return_if_fail (site != NULL);
if ((site->flags & GTK_DEST_DEFAULT_HIGHLIGHT) && site->have_drag)
gtk_drag_unhighlight (widget);
if (!(site->flags & GTK_DEST_DEFAULT_MOTION) || site->have_drag ||
site->track_motion)
g_signal_emit_by_name (widget, "drag-leave", context, time);
site->have_drag = FALSE;
}
static gboolean
gtk_drag_dest_motion (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time)
{
GtkDragDestSite *site;
GdkDragAction action = 0;
gboolean retval;
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
g_return_val_if_fail (site != NULL, FALSE);
if (site->track_motion || site->flags & GTK_DEST_DEFAULT_MOTION)
{
if (gdk_drag_context_get_suggested_action (context) & site->actions)
action = gdk_drag_context_get_suggested_action (context);
if (action && gtk_drag_dest_find_target (widget, context, NULL))
{
if (!site->have_drag)
{
site->have_drag = TRUE;
if (site->flags & GTK_DEST_DEFAULT_HIGHLIGHT)
gtk_drag_highlight (widget);
}
gdk_drag_status (context, action, time);
}
else
{
gdk_drag_status (context, 0, time);
if (!site->track_motion)
return TRUE;
}
}
g_signal_emit_by_name (widget, "drag-motion",
context, x, y, time, &retval);
return (site->flags & GTK_DEST_DEFAULT_MOTION) ? TRUE : retval;
}
static gboolean
gtk_drag_dest_drop (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time)
{
GtkDragDestSite *site;
GtkDragDestInfo *info;
gboolean retval;
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
g_return_val_if_fail (site != NULL, FALSE);
info = gtk_drag_get_dest_info (context, FALSE);
g_return_val_if_fail (info != NULL, FALSE);
info->drop_x = x;
info->drop_y = y;
if (site->flags & GTK_DEST_DEFAULT_DROP)
{
GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
if (target == GDK_NONE)
{
gtk_drag_finish (context, FALSE, FALSE, time);
return TRUE;
}
else
gtk_drag_get_data (widget, context, target, time);
}
g_signal_emit_by_name (widget, "drag-drop",
context, x, y, time, &retval);
return (site->flags & GTK_DEST_DEFAULT_DROP) ? TRUE : retval;
}
/**
* gtk_drag_dest_set_track_motion: (method)
* @widget: a #GtkWidget that’s a drag destination
* @track_motion: whether to accept all targets
*/
void
gtk_drag_dest_set_track_motion (GtkWidget *widget,
gboolean track_motion)
{
GtkDragDestSite *site;
g_return_if_fail (GTK_IS_WIDGET (widget));
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
g_return_if_fail (site != NULL);
site->track_motion = track_motion != FALSE;
}
/**
* gtk_drag_dest_get_track_motion: (method)
* @widget: a #GtkWidget that’s a drag destination
*/
gboolean
gtk_drag_dest_get_track_motion (GtkWidget *widget)
{
GtkDragDestSite *site;
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
if (site)
return site->track_motion;
return FALSE;
}
void
_gtk_drag_dest_handle_event (GtkWidget *toplevel,
GdkEvent *event)
{
GtkDragDestInfo *info;
GdkDragContext *context;
g_return_if_fail (toplevel != NULL);
g_return_if_fail (event != NULL);
context = event->dnd.context;
info = gtk_drag_get_dest_info (context, TRUE);
/* Find the widget for the event */
switch (event->type)
{
case GDK_DRAG_ENTER:
break;
case GDK_DRAG_LEAVE:
if (info->widget)
{
gtk_drag_dest_leave (info->widget, context, event->dnd.time);
info->widget = NULL;
}
break;
case GDK_DRAG_MOTION:
case GDK_DROP_START:
{
GtkDragFindData data;
gint tx, ty;
if (event->type == GDK_DROP_START)
{
info->dropped = TRUE;
/* We send a leave here so that the widget unhighlights
* properly.
*/
if (info->widget)
{
gtk_drag_dest_leave (info->widget, context, event->dnd.time);
info->widget = NULL;
}
}
gdk_window_get_position (gtk_widget_get_window (toplevel), &tx, &ty);
data.x = event->dnd.x_root - tx;
data.y = event->dnd.y_root - ty;
data.context = context;
data.info = info;
data.found = FALSE;
data.toplevel = TRUE;
data.callback = (event->type == GDK_DRAG_MOTION) ?
gtk_drag_dest_motion : gtk_drag_dest_drop;
data.time = event->dnd.time;
gtk_drag_find_widget (toplevel, &data);
if (info->widget && !data.found)
{
gtk_drag_dest_leave (info->widget, context, event->dnd.time);
info->widget = NULL;
}
/* Send a reply.
*/
if (event->type == GDK_DRAG_MOTION)
{
if (!data.found)
gdk_drag_status (context, 0, event->dnd.time);
}
break;
default:
g_assert_not_reached ();
}
}
}
/**
* gtk_drag_dest_find_target: (method)
* @widget: drag destination widget
* @context: drag context
* @target_list: (allow-none): list of droppable targets, or %NULL to use
* gtk_drag_dest_get_target_list (@widget).
*
* Returns: (transfer none):
*/
GdkAtom
gtk_drag_dest_find_target (GtkWidget *widget,
GdkDragContext *context,
GtkTargetList *target_list)
{
id dragging_info;
NSPasteboard *pasteboard;
GtkWidget *source_widget;
GList *tmp_target;
GList *tmp_source = NULL;
GList *source_targets;
g_return_val_if_fail (GTK_IS_WIDGET (widget), GDK_NONE);
g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), GDK_NONE);
dragging_info = gdk_quartz_drag_context_get_dragging_info_libgtk_only (context);
pasteboard = [dragging_info draggingPasteboard];
source_widget = gtk_drag_get_source_widget (context);
if (target_list == NULL)
target_list = gtk_drag_dest_get_target_list (widget);
if (target_list == NULL)
return GDK_NONE;
source_targets = _gtk_quartz_pasteboard_types_to_atom_list ([pasteboard types]);
tmp_target = target_list->list;
while (tmp_target)
{
GtkTargetPair *pair = tmp_target->data;
tmp_source = source_targets;
while (tmp_source)
{
if (tmp_source->data == GUINT_TO_POINTER (pair->target))
{
if ((!(pair->flags & GTK_TARGET_SAME_APP) || source_widget) &&
(!(pair->flags & GTK_TARGET_SAME_WIDGET) || (source_widget == widget)))
{
g_list_free (source_targets);
return pair->target;
}
else
break;
}
tmp_source = tmp_source->next;
}
tmp_target = tmp_target->next;
}
g_list_free (source_targets);
return GDK_NONE;
}
static gboolean
gtk_drag_begin_idle (gpointer arg)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
GdkDragContext* context = (GdkDragContext*) arg;
GtkDragSourceInfo* info = gtk_drag_get_source_info (context, FALSE);
NSWindow *nswindow;
NSPasteboard *pasteboard;
GtkDragSourceOwner *owner;
NSPoint point;
NSSet *types;
NSImage *drag_image;
g_assert (info != NULL);
pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
owner = [[GtkDragSourceOwner alloc] initWithInfo:info];
types = _gtk_quartz_target_list_to_pasteboard_types (info->target_list);
[pasteboard declareTypes:[types allObjects] owner:owner];
[owner release];
[types release];
if ((nswindow = get_toplevel_nswindow (info->source_widget)) == NULL)
return G_SOURCE_REMOVE;
/* Ref the context. It's unreffed when the drag has been aborted */
g_object_ref (info->context);
/* FIXME: If the event isn't a mouse event, use the global cursor position instead */
point = [info->nsevent locationInWindow];
drag_image = _gtk_quartz_create_image_from_surface (info->icon_surface);
if (drag_image == NULL)
{
g_object_unref (info->context);
return G_SOURCE_REMOVE;
}
point.x -= info->hot_x;
point.y -= [drag_image size].height - info->hot_y;
[nswindow dragImage:drag_image
at:point
offset:NSZeroSize
event:info->nsevent
pasteboard:pasteboard
source:nswindow
slideBack:YES];
[info->nsevent release];
[drag_image release];
[pool release];
return G_SOURCE_REMOVE;
}
/* Fake protocol to let us call GdkNSView gdkWindow without including
* gdk/GdkNSView.h (which we can’t because it pulls in the internal-only
* gdkwindow.h).
*/
@protocol GdkNSView
- (GdkWindow *)gdkWindow;
@end
GdkDragContext *
gtk_drag_begin_internal (GtkWidget *widget,
gboolean *out_needs_icon,
GtkTargetList *target_list,
GdkDragAction actions,
gint button,
const GdkEvent *event,
int x,
int y)
{
GtkDragSourceInfo *info;
GdkDevice *pointer;
GdkWindow *window;
GdkDragContext *context;
NSWindow *nswindow = get_toplevel_nswindow (widget);
NSPoint point = {0, 0};
double time = (double)g_get_real_time ();
NSEvent *nsevent;
NSTimeInterval nstime;
if ((x != -1 && y != -1) || event)
{
GdkWindow *window;
gdouble dx, dy;
if (x != -1 && y != -1)
{
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
window = gtk_widget_get_window (toplevel);
gtk_widget_translate_coordinates (widget, toplevel, x, y, &x, &y);
gdk_window_get_root_coords (gtk_widget_get_window (toplevel), x, y,
&x, &y);
dx = (gdouble)x;
dy = (gdouble)y;
}
else if (event)
{
if (gdk_event_get_coords (event, &dx, &dy))
{
/* We need to translate (x, y) to coordinates relative to the
* toplevel GdkWindow, which should be the GdkWindow backing
* nswindow. Then, we convert to the NSWindow coordinate system.
*/
window = event->any.window;
GdkWindow *toplevel = gdk_window_get_effective_toplevel (window);
while (window != toplevel)
{
double old_x = dx;
double old_y = dy;
gdk_window_coords_to_parent (window, old_x, old_y,
&dx, &dy);
window = gdk_window_get_effective_parent (window);
}
}
time = (double)gdk_event_get_time (event);
}
point.x = dx;
point.y = gdk_window_get_height (window) - dy;
}
nstime = [[NSDate dateWithTimeIntervalSince1970: time / 1000] timeIntervalSinceReferenceDate];
nsevent = [NSEvent mouseEventWithType: NSLeftMouseDown
location: point
modifierFlags: 0
timestamp: nstime
windowNumber: [nswindow windowNumber]
context: [nswindow graphicsContext]
eventNumber: 0
clickCount: 1
pressure: 0.0 ];
window = [(id)[nswindow contentView] gdkWindow];
g_return_val_if_fail (nsevent != NULL, NULL);
g_return_val_if_fail (target_list != NULL, NULL);
context = gdk_drag_begin (window, g_list_copy (target_list->list));
g_return_val_if_fail (context != NULL, NULL);
info = gtk_drag_get_source_info (context, TRUE);
info->nsevent = nsevent;
[info->nsevent retain];
info->source_widget = g_object_ref (widget);
info->widget = g_object_ref (widget);
info->target_list = target_list;
gtk_target_list_ref (target_list);
info->possible_actions = actions;
g_signal_emit_by_name (widget, "drag-begin", info->context);
/* Ensure that we have an icon before we start the drag; the
* application may have set one in ::drag_begin, or it may
* not have set one.
*/
if (info->icon_surface == NULL && out_needs_icon == NULL)
gtk_drag_set_icon_default (context);
if (out_needs_icon != NULL)
*out_needs_icon = (info->icon_surface == NULL);
/* no image def or no supported type -> set the default */
if (!info->icon_surface)
gtk_drag_set_icon_default (context);
/* drag will begin in an idle handler to avoid nested run loops */
g_idle_add_full (G_PRIORITY_HIGH_IDLE, gtk_drag_begin_idle, context, NULL);
pointer = gdk_drag_context_get_device (info->context);
gdk_device_ungrab (pointer, 0);
return context;
}
/**
* gtk_drag_begin_with_coordinates: (method)
* @widget:
* @targets:
* @actions:
* @button:
* @event:
* @x:
* @y:
*
* Returns: (transfer none):
*/
GdkDragContext *
gtk_drag_begin_with_coordinates (GtkWidget *widget,
GtkTargetList *targets,
GdkDragAction actions,
gint button,
GdkEvent *event,
gint x,
gint y)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
g_return_val_if_fail (targets != NULL, NULL);
return gtk_drag_begin_internal (widget, NULL, targets,
actions, button, event, x, y);
}
/**
* gtk_drag_begin: (method)
* @widget: the source widget.
* @targets: The targets (data formats) in which the
* source can provide the data.
* @actions: A bitmask of the allowed drag actions for this drag.
* @button: The button the user clicked to start the drag.
* @event: The event that triggered the start of the drag.
*
* Returns: (transfer none):
*/
GdkDragContext *
gtk_drag_begin (GtkWidget *widget,
GtkTargetList *targets,
GdkDragAction actions,
gint button,
GdkEvent *event)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
g_return_val_if_fail (targets != NULL, NULL);
return gtk_drag_begin_internal (widget, NULL, targets,
actions, button, event, -1, -1);
}
/**
* gtk_drag_cancel:
* @context: a #GdkDragContext, as e.g. returned by gtk_drag_begin_with_coordinates()
*
*/
void
gtk_drag_cancel (GdkDragContext *context)
{
GtkDragSourceInfo *info;
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
info = gtk_drag_get_source_info (context, FALSE);
if (info != NULL)
gtk_drag_drop_finished (info, GTK_DRAG_RESULT_ERROR);
}
/**
* gtk_drag_set_icon_widget: (method)
* @context: the context for a drag. (This must be called
with a context for the source side of a drag)
* @widget: a toplevel window to use as an icon.
* @hot_x: the X offset within @widget of the hotspot.
* @hot_y: the Y offset within @widget of the hotspot.
*
* Changes the icon for a widget to a given widget. GTK+
* will not destroy the icon, so if you don’t want
* it to persist, you should connect to the “drag-end”
* signal and destroy it yourself.
**/
void
gtk_drag_set_icon_widget (GdkDragContext *context,
GtkWidget *widget,
gint hot_x,
gint hot_y)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_warning ("gtk_drag_set_icon_widget is not supported on Mac OS X");
}
static void
set_icon_stock_pixbuf (GdkDragContext *context,
const gchar *stock_id,
GdkPixbuf *pixbuf,
gint hot_x,
gint hot_y)
{
GtkDragSourceInfo *info;
cairo_surface_t *surface;
cairo_t *cr;
info = gtk_drag_get_source_info (context, FALSE);
if (stock_id)
{
pixbuf = gtk_widget_render_icon_pixbuf (info->widget, stock_id,
GTK_ICON_SIZE_DND);
if (!pixbuf)
{
g_warning ("Cannot load drag icon from stock_id %s", stock_id);
return;
}
}
else
g_object_ref (pixbuf);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf));
cr = cairo_create (surface);
gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
cairo_paint (cr);
cairo_destroy (cr);
g_object_unref (pixbuf);
cairo_surface_set_device_offset (surface, -hot_x, -hot_y);
gtk_drag_set_icon_surface (context, surface);
cairo_surface_destroy (surface);
}
void
gtk_drag_set_icon_definition (GdkDragContext *context,
GtkImageDefinition *def,
gint hot_x,
gint hot_y)
{
switch (gtk_image_definition_get_storage_type (def))
{
case GTK_IMAGE_EMPTY:
gtk_drag_set_icon_default (context);
break;
case GTK_IMAGE_PIXBUF:
gtk_drag_set_icon_pixbuf (context,
gtk_image_definition_get_pixbuf (def),
hot_x, hot_y);
break;
case GTK_IMAGE_STOCK:
gtk_drag_set_icon_stock (context,
gtk_image_definition_get_stock (def),
hot_x, hot_y);
break;
case GTK_IMAGE_ICON_NAME:
gtk_drag_set_icon_name (context,
gtk_image_definition_get_icon_name (def),
hot_x, hot_y);
break;
default:
g_warning ("FIXME: setting drag icon of type %u not implemented, using default.", gtk_image_definition_get_storage_type (def));
gtk_drag_set_icon_default (context);
break;
}
}
/**
* gtk_drag_set_icon_pixbuf:
* @context: the context for a drag. (This must be called
* with a context for the source side of a drag)
* @pixbuf: the #GdkPixbuf to use as the drag icon.
* @hot_x: the X offset within @widget of the hotspot.
* @hot_y: the Y offset within @widget of the hotspot.
*
* Sets @pixbuf as the icon for a given drag.
**/
void
gtk_drag_set_icon_pixbuf (GdkDragContext *context,
GdkPixbuf *pixbuf,
gint hot_x,
gint hot_y)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
set_icon_stock_pixbuf (context, NULL, pixbuf, hot_x, hot_y);
}
/**
* gtk_drag_set_icon_stock:
* @context: the context for a drag. (This must be called
* with a context for the source side of a drag)
* @stock_id: the ID of the stock icon to use for the drag.
* @hot_x: the X offset within the icon of the hotspot.
* @hot_y: the Y offset within the icon of the hotspot.
*
* Sets the icon for a given drag from a stock ID.
**/
void
gtk_drag_set_icon_stock (GdkDragContext *context,
const gchar *stock_id,
gint hot_x,
gint hot_y)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (stock_id != NULL);
set_icon_stock_pixbuf (context, stock_id, NULL, hot_x, hot_y);
}
/**
* gtk_drag_set_icon_surface:
* @context: the context for a drag. (This must be called
* with a context for the source side of a drag)
* @surface: the surface to use as icon
*
* Sets @surface as the icon for a given drag. GTK+ retains
* references for the arguments, and will release them when
* they are no longer needed.
*
* To position the surface relative to the mouse, use
* cairo_surface_set_device_offset() on @surface. The mouse
* cursor will be positioned at the (0,0) coordinate of the
* surface.
**/
void
gtk_drag_set_icon_surface (GdkDragContext *context,
cairo_surface_t *surface)
{
double x_offset, y_offset;
GtkDragSourceInfo *info;
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (surface != NULL);
cairo_surface_get_device_offset (surface, &x_offset, &y_offset);
info = gtk_drag_get_source_info (context, FALSE);
cairo_surface_reference (surface);
if (info->icon_surface)
cairo_surface_destroy (info->icon_surface);
info->icon_surface = surface;
info->hot_x = -x_offset;
info->hot_y = -y_offset;
}
/**
* gtk_drag_set_icon_name:
* @context: the context for a drag. (This must be called
* with a context for the source side of a drag)
* @icon_name: name of icon to use
* @hot_x: the X offset of the hotspot within the icon
* @hot_y: the Y offset of the hotspot within the icon
*
* Sets the icon for a given drag from a named themed icon. See
* the docs for #GtkIconTheme for more details. Note that the
* size of the icon depends on the icon theme (the icon is
* loaded at the symbolic size #GTK_ICON_SIZE_DND), thus
* @hot_x and @hot_y have to be used with care.
*
* Since: 2.8
**/
void
gtk_drag_set_icon_name (GdkDragContext *context,
const gchar *icon_name,
gint hot_x,
gint hot_y)
{
GdkScreen *screen;
GtkIconTheme *icon_theme;
GdkPixbuf *pixbuf;
gint width, height, icon_size;
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (icon_name != NULL);
screen = gdk_window_get_screen (gdk_drag_context_get_source_window (context));
g_return_if_fail (screen != NULL);
gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &width, &height);
icon_size = MAX (width, height);
icon_theme = gtk_icon_theme_get_for_screen (screen);
pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name,
icon_size, 0, NULL);
if (pixbuf)
set_icon_stock_pixbuf (context, NULL, pixbuf, hot_x, hot_y);
else
g_warning ("Cannot load drag icon from icon name %s", icon_name);
}
/**
* gtk_drag_set_icon_default: (method)
* @context: the context for a drag. (This must be called
with a context for the source side of a drag)
*
* Sets the icon for a particular drag to the default
* icon.
**/
void
gtk_drag_set_icon_default (GdkDragContext *context)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
gtk_drag_set_icon_name (context, "text-x-generic", -2, -2);
}
static void
gtk_drag_source_info_destroy (GtkDragSourceInfo *info)
{
NSPasteboard *pasteboard;
NSAutoreleasePool *pool;
if (info->icon_surface)
cairo_surface_destroy (info->icon_surface);
g_signal_emit_by_name (info->widget, "drag-end",
info->context);
if (info->source_widget)
g_object_unref (info->source_widget);
if (info->widget)
g_object_unref (info->widget);
gtk_target_list_unref (info->target_list);
pool = [[NSAutoreleasePool alloc] init];
/* Empty the pasteboard, so that it will not accidentally access
* info->context after it has been destroyed.
*/
pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard];
[pasteboard clearContents];
[pool release];
gtk_drag_clear_source_info (info->context);
g_object_unref (info->context);
g_free (info);
info = NULL;
}
static gboolean
drag_drop_finished_idle_cb (gpointer data)
{
GtkDragSourceInfo* info = (GtkDragSourceInfo*) data;
if (info->success)
gtk_drag_source_info_destroy (data);
return G_SOURCE_REMOVE;
}
static void
gtk_drag_drop_finished (GtkDragSourceInfo *info,
GtkDragResult result)
{
gboolean success = (result == GTK_DRAG_RESULT_SUCCESS);
if (!success)
g_signal_emit_by_name (info->source_widget, "drag-failed",
info->context, result, &success);
if (success && info->delete)
g_signal_emit_by_name (info->source_widget, "drag-data-delete",
info->context);
/* Workaround for the fact that the NS API blocks until the drag is
* over. This way the context is still valid when returning from
* drag_begin, even if it will still be quite useless. See bug #501588.
*/
g_idle_add (drag_drop_finished_idle_cb, info);
}
/*************************************************************
* _gtk_drag_source_handle_event:
* Called from widget event handling code on Drag events
* for drag sources.
*
* arguments:
* toplevel: Toplevel widget that received the event
* event:
* results:
*************************************************************/
void
_gtk_drag_source_handle_event (GtkWidget *widget,
GdkEvent *event)
{
GtkDragSourceInfo *info;
GdkDragContext *context;
GtkDragResult result;
g_return_if_fail (widget != NULL);
g_return_if_fail (event != NULL);
context = event->dnd.context;
info = gtk_drag_get_source_info (context, FALSE);
if (!info)
return;
switch (event->type)
{
case GDK_DROP_FINISHED:
result = (gdk_drag_context_get_dest_window (context) != NULL) ? GTK_DRAG_RESULT_SUCCESS : GTK_DRAG_RESULT_NO_TARGET;
gtk_drag_drop_finished (info, result);
break;
default:
g_assert_not_reached ();
}
}
/**
* gtk_drag_check_threshold: (method)
* @widget: a #GtkWidget
* @start_x: X coordinate of start of drag
* @start_y: Y coordinate of start of drag
* @current_x: current X coordinate
* @current_y: current Y coordinate
*/
gboolean
gtk_drag_check_threshold (GtkWidget *widget,
gint start_x,
gint start_y,
gint current_x,
gint current_y)
{
gint drag_threshold;
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
g_object_get (gtk_widget_get_settings (widget),
"gtk-dnd-drag-threshold", &drag_threshold,
NULL);
return (ABS (current_x - start_x) > drag_threshold ||
ABS (current_y - start_y) > drag_threshold);
}