1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 #include "glass_dnd.h"
  26 #include "glass_gtkcompat.h"
  27 #include "glass_general.h"
  28 #include "glass_evloop.h"
  29 
  30 #include "com_sun_glass_events_DndEvent.h"
  31 #include "com_sun_glass_ui_gtk_GtkDnDClipboard.h"
  32 
  33 #include <jni.h>
  34 #include <cstring>
  35 
  36 #include <gtk/gtk.h>
  37 #include <gdk/gdkx.h>
  38 
  39 /************************* COMMON *********************************************/
  40 static jint translate_gdk_action_to_glass(GdkDragAction action)
  41 {
  42     jint result = 0;
  43     result |= (action & GDK_ACTION_COPY)? com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_COPY : 0;
  44     result |= (action & GDK_ACTION_MOVE)? com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_MOVE : 0;
  45     result |= (action & GDK_ACTION_LINK)? com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_REFERENCE : 0;
  46     return result;
  47 }
  48 
  49 static GdkDragAction translate_glass_action_to_gdk(jint action)
  50 {
  51     int result = 0;
  52     result |= (action & com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_COPY)? GDK_ACTION_COPY : 0;
  53     result |= (action & com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_MOVE)? GDK_ACTION_MOVE : 0;
  54     result |= (action & com_sun_glass_ui_gtk_GtkDnDClipboard_ACTION_REFERENCE)? GDK_ACTION_LINK : 0;
  55     return static_cast<GdkDragAction>(result);
  56 }
  57 
  58 static gboolean target_atoms_initialized = FALSE;
  59 static GdkAtom TARGET_UTF8_STRING_ATOM;
  60 static GdkAtom TARGET_MIME_TEXT_PLAIN_ATOM;
  61 static GdkAtom TARGET_COMPOUND_TEXT_ATOM;
  62 static GdkAtom TARGET_STRING_ATOM;
  63 
  64 static GdkAtom TARGET_MIME_URI_LIST_ATOM;
  65 
  66 static GdkAtom TARGET_MIME_PNG_ATOM;
  67 static GdkAtom TARGET_MIME_JPEG_ATOM;
  68 static GdkAtom TARGET_MIME_TIFF_ATOM;
  69 static GdkAtom TARGET_MIME_BMP_ATOM;
  70 
  71 static void init_target_atoms()
  72 {
  73     if (target_atoms_initialized) {
  74         return;
  75     }
  76     TARGET_UTF8_STRING_ATOM = gdk_atom_intern_static_string("UTF8_STRING");
  77     TARGET_MIME_TEXT_PLAIN_ATOM = gdk_atom_intern_static_string("text/plain");
  78     TARGET_COMPOUND_TEXT_ATOM = gdk_atom_intern_static_string("COMPOUND_TEXT");
  79     TARGET_STRING_ATOM = gdk_atom_intern_static_string("STRING");
  80 
  81     TARGET_MIME_URI_LIST_ATOM = gdk_atom_intern_static_string("text/uri-list");
  82 
  83     TARGET_MIME_PNG_ATOM = gdk_atom_intern_static_string("image/png");
  84     TARGET_MIME_JPEG_ATOM = gdk_atom_intern_static_string("image/jpeg");
  85     TARGET_MIME_TIFF_ATOM = gdk_atom_intern_static_string("image/tiff");
  86     TARGET_MIME_BMP_ATOM = gdk_atom_intern_static_string("image/bmp");
  87 
  88     target_atoms_initialized = TRUE;
  89 }
  90 
  91 static gboolean target_is_text(GdkAtom target)
  92 {
  93     init_target_atoms();
  94 
  95     return (target == TARGET_UTF8_STRING_ATOM ||
  96             target == TARGET_STRING_ATOM ||
  97             target == TARGET_MIME_TEXT_PLAIN_ATOM/* ||
  98             target == TARGET_COMPOUND_TEXT_ATOM*/);
  99 }
 100 
 101 static gboolean target_is_uri(GdkAtom target)
 102 {
 103     init_target_atoms();
 104     return target == TARGET_MIME_URI_LIST_ATOM;
 105 }
 106 
 107 static gboolean target_is_image(GdkAtom target)
 108 {
 109     init_target_atoms();
 110     return (target == TARGET_MIME_PNG_ATOM ||
 111             target == TARGET_MIME_JPEG_ATOM ||
 112             target == TARGET_MIME_TIFF_ATOM ||
 113             target == TARGET_MIME_BMP_ATOM);
 114 }
 115 
 116 static void clear_global_ref(gpointer data)
 117 {
 118     mainEnv->DeleteGlobalRef((jobject)data);
 119 }
 120 
 121 static void dnd_set_performed_action(jint performed_action);
 122 static jint dnd_get_performed_action();
 123 
 124 /************************* TARGET *********************************************/
 125 struct selection_data_ctx {
 126     gboolean received;
 127     guchar *data;
 128     GdkAtom type;
 129     gint format;
 130     gint length;
 131 };
 132 
 133 static gboolean dnd_target_receive_data(JNIEnv *env, GdkAtom target, selection_data_ctx *selection_ctx);
 134 
 135 static struct {
 136     GdkDragContext *ctx;
 137     gboolean just_entered;
 138     jobjectArray mimes;
 139     gint dx, dy;
 140 } enter_ctx = {NULL, FALSE, NULL, 0, 0};
 141 
 142 gboolean is_dnd_owner = FALSE;
 143 
 144 static void reset_enter_ctx() {
 145     if (enter_ctx.mimes != NULL) {
 146         mainEnv->DeleteGlobalRef(enter_ctx.mimes);
 147     }
 148 
 149     memset(&enter_ctx, 0, sizeof(enter_ctx));
 150 }
 151 
 152 static void process_dnd_target_drag_enter(WindowContext *ctx, GdkEventDND *event)
 153 {
 154     reset_enter_ctx();
 155     enter_ctx.ctx = event->context;
 156     enter_ctx.just_entered = TRUE;
 157     gdk_window_get_origin(ctx->get_gdk_window(), &enter_ctx.dx, &enter_ctx.dy);
 158     is_dnd_owner = is_in_drag();
 159 }
 160 
 161 static void process_dnd_target_drag_motion(WindowContext *ctx, GdkEventDND *event)
 162 {
 163     if (!enter_ctx.ctx) {
 164         gdk_drag_status(event->context, static_cast<GdkDragAction>(0), GDK_CURRENT_TIME);
 165         return; // Do not process motion events if no enter event was received
 166     }
 167     jmethodID method = enter_ctx.just_entered ? jViewNotifyDragEnter : jViewNotifyDragOver;
 168     GdkDragAction suggested = GLASS_GDK_DRAG_CONTEXT_GET_SUGGESTED_ACTION(event->context);
 169     GdkDragAction result = translate_glass_action_to_gdk(mainEnv->CallIntMethod(ctx->get_jview(), method,
 170             (jint)event->x_root - enter_ctx.dx, (jint)event->y_root - enter_ctx.dy,
 171             (jint)event->x_root, (jint)event->y_root,
 172             translate_gdk_action_to_glass(suggested)));
 173     CHECK_JNI_EXCEPTION(mainEnv)
 174 
 175     if (enter_ctx.just_entered) {
 176         enter_ctx.just_entered = FALSE;
 177     }
 178     gdk_drag_status(event->context, result, GDK_CURRENT_TIME);
 179 }
 180 
 181 static void process_dnd_target_drag_leave(WindowContext *ctx, GdkEventDND *event)
 182 {
 183     (void)event;
 184 
 185     mainEnv->CallVoidMethod(ctx->get_jview(), jViewNotifyDragLeave, NULL);
 186     CHECK_JNI_EXCEPTION(mainEnv)
 187 }
 188 
 189 static void process_dnd_target_drop_start(WindowContext *ctx, GdkEventDND *event)
 190 {
 191     if (!enter_ctx.ctx || enter_ctx.just_entered) {
 192         gdk_drop_finish(event->context, FALSE, GDK_CURRENT_TIME);
 193         gdk_drop_reply(event->context, FALSE, GDK_CURRENT_TIME);
 194         return; // Do not process drop events if no enter event and subsequent motion event were received
 195     }
 196     GdkDragAction selected = GLASS_GDK_DRAG_CONTEXT_GET_SELECTED_ACTION(event->context);
 197 
 198     mainEnv->CallIntMethod(ctx->get_jview(), jViewNotifyDragDrop,
 199             (jint)event->x_root - enter_ctx.dx, (jint)event->y_root - enter_ctx.dy,
 200             (jint)event->x_root, (jint)event->y_root,
 201             translate_gdk_action_to_glass(selected));
 202     LOG_EXCEPTION(mainEnv)
 203 
 204     gdk_drop_finish(event->context, TRUE, GDK_CURRENT_TIME);
 205     gdk_drop_reply(event->context, TRUE, GDK_CURRENT_TIME);
 206 }
 207 
 208 static gboolean check_state_in_drag(JNIEnv *env)
 209 {
 210     if (!enter_ctx.ctx) {
 211         jclass jc = env->FindClass("java/lang/IllegalStateException");
 212         if (!env->ExceptionCheck()) {
 213             env->ThrowNew(jc,
 214                     "Cannot get supported actions. Drag pointer haven't entered the application window");
 215         }
 216         return TRUE;
 217     }
 218     return FALSE;
 219 }
 220 
 221 // Events coming from application that are related to us being a DnD target
 222 void process_dnd_target(WindowContext *ctx, GdkEventDND *event)
 223 {
 224     switch (event->type) {
 225         case GDK_DRAG_ENTER:
 226             process_dnd_target_drag_enter(ctx, event);
 227             break;
 228         case GDK_DRAG_MOTION:
 229             process_dnd_target_drag_motion(ctx, event);
 230             break;
 231         case GDK_DRAG_LEAVE:
 232             process_dnd_target_drag_leave(ctx, event);
 233             break;
 234         case GDK_DROP_START:
 235             process_dnd_target_drop_start(ctx, event);
 236             break;
 237         default:
 238             break;
 239     }
 240 }
 241 
 242 jobjectArray dnd_target_get_mimes(JNIEnv *env)
 243 {
 244     if (check_state_in_drag(env)) {
 245         return NULL;
 246     }
 247     if (!enter_ctx.mimes) {
 248         GList* targets = GLASS_GDK_DRAG_CONTEXT_LIST_TARGETS(enter_ctx.ctx);
 249         jobject set = env->NewObject(jHashSetCls, jHashSetInit, NULL);
 250         EXCEPTION_OCCURED(env);
 251 
 252         while (targets) {
 253             GdkAtom target = GDK_POINTER_TO_ATOM(targets->data);
 254             gchar *name = gdk_atom_name(target);
 255 
 256             if (target_is_text(target)) {
 257                 jstring jStr = env->NewStringUTF("text/plain");
 258                 EXCEPTION_OCCURED(env);
 259                 env->CallBooleanMethod(set, jSetAdd, jStr, NULL);
 260                 EXCEPTION_OCCURED(env);
 261             }
 262 
 263             if (target_is_image(target)) {
 264                 jstring jStr = env->NewStringUTF("application/x-java-rawimage");
 265                 EXCEPTION_OCCURED(env);
 266                 env->CallBooleanMethod(set, jSetAdd, jStr, NULL);
 267                 EXCEPTION_OCCURED(env);
 268             }
 269 
 270             if (target_is_uri(target)) {
 271                 selection_data_ctx ctx;
 272                 if (dnd_target_receive_data(env, TARGET_MIME_URI_LIST_ATOM, &ctx)) {
 273                     gchar** uris = g_uri_list_extract_uris((gchar *) ctx.data);
 274                     guint size = g_strv_length(uris);
 275                     guint files_cnt = get_files_count(uris);
 276                     if (files_cnt) {
 277                         jstring jStr = env->NewStringUTF("application/x-java-file-list");
 278                         EXCEPTION_OCCURED(env);
 279                         env->CallBooleanMethod(set, jSetAdd, jStr, NULL);
 280                         EXCEPTION_OCCURED(env);
 281                     }
 282                     if (size - files_cnt) {
 283                         jstring jStr = env->NewStringUTF("text/uri-list");
 284                         EXCEPTION_OCCURED(env);
 285                         env->CallBooleanMethod(set, jSetAdd, jStr, NULL);
 286                         EXCEPTION_OCCURED(env);
 287                     }
 288                     g_strfreev(uris);
 289                 }
 290                 g_free(ctx.data);
 291             } else {
 292                 jstring jStr = env->NewStringUTF(name);
 293                 EXCEPTION_OCCURED(env);
 294                 env->CallBooleanMethod(set, jSetAdd, jStr, NULL);
 295                 EXCEPTION_OCCURED(env);
 296             }
 297 
 298             g_free(name);
 299             targets = targets->next;
 300         }
 301         enter_ctx.mimes = env->NewObjectArray(env->CallIntMethod(set, jSetSize, NULL),
 302                 jStringCls, NULL);
 303         EXCEPTION_OCCURED(env);
 304         enter_ctx.mimes = (jobjectArray)env->CallObjectMethod(set, jSetToArray, enter_ctx.mimes, NULL);
 305         enter_ctx.mimes = (jobjectArray)env->NewGlobalRef(enter_ctx.mimes);
 306     }
 307     return enter_ctx.mimes;
 308 }
 309 
 310 jint dnd_target_get_supported_actions(JNIEnv *env)
 311 {
 312     if (check_state_in_drag(env)) {
 313         return 0;
 314     }
 315     return translate_gdk_action_to_glass(GLASS_GDK_DRAG_CONTEXT_GET_ACTIONS(enter_ctx.ctx));
 316 }
 317 
 318 static void wait_for_selection_data_hook(GdkEvent * event, void * data)
 319 {
 320     selection_data_ctx *ctx = (selection_data_ctx*)data;
 321     GdkWindow *dest = GLASS_GDK_DRAG_CONTEXT_GET_DEST_WINDOW(enter_ctx.ctx);
 322     if (event->type == GDK_SELECTION_NOTIFY &&
 323             event->selection.window == dest) {
 324         if (event->selection.property) { // if 0, that we received negative response
 325             ctx->length = gdk_selection_property_get(dest, &(ctx->data), &(ctx->type), &(ctx->format));
 326         }
 327         ctx->received = TRUE;
 328     }
 329 }
 330 
 331 static gboolean dnd_target_receive_data(JNIEnv *env, GdkAtom target, selection_data_ctx *selection_ctx)
 332 {
 333     GevlHookRegistration hookReg;
 334 
 335     memset(selection_ctx, 0, sizeof(selection_data_ctx));
 336 
 337     gdk_selection_convert(GLASS_GDK_DRAG_CONTEXT_GET_DEST_WINDOW(enter_ctx.ctx), gdk_drag_get_selection(enter_ctx.ctx), target,
 338                           GDK_CURRENT_TIME);
 339 
 340     hookReg =
 341             glass_evloop_hook_add(
 342                     (GevlHookFunction) wait_for_selection_data_hook,
 343                     selection_ctx);
 344     if (HANDLE_MEM_ALLOC_ERROR(env, hookReg,
 345                                "Failed to allocate event hook")) {
 346         return TRUE;
 347     }
 348 
 349     do {
 350         gtk_main_iteration();
 351     } while (!(selection_ctx->received));
 352 
 353 
 354     glass_evloop_hook_remove(hookReg);
 355     return selection_ctx->data != NULL;
 356 }
 357 
 358 static jobject dnd_target_get_string(JNIEnv *env)
 359 {
 360     jobject result = NULL;
 361     selection_data_ctx ctx;
 362 
 363     if (dnd_target_receive_data(env, TARGET_UTF8_STRING_ATOM, &ctx)) {
 364         result = env->NewStringUTF((char *)ctx.data);
 365         EXCEPTION_OCCURED(env);
 366         g_free(ctx.data);
 367     }
 368     if (!result && dnd_target_receive_data(env, TARGET_MIME_TEXT_PLAIN_ATOM, &ctx)) {
 369         result = env->NewStringUTF((char *)ctx.data);
 370         EXCEPTION_OCCURED(env);
 371         g_free(ctx.data);
 372     }
 373     // TODO find out how to convert from compound text
 374     // if (!result && dnd_target_receive_data(env, TARGET_COMPOUND_TEXT_ATOM, &ctx)) {
 375     // }
 376     if (!result && dnd_target_receive_data(env, TARGET_STRING_ATOM, &ctx)) {
 377         gchar *str;
 378         str = g_convert( (gchar *)ctx.data, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
 379         if (str != NULL) {
 380             result = env->NewStringUTF(str);
 381             EXCEPTION_OCCURED(env);
 382             g_free(str);
 383         }
 384         g_free(ctx.data);
 385     }
 386     return result;
 387 }
 388 
 389 static jobject dnd_target_get_list(JNIEnv *env, gboolean files)
 390 {
 391     jobject result = NULL;
 392     selection_data_ctx ctx;
 393 
 394     if (dnd_target_receive_data(env, TARGET_MIME_URI_LIST_ATOM, &ctx)) {
 395         result = uris_to_java(env, g_uri_list_extract_uris((gchar *)ctx.data), files);
 396         g_free(ctx.data);
 397     }
 398 
 399     return result;
 400 }
 401 
 402 static jobject dnd_target_get_image(JNIEnv *env)
 403 {
 404     GdkPixbuf *buf;
 405     GInputStream *stream;
 406     jobject result = NULL;
 407     GdkAtom targets[] = {
 408         TARGET_MIME_PNG_ATOM,
 409         TARGET_MIME_JPEG_ATOM,
 410         TARGET_MIME_TIFF_ATOM,
 411         TARGET_MIME_BMP_ATOM,
 412         0};
 413     GdkAtom *cur_target = targets;
 414     selection_data_ctx ctx;
 415 
 416     while(*cur_target != 0 && result == NULL) {
 417         if (dnd_target_receive_data(env, *cur_target, &ctx)) {
 418             stream = g_memory_input_stream_new_from_data(ctx.data, ctx.length * (ctx.format / 8),
 419                     (GDestroyNotify)g_free);
 420             buf = gdk_pixbuf_new_from_stream(stream, NULL, NULL);
 421             if (buf) {
 422                 int w;
 423                 int h;
 424                 int stride;
 425                 guchar *data;
 426                 jbyteArray data_array;
 427                 jobject buffer;
 428 
 429                 if (!gdk_pixbuf_get_has_alpha(buf)) {
 430                     GdkPixbuf *tmp_buf = gdk_pixbuf_add_alpha(buf, FALSE, 0, 0, 0);
 431                     g_object_unref(buf);
 432                     buf = tmp_buf;
 433                 }
 434 
 435                 w = gdk_pixbuf_get_width(buf);
 436                 h = gdk_pixbuf_get_height(buf);
 437                 stride = gdk_pixbuf_get_rowstride(buf);
 438                 data = gdk_pixbuf_get_pixels(buf);
 439 
 440                 //Actually, we are converting RGBA to BGRA, but that's the same operation
 441                 data = (guchar*) convert_BGRA_to_RGBA((int*) data, stride, h);
 442                 data_array = env->NewByteArray(stride * h);
 443                 EXCEPTION_OCCURED(env);
 444                 env->SetByteArrayRegion(data_array, 0, stride*h, (jbyte*) data);
 445                 EXCEPTION_OCCURED(env);
 446 
 447                 buffer = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, data_array);
 448                 result = env->NewObject(jGtkPixelsCls, jGtkPixelsInit, w, h, buffer);
 449                 EXCEPTION_OCCURED(env);
 450 
 451                 g_object_unref(buf);
 452                 g_free(data); // data from convert_BGRA_to_RGBA
 453             }
 454             g_object_unref(stream);
 455         }
 456         ++cur_target;
 457     }
 458     return result;
 459 }
 460 
 461 static jobject dnd_target_get_raw(JNIEnv *env, GdkAtom target, gboolean string_data)
 462 {
 463     selection_data_ctx ctx;
 464     jobject result = NULL;
 465     if (dnd_target_receive_data(env, target, &ctx)) {
 466         if (string_data) {
 467              result = env->NewStringUTF((char *)ctx.data);
 468              EXCEPTION_OCCURED(env);
 469         } else {
 470             jsize length = ctx.length * (ctx.format / 8);
 471             jbyteArray array = env->NewByteArray(length);
 472             EXCEPTION_OCCURED(env);
 473             env->SetByteArrayRegion(array, 0, length, (const jbyte*)ctx.data);
 474             EXCEPTION_OCCURED(env);
 475             result = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, array);
 476         }
 477     }
 478     g_free(ctx.data);
 479     return result;
 480 }
 481 
 482 jobject dnd_target_get_data(JNIEnv *env, jstring mime)
 483 {
 484     if (check_state_in_drag(env)) {
 485         return NULL;
 486     }
 487     const char *cmime = env->GetStringUTFChars(mime, NULL);
 488     jobject ret = NULL;
 489 
 490     init_target_atoms();
 491 
 492     if (g_strcmp0(cmime, "text/plain") == 0) {
 493         ret = dnd_target_get_string(env);
 494     } else if (g_strcmp0(cmime, "text/uri-list") == 0) {
 495         ret = dnd_target_get_list(env, FALSE);
 496     } else if (g_str_has_prefix(cmime, "text/")) {
 497         ret = dnd_target_get_raw(env, gdk_atom_intern(cmime, FALSE), TRUE);
 498     } else if (g_strcmp0(cmime, "application/x-java-file-list") == 0) {
 499         ret = dnd_target_get_list(env, TRUE);
 500     } else if (g_strcmp0(cmime, "application/x-java-rawimage") == 0 ) {
 501         ret = dnd_target_get_image(env);
 502     } else {
 503         ret = dnd_target_get_raw(env, gdk_atom_intern(cmime, FALSE), FALSE);
 504     }
 505     LOG_EXCEPTION(env)
 506     env->ReleaseStringUTFChars(mime, cmime);
 507 
 508     return ret;
 509 }
 510 
 511 /************************* SOURCE *********************************************/
 512 
 513 
 514 static GdkWindow *dnd_window = NULL;
 515 static jint dnd_performed_action;
 516 
 517 const char * const SOURCE_DND_CONTEXT = "fx-dnd-context";
 518 const char * const SOURCE_DND_DATA = "fx-dnd-data";
 519 const char * const SOURCE_DND_ACTIONS = "fx-dnd-actions";
 520 
 521 static GdkWindow* get_dnd_window()
 522 {
 523     if (dnd_window == NULL) {
 524         GdkWindowAttr attr;
 525         memset(&attr, 0, sizeof (GdkWindowAttr));
 526         attr.override_redirect = TRUE;
 527         attr.window_type = GDK_WINDOW_TEMP;
 528         attr.type_hint = GDK_WINDOW_TYPE_HINT_UTILITY;
 529         attr.wclass = GDK_INPUT_OUTPUT;
 530         attr.event_mask = GDK_ALL_EVENTS_MASK;
 531         dnd_window = gdk_window_new(NULL, &attr, GDK_WA_NOREDIR | GDK_WA_TYPE_HINT);
 532 
 533         gdk_window_move(dnd_window, -100, -100);
 534         gdk_window_resize(dnd_window, 1, 1);
 535         gdk_window_show(dnd_window);
 536     }
 537     return dnd_window;
 538 }
 539 
 540 static void dnd_set_performed_action(jint performed_action) {
 541     dnd_performed_action = performed_action;
 542 }
 543 
 544 static jint dnd_get_performed_action() {
 545     return dnd_performed_action;
 546 }
 547 
 548 static void dnd_pointer_grab(GdkCursor *cursor)
 549 {
 550     glass_gdk_master_pointer_grab(dnd_window, cursor);
 551 }
 552 
 553 static GdkDragContext *get_drag_context() {
 554     GdkDragContext *ctx;
 555     ctx = (GdkDragContext*)g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_CONTEXT);
 556     return ctx;
 557 }
 558 
 559 static gboolean dnd_finish_callback() {
 560     if (dnd_window) {
 561         dnd_set_performed_action(
 562                 translate_gdk_action_to_glass(
 563                     GLASS_GDK_DRAG_CONTEXT_GET_SELECTED_ACTION(
 564                         get_drag_context())));
 565 
 566         gdk_window_destroy(dnd_window);
 567         dnd_window = NULL;
 568         DragView::reset_drag_view();
 569     }
 570 
 571     return FALSE;
 572 }
 573 
 574 gboolean is_in_drag()
 575 {
 576     return dnd_window != NULL;
 577 }
 578 
 579 static void determine_actions(guint state, GdkDragAction *action, GdkDragAction *possible_actions)
 580 {
 581     GdkDragAction suggested = static_cast<GdkDragAction>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_ACTIONS)));
 582 
 583     if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
 584         if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK) && (suggested & GDK_ACTION_LINK)) {
 585             *action = *possible_actions = GDK_ACTION_LINK;
 586             return;
 587         } else if ((state & GDK_SHIFT_MASK) && (suggested & GDK_ACTION_MOVE)) {
 588             *action = *possible_actions = GDK_ACTION_MOVE;
 589             return;
 590         } else if (suggested & GDK_ACTION_COPY){
 591             *action = *possible_actions = GDK_ACTION_COPY;
 592             return;
 593         }
 594     }
 595 
 596     *possible_actions = suggested;
 597 
 598     if (suggested & GDK_ACTION_COPY) {
 599         *action = GDK_ACTION_COPY;
 600     } else if (suggested & GDK_ACTION_MOVE) {
 601         *action = GDK_ACTION_MOVE;
 602     } else if (suggested & GDK_ACTION_LINK) {
 603         *action = GDK_ACTION_LINK;
 604     } else {
 605         *action = static_cast<GdkDragAction>(0);
 606     }
 607 }
 608 
 609 static jobject dnd_source_get_data(const char *key)
 610 {
 611     jobject data = (jobject)g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_DATA);
 612     jstring string = mainEnv->NewStringUTF(key);
 613     EXCEPTION_OCCURED(mainEnv);
 614     jobject result = mainEnv->CallObjectMethod(data, jMapGet, string, NULL);
 615 
 616     return (EXCEPTION_OCCURED(mainEnv)) ? NULL : result;
 617 }
 618 
 619 static gboolean dnd_source_set_utf8_string(GdkWindow *requestor, GdkAtom property)
 620 {
 621     jstring string = (jstring)dnd_source_get_data("text/plain");
 622     if (!string) {
 623         return FALSE;
 624     }
 625 
 626     const char *cstring = mainEnv->GetStringUTFChars(string, NULL);
 627     if (!cstring) {
 628         return FALSE;
 629     }
 630     gint size = strlen(cstring);
 631 
 632     gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING,
 633             8, GDK_PROP_MODE_REPLACE, (guchar *)cstring, size);
 634 
 635     mainEnv->ReleaseStringUTFChars(string, cstring);
 636     return TRUE;
 637 }
 638 
 639 static gboolean dnd_source_set_string(GdkWindow *requestor, GdkAtom property)
 640 {
 641     jstring string = (jstring)dnd_source_get_data("text/plain");
 642     if (!string) {
 643         return FALSE;
 644     }
 645 
 646     gboolean is_data_set = FALSE;
 647     const char *cstring = mainEnv->GetStringUTFChars(string, NULL);
 648     if (cstring) {
 649         gchar *res_str = g_convert((gchar *)cstring, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL);
 650 
 651         if (res_str) {
 652             gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING,
 653                     8, GDK_PROP_MODE_REPLACE, (guchar *)res_str, strlen(res_str));
 654             g_free(res_str);
 655             is_data_set = TRUE;
 656         }
 657 
 658         mainEnv->ReleaseStringUTFChars(string, cstring);
 659     }
 660     return is_data_set;
 661 }
 662 
 663 static gboolean dnd_source_set_image(GdkWindow *requestor, GdkAtom property, GdkAtom target)
 664 {
 665     jobject pixels = dnd_source_get_data("application/x-java-rawimage");
 666     if (!pixels) {
 667         return FALSE;
 668     }
 669 
 670     gchar *buffer;
 671     gsize size;
 672     const char * type;
 673     GdkPixbuf *pixbuf = NULL;
 674     gboolean result = FALSE;
 675 
 676     if (target == TARGET_MIME_PNG_ATOM) {
 677         type = "png";
 678     } else if (target == TARGET_MIME_JPEG_ATOM) {
 679         type = "jpeg";
 680     } else if (target == TARGET_MIME_TIFF_ATOM) {
 681         type = "tiff";
 682     } else if (target == TARGET_MIME_BMP_ATOM) {
 683         type = "bmp";
 684     } else {
 685         return FALSE;
 686     }
 687 
 688     mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf));
 689 
 690     if (!EXCEPTION_OCCURED(mainEnv)
 691             && gdk_pixbuf_save_to_buffer(pixbuf, &buffer, &size, type, NULL, NULL)) {
 692         gdk_property_change(requestor, property, target,
 693                 8, GDK_PROP_MODE_REPLACE, (guchar *)buffer, size);
 694         result = TRUE;
 695     }
 696     g_object_unref(pixbuf);
 697     return result;
 698 }
 699 
 700 static gboolean dnd_source_set_uri_list(GdkWindow *requestor, GdkAtom property)
 701 {
 702     const gchar* url = NULL;
 703     jstring jurl = NULL;
 704 
 705     jobjectArray files_array = NULL;
 706     gsize files_cnt = 0;
 707 
 708     if (jurl = (jstring) dnd_source_get_data("text/uri-list")) {
 709         url = mainEnv->GetStringUTFChars(jurl, NULL);
 710     }
 711 
 712     if (files_array = (jobjectArray) dnd_source_get_data("application/x-java-file-list")) {
 713         files_cnt = mainEnv->GetArrayLength(files_array);
 714     }
 715     if (!url && !files_cnt) {
 716         return FALSE;
 717     }
 718 
 719     GString* res = g_string_new (NULL); //http://www.ietf.org/rfc/rfc2483.txt
 720 
 721     if (files_cnt > 0) {
 722         for (gsize i = 0; i < files_cnt; ++i) {
 723             jstring string = (jstring) mainEnv->GetObjectArrayElement(files_array, i);
 724             EXCEPTION_OCCURED(mainEnv);
 725             const gchar* file = mainEnv->GetStringUTFChars(string, NULL);
 726             gchar* uri = g_filename_to_uri(file, NULL, NULL);
 727 
 728             g_string_append(res, uri);
 729             g_string_append(res, URI_LIST_LINE_BREAK);
 730 
 731             g_free(uri);
 732             mainEnv->ReleaseStringUTFChars(string, file);
 733         }
 734     }
 735     if (url) {
 736         g_string_append(res, url);
 737         g_string_append(res, URI_LIST_LINE_BREAK);
 738         mainEnv->ReleaseStringUTFChars(jurl, url);
 739     }
 740 
 741     gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING,
 742             8, GDK_PROP_MODE_REPLACE, (guchar *) res->str, res->len);
 743 
 744     g_string_free(res, TRUE);
 745     return TRUE;
 746 }
 747 
 748 static gboolean dnd_source_set_raw(GdkWindow *requestor, GdkAtom property, GdkAtom target)
 749 {
 750     gchar *target_name = gdk_atom_name(target);
 751     jobject data = dnd_source_get_data(target_name);
 752     gboolean is_data_set = FALSE;
 753     if (data) {
 754         if (mainEnv->IsInstanceOf(data, jStringCls)) {
 755             const char *cstring = mainEnv->GetStringUTFChars((jstring)data, NULL);
 756             if (cstring) {
 757                 gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING,
 758                         8, GDK_PROP_MODE_REPLACE, (guchar *) cstring, strlen(cstring));
 759 
 760                 mainEnv->ReleaseStringUTFChars((jstring)data, cstring);
 761                 is_data_set = TRUE;
 762             }
 763         } else if (mainEnv->IsInstanceOf(data, jByteBufferCls)) {
 764             jbyteArray byteArray = (jbyteArray)mainEnv->CallObjectMethod(data, jByteBufferArray);
 765             if (!EXCEPTION_OCCURED(mainEnv)) {
 766                 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL);
 767                 if (raw) {
 768                     jsize nraw = mainEnv->GetArrayLength(byteArray);
 769 
 770                     gdk_property_change(requestor, property, target,
 771                             8, GDK_PROP_MODE_REPLACE, (guchar *) raw, nraw);
 772 
 773                     mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT);
 774                     is_data_set = TRUE;
 775                 }
 776             }
 777         }
 778     }
 779 
 780     g_free(target_name);
 781     return is_data_set;
 782 }
 783 
 784 static void process_dnd_source_selection_req(GdkWindow *window, GdkEventSelection* event)
 785 {
 786     (void)window;
 787 
 788     GdkWindow *requestor = GLASS_GDK_SELECTION_EVENT_GET_REQUESTOR(event);
 789 
 790     gboolean is_data_set = FALSE;
 791     if (event->target == TARGET_UTF8_STRING_ATOM
 792             || event->target == TARGET_MIME_TEXT_PLAIN_ATOM) {
 793         is_data_set = dnd_source_set_utf8_string(requestor, event->property);
 794     } else if (event->target == TARGET_STRING_ATOM) {
 795         is_data_set = dnd_source_set_string(requestor, event->property);
 796 //    } else if (event->target == TARGET_COMPOUND_TEXT_ATOM) { // XXX compound text
 797     } else if (target_is_image(event->target)) {
 798         is_data_set = dnd_source_set_image(requestor, event->property, event->target);
 799     } else if (event->target == TARGET_MIME_URI_LIST_ATOM) {
 800         is_data_set = dnd_source_set_uri_list(requestor, event->property);
 801     } else {
 802         is_data_set = dnd_source_set_raw(requestor, event->property, event->target);
 803     }
 804 
 805     gdk_selection_send_notify(event->requestor, event->selection, event->target,
 806                                (is_data_set) ? event->property : GDK_NONE, event->time);
 807 }
 808 
 809 static void process_dnd_source_mouse_release(GdkWindow *window, GdkEventButton *event) {
 810     (void)window;
 811     (void)event;
 812 
 813     glass_gdk_master_pointer_ungrab();
 814 
 815     if (GLASS_GDK_DRAG_CONTEXT_GET_SELECTED_ACTION(get_drag_context())) {
 816         gdk_drag_drop(get_drag_context(), GDK_CURRENT_TIME);
 817     } else {
 818         gdk_drag_abort(get_drag_context(), GDK_CURRENT_TIME);
 819         /* let the gdk_drag_abort messages handled before finish */
 820         gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL);
 821     }
 822 }
 823 
 824 static void process_drag_motion(gint x_root, gint y_root, guint state)
 825 {
 826     DragView::move(x_root, y_root);
 827 
 828     GdkWindow *dest_window;
 829     GdkDragProtocol prot;
 830 
 831     gdk_drag_find_window_for_screen(get_drag_context(), NULL, gdk_screen_get_default(),
 832             x_root, y_root, &dest_window, &prot);
 833 
 834     if (prot != GDK_DRAG_PROTO_NONE) {
 835         GdkDragAction action, possible_actions;
 836         determine_actions(state, &action, &possible_actions);
 837         gdk_drag_motion(get_drag_context(), dest_window, prot, x_root, y_root,
 838                 action, possible_actions, GDK_CURRENT_TIME);
 839     }
 840 }
 841 
 842 static void process_dnd_source_mouse_motion(GdkWindow *window, GdkEventMotion *event)
 843 {
 844     (void)window;
 845 
 846     process_drag_motion(event->x_root, event->y_root, event->state);
 847 }
 848 
 849 static void process_dnd_source_key_press_release(GdkWindow *window, GdkEventKey *event)
 850 {
 851     (void)window;
 852 
 853     if (event->is_modifier) {
 854         guint state = event->state;
 855         guint new_mod = 0;
 856         gint x,y;
 857         if (event->keyval == GLASS_GDK_KEY_CONSTANT(Control_L) ||
 858                 event->keyval == GLASS_GDK_KEY_CONSTANT(Control_R)) {
 859             new_mod = GDK_CONTROL_MASK;
 860         } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_L) ||
 861                 event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_R)) {
 862             new_mod = GDK_MOD1_MASK;
 863         } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_L) ||
 864                 event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_R)) {
 865             new_mod = GDK_SHIFT_MASK;
 866         }
 867 
 868         if (event->type == GDK_KEY_PRESS) {
 869             state |= new_mod;
 870         } else {
 871             state ^= new_mod;
 872         }
 873 
 874         glass_gdk_master_pointer_get_position(&x, &y);
 875         process_drag_motion(x, y, state);
 876 
 877     }
 878 }
 879 
 880 static void process_dnd_source_drag_status(GdkWindow *window, GdkEventDND *event)
 881 {
 882     (void)window;
 883 
 884     GdkDragAction selected = GLASS_GDK_DRAG_CONTEXT_GET_SELECTED_ACTION(event->context);
 885     GdkCursor* cursor;
 886 
 887     if (selected & GDK_ACTION_COPY) {
 888         cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-copy");
 889         if (cursor == NULL) {
 890             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "copy");
 891         }
 892     } else if (selected & (GDK_ACTION_MOVE | GDK_ACTION_PRIVATE)) {
 893         cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-move");
 894         if (cursor == NULL) {
 895             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "move");
 896         }
 897         if (cursor == NULL) {
 898             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "closedhand");
 899         }
 900     } else if (selected & GDK_ACTION_LINK) {
 901         cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-link");
 902         if (cursor == NULL) {
 903             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "link");
 904         }
 905     } else {
 906         cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-no-drop");
 907         if (cursor == NULL) {
 908             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "no-drop");
 909         }
 910         if (cursor == NULL) {
 911             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "not-allowed");
 912         }
 913         if (cursor == NULL) {
 914             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "forbidden");
 915         }
 916         if (cursor == NULL) {
 917             cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-none");
 918         }
 919     }
 920     if (cursor == NULL) {
 921         cursor = gdk_cursor_new(GDK_LEFT_PTR);
 922     }
 923 
 924     dnd_pointer_grab(cursor);
 925 }
 926 
 927 static void process_dnd_source_drop_finished(GdkWindow *window, GdkEventDND *event)
 928 {
 929     (void)window;
 930     (void)event;
 931 
 932     gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL);
 933 }
 934 
 935 void process_dnd_source(GdkWindow *window, GdkEvent *event) {
 936     switch(event->type) {
 937         case GDK_MOTION_NOTIFY:
 938             process_dnd_source_mouse_motion(window, &event->motion);
 939             break;
 940         case GDK_BUTTON_RELEASE:
 941             process_dnd_source_mouse_release(window, &event->button);
 942             break;
 943         case GDK_DRAG_STATUS:
 944             process_dnd_source_drag_status(window, &event->dnd);
 945             break;
 946         case GDK_DROP_FINISHED:
 947             process_dnd_source_drop_finished(window, &event->dnd);
 948             break;
 949         case GDK_KEY_PRESS:
 950         case GDK_KEY_RELEASE:
 951             process_dnd_source_key_press_release(window, &event->key);
 952             break;
 953         case GDK_DRAG_ENTER:
 954             gdk_selection_owner_set(dnd_window, gdk_drag_get_selection(get_drag_context()), GDK_CURRENT_TIME, FALSE);
 955             break;
 956         case GDK_SELECTION_REQUEST:
 957             process_dnd_source_selection_req(window, &event->selection);
 958             break;
 959         default:
 960             break;
 961     }
 962 }
 963 
 964 static void add_target_from_jstring(JNIEnv *env, GList **list, jstring string)
 965 {
 966     const char *gstring = env->GetStringUTFChars(string, NULL);
 967     if (g_strcmp0(gstring, "text/plain") == 0) {
 968         *list = g_list_append(*list, TARGET_UTF8_STRING_ATOM);
 969         *list = g_list_append(*list, TARGET_MIME_TEXT_PLAIN_ATOM);
 970         *list = g_list_append(*list, TARGET_STRING_ATOM);
 971         //*list = g_list_append(list, TARGET_COMPOUND_TEXT_ATOM);
 972     } else if (g_strcmp0(gstring, "application/x-java-rawimage") == 0) {
 973         *list = g_list_append(*list, TARGET_MIME_PNG_ATOM);
 974         *list = g_list_append(*list, TARGET_MIME_JPEG_ATOM);
 975         *list = g_list_append(*list, TARGET_MIME_TIFF_ATOM);
 976         *list = g_list_append(*list, TARGET_MIME_BMP_ATOM);
 977     } else if (g_strcmp0(gstring, "application/x-java-file-list") == 0) {
 978         *list = g_list_append(*list, TARGET_MIME_URI_LIST_ATOM);
 979     } else {
 980         *list = g_list_append(*list, gdk_atom_intern(gstring, FALSE));
 981     }
 982     env->ReleaseStringUTFChars(string, gstring);
 983 
 984 }
 985 
 986 static GList* data_to_targets(JNIEnv *env, jobject data)
 987 {
 988     jobject keys;
 989     jobject keysIterator;
 990     jstring next;
 991 
 992     GList *list = NULL;
 993 
 994     init_target_atoms();
 995 
 996     keys = env->CallObjectMethod(data, jMapKeySet, NULL);
 997     JNI_EXCEPTION_TO_CPP(env)
 998     keysIterator = env->CallObjectMethod(keys, jIterableIterator, NULL);
 999     JNI_EXCEPTION_TO_CPP(env)
1000     while (env->CallBooleanMethod(keysIterator, jIteratorHasNext) == JNI_TRUE) {
1001         next = (jstring)env->CallObjectMethod(keysIterator, jIteratorNext, NULL);
1002         JNI_EXCEPTION_TO_CPP(env)
1003         add_target_from_jstring(env, &list, next);
1004     }
1005     return list;
1006 }
1007 
1008 static void dnd_source_push_data(JNIEnv *env, jobject data, jint supported)
1009 {
1010     GdkWindow *src_window = get_dnd_window();
1011     GList *targets;
1012     GdkDragContext *ctx;
1013 
1014     if (supported == 0) {
1015         return; // No supported actions, do nothing
1016     }
1017 
1018     targets = data_to_targets(env, data);
1019 
1020     data = env->NewGlobalRef(data);
1021 
1022     g_object_set_data_full(G_OBJECT(src_window), SOURCE_DND_DATA, data, clear_global_ref);
1023     g_object_set_data(G_OBJECT(src_window), SOURCE_DND_ACTIONS, (gpointer)translate_glass_action_to_gdk(supported));
1024 
1025     DragView::set_drag_view();
1026 
1027     ctx = gdk_drag_begin(src_window, targets);
1028 
1029     g_list_free(targets);
1030 
1031     g_object_set_data(G_OBJECT(src_window), SOURCE_DND_CONTEXT, ctx);
1032 
1033     dnd_pointer_grab(NULL);
1034 
1035     is_dnd_owner = TRUE;
1036 }
1037 
1038 jint execute_dnd(JNIEnv *env, jobject data, jint supported) {
1039     try {
1040         dnd_source_push_data(env, data, supported);
1041     } catch (jni_exception&) {
1042         return 0;
1043     }
1044 
1045     while (is_in_drag()) {
1046         gtk_main_iteration();
1047     }
1048 
1049     return dnd_get_performed_action();
1050 }
1051 
1052  /******************** DRAG VIEW ***************************/
1053 DragView::View* DragView::view = NULL;
1054 
1055 void DragView::reset_drag_view() {
1056     delete view;
1057     view = NULL;
1058 }
1059 
1060 gboolean DragView::get_drag_image_offset(int* x, int* y) {
1061     gboolean offset_set = FALSE;
1062     jobject bb = dnd_source_get_data("application/x-java-drag-image-offset");
1063     if (bb) {
1064         jbyteArray byteArray = (jbyteArray)mainEnv->CallObjectMethod(bb, jByteBufferArray);
1065         if (!EXCEPTION_OCCURED(mainEnv)) {
1066             jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL);
1067             jsize nraw = mainEnv->GetArrayLength(byteArray);
1068 
1069             if ((size_t) nraw >= sizeof(jint) * 2) {
1070                 jint* r = (jint*) raw;
1071                 *x = BSWAP_32(r[0]);
1072                 *y = BSWAP_32(r[1]);
1073                 offset_set = TRUE;
1074             }
1075 
1076             mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT);
1077         }
1078     }
1079     return offset_set;
1080 }
1081 
1082 GdkPixbuf* DragView::get_drag_image(gboolean* is_raw_image, gint* width, gint* height) {
1083     GdkPixbuf *pixbuf = NULL;
1084     gboolean is_raw = FALSE;
1085 
1086     jobject drag_image = dnd_source_get_data("application/x-java-drag-image");
1087 
1088     if (drag_image) {
1089         jbyteArray byteArray = (jbyteArray) mainEnv->CallObjectMethod(drag_image, jByteBufferArray);
1090         if (!EXCEPTION_OCCURED(mainEnv)) {
1091 
1092             jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL);
1093             jsize nraw = mainEnv->GetArrayLength(byteArray);
1094 
1095             int w = 0, h = 0;
1096             int whsz = sizeof(jint) * 2; // Pixels are stored right after two ints
1097                                          // in this byteArray: width and height
1098             if (nraw > whsz) {
1099                 jint* int_raw = (jint*) raw;
1100                 w = BSWAP_32(int_raw[0]);
1101                 h = BSWAP_32(int_raw[1]);
1102 
1103                 // We should have enough pixels for requested width and height
1104                 if ((nraw - whsz) / 4 - w * h >= 0 ) {
1105                     guchar* data = (guchar*) g_try_malloc0(nraw - whsz);
1106                     if (data) {
1107                         memcpy(data, (raw + whsz), nraw - whsz);
1108                         pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
1109                                 w, h, w * 4, (GdkPixbufDestroyNotify) g_free, NULL);
1110                     }
1111                 }
1112             }
1113             mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT);
1114         }
1115     }
1116 
1117     if (!GDK_IS_PIXBUF(pixbuf)) {
1118         jobject pixels = dnd_source_get_data("application/x-java-rawimage");
1119         if (pixels) {
1120             is_raw = TRUE;
1121             mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf));
1122             CHECK_JNI_EXCEPTION_RET(mainEnv, NULL)
1123         }
1124     }
1125 
1126     if (!GDK_IS_PIXBUF(pixbuf)) {
1127         return NULL;
1128     }
1129 
1130     int w = gdk_pixbuf_get_width(pixbuf);
1131     int h = gdk_pixbuf_get_height(pixbuf);
1132 
1133     if (w > DRAG_IMAGE_MAX_WIDTH || h > DRAG_IMAGE_MAX_HEIGH) {
1134         double rw = DRAG_IMAGE_MAX_WIDTH / (double)w;
1135         double rh =  DRAG_IMAGE_MAX_HEIGH / (double)h;
1136         double r = MIN(rw, rh);
1137 
1138 
1139         int new_w = w * r;
1140         int new_h = h * r;
1141 
1142         w = new_w;
1143         h = new_h;
1144 
1145         GdkPixbuf *tmp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, new_w, new_h, GDK_INTERP_TILES);
1146         g_object_unref(pixbuf);
1147         if (!GDK_IS_PIXBUF(tmp_pixbuf)) {
1148             return NULL;
1149         }
1150         pixbuf = tmp_pixbuf;
1151     }
1152 
1153     *is_raw_image = is_raw;
1154     *width = w;
1155     *height = h;
1156 
1157     return pixbuf;
1158 }
1159 
1160 void DragView::set_drag_view() {
1161     reset_drag_view();
1162 
1163     gboolean is_raw_image = FALSE;
1164     gint w = 0, h = 0;
1165     GdkPixbuf* pixbuf = get_drag_image(&is_raw_image, &w, &h);
1166 
1167     if (GDK_IS_PIXBUF(pixbuf)) {
1168         gint offset_x = w / 2;
1169         gint offset_y = h / 2;
1170 
1171         gboolean is_offset_set = get_drag_image_offset(&offset_x, &offset_y);
1172 
1173         DragView::view = new DragView::View(pixbuf, is_raw_image, is_offset_set, offset_x, offset_y);
1174     }
1175 }
1176 void DragView::move(gint x, gint y) {
1177     if (view) {
1178         view->move(x, y);
1179     }
1180 }
1181 
1182 static void on_screen_changed(GtkWidget *widget, GdkScreen *previous_screen, gpointer view) {
1183     (void)widget;
1184     (void)previous_screen;
1185 
1186     ((DragView::View*) view)->screen_changed();
1187 }
1188 
1189 static gboolean on_expose(GtkWidget *widget, GdkEventExpose *event, gpointer view) {
1190     (void)widget;
1191     (void)event;
1192 
1193     ((DragView::View*) view)->expose();
1194     return FALSE;
1195 }
1196 
1197 DragView::View::View(GdkPixbuf* _pixbuf, gboolean _is_raw_image,
1198                                 gboolean _is_offset_set, gint _offset_x, gint _offset_y) :
1199         pixbuf(_pixbuf),
1200         is_raw_image(_is_raw_image),
1201         is_offset_set(_is_offset_set),
1202         offset_x(_offset_x),
1203         offset_y(_offset_y)
1204 {
1205     width = gdk_pixbuf_get_width(pixbuf);
1206     height = gdk_pixbuf_get_height(pixbuf);
1207 
1208     widget = gtk_window_new(GTK_WINDOW_POPUP);
1209     gtk_window_set_type_hint(GTK_WINDOW(widget), GDK_WINDOW_TYPE_HINT_DND);
1210 
1211     screen_changed();
1212 
1213     gtk_widget_realize(widget);
1214 
1215     GdkRegion* region = gdk_region_new();
1216     gdk_window_input_shape_combine_region(gtk_widget_get_window(widget), region, 0,0);
1217     gdk_region_destroy(region);
1218 
1219     gtk_widget_set_app_paintable(widget, TRUE);
1220 
1221     g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(on_expose), this);
1222     g_signal_connect(G_OBJECT(widget), "screen-changed", G_CALLBACK(on_screen_changed), this);
1223 
1224     gtk_widget_set_size_request(widget, width, height);
1225 
1226     gtk_window_set_decorated(GTK_WINDOW(widget), FALSE);
1227     gtk_window_move(GTK_WINDOW(widget), -10000, -10000);
1228     gtk_window_set_opacity(GTK_WINDOW(widget), .7);
1229     gtk_widget_show_all(widget);
1230 }
1231 
1232 void DragView::View::screen_changed() {
1233     GdkScreen *screen = gtk_widget_get_screen(widget);
1234     GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen);
1235 
1236     if (!colormap || !gdk_screen_is_composited(screen)) {
1237         if (!is_offset_set) {
1238             offset_x = 1;
1239             offset_y = 1;
1240         }
1241     }
1242 
1243     if (!colormap) {
1244         colormap = gdk_screen_get_rgb_colormap(screen);
1245     }
1246     gtk_widget_set_colormap(widget, colormap);
1247 }
1248 
1249 void DragView::View::expose() {
1250     cairo_t *context = gdk_cairo_create(widget->window);
1251 
1252     cairo_surface_t* cairo_surface;
1253 
1254     guchar* pixels = is_raw_image
1255             ? (guchar*) convert_BGRA_to_RGBA((const int*) gdk_pixbuf_get_pixels(pixbuf),
1256                                                 gdk_pixbuf_get_rowstride(pixbuf),
1257                                                 height)
1258             : gdk_pixbuf_get_pixels(pixbuf);
1259 
1260     cairo_surface = cairo_image_surface_create_for_data(
1261             pixels,
1262             CAIRO_FORMAT_ARGB32,
1263             width, height, width * 4);
1264 
1265     cairo_set_source_surface(context, cairo_surface, 0, 0);
1266     cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
1267     cairo_paint(context);
1268 
1269     if (is_raw_image) {
1270         g_free(pixels);
1271     }
1272     cairo_destroy(context);
1273     cairo_surface_destroy(cairo_surface);
1274 }
1275 
1276 void DragView::View::move(gint x, gint y) {
1277     if (!gtk_events_pending()) { // avoid sluggish window move
1278         gtk_window_move(GTK_WINDOW(widget), x - offset_x, y - offset_y);
1279     }
1280 }
1281 
1282 DragView::View::~View() {
1283     if (widget) {
1284         gtk_widget_destroy(widget);
1285         widget == NULL;
1286     }
1287     if (pixbuf) {
1288         g_object_unref(pixbuf);
1289         pixbuf == NULL;
1290     }
1291 }
1292