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