gtk2/gtk/gtkdnd-quartz.c
Philippe Normand cde5a3f4b4 gtk: Fix paste/clipboard usage on macOS
In Mojave the build fails because declareTypes is nil. Instead use the
clearContents method, although I'm not really sure this is the same.
2019-04-11 16:45:36 +02:00

1696 lines
45 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 <stdlib.h>
#include <string.h>
#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 "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 <NSDraggingInfo> 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_new (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
*/
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 thats 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 thats 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 thats 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 thats 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 thats 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 thats 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 <NSDraggingInfo> 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 cant 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<GdkNSView>)[nswindow contentView] gdkWindow];
g_return_val_if_fail (nsevent != 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 dont 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);
}