forked from AuroraMiddleware/gtk
1933 lines
50 KiB
C
1933 lines
50 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, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* 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 "gtkiconfactory.h"
|
|
#include "gtkicontheme.h"
|
|
#include "gtkimageprivate.h"
|
|
#include "gtkinvisible.h"
|
|
#include "gtkmain.h"
|
|
#include "gtkplug.h"
|
|
#include "gtkstock.h"
|
|
#include "gtkwindow.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkquartz.h"
|
|
#include "gdk/quartz/gdkquartz.h"
|
|
#include "gtkselectionprivate.h"
|
|
|
|
typedef struct _GtkDragSourceSite GtkDragSourceSite;
|
|
typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
|
|
typedef struct _GtkDragDestSite GtkDragDestSite;
|
|
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);
|
|
|
|
extern GdkDragContext *gdk_quartz_drag_source_context (); /* gdk/quartz/gdkdnd-quartz.c */
|
|
|
|
struct _GtkDragSourceSite
|
|
{
|
|
GdkModifierType start_button_mask;
|
|
GtkTargetList *target_list; /* Targets for drag data */
|
|
GdkDragAction actions; /* Possible actions */
|
|
|
|
/* Drag icon */
|
|
GtkImageType icon_type;
|
|
union
|
|
{
|
|
GtkImagePixbufData pixbuf;
|
|
GtkImageStockData stock;
|
|
GtkImageIconNameData name;
|
|
} icon_data;
|
|
|
|
/* Stored button press information to detect drag beginning */
|
|
gint state;
|
|
gint x, y;
|
|
};
|
|
|
|
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 */
|
|
GdkPixbuf *icon_pixbuf;
|
|
gboolean success;
|
|
gboolean delete;
|
|
};
|
|
|
|
struct _GtkDragDestSite
|
|
{
|
|
GtkDestDefaults flags;
|
|
GtkTargetList *target_list;
|
|
GdkDragAction actions;
|
|
guint have_drag : 1;
|
|
guint track_motion : 1;
|
|
};
|
|
|
|
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 = _gtk_quartz_pasteboard_type_to_atom (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
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void
|
|
gtk_drag_finish (GdkDragContext *context,
|
|
gboolean success,
|
|
gboolean del,
|
|
guint32 time)
|
|
{
|
|
GtkDragSourceInfo *info;
|
|
GdkDragContext* source_context = gdk_quartz_drag_source_context ();
|
|
|
|
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);
|
|
}
|
|
|
|
GtkWidget *
|
|
gtk_drag_get_source_widget (GdkDragContext *context)
|
|
{
|
|
GtkDragSourceInfo *info;
|
|
GdkDragContext* real_source_context = gdk_quartz_drag_source_context();
|
|
|
|
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_draw:
|
|
* Callback for expose_event for highlighted widgets.
|
|
* arguments:
|
|
* widget:
|
|
* event:
|
|
* data:
|
|
* results:
|
|
*************************************************************/
|
|
|
|
static gboolean
|
|
gtk_drag_highlight_draw (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gpointer data)
|
|
{
|
|
int width = gtk_widget_get_allocated_width (widget);
|
|
int height = gtk_widget_get_allocated_height (widget);
|
|
GtkStyleContext *context = gtk_widget_get_style_context (widget);
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_add_class (context, GTK_STYLE_CLASS_DND);
|
|
|
|
gtk_render_frame (context, cr, 0, 0, width, height);
|
|
|
|
gtk_style_context_restore (context);
|
|
|
|
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
|
|
cairo_set_line_width (cr, 1.0);
|
|
cairo_rectangle (cr,
|
|
0.5, 0.5,
|
|
width - 1, height - 1);
|
|
cairo_stroke (cr);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*************************************************************
|
|
* gtk_drag_highlight:
|
|
* Highlight the given widget in the default manner.
|
|
* arguments:
|
|
* widget:
|
|
* results:
|
|
*************************************************************/
|
|
|
|
void
|
|
gtk_drag_highlight (GtkWidget *widget)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
g_signal_connect_after (widget, "draw",
|
|
G_CALLBACK (gtk_drag_highlight_draw),
|
|
NULL);
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
/*************************************************************
|
|
* gtk_drag_unhighlight:
|
|
* Refresh the given widget to remove the highlight.
|
|
* arguments:
|
|
* widget:
|
|
* results:
|
|
*************************************************************/
|
|
|
|
void
|
|
gtk_drag_unhighlight (GtkWidget *widget)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
g_signal_handlers_disconnect_by_func (widget,
|
|
gtk_drag_highlight_draw,
|
|
NULL);
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
static NSWindow *
|
|
get_toplevel_nswindow (GtkWidget *widget)
|
|
{
|
|
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
|
|
GdkWindow *window = gtk_widget_get_window (toplevel);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 ();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 FALSE;
|
|
|
|
/* 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_pixbuf (info->icon_pixbuf);
|
|
if (drag_image == NULL)
|
|
{
|
|
g_object_unref (info->context);
|
|
return FALSE;
|
|
}
|
|
|
|
point.x -= info->hot_x;
|
|
point.y -= info->hot_y;
|
|
|
|
[nswindow dragImage:drag_image
|
|
at:point
|
|
offset:NSMakeSize(0, 0)
|
|
event:info->nsevent
|
|
pasteboard:pasteboard
|
|
source:nswindow
|
|
slideBack:YES];
|
|
|
|
[info->nsevent release];
|
|
[drag_image release];
|
|
|
|
[pool release];
|
|
|
|
return FALSE;
|
|
}
|
|
/* 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
|
|
|
|
static GdkDragContext *
|
|
gtk_drag_begin_internal (GtkWidget *widget,
|
|
GtkDragSourceSite *site,
|
|
GtkTargetList *target_list,
|
|
GdkDragAction actions,
|
|
gint button,
|
|
GdkEvent *event)
|
|
{
|
|
GtkDragSourceInfo *info;
|
|
GdkDevice *pointer;
|
|
GdkWindow *window;
|
|
GdkDragContext *context;
|
|
NSWindow *nswindow = get_toplevel_nswindow (widget);
|
|
NSPoint point = {0, 0};
|
|
gdouble x, y;
|
|
double time = (double)g_get_real_time ();
|
|
NSEvent *nsevent;
|
|
NSTimeInterval nstime;
|
|
|
|
if (event)
|
|
{
|
|
if (gdk_event_get_coords (event, &x, &y))
|
|
{
|
|
point.x = x;
|
|
point.y = y;
|
|
}
|
|
time = (double)gdk_event_get_time (event);
|
|
}
|
|
|
|
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, NULL);
|
|
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_pixbuf)
|
|
{
|
|
if (!site || site->icon_type == GTK_IMAGE_EMPTY)
|
|
gtk_drag_set_icon_default (context);
|
|
else
|
|
{
|
|
switch (site->icon_type)
|
|
{
|
|
case GTK_IMAGE_PIXBUF:
|
|
gtk_drag_set_icon_pixbuf (context,
|
|
site->icon_data.pixbuf.pixbuf,
|
|
-2, -2);
|
|
break;
|
|
case GTK_IMAGE_STOCK:
|
|
gtk_drag_set_icon_stock (context,
|
|
site->icon_data.stock.stock_id,
|
|
-2, -2);
|
|
break;
|
|
case GTK_IMAGE_ICON_NAME:
|
|
gtk_drag_set_icon_name (context,
|
|
site->icon_data.name.icon_name,
|
|
-2, -2);
|
|
break;
|
|
case GTK_IMAGE_EMPTY:
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gtk_drag_source_event_cb (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
gpointer data)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
gboolean retval = FALSE;
|
|
site = (GtkDragSourceSite *)data;
|
|
|
|
switch (event->type)
|
|
{
|
|
case GDK_BUTTON_PRESS:
|
|
if ((GDK_BUTTON1_MASK << (event->button.button - 1)) & site->start_button_mask)
|
|
{
|
|
site->state |= (GDK_BUTTON1_MASK << (event->button.button - 1));
|
|
site->x = event->button.x;
|
|
site->y = event->button.y;
|
|
}
|
|
break;
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
if ((GDK_BUTTON1_MASK << (event->button.button - 1)) & site->start_button_mask)
|
|
site->state &= ~(GDK_BUTTON1_MASK << (event->button.button - 1));
|
|
break;
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
if (site->state & event->motion.state & site->start_button_mask)
|
|
{
|
|
/* FIXME: This is really broken and can leave us
|
|
* with a stuck grab
|
|
*/
|
|
int i;
|
|
for (i=1; i<6; i++)
|
|
{
|
|
if (site->state & event->motion.state &
|
|
GDK_BUTTON1_MASK << (i - 1))
|
|
break;
|
|
}
|
|
|
|
if (gtk_drag_check_threshold (widget, site->x, site->y,
|
|
event->motion.x, event->motion.y))
|
|
{
|
|
site->state = 0;
|
|
gtk_drag_begin_internal (widget, site, site->target_list,
|
|
site->actions,
|
|
i, event);
|
|
|
|
retval = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: /* hit for 2/3BUTTON_PRESS */
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
gtk_drag_source_set (GtkWidget *widget,
|
|
GdkModifierType start_button_mask,
|
|
const GtkTargetEntry *targets,
|
|
gint n_targets,
|
|
GdkDragAction actions)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
|
|
gtk_widget_add_events (widget,
|
|
gtk_widget_get_events (widget) |
|
|
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
|
GDK_BUTTON_MOTION_MASK);
|
|
|
|
if (site)
|
|
{
|
|
if (site->target_list)
|
|
gtk_target_list_unref (site->target_list);
|
|
}
|
|
else
|
|
{
|
|
site = g_new0 (GtkDragSourceSite, 1);
|
|
|
|
site->icon_type = GTK_IMAGE_EMPTY;
|
|
|
|
g_signal_connect (widget, "button-press-event",
|
|
G_CALLBACK (gtk_drag_source_event_cb),
|
|
site);
|
|
g_signal_connect (widget, "button-release-event",
|
|
G_CALLBACK (gtk_drag_source_event_cb),
|
|
site);
|
|
g_signal_connect (widget, "motion-notify-event",
|
|
G_CALLBACK (gtk_drag_source_event_cb),
|
|
site);
|
|
|
|
g_object_set_data_full (G_OBJECT (widget),
|
|
I_("gtk-site-data"),
|
|
site, gtk_drag_source_site_destroy);
|
|
}
|
|
|
|
site->start_button_mask = start_button_mask;
|
|
|
|
site->target_list = gtk_target_list_new (targets, n_targets);
|
|
|
|
site->actions = actions;
|
|
}
|
|
|
|
/*************************************************************
|
|
* gtk_drag_source_unset
|
|
* Unregister this widget as a drag source.
|
|
* arguments:
|
|
* widget:
|
|
* results:
|
|
*************************************************************/
|
|
|
|
void
|
|
gtk_drag_source_unset (GtkWidget *widget)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
|
|
if (site)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (widget,
|
|
gtk_drag_source_event_cb,
|
|
site);
|
|
g_object_set_data (G_OBJECT (widget), I_("gtk-site-data"), NULL);
|
|
}
|
|
}
|
|
|
|
GtkTargetList *
|
|
gtk_drag_source_get_target_list (GtkWidget *widget)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
|
|
return site ? site->target_list : NULL;
|
|
|
|
}
|
|
|
|
void
|
|
gtk_drag_source_set_target_list (GtkWidget *widget,
|
|
GtkTargetList *target_list)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
if (site == NULL)
|
|
{
|
|
g_warning ("gtk_drag_source_set_target_list() requires the widget "
|
|
"to already be a drag source.");
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* gtk_drag_source_add_text_targets:
|
|
* @widget: a #GtkWidget that's is a drag source
|
|
*
|
|
* Add the text targets supported by #GtkSelection to
|
|
* the target list of the drag source. The targets
|
|
* are added with @info = 0. If you need another value,
|
|
* use gtk_target_list_add_text_targets() and
|
|
* gtk_drag_source_set_target_list().
|
|
*
|
|
* Since: 2.6
|
|
**/
|
|
void
|
|
gtk_drag_source_add_text_targets (GtkWidget *widget)
|
|
{
|
|
GtkTargetList *target_list;
|
|
|
|
target_list = gtk_drag_source_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_source_set_target_list (widget, target_list);
|
|
gtk_target_list_unref (target_list);
|
|
}
|
|
|
|
void
|
|
gtk_drag_source_add_image_targets (GtkWidget *widget)
|
|
{
|
|
GtkTargetList *target_list;
|
|
|
|
target_list = gtk_drag_source_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, TRUE);
|
|
gtk_drag_source_set_target_list (widget, target_list);
|
|
gtk_target_list_unref (target_list);
|
|
}
|
|
|
|
void
|
|
gtk_drag_source_add_uri_targets (GtkWidget *widget)
|
|
{
|
|
GtkTargetList *target_list;
|
|
|
|
target_list = gtk_drag_source_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_source_set_target_list (widget, target_list);
|
|
gtk_target_list_unref (target_list);
|
|
}
|
|
|
|
static void
|
|
gtk_drag_source_unset_icon (GtkDragSourceSite *site)
|
|
{
|
|
switch (site->icon_type)
|
|
{
|
|
case GTK_IMAGE_EMPTY:
|
|
break;
|
|
case GTK_IMAGE_PIXBUF:
|
|
g_object_unref (site->icon_data.pixbuf.pixbuf);
|
|
break;
|
|
case GTK_IMAGE_STOCK:
|
|
g_free (site->icon_data.stock.stock_id);
|
|
break;
|
|
case GTK_IMAGE_ICON_NAME:
|
|
g_free (site->icon_data.name.icon_name);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
site->icon_type = GTK_IMAGE_EMPTY;
|
|
}
|
|
|
|
static void
|
|
gtk_drag_source_site_destroy (gpointer data)
|
|
{
|
|
GtkDragSourceSite *site = data;
|
|
|
|
if (site->target_list)
|
|
gtk_target_list_unref (site->target_list);
|
|
|
|
gtk_drag_source_unset_icon (site);
|
|
g_free (site);
|
|
}
|
|
|
|
void
|
|
gtk_drag_source_set_icon_pixbuf (GtkWidget *widget,
|
|
GdkPixbuf *pixbuf)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
g_return_if_fail (site != NULL);
|
|
g_object_ref (pixbuf);
|
|
|
|
gtk_drag_source_unset_icon (site);
|
|
|
|
site->icon_type = GTK_IMAGE_PIXBUF;
|
|
site->icon_data.pixbuf.pixbuf = pixbuf;
|
|
}
|
|
|
|
/**
|
|
* gtk_drag_source_set_icon_stock:
|
|
* @widget: a #GtkWidget
|
|
* @stock_id: the ID of the stock icon to use
|
|
*
|
|
* Sets the icon that will be used for drags from a particular source
|
|
* to a stock icon.
|
|
**/
|
|
void
|
|
gtk_drag_source_set_icon_stock (GtkWidget *widget,
|
|
const gchar *stock_id)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (stock_id != NULL);
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
g_return_if_fail (site != NULL);
|
|
|
|
gtk_drag_source_unset_icon (site);
|
|
|
|
site->icon_type = GTK_IMAGE_STOCK;
|
|
site->icon_data.stock.stock_id = g_strdup (stock_id);
|
|
}
|
|
|
|
/**
|
|
* gtk_drag_source_set_icon_name:
|
|
* @widget: a #GtkWidget
|
|
* @icon_name: name of icon to use
|
|
*
|
|
* Sets the icon that will be used for drags from a particular source
|
|
* to a themed icon. See the docs for #GtkIconTheme for more details.
|
|
*
|
|
* Since: 2.8
|
|
**/
|
|
void
|
|
gtk_drag_source_set_icon_name (GtkWidget *widget,
|
|
const gchar *icon_name)
|
|
{
|
|
GtkDragSourceSite *site;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (icon_name != NULL);
|
|
|
|
site = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
|
|
g_return_if_fail (site != NULL);
|
|
|
|
gtk_drag_source_unset_icon (site);
|
|
|
|
site->icon_type = GTK_IMAGE_ICON_NAME;
|
|
site->icon_data.name.icon_name = g_strdup (icon_name);
|
|
}
|
|
|
|
|
|
/**
|
|
* gtk_drag_set_icon_widget:
|
|
* @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;
|
|
|
|
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);
|
|
|
|
if (info->icon_pixbuf)
|
|
g_object_unref (info->icon_pixbuf);
|
|
info->icon_pixbuf = pixbuf;
|
|
info->hot_x = hot_x;
|
|
info->hot_y = hot_y;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
|
|
/* XXX: This function is in gdk, too. Should it be in Cairo? */
|
|
static gboolean
|
|
_gtk_cairo_surface_extents (cairo_surface_t *surface,
|
|
GdkRectangle *extents)
|
|
{
|
|
double x1, x2, y1, y2;
|
|
cairo_t *cr;
|
|
|
|
g_return_val_if_fail (surface != NULL, FALSE);
|
|
g_return_val_if_fail (extents != NULL, FALSE);
|
|
|
|
cr = cairo_create (surface);
|
|
cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
|
|
|
|
x1 = floor (x1);
|
|
y1 = floor (y1);
|
|
x2 = ceil (x2);
|
|
y2 = ceil (y2);
|
|
x2 -= x1;
|
|
y2 -= y1;
|
|
|
|
if (x1 < G_MININT || x1 > G_MAXINT ||
|
|
y1 < G_MININT || y1 > G_MAXINT ||
|
|
x2 > G_MAXINT || y2 > G_MAXINT)
|
|
{
|
|
extents->x = extents->y = extents->width = extents->height = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
extents->x = x1;
|
|
extents->y = y1;
|
|
extents->width = x2;
|
|
extents->height = y2;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
GdkRectangle extents;
|
|
double x_offset, y_offset;
|
|
|
|
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
|
|
g_return_if_fail (surface != NULL);
|
|
|
|
_gtk_cairo_surface_extents (surface, &extents);
|
|
cairo_surface_get_device_offset (surface, &x_offset, &y_offset);
|
|
|
|
pixbuf = gdk_pixbuf_get_from_surface (surface,
|
|
extents.x, extents.y,
|
|
extents.width, extents.height);
|
|
gtk_drag_set_icon_pixbuf (context, pixbuf, -x_offset, -y_offset);
|
|
g_object_unref (pixbuf);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
GtkSettings *settings;
|
|
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);
|
|
|
|
settings = gtk_settings_get_for_screen (screen);
|
|
if (gtk_icon_size_lookup_for_settings (settings,
|
|
GTK_ICON_SIZE_DND,
|
|
&width, &height))
|
|
icon_size = MAX (width, height);
|
|
else
|
|
icon_size = 32; /* default value for GTK_ICON_SIZE_DND */
|
|
|
|
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:
|
|
* @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_stock (context, GTK_STOCK_DND, -2, -2);
|
|
}
|
|
|
|
static void
|
|
gtk_drag_source_info_destroy (GtkDragSourceInfo *info)
|
|
{
|
|
NSPasteboard *pasteboard;
|
|
NSAutoreleasePool *pool;
|
|
|
|
if (info->icon_pixbuf)
|
|
g_object_unref (info->icon_pixbuf);
|
|
|
|
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 declareTypes: nil owner: nil];
|
|
|
|
[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)
|
|
{
|
|
gtk_drag_source_info_destroy (data);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_drag_drop_finished (GtkDragSourceInfo *info)
|
|
{
|
|
if (info->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;
|
|
|
|
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:
|
|
gtk_drag_drop_finished (info);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|