gtkdnd.c 129 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2
 * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
3 4
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6 7 8 9 10 11
 * 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
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17
 */

18
/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21 22 23 24
 * 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/. 
 */

25
#include "config.h"
26

27
#include <math.h>
28 29 30
#include <stdlib.h>
#include <string.h>

31
#include "gdk/gdk.h"
32

33 34 35 36
#ifdef GDK_WINDOWING_X11
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include "gdk/x11/gdkx.h"
37 38 39
#ifdef XINPUT_2
#include <X11/extensions/XInput2.h>
#endif
40 41
#endif

42
#include "gtkdnd.h"
43
#include "gtkiconhelperprivate.h"
44
#include "gtkicontheme.h"
45 46
#include "gtkinvisible.h"
#include "gtkmain.h"
47
#include "gtkplug.h"
48
#include "gtktooltip.h"
Owen Taylor's avatar
Owen Taylor committed
49
#include "gtkwindow.h"
50
#include "gtkintl.h"
51
#include "gtkselectionprivate.h"
52

53 54 55 56 57 58 59

/**
 * SECTION:gtkdnd
 * @Short_description: Functions for controlling drag and drop handling
 * @Title: Drag and Drop
 *
 * GTK+ has a rich set of functions for doing inter-process
60
 * communication via the drag-and-drop metaphor.
61 62 63 64 65 66 67 68 69
 *
 * As well as the functions listed here, applications
 * may need to use some facilities provided for
 * <link linkend="gtk-Selections">Selections</link>.
 * Also, the Drag and Drop API makes use of signals
 * in the #GtkWidget class.
 */


70
static GSList *source_widgets = NULL;
71 72 73 74 75 76 77 78

typedef struct _GtkDragSourceSite GtkDragSourceSite;
typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
typedef struct _GtkDragDestSite GtkDragDestSite;
typedef struct _GtkDragDestInfo GtkDragDestInfo;
typedef struct _GtkDragAnim GtkDragAnim;


Owen Taylor's avatar
Owen Taylor committed
79 80
typedef enum 
{
81 82 83 84 85
  GTK_DRAG_STATUS_DRAG,
  GTK_DRAG_STATUS_WAIT,
  GTK_DRAG_STATUS_DROP
} GtkDragStatus;

Owen Taylor's avatar
Owen Taylor committed
86 87
struct _GtkDragSourceSite 
{
88 89 90
  GdkModifierType    start_button_mask;
  GtkTargetList     *target_list;        /* Targets for drag data */
  GdkDragAction      actions;            /* Possible actions */
91

92
  GtkIconHelper     *icon_helper;
93

94 95 96 97
  /* Stored button press information to detect drag beginning */
  gint               state;
  gint               x, y;
};
98
  
Owen Taylor's avatar
Owen Taylor committed
99 100
struct _GtkDragSourceInfo 
{
101 102
  GtkWidget         *widget;
  GtkTargetList     *target_list; /* Targets for drag data */
103
  GdkDragAction      possible_actions; /* Actions allowed by source */
104 105
  GdkDragContext    *context;	  /* drag context */
  GtkWidget         *icon_window; /* Window for drag */
106
  GtkWidget         *fallback_icon; /* Window for drag used on other screens */
107 108 109 110
  GtkWidget         *ipc_widget;  /* GtkInvisible for grab, message passing */
  GdkCursor         *cursor;	  /* Cursor for drag */
  gint hot_x, hot_y;		  /* Hot spot for drag */
  gint button;			  /* mouse button starting drag */
111

112
  GtkDragStatus      status;	  /* drag status */
113
  GdkEvent          *last_event;  /* pending event */
114

115 116
  gint               start_x, start_y; /* Initial position */
  gint               cur_x, cur_y;     /* Current Position */
117
  GdkScreen         *cur_screen;       /* Current screen for pointer */
118

119
  guint32            grab_time;   /* timestamp for initial grab */
120 121 122
  GList             *selections;  /* selections we've claimed */
  
  GtkDragDestInfo   *proxy_dest;  /* Set if this is a proxy drag */
123

124
  guint              update_idle;      /* Idle function to update the drag */
Owen Taylor's avatar
Owen Taylor committed
125
  guint              drop_timeout;     /* Timeout for aborting drop */
126 127
  guint              destroy_icon : 1; /* If true, destroy icon_window */
  guint              have_grab : 1;    /* Do we still have the pointer grab */
128
  GtkIconHelper     *icon_helper;
129
  GdkCursor         *drag_cursors[6];
130 131
};

132
struct _GtkDragDestSite
Owen Taylor's avatar
Owen Taylor committed
133
{
134 135 136 137 138
  GtkDestDefaults    flags;
  GtkTargetList     *target_list;
  GdkDragAction      actions;
  GdkWindow         *proxy_window;
  GdkDragProtocol    proxy_protocol;
139
  guint              do_proxy     : 1;
140
  guint              proxy_coords : 1;
141
  guint              have_drag    : 1;
142
  guint              track_motion : 1;
143
};
144 145 146 147 148 149 150 151

struct _GtkDragDestInfo
{
  GtkWidget         *widget;              /* Widget in which drag is in */
  GdkDragContext    *context;             /* Drag context */
  GtkDragSourceInfo *proxy_source;        /* Set if this is a proxy drag */
  GtkSelectionData  *proxy_data;          /* Set while retrieving proxied data */
  guint32            proxy_drop_time;     /* Timestamp for proxied drop */
152
  guint              proxy_drop_wait : 1; /* Set if we are waiting for a
153 154 155 156 157
                                           * status reply before sending
                                           * a proxied drop on.
                                           */
  guint              dropped : 1;         /* Set after we receive a drop */
  gint               drop_x, drop_y;      /* Position of drop */
158 159
};

160
#define DROP_ABORT_TIME 300000
161

162
#define ANIM_TIME (0.5 * 1000 * 1000) /* half a second */
163

Owen Taylor's avatar
Owen Taylor committed
164 165
struct _GtkDragAnim 
{
166
  GtkDragSourceInfo *info;
167
  gint64 start_time;
168 169
};

170 171 172 173 174
typedef gboolean (* GtkDragDestCallback) (GtkWidget      *widget,
                                          GdkDragContext *context,
                                          gint            x,
                                          gint            y,
                                          guint32         time);
175 176 177

/* Enumeration for some targets we handle internally */

178
enum {
Matthias Clasen's avatar
Matthias Clasen committed
179
  TARGET_DELETE = 0x40000002
180 181 182
};

/* Forward declarations */
Owen Taylor's avatar
Owen Taylor committed
183 184 185 186 187
static void          gtk_drag_get_event_actions (GdkEvent        *event, 
					         gint             button,
					         GdkDragAction    actions,
					         GdkDragAction   *suggested_action,
					         GdkDragAction   *possible_actions);
188 189
static GdkCursor *   gtk_drag_get_cursor         (GtkWidget      *widget,
                                                  GdkDisplay     *display,
190 191
						  GdkDragAction   action,
						  GtkDragSourceInfo *info);
192
static void          gtk_drag_update_cursor      (GtkDragSourceInfo *info);
Matthias Clasen's avatar
Matthias Clasen committed
193 194
static GtkWidget    *gtk_drag_get_ipc_widget            (GtkWidget *widget);
static GtkWidget    *gtk_drag_get_ipc_widget_for_screen (GdkScreen *screen);
Owen Taylor's avatar
Owen Taylor committed
195
static void          gtk_drag_release_ipc_widget (GtkWidget      *widget);
196

197 198
static void     gtk_drag_selection_received     (GtkWidget        *widget,
						 GtkSelectionData *selection_data,
199
						 guint             time,
200
						 gpointer          data);
201 202 203 204 205 206 207
static gboolean gtk_drag_find_widget            (GtkWidget        *widget,
                                                 GdkDragContext   *context,
                                                 GtkDragDestInfo  *info,
                                                 gint              x,
                                                 gint              y,
                                                 guint32           time,
                                                 GtkDragDestCallback callback);
208 209 210 211 212
static void     gtk_drag_proxy_begin            (GtkWidget        *widget,
						 GtkDragDestInfo  *dest_info,
						 guint32           time);
static void     gtk_drag_dest_realized          (GtkWidget        *widget);
static void     gtk_drag_dest_hierarchy_changed (GtkWidget        *widget,
Owen Taylor's avatar
Owen Taylor committed
213
						 GtkWidget        *previous_toplevel);
214 215 216 217 218 219 220 221 222 223 224 225 226 227
static void     gtk_drag_dest_site_destroy      (gpointer          data);
static void     gtk_drag_dest_leave             (GtkWidget        *widget,
						 GdkDragContext   *context,
						 guint             time);
static gboolean gtk_drag_dest_motion            (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint              x,
						 gint              y,
						 guint             time);
static gboolean gtk_drag_dest_drop              (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint              x,
						 gint              y,
						 guint             time);
Owen Taylor's avatar
Owen Taylor committed
228 229 230 231 232 233

static GtkDragDestInfo *  gtk_drag_get_dest_info     (GdkDragContext *context,
						      gboolean        create);
static GtkDragSourceInfo *gtk_drag_get_source_info   (GdkDragContext *context,
						      gboolean        create);
static void               gtk_drag_clear_source_info (GdkDragContext *context);
234 235 236 237 238 239 240 241 242

static void gtk_drag_source_check_selection    (GtkDragSourceInfo *info, 
					        GdkAtom            selection,
					        guint32            time);
static void gtk_drag_source_release_selections (GtkDragSourceInfo *info,
						guint32            time);
static void gtk_drag_drop                      (GtkDragSourceInfo *info,
						guint32            time);
static void gtk_drag_drop_finished             (GtkDragSourceInfo *info,
243
						GtkDragResult      result,
244
						guint              time);
245
static void gtk_drag_cancel                    (GtkDragSourceInfo *info,
246
						GtkDragResult      result,
247
						guint32            time);
248

249
static gboolean gtk_drag_source_event_cb       (GtkWidget         *widget,
250 251 252 253 254 255 256 257
						GdkEvent          *event,
						gpointer           data);
static void gtk_drag_source_site_destroy       (gpointer           data);
static void gtk_drag_selection_get             (GtkWidget         *widget, 
						GtkSelectionData  *selection_data,
						guint              sel_info,
						guint32            time,
						gpointer           data);
258 259 260 261
static void gtk_drag_anim_destroy              (GtkDragAnim       *anim);
static gboolean gtk_drag_anim_tick             (GtkWidget         *widget,
                                                GdkFrameClock     *frame_clock,
                                                gpointer           data);
262
static void gtk_drag_remove_icon               (GtkDragSourceInfo *info);
Owen Taylor's avatar
Owen Taylor committed
263
static void gtk_drag_source_info_destroy       (GtkDragSourceInfo *info);
264 265
static void gtk_drag_add_update_idle           (GtkDragSourceInfo *info);

266
static void gtk_drag_update                    (GtkDragSourceInfo *info,
267
						GdkScreen         *screen,
268 269 270
						gint               x_root,
						gint               y_root,
						GdkEvent          *event);
271
static gboolean gtk_drag_motion_cb             (GtkWidget         *widget, 
272 273
					        GdkEventMotion    *event, 
					        gpointer           data);
274
static gboolean gtk_drag_key_cb                (GtkWidget         *widget, 
275 276
					        GdkEventKey       *event, 
					        gpointer           data);
277 278 279
static gboolean gtk_drag_grab_broken_event_cb  (GtkWidget          *widget,
						GdkEventGrabBroken *event,
						gpointer            data);
280 281 282
static void     gtk_drag_grab_notify_cb        (GtkWidget         *widget,
						gboolean           was_grabbed,
						gpointer           data);
283
static gboolean gtk_drag_button_release_cb     (GtkWidget         *widget, 
284 285
					        GdkEventButton    *event, 
					        gpointer           data);
286
static gboolean gtk_drag_abort_timeout         (gpointer           data);
287

288 289 290 291 292
static void     set_icon_helper (GdkDragContext    *context,
                                 GtkIconHelper     *helper,
                                 gint               hot_x,
                                 gint               hot_y,
                                 gboolean           force_window);
293

294 295 296 297
/************************
 * Cursor and Icon data *
 ************************/

298
static struct {
299
  GdkDragAction action;
300 301
  const gchar  *name;
  GdkPixbuf    *pixbuf;
302 303
  GdkCursor    *cursor;
} drag_cursors[] = {
304
  { GDK_ACTION_DEFAULT, NULL },
305 306 307 308 309
  { GDK_ACTION_ASK,   "dnd-ask",  NULL, NULL },
  { GDK_ACTION_COPY,  "dnd-copy", NULL, NULL },
  { GDK_ACTION_MOVE,  "dnd-move", NULL, NULL },
  { GDK_ACTION_LINK,  "dnd-link", NULL, NULL },
  { 0              ,  "dnd-none", NULL, NULL },
310 311 312 313 314 315
};

/*********************
 * Utility functions *
 *********************/

Manish Singh's avatar
Manish Singh committed
316
static void
317 318 319 320 321
set_can_change_screen (GtkWidget *widget,
		       gboolean   can_change_screen)
{
  can_change_screen = can_change_screen != FALSE;
  
322
  g_object_set_data (G_OBJECT (widget), I_("gtk-dnd-can-change-screen"),
323 324 325 326 327 328 329 330 331 332
		     GUINT_TO_POINTER (can_change_screen));
}

static gboolean
get_can_change_screen (GtkWidget *widget)
{
  return g_object_get_data (G_OBJECT (widget), "gtk-dnd-can-change-screen") != NULL;

}

333
static GtkWidget *
Matthias Clasen's avatar
Matthias Clasen committed
334
gtk_drag_get_ipc_widget_for_screen (GdkScreen *screen)
335 336
{
  GtkWidget *result;
337 338
  GSList *drag_widgets = g_object_get_data (G_OBJECT (screen), 
					    "gtk-dnd-ipc-widgets");
339 340 341 342 343 344
  
  if (drag_widgets)
    {
      GSList *tmp = drag_widgets;
      result = drag_widgets->data;
      drag_widgets = drag_widgets->next;
345
      g_object_set_data (G_OBJECT (screen),
346
			 I_("gtk-dnd-ipc-widgets"),
347
			 drag_widgets);
348 349 350 351
      g_slist_free_1 (tmp);
    }
  else
    {
Matthias Clasen's avatar
Matthias Clasen committed
352 353 354
      result = gtk_window_new (GTK_WINDOW_POPUP);
      gtk_window_set_screen (GTK_WINDOW (result), screen);
      gtk_window_resize (GTK_WINDOW (result), 1, 1);
355
      gtk_window_move (GTK_WINDOW (result), -99, -99);
356
      gtk_widget_show (result);
Matthias Clasen's avatar
Matthias Clasen committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
    }  

  return result;
}

static GtkWidget *
gtk_drag_get_ipc_widget (GtkWidget *widget)
{
  GtkWidget *result;
  GtkWidget *toplevel;

  result = gtk_drag_get_ipc_widget_for_screen (gtk_widget_get_screen (widget));
  
  toplevel = gtk_widget_get_toplevel (widget);
  
  if (GTK_IS_WINDOW (toplevel))
    {
374 375
      if (gtk_window_has_group (GTK_WINDOW (toplevel)))
        gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
Matthias Clasen's avatar
Matthias Clasen committed
376
                                     GTK_WINDOW (result));
377
    }
378

379 380 381
  return result;
}

382
#if defined (GDK_WINDOWING_X11)
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

/*
 * We want to handle a handful of keys during DND, e.g. Escape to abort.
 * Grabbing the keyboard has the unfortunate side-effect of preventing
 * useful things such as using Alt-Tab to cycle between windows or
 * switching workspaces. Therefore, we just grab the few keys we are
 * interested in. Note that we need to put the grabs on the root window
 * in order for them to still work when the focus is moved to another
 * app/workspace.
 *
 * GDK needs a little help to successfully deliver root key events...
 */

static GdkFilterReturn
root_key_filter (GdkXEvent *xevent,
                 GdkEvent  *event,
                 gpointer   data)
{
401
  XEvent *ev = (XEvent *) xevent;
402 403 404 405

  if ((ev->type == KeyPress || ev->type == KeyRelease) &&
      ev->xkey.root == ev->xkey.window)
    ev->xkey.window = (Window)data;
406 407 408 409 410 411 412 413 414 415 416 417
  else if (ev->type == GenericEvent)
    {
      XGenericEventCookie *cookie;
      XIDeviceEvent *dev;

      cookie = &ev->xcookie;
      dev = (XIDeviceEvent *) cookie->data;

      if (dev->evtype == XI_KeyPress ||
          dev->evtype == XI_KeyRelease)
        dev->event = (Window)data;
    }
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452

  return GDK_FILTER_CONTINUE;
}

typedef struct {
  gint keysym;
  gint modifiers;
} GrabKey;

static GrabKey grab_keys[] = {
  { XK_Escape, 0 },
  { XK_space, 0 },
  { XK_KP_Space, 0 },
  { XK_Return, 0 },
  { XK_KP_Enter, 0 },
  { XK_Up, 0 },
  { XK_Up, Mod1Mask },
  { XK_Down, 0 },
  { XK_Down, Mod1Mask },
  { XK_Left, 0 },
  { XK_Left, Mod1Mask },
  { XK_Right, 0 },
  { XK_Right, Mod1Mask },
  { XK_KP_Up, 0 },
  { XK_KP_Up, Mod1Mask },
  { XK_KP_Down, 0 },
  { XK_KP_Down, Mod1Mask },
  { XK_KP_Left, 0 },
  { XK_KP_Left, Mod1Mask },
  { XK_KP_Right, 0 },
  { XK_KP_Right, Mod1Mask }
};

static void
grab_dnd_keys (GtkWidget *widget,
453
               GdkDevice *device,
454 455 456 457 458
               guint32    time)
{
  guint i;
  GdkWindow *window, *root;
  gint keycode;
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
#ifdef XINPUT_2
  gint deviceid;
  XIGrabModifiers mods;
  gint num_mods;
  XIEventMask evmask;
  unsigned char mask[(XI_LASTEVENT + 7)/8];
  gboolean using_xi2;

  deviceid = gdk_x11_device_get_id (device);

  if (GDK_IS_X11_DEVICE_MANAGER_XI2 (gdk_display_get_device_manager (gtk_widget_get_display (widget))))
    using_xi2 = TRUE;
  else
    using_xi2 = FALSE;
#endif
474

475
  window = gtk_widget_get_window (widget);
476 477 478 479 480 481 482 483 484 485 486
  if (!GDK_IS_X11_WINDOW (window))
    {
      gdk_device_grab (device,
                       gtk_widget_get_window (widget),
                       GDK_OWNERSHIP_APPLICATION, FALSE,
                       GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
                       NULL, time);
      return;
    }


487 488 489 490 491 492 493
  root = gdk_screen_get_root_window (gtk_widget_get_screen (widget));

  gdk_error_trap_push ();

  for (i = 0; i < G_N_ELEMENTS (grab_keys); ++i)
    {
      keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (window), grab_keys[i].keysym);
494 495
      if (keycode == NoSymbol)
        continue;
496 497 498 499 500 501 502 503 504 505 506 507

#ifdef XINPUT_2
      if (using_xi2)
        {
          memset (mask, 0, sizeof (mask));
          XISetMask (mask, XI_KeyPress);
          XISetMask (mask, XI_KeyRelease);

          evmask.deviceid = deviceid;
          evmask.mask_len = sizeof (mask);
          evmask.mask = mask;

508 509
          num_mods = 1;
          mods.modifiers = grab_keys[i].modifiers;
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529

          XIGrabKeycode (GDK_WINDOW_XDISPLAY (window),
                         deviceid,
                         keycode,
                         GDK_WINDOW_XID (root),
                         GrabModeAsync,
                         GrabModeAsync,
                         False,
                         &evmask,
                         num_mods,
                         &mods);
        }
      else
#endif
        XGrabKey (GDK_WINDOW_XDISPLAY (window),
                  keycode, grab_keys[i].modifiers,
                  GDK_WINDOW_XID (root),
                  FALSE,
                  GrabModeAsync,
                  GrabModeAsync);
530 531 532
    }

  gdk_flush ();
533
  gdk_error_trap_pop_ignored ();
534

Benjamin Otte's avatar
Benjamin Otte committed
535
  gdk_window_add_filter (NULL, root_key_filter, (gpointer) GDK_WINDOW_XID (window));
536 537
}

538
static void
539
ungrab_dnd_keys (GtkWidget *widget,
540
                 GdkDevice *device,
541 542 543 544 545
                 guint32    time)
{
  guint i;
  GdkWindow *window, *root;
  gint keycode;
546 547 548 549 550 551 552 553 554 555 556 557
#ifdef XINPUT_2
  XIGrabModifiers mods;
  gint num_mods;
  gint deviceid;
  gboolean using_xi2;

  deviceid = gdk_x11_device_get_id (device);
  if (GDK_IS_X11_DEVICE_MANAGER_XI2 (gdk_display_get_device_manager (gtk_widget_get_display (widget))))
    using_xi2 = TRUE;
  else
    using_xi2 = FALSE;
#endif
558

559
  window = gtk_widget_get_window (widget);
560 561 562 563 564 565
  if (!GDK_IS_X11_WINDOW (window))
    {
      gdk_device_ungrab (device, time);
      return;
    }

566 567
  root = gdk_screen_get_root_window (gtk_widget_get_screen (widget));

Benjamin Otte's avatar
Benjamin Otte committed
568
  gdk_window_remove_filter (NULL, root_key_filter, (gpointer) GDK_WINDOW_XID (window));
569 570 571 572 573 574

  gdk_error_trap_push ();

  for (i = 0; i < G_N_ELEMENTS (grab_keys); ++i)
    {
      keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (window), grab_keys[i].keysym);
575 576
      if (keycode == NoSymbol)
        continue;
577 578 579 580

#ifdef XINPUT_2
      if (using_xi2)
        {
581 582
          num_mods = 1;
          mods.modifiers = grab_keys[i].modifiers;
583 584 585 586 587 588 589 590 591 592 593 594 595

          XIUngrabKeycode (GDK_WINDOW_XDISPLAY (window),
                           deviceid,
                           keycode,
                           GDK_WINDOW_XID (root),
                           num_mods,
                           &mods);
        }
      else
#endif
        XUngrabKey (GDK_WINDOW_XDISPLAY (window),
                    keycode, grab_keys[i].modifiers,
                    GDK_WINDOW_XID (root));
596 597 598
    }

  gdk_flush ();
599
  gdk_error_trap_pop_ignored ();
600 601
}

602
#else /* !GDK_WINDOWING_X11 */
603 604 605

static void
grab_dnd_keys (GtkWidget *widget,
606
               GdkDevice *device,
607 608
               guint32    time)
{
609 610
  gdk_device_grab (device,
                   gtk_widget_get_window (widget),
611 612 613
                   GDK_OWNERSHIP_APPLICATION, FALSE,
                   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
                   NULL, time);
614 615 616 617
}

static void
ungrab_dnd_keys (GtkWidget *widget,
618
                 GdkDevice *device,
619 620
                 guint32    time)
{
621
  gdk_device_ungrab (device, time);
622 623
}

624
#endif /* GDK_WINDOWING_X11 */
625 626


Owen Taylor's avatar
Owen Taylor committed
627
/***************************************************************
628
 * gtk_drag_release_ipc_widget:
Owen Taylor's avatar
Owen Taylor committed
629
 *     Releases widget retrieved with gtk_drag_get_ipc_widget ()
630 631 632
 *   arguments:
 *     widget: the widget to release.
 *   results:
Owen Taylor's avatar
Owen Taylor committed
633
 ***************************************************************/
634 635 636 637

static void
gtk_drag_release_ipc_widget (GtkWidget *widget)
{
Matthias Clasen's avatar
Matthias Clasen committed
638
  GtkWindow *window = GTK_WINDOW (widget);
639
  GdkScreen *screen = gtk_widget_get_screen (widget);
640
  GdkDragContext *context = g_object_get_data (G_OBJECT (widget), "drag-context");
641 642
  GSList *drag_widgets = g_object_get_data (G_OBJECT (screen),
					    "gtk-dnd-ipc-widgets");
643 644 645 646 647 648 649 650 651 652 653
  GdkDevice *pointer, *keyboard;

  if (context)
    {
      pointer = gdk_drag_context_get_device (context);
      keyboard = gdk_device_get_associated_device (pointer);

      if (keyboard)
        ungrab_dnd_keys (widget, keyboard, GDK_CURRENT_TIME);
    }

654 655 656
  if (gtk_window_has_group (window))
    gtk_window_group_remove_window (gtk_window_get_group (window),
                                    window);
657
  drag_widgets = g_slist_prepend (drag_widgets, widget);
658
  g_object_set_data (G_OBJECT (screen),
659
		     I_("gtk-dnd-ipc-widgets"),
660
		     drag_widgets);
661 662
}

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
static guint32
gtk_drag_get_event_time (GdkEvent *event)
{
  guint32 tm = GDK_CURRENT_TIME;
  
  if (event)
    switch (event->type)
      {
      case GDK_MOTION_NOTIFY:
	tm = event->motion.time; break;
      case GDK_BUTTON_PRESS:
      case GDK_2BUTTON_PRESS:
      case GDK_3BUTTON_PRESS:
      case GDK_BUTTON_RELEASE:
	tm = event->button.time; break;
      case GDK_KEY_PRESS:
      case GDK_KEY_RELEASE:
	tm = event->key.time; break;
      case GDK_ENTER_NOTIFY:
      case GDK_LEAVE_NOTIFY:
	tm = event->crossing.time; break;
      case GDK_PROPERTY_NOTIFY:
	tm = event->property.time; break;
      case GDK_SELECTION_CLEAR:
      case GDK_SELECTION_REQUEST:
      case GDK_SELECTION_NOTIFY:
	tm = event->selection.time; break;
      case GDK_PROXIMITY_IN:
      case GDK_PROXIMITY_OUT:
	tm = event->proximity.time; break;
      default:			/* use current time */
	break;
      }
  
  return tm;
}

700 701 702 703 704 705
static void
gtk_drag_get_event_actions (GdkEvent *event, 
			    gint button, 
			    GdkDragAction  actions,
			    GdkDragAction *suggested_action,
			    GdkDragAction *possible_actions)
706
{
707 708
  *suggested_action = 0;
  *possible_actions = 0;
709

710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
  if (event)
    {
      GdkModifierType state = 0;
      
      switch (event->type)
	{
	case GDK_MOTION_NOTIFY:
	  state = event->motion.state;
	  break;
	case GDK_BUTTON_PRESS:
	case GDK_2BUTTON_PRESS:
	case GDK_3BUTTON_PRESS:
	case GDK_BUTTON_RELEASE:
	  state = event->button.state;
	  break;
	case GDK_KEY_PRESS:
	case GDK_KEY_RELEASE:
	  state = event->key.state;
	  break;
Owen Taylor's avatar
Owen Taylor committed
729 730
	case GDK_ENTER_NOTIFY:
	case GDK_LEAVE_NOTIFY:
731 732
	  state = event->crossing.state;
	  break;
Owen Taylor's avatar
Owen Taylor committed
733 734 735
	default:
	  break;
	}
736

737
      if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
738 739 740 741 742
	{
	  *suggested_action = GDK_ACTION_ASK;
	  *possible_actions = actions;
	}
      else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
743 744
	{
	  if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
745 746 747 748 749 750 751
	    {
	      if (actions & GDK_ACTION_LINK)
		{
		  *suggested_action = GDK_ACTION_LINK;
		  *possible_actions = GDK_ACTION_LINK;
		}
	    }
752
	  else if (state & GDK_CONTROL_MASK)
753 754 755 756 757 758 759 760
	    {
	      if (actions & GDK_ACTION_COPY)
		{
		  *suggested_action = GDK_ACTION_COPY;
		  *possible_actions = GDK_ACTION_COPY;
		}
	      return;
	    }
761
	  else
762 763 764 765 766 767 768 769
	    {
	      if (actions & GDK_ACTION_MOVE)
		{
		  *suggested_action = GDK_ACTION_MOVE;
		  *possible_actions = GDK_ACTION_MOVE;
		}
	      return;
	    }
770 771 772
	}
      else
	{
773
	  *possible_actions = actions;
774

775 776 777 778
	  if ((state & (GDK_MOD1_MASK)) && (actions & GDK_ACTION_ASK))
	    *suggested_action = GDK_ACTION_ASK;
	  else if (actions & GDK_ACTION_COPY)
	    *suggested_action =  GDK_ACTION_COPY;
779
	  else if (actions & GDK_ACTION_MOVE)
780
	    *suggested_action = GDK_ACTION_MOVE;
781
	  else if (actions & GDK_ACTION_LINK)
782
	    *suggested_action = GDK_ACTION_LINK;
783 784
	}
    }
Owen Taylor's avatar
Owen Taylor committed
785 786 787 788 789 790 791 792 793 794 795
  else
    {
      *possible_actions = actions;
      
      if (actions & GDK_ACTION_COPY)
	*suggested_action =  GDK_ACTION_COPY;
      else if (actions & GDK_ACTION_MOVE)
	*suggested_action = GDK_ACTION_MOVE;
      else if (actions & GDK_ACTION_LINK)
	*suggested_action = GDK_ACTION_LINK;
    }
796 797 798 799 800 801 802 803
}

static gboolean
gtk_drag_can_use_rgba_cursor (GdkDisplay *display, 
			      gint        width,
			      gint        height)
{
  guint max_width, max_height;
804
  
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
  if (!gdk_display_supports_cursor_color (display))
    return FALSE;

  if (!gdk_display_supports_cursor_alpha (display))
    return FALSE;

  gdk_display_get_maximal_cursor_size (display, 
                                       &max_width,
                                       &max_height);
  if (width > max_width || height > max_height)
    {
       /* can't use rgba cursor (too large) */
      return FALSE;
    }

  return TRUE;
821 822
}

823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
static void
ensure_drag_cursor_pixbuf (int i)
{
  if (drag_cursors[i].pixbuf == NULL)
    {
      char *path = g_strconcat ("/org/gtk/libgtk/cursor/",  drag_cursors[i].name, ".png", NULL);
      GInputStream *stream = g_resources_open_stream (path, 0, NULL);
      if (stream != NULL)
	{
	  drag_cursors[i].pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
	  g_object_unref (stream);
	}
      g_free (path);
    }
}

839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
static void
get_surface_size (cairo_surface_t *surface,
		  int *width,
		  int *height)
{
  double x_scale, y_scale;

  x_scale = y_scale = 1;

#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
  cairo_surface_get_device_scale (surface, &x_scale, &y_scale);
#endif

  /* Assume any set scaling is icon scale */
  *width =
    ceil (cairo_image_surface_get_width (surface) / x_scale);
  *height =
    ceil (cairo_image_surface_get_height (surface) / y_scale);
}

859
static GdkCursor *
860 861
gtk_drag_get_cursor (GtkWidget         *widget,
                     GdkDisplay        *display,
862 863
		     GdkDragAction      action,
		     GtkDragSourceInfo *info)
864
{
865
  gint i;
866 867 868 869 870 871

  /* reconstruct the cursors for each new drag (thus !info),
   * to catch cursor theme changes 
   */ 
  if (!info)
    {
Benjamin Otte's avatar
Benjamin Otte committed
872
      for (i = 0 ; i < G_N_ELEMENTS (drag_cursors) - 1; i++)
873 874
	if (drag_cursors[i].cursor != NULL)
	  {
875
	    g_object_unref (drag_cursors[i].cursor);
876 877 878 879
	    drag_cursors[i].cursor = NULL;
	  }
    }
 
Benjamin Otte's avatar
Benjamin Otte committed
880
  for (i = 0 ; i < G_N_ELEMENTS (drag_cursors) - 1; i++)
881 882
    if (drag_cursors[i].action == action)
      break;
883

884 885
  if (drag_cursors[i].cursor != NULL)
    {
886
      if (display != gdk_cursor_get_display (drag_cursors[i].cursor))
887
	{
888
	  g_object_unref (drag_cursors[i].cursor);
889 890 891
	  drag_cursors[i].cursor = NULL;
	}
    }
892 893 894 895
  
  if (drag_cursors[i].cursor == NULL)
    drag_cursors[i].cursor = gdk_cursor_new_from_name (display, drag_cursors[i].name);
  
896
  if (drag_cursors[i].cursor == NULL)
897 898 899 900
    {
      ensure_drag_cursor_pixbuf (i);
      drag_cursors[i].cursor = gdk_cursor_new_from_pixbuf (display, drag_cursors[i].pixbuf, 0, 0);
    }
901

902
  if (info && info->icon_helper) 
903
    {
904 905 906
      gint cursor_width, cursor_height;
      gint icon_width, icon_height;
      gint width, height;
907 908
      cairo_surface_t *icon_surface, *cursor_surface;
      gdouble hot_x, hot_y;
Manish Singh's avatar
Manish Singh committed
909
      gint icon_x, icon_y, ref_x, ref_y;
910
      gint scale;
911 912 913 914 915 916

      if (info->drag_cursors[i] != NULL)
        {
          if (display == gdk_cursor_get_display (info->drag_cursors[i]))
	    return info->drag_cursors[i];
	  
917
	  g_object_unref (info->drag_cursors[i]);
918 919
	  info->drag_cursors[i] = NULL;
        }
920

921 922 923 924 925 926
      scale = gtk_widget_get_scale_factor (widget);
      _gtk_icon_helper_get_size (info->icon_helper,
				 gtk_widget_get_style_context (widget),
				 &icon_width, &icon_height);
      icon_surface = _gtk_icon_helper_ensure_surface (info->icon_helper,
						      gtk_widget_get_style_context (widget));
927

928 929
      icon_x = info->hot_x;
      icon_y = info->hot_y;
Matthias Clasen's avatar
Matthias Clasen committed
930 931

      hot_x = hot_y = 0;
932 933 934
      cursor_surface = gdk_cursor_get_surface (drag_cursors[i].cursor,
					       &hot_x, &hot_y);
      if (!cursor_surface)
935 936
	{
	  ensure_drag_cursor_pixbuf (i);
937
	  cursor_surface = gdk_cairo_surface_create_from_pixbuf (drag_cursors[i].pixbuf, 1, NULL);
938
	}
939 940
      else
	{
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
#if 0	  
	  /* The code below is an attempt to let cursor themes
	   * determine the attachment of the icon to enable things
	   * like the following:
	   *
	   *    +-----+
           *    |     |
           *    |     ||
           *    +-----+|
           *        ---+
           * 
           * It does not work since Xcursor doesn't allow to attach
           * any additional information to cursors in a retrievable
           * way  (there are comments, but no way to get at them
           * short of searching for the actual cursor file).
           * If this code ever gets used, the icon_window placement
           * must be changed to recognize these placement options
           * as well. Note that this code ignores info->hot_x/y.
           */ 
960 961 962
	  for (j = 0; j < 10; j++)
	    {
	      const gchar *opt;
963
	      gchar key[32];
964
	      gchar **toks;
965
	      GtkAnchorType icon_anchor;
966

967 968 969 970 971 972 973 974 975 976 977 978 979
	      g_snprintf (key, 32, "comment%d", j);
	      opt = gdk_pixbuf_get_option (cursor_pixbuf, key);
	      if (opt && g_str_has_prefix ("icon-attach:", opt))
		{
		  toks = g_strsplit (opt + strlen ("icon-attach:"), "'", -1);
		  if (g_strv_length (toks) != 3)
		    {
		      g_strfreev (toks);
		      break;
		    }
		  icon_anchor = atoi (toks[0]);
		  icon_x = atoi (toks[1]);
		  icon_y = atoi (toks[2]);
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
		  
		  switch (icon_anchor)
		    {
		    case GTK_ANCHOR_NORTH:
		    case GTK_ANCHOR_CENTER:
		    case GTK_ANCHOR_SOUTH:
		      icon_x += icon_width / 2;
		      break;
		    case GTK_ANCHOR_NORTH_EAST:
		    case GTK_ANCHOR_EAST:
		    case GTK_ANCHOR_SOUTH_EAST:
		      icon_x += icon_width;
		      break;
		    default: ;
		    }
		  
		  switch (icon_anchor)
		    {
		    case GTK_ANCHOR_WEST:
		    case GTK_ANCHOR_CENTER:
		    case GTK_ANCHOR_EAST:
		      icon_y += icon_height / 2;
		      break;
		    case GTK_ANCHOR_SOUTH_WEST:
		    case GTK_ANCHOR_SOUTH:
		    case GTK_ANCHOR_SOUTH_EAST:
		      icon_x += icon_height;
		      break;
		    default: ;
		    }
1010 1011 1012 1013 1014

		  g_strfreev (toks);
		  break;
		}
	    }
1015
#endif
1016
	}
Matthias Clasen's avatar
Matthias Clasen committed
1017

1018
      get_surface_size (cursor_surface, &cursor_width, &cursor_height);
Matthias Clasen's avatar
Matthias Clasen committed
1019
      
1020 1021 1022 1023
      ref_x = MAX (hot_x, icon_x);
      ref_y = MAX (hot_y, icon_y);
      width = ref_x + MAX (cursor_width - hot_x, icon_width - icon_x);
      height = ref_y + MAX (cursor_height - hot_y, icon_height - icon_y);
1024 1025

      if (gtk_drag_can_use_rgba_cursor (display, width * scale, height * scale))
1026
	{
1027 1028 1029
	  cairo_surface_t *surface;
	  cairo_t *cr;

1030 1031 1032
	  /* Composite cursor and icon so that both hotspots
	   * end up at (ref_x, ref_y)
	   */
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
	  surface =
              gdk_window_create_similar_image_surface (NULL,
						       CAIRO_FORMAT_ARGB32,
						       width * scale, height * scale, scale);

	  cr = cairo_create (surface);
	  cairo_set_source_surface (cr, icon_surface,
				    ref_x - icon_x, ref_y - icon_y);
	  cairo_paint (cr);

	  cairo_set_source_surface (cr, cursor_surface,
				    ref_x - hot_x, ref_y - hot_y);
	  cairo_paint (cr);

	  cairo_destroy (cr);

1049
	  info->drag_cursors[i] = 
1050
	    gdk_cursor_new_from_surface (display, surface, ref_x, ref_y);
1051
	  
1052
	  cairo_surface_destroy (surface);
1053 1054
	}
      
1055 1056
      cairo_surface_destroy (cursor_surface);
      cairo_surface_destroy (icon_surface);
1057 1058 1059 1060 1061
      
      if (info->drag_cursors[i] != NULL)
	return info->drag_cursors[i];
    }
 
1062 1063 1064
  return drag_cursors[i].cursor;
}

1065 1066 1067 1068 1069 1070 1071 1072 1073
static void
gtk_drag_update_cursor (GtkDragSourceInfo *info)
{
  GdkCursor *cursor;
  gint i;

  if (!info->have_grab)
    return;

Benjamin Otte's avatar
Benjamin Otte committed
1074
  for (i = 0 ; i < G_N_ELEMENTS (drag_cursors) - 1; i++)
1075 1076 1077 1078
    if (info->cursor == drag_cursors[i].cursor ||
	info->cursor == info->drag_cursors[i])
      break;
  
Benjamin Otte's avatar
Benjamin Otte committed
1079
  if (i == G_N_ELEMENTS (drag_cursors))
1080 1081
    return;

1082 1083
  cursor = gtk_drag_get_cursor (info->widget,
                                gdk_cursor_get_display (info->cursor), 
1084
				drag_cursors[i].action, info);
1085 1086 1087
  
  if (cursor != info->cursor)
    {
1088 1089 1090
      GdkDevice *pointer;

      pointer = gdk_drag_context_get_device (info->context);
1091 1092
      gdk_device_grab (pointer,
                       gtk_widget_get_window (info->ipc_widget),
1093 1094 1095
                       GDK_OWNERSHIP_APPLICATION, FALSE,
                       GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                       cursor, info->grab_time);
1096 1097 1098 1099
      info->cursor = cursor;
    }
}

1100 1101 1102 1103
/********************
 * Destination side *
 ********************/

1104
/**
1105
 * gtk_drag_get_data: (method)
1106 1107 1108 1109 1110 1111 1112
 * @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.
1113
 *
1114 1115 1116 1117 1118 1119 1120 1121
 * Gets the data associated with a drag. When the data
 * is received or the retrieval fails, GTK+ will emit a
 * #GtkWidget::drag-data-received signal. Failure of the retrieval
 * is indicated by the length field of the @selection_data
 * signal parameter being negative. However, when gtk_drag_get_data()
 * is called implicitely because the %GTK_DEST_DEFAULT_DROP was set,
 * then the widget will not receive notification of failed
 * drops.
1122
 */
1123
void
1124 1125 1126
gtk_drag_get_data (GtkWidget      *widget,
		   GdkDragContext *context,
		   GdkAtom         target,
1127
		   guint32         time_)
1128 1129
{
  GtkWidget *selection_widget;
1130

1131 1132
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
1133

Matthias Clasen's avatar
Matthias Clasen committed
1134
  selection_widget = gtk_drag_get_ipc_widget (widget);
1135

Manish Singh's avatar
Manish Singh committed
1136 1137
  g_object_ref (context);
  g_object_ref (widget);
1138

1139
  g_signal_connect (selection_widget, "selection-received",
Manish Singh's avatar
Manish Singh committed
1140
		    G_CALLBACK (gtk_drag_selection_received), widget);
1141

1142
  g_object_set_data (G_OBJECT (selection_widget), I_("drag-context"), context);
1143

1144
  gtk_selection_convert (selection_widget,
Owen Taylor's avatar
Owen Taylor committed
1145
			 gdk_drag_get_selection (context),
1146
			 target,
1147
			 time_);
1148 1149
}

1150

1151
/**
1152
 * gtk_drag_get_source_widget: (method)
1153 1154 1155 1156 1157 1158 1159 1160
 * @context: a (destination side) drag context
 *
 * Determines the source widget for a drag.
 *
 * Return value: (transfer none): if the drag is occurring
 *     within a single application, a pointer to the source widget.
 *     Otherwise, %NULL.
 */
1161
GtkWidget *
1162 1163 1164
gtk_drag_get_source_widget (GdkDragContext *context)
{
  GSList *tmp_list;
1165

1166 1167
  g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), NULL);
  
1168 1169 1170 1171
  tmp_list = source_widgets;
  while (tmp_list)
    {
      GtkWidget *ipc_widget = tmp_list->data;
1172

1173
      if (gtk_widget_get_window (ipc_widget) == gdk_drag_context_get_source_window (context))
1174 1175
	{
	  GtkDragSourceInfo *info;
Manish Singh's avatar
Manish Singh committed
1176
	  info = g_object_get_data (G_OBJECT (ipc_widget), "gtk-info");
1177

1178 1179
	  return info ? info->widget : NULL;
	}
1180

1181 1182
      tmp_list = tmp_list->next;
    }
1183

1184 1185 1186
  return NULL;
}

1187
/**
1188
 * gtk_drag_finish: (method)
1189 1190 1191 1192 1193 1194 1195 1196 1197
 * @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.
 *
 * Informs the drag source that the drop is finished, and
 * that the data of the drag will no longer be required.
 */
1198 1199 1200
void 
gtk_drag_finish (GdkDragContext *context,
		 gboolean        success,
1201
		 gboolean        del,
1202 1203 1204
		 guint32         time)
{
  GdkAtom target = GDK_NONE;
1205

1206
  g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
1207

1208
  if (success && del)
1209
    {
1210
      target = gdk_atom_intern_static_string ("DELETE");
1211
    }
1212

1213 1214
  if (target != GDK_NONE)
    {
1215
      GtkWidget *selection_widget = gtk_drag_get_ipc_widget_for_screen (gdk_window_get_screen (gdk_drag_context_get_source_window (context)));
1216

Manish Singh's avatar
Manish Singh committed
1217
      g_object_ref (context);
1218
      
1219
      g_object_set_data (G_OBJECT (selection_widget), I_("drag-context"), context);
1220
      g_signal_connect (selection_widget, "selection-received",
Manish Singh's avatar
Manish Singh committed
1221 1222
			G_CALLBACK (gtk_drag_selection_received),
			NULL);
1223 1224
      
      gtk_selection_convert (selection_widget,
Owen Taylor's avatar
Owen Taylor committed
1225
			     gdk_drag_get_selection (context),
1226 1227 1228 1229
			     target,
			     time);
    }
  
1230
  if (!(success && del))
1231 1232 1233 1234
    gdk_drop_finish (context, success, time);
}

/*************************************************************
1235
 * gtk_drag_highlight_draw:
Owen Taylor's avatar
Owen Taylor committed
1236
 *     Callback for expose_event for highlighted widgets.
1237 1238
 *   arguments:
 *     widget:
Owen Taylor's avatar
Owen Taylor committed
1239 1240
 *     event:
 *     data:
1241 1242 1243
 *   results:
 *************************************************************/

Owen Taylor's avatar
Owen Taylor committed
1244
static gboolean
1245 1246 1247
gtk_drag_highlight_draw (GtkWidget *widget,
			 cairo_t   *cr,
			 gpointer   data)
1248
{
1249 1250
  int width = gtk_widget_get_allocated_width (widget);
  int height = gtk_widget_get_allocated_height (widget);
1251
  GtkStyleContext *context;
1252

1253 1254 1255 1256 1257 1258 1259 1260
  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);
1261

1262 1263 1264 1265 1266 1267
  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);
1268

Owen Taylor's avatar
Owen Taylor committed
1269
  return FALSE;
1270 1271
}

1272
/**
1273
 * gtk_drag_highlight: (method)
1274
 * @widget: a widget to highlight
1275
 *
1276 1277 1278 1279
 * Draws a highlight around a widget. This will attach
 * handlers to #GtkWidget::draw, so the highlight
 * will continue to be displayed until gtk_drag_unhighlight()
 * is called.
1280
 */
1281 1282 1283
void 
gtk_drag_highlight (GtkWidget  *widget)
{
1284
  g_return_if_fail (GTK_IS_WIDGET (widget));
Manish Singh's avatar
Manish Singh committed
1285

1286 1287
  g_signal_connect_after (widget, "draw",
			  G_CALLBACK (gtk_drag_highlight_draw),
Manish Singh's avatar
Manish Singh committed
1288
			  NULL);
1289

1290
  gtk_widget_queue_draw (widget);
1291 1292
}

1293
/**
1294
 * gtk_drag_unhighlight: (method)
1295
 * @widget: a widget to remove the highlight from.
1296
 *
1297 1298
 * Removes a highlight set by gtk_drag_highlight() from
 * a widget.
1299
 */
1300
void 
Owen Taylor's avatar
Owen Taylor committed
1301
gtk_drag_unhighlight (GtkWidget *widget)
1302
{
1303
  g_return_if_fail (GTK_IS_WIDGET (widget));
Manish Singh's avatar
Manish Singh committed
1304 1305

  g_signal_handlers_disconnect_by_func (widget,
1306
					gtk_drag_highlight_draw,
Manish Singh's avatar
Manish Singh committed
1307
					NULL);
1308
  
Manish Singh's avatar
Manish Singh committed
1309
  gtk_widget_queue_draw (widget);
1310 1311
}

Owen Taylor's avatar
Owen Taylor committed
1312 1313 1314 1315 1316 1317 1318 1319 1320
static void
gtk_drag_dest_set_internal (GtkWidget       *widget,
			    GtkDragDestSite *site)
{
  GtkDragDestSite *old_site;
  
  g_return_if_fail (widget != NULL);

  /* HACK, do this in the destroy */
Manish Singh's avatar
Manish Singh committed
1321
  old_site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
Owen Taylor's avatar
Owen Taylor committed
1322
  if (old_site)
Manish Singh's avatar
Manish Singh committed
1323 1324 1325 1326 1327 1328 1329
    {
      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);
1330 1331

      site->track_motion = old_site->track_motion;
Manish Singh's avatar
Manish Singh committed
1332
    }
Owen Taylor's avatar
Owen Taylor committed
1333

1334
  if (gtk_widget_get_realized (widget))
Owen Taylor's avatar
Owen Taylor committed
1335 1336
    gtk_drag_dest_realized (widget);

Manish Singh's avatar
Manish Singh committed
1337 1338
  g_signal_connect (widget, "realize",
		    G_CALLBACK (gtk_drag_dest_realized), site);
1339
  g_signal_connect (widget, "hierarchy-changed",
Manish Singh's avatar
Manish Singh committed
1340
		    G_CALLBACK (gtk_drag_dest_hierarchy_changed), site);
Owen Taylor's avatar
Owen Taylor committed
1341

1342
  g_object_set_data_full (G_OBJECT (widget), I_("gtk-drag-dest"),
Manish Singh's avatar
Manish Singh committed
1343
			  site, gtk_drag_dest_site_destroy);
Owen Taylor's avatar
Owen Taylor committed
1344 1345
}

1346
/**
1347
 * gtk_drag_dest_set: (method)
1348 1349
 * @widget: a #GtkWidget
 * @flags: which types of default drag behavior to use
1350
 * @targets: (allow-none) (array length=n_targets): a pointer to an array of #GtkTargetEntry<!-- -->s
1351 1352 1353
 *     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().
1354
 * @n_targets: the number of entries in @targets
1355 1356 1357 1358 1359 1360
 * @actions: a bitmask of possible actions for a drop onto this @widget.
 *
 * Sets a widget as a potential drop destination, and adds default behaviors.
 *
 * The default behaviors listed in @flags have an effect similar
 * to installing default handlers for the widget's drag-and-drop signals
1361
 * (#GtkWidget::drag-motion, #GtkWidget::drag-drop, ...). They all exist
1362 1363 1364
 * for convenience. When passing #GTK_DEST_DEFAULT_ALL for instance it is
 * sufficient to connect to the widget's #GtkWidget::drag-data-received
 * signal to get primitive, but consistent drag-and-drop support.
1365
 *
1366
 * Things become more complicated when you try to preview the dragged data,
1367
 * as described in the documentation for #GtkWidget::drag-motion. The default
1368 1369
 * behaviors described by @flags make some assumptions, that can conflict
 * with your own signal handlers. For instance #GTK_DEST_DEFAULT_DROP causes
1370 1371 1372
 * invokations of gdk_drag_status() in the context of #GtkWidget::drag-motion,
 * and invokations of gtk_drag_finish() in #GtkWidget::drag-data-received.
 * Especially the later is dramatic, when your own #GtkWidget::drag-motion
1373
 * handler calls gtk_drag_get_data() to inspect the dragged data.
1374 1375
 *
 * There's no way to set a default action here, you can use the
1376
 * #GtkWidget::drag-motion callback for that. Here's an example which selects
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
 * the action to use depending on whether the control key is pressed or not:
 * |[
 * static void
 * drag_motion (GtkWidget *widget,
 *              GdkDragContext *context,
 *              gint x,
 *              gint y,
 *              guint time)
 * {
 *   GdkModifierType mask;
 *
 *   gdk_window_get_pointer (gtk_widget_get_window (widget),
 *                           NULL, NULL, &mask);
 *   if (mask & GDK_CONTROL_MASK)
 *     gdk_drag_status (context, GDK_ACTION_COPY, time);
 *   else
 *     gdk_drag_status (context, GDK_ACTION_MOVE, time);
 * }
 * ]|
1396 1397 1398 1399 1400 1401 1402
 */
void
gtk_drag_dest_set (GtkWidget            *widget,
		   GtkDestDefaults       flags,
		   const GtkTargetEntry *targets,
		   gint                  n_targets,
		   GdkDragAction         actions)
1403 1404 1405
{
  GtkDragDestSite *site;
  
1406
  g_return_if_fail (GTK_IS_WIDGET (widget));
1407

1408
  site = g_slice_new0 (GtkDragDestSite);
1409

1410 1411
  site->flags = flags;
  site->have_drag = FALSE;
1412 1413 1414 1415
  if (targets)
    site->target_list = gtk_target_list_new (targets, n_targets);
  else
    site->target_list = NULL;
1416
  site->actions = actions;
1417
  site->do_proxy = FALSE;
1418
  site->proxy_window = NULL;
1419
  site->track_motion = FALSE;
1420

Owen Taylor's avatar
Owen Taylor committed
1421
  gtk_drag_dest_set_internal (widget, site);
1422 1423
}

1424
/**
1425
 * gtk_drag_dest_set_proxy: (method)
1426
 * @widget: a #GtkWidget