1 /* 2 * Copyright (c) 2011, 2018, 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_general.h" 27 #include "glass_evloop.h" 28 29 #include "com_sun_glass_events_DndEvent.h" 30 #include "com_sun_glass_ui_gtk_GtkDnDClipboard.h" 31 32 #include <jni.h> 33 #include <cstring> 34 35 #include <gtk/gtk.h> 36 #include <gdk/gdkx.h> 37 #include <gdk/gdkkeysyms.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 = 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 = 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 = 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(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 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 #ifdef GLASS_GTK3 789 GdkWindow *requestor = (event->requestor); 790 #else 791 GdkWindow *requestor = 792 gdk_x11_window_foreign_new_for_display(gdk_display_get_default(), event->requestor); 793 #endif 794 795 gboolean is_data_set = FALSE; 796 if (event->target == TARGET_UTF8_STRING_ATOM 797 || event->target == TARGET_MIME_TEXT_PLAIN_ATOM) { 798 is_data_set = dnd_source_set_utf8_string(requestor, event->property); 799 } else if (event->target == TARGET_STRING_ATOM) { 800 is_data_set = dnd_source_set_string(requestor, event->property); 801 // } else if (event->target == TARGET_COMPOUND_TEXT_ATOM) { // XXX compound text 802 } else if (target_is_image(event->target)) { 803 is_data_set = dnd_source_set_image(requestor, event->property, event->target); 804 } else if (event->target == TARGET_MIME_URI_LIST_ATOM) { 805 is_data_set = dnd_source_set_uri_list(requestor, event->property); 806 } else { 807 is_data_set = dnd_source_set_raw(requestor, event->property, event->target); 808 } 809 810 gdk_selection_send_notify(event->requestor, event->selection, event->target, 811 (is_data_set) ? event->property : GDK_NONE, event->time); 812 } 813 814 static void process_dnd_source_mouse_release(GdkWindow *window, GdkEventButton *event) { 815 (void)window; 816 (void)event; 817 818 glass_gdk_master_pointer_ungrab(); 819 820 if (gdk_drag_context_get_selected_action(get_drag_context())) { 821 gdk_drag_drop(get_drag_context(), GDK_CURRENT_TIME); 822 } else { 823 gdk_drag_abort(get_drag_context(), GDK_CURRENT_TIME); 824 /* let the gdk_drag_abort messages handled before finish */ 825 gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL); 826 } 827 } 828 829 static void process_drag_motion(gint x_root, gint y_root, guint state) 830 { 831 DragView::move(x_root, y_root); 832 833 GdkWindow *dest_window; 834 GdkDragProtocol prot; 835 836 gdk_drag_find_window_for_screen(get_drag_context(), NULL, gdk_screen_get_default(), 837 x_root, y_root, &dest_window, &prot); 838 839 if (prot != GDK_DRAG_PROTO_NONE) { 840 GdkDragAction action, possible_actions; 841 determine_actions(state, &action, &possible_actions); 842 gdk_drag_motion(get_drag_context(), dest_window, prot, x_root, y_root, 843 action, possible_actions, GDK_CURRENT_TIME); 844 } 845 } 846 847 static void process_dnd_source_mouse_motion(GdkWindow *window, GdkEventMotion *event) 848 { 849 (void)window; 850 851 process_drag_motion(event->x_root, event->y_root, event->state); 852 } 853 854 static void process_dnd_source_key_press_release(GdkWindow *window, GdkEventKey *event) 855 { 856 (void)window; 857 858 if (event->is_modifier) { 859 guint state = event->state; 860 guint new_mod = 0; 861 gint x,y; 862 if (event->keyval == GLASS_GDK_KEY_CONSTANT(Control_L) || 863 event->keyval == GLASS_GDK_KEY_CONSTANT(Control_R)) { 864 new_mod = GDK_CONTROL_MASK; 865 } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_L) || 866 event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_R)) { 867 new_mod = GDK_MOD1_MASK; 868 } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_L) || 869 event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_R)) { 870 new_mod = GDK_SHIFT_MASK; 871 } 872 873 if (event->type == GDK_KEY_PRESS) { 874 state |= new_mod; 875 } else { 876 state ^= new_mod; 877 } 878 879 glass_gdk_master_pointer_get_position(&x, &y); 880 process_drag_motion(x, y, state); 881 882 } 883 } 884 885 static void process_dnd_source_drag_status(GdkWindow *window, GdkEventDND *event) 886 { 887 (void)window; 888 889 GdkDragAction selected = gdk_drag_context_get_selected_action(event->context); 890 GdkCursor* cursor; 891 892 if (selected & GDK_ACTION_COPY) { 893 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-copy"); 894 if (cursor == NULL) { 895 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "copy"); 896 } 897 } else if (selected & (GDK_ACTION_MOVE | GDK_ACTION_PRIVATE)) { 898 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-move"); 899 if (cursor == NULL) { 900 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "move"); 901 } 902 if (cursor == NULL) { 903 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "closedhand"); 904 } 905 } else if (selected & GDK_ACTION_LINK) { 906 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-link"); 907 if (cursor == NULL) { 908 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "link"); 909 } 910 } else { 911 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-no-drop"); 912 if (cursor == NULL) { 913 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "no-drop"); 914 } 915 if (cursor == NULL) { 916 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "not-allowed"); 917 } 918 if (cursor == NULL) { 919 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "forbidden"); 920 } 921 if (cursor == NULL) { 922 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-none"); 923 } 924 } 925 if (cursor == NULL) { 926 cursor = gdk_cursor_new(GDK_LEFT_PTR); 927 } 928 929 dnd_pointer_grab(cursor); 930 } 931 932 static void process_dnd_source_drop_finished(GdkWindow *window, GdkEventDND *event) 933 { 934 (void)window; 935 (void)event; 936 937 gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL); 938 } 939 940 void process_dnd_source(GdkWindow *window, GdkEvent *event) { 941 switch(event->type) { 942 case GDK_MOTION_NOTIFY: 943 process_dnd_source_mouse_motion(window, &event->motion); 944 break; 945 case GDK_BUTTON_RELEASE: 946 process_dnd_source_mouse_release(window, &event->button); 947 break; 948 case GDK_DRAG_STATUS: 949 process_dnd_source_drag_status(window, &event->dnd); 950 break; 951 case GDK_DROP_FINISHED: 952 process_dnd_source_drop_finished(window, &event->dnd); 953 break; 954 case GDK_KEY_PRESS: 955 case GDK_KEY_RELEASE: 956 process_dnd_source_key_press_release(window, &event->key); 957 break; 958 case GDK_DRAG_ENTER: 959 gdk_selection_owner_set(dnd_window, gdk_drag_get_selection(get_drag_context()), GDK_CURRENT_TIME, FALSE); 960 break; 961 case GDK_SELECTION_REQUEST: 962 process_dnd_source_selection_req(window, &event->selection); 963 break; 964 default: 965 break; 966 } 967 } 968 969 static void add_target_from_jstring(JNIEnv *env, GList **list, jstring string) 970 { 971 const char *gstring = env->GetStringUTFChars(string, NULL); 972 if (g_strcmp0(gstring, "text/plain") == 0) { 973 *list = g_list_append(*list, TARGET_UTF8_STRING_ATOM); 974 *list = g_list_append(*list, TARGET_MIME_TEXT_PLAIN_ATOM); 975 *list = g_list_append(*list, TARGET_STRING_ATOM); 976 //*list = g_list_append(list, TARGET_COMPOUND_TEXT_ATOM); 977 } else if (g_strcmp0(gstring, "application/x-java-rawimage") == 0) { 978 *list = g_list_append(*list, TARGET_MIME_PNG_ATOM); 979 *list = g_list_append(*list, TARGET_MIME_JPEG_ATOM); 980 *list = g_list_append(*list, TARGET_MIME_TIFF_ATOM); 981 *list = g_list_append(*list, TARGET_MIME_BMP_ATOM); 982 } else if (g_strcmp0(gstring, "application/x-java-file-list") == 0) { 983 *list = g_list_append(*list, TARGET_MIME_URI_LIST_ATOM); 984 } else { 985 *list = g_list_append(*list, gdk_atom_intern(gstring, FALSE)); 986 } 987 env->ReleaseStringUTFChars(string, gstring); 988 989 } 990 991 static GList* data_to_targets(JNIEnv *env, jobject data) 992 { 993 jobject keys; 994 jobject keysIterator; 995 jstring next; 996 997 GList *list = NULL; 998 999 init_target_atoms(); 1000 1001 keys = env->CallObjectMethod(data, jMapKeySet, NULL); 1002 JNI_EXCEPTION_TO_CPP(env) 1003 keysIterator = env->CallObjectMethod(keys, jIterableIterator, NULL); 1004 JNI_EXCEPTION_TO_CPP(env) 1005 while (env->CallBooleanMethod(keysIterator, jIteratorHasNext) == JNI_TRUE) { 1006 next = (jstring)env->CallObjectMethod(keysIterator, jIteratorNext, NULL); 1007 JNI_EXCEPTION_TO_CPP(env) 1008 add_target_from_jstring(env, &list, next); 1009 } 1010 return list; 1011 } 1012 1013 static void dnd_source_push_data(JNIEnv *env, jobject data, jint supported) 1014 { 1015 GdkWindow *src_window = get_dnd_window(); 1016 GList *targets; 1017 GdkDragContext *ctx; 1018 1019 if (supported == 0) { 1020 return; // No supported actions, do nothing 1021 } 1022 1023 targets = data_to_targets(env, data); 1024 1025 data = env->NewGlobalRef(data); 1026 1027 g_object_set_data_full(G_OBJECT(src_window), SOURCE_DND_DATA, data, clear_global_ref); 1028 g_object_set_data(G_OBJECT(src_window), SOURCE_DND_ACTIONS, (gpointer)translate_glass_action_to_gdk(supported)); 1029 1030 DragView::set_drag_view(); 1031 1032 ctx = gdk_drag_begin(src_window, targets); 1033 1034 g_list_free(targets); 1035 1036 g_object_set_data(G_OBJECT(src_window), SOURCE_DND_CONTEXT, ctx); 1037 1038 dnd_pointer_grab(NULL); 1039 1040 is_dnd_owner = TRUE; 1041 } 1042 1043 jint execute_dnd(JNIEnv *env, jobject data, jint supported) { 1044 try { 1045 dnd_source_push_data(env, data, supported); 1046 } catch (jni_exception&) { 1047 return 0; 1048 } 1049 1050 while (is_in_drag()) { 1051 gtk_main_iteration(); 1052 } 1053 1054 return dnd_get_performed_action(); 1055 } 1056 1057 /******************** DRAG VIEW ***************************/ 1058 DragView::View* DragView::view = NULL; 1059 1060 void DragView::reset_drag_view() { 1061 delete view; 1062 view = NULL; 1063 } 1064 1065 gboolean DragView::get_drag_image_offset(int* x, int* y) { 1066 gboolean offset_set = FALSE; 1067 jobject bb = dnd_source_get_data("application/x-java-drag-image-offset"); 1068 if (bb) { 1069 jbyteArray byteArray = (jbyteArray)mainEnv->CallObjectMethod(bb, jByteBufferArray); 1070 if (!EXCEPTION_OCCURED(mainEnv)) { 1071 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); 1072 jsize nraw = mainEnv->GetArrayLength(byteArray); 1073 1074 if ((size_t) nraw >= sizeof(jint) * 2) { 1075 jint* r = (jint*) raw; 1076 *x = BSWAP_32(r[0]); 1077 *y = BSWAP_32(r[1]); 1078 offset_set = TRUE; 1079 } 1080 1081 mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); 1082 } 1083 } 1084 return offset_set; 1085 } 1086 1087 GdkPixbuf* DragView::get_drag_image(gboolean* is_raw_image, gint* width, gint* height) { 1088 GdkPixbuf *pixbuf = NULL; 1089 gboolean is_raw = FALSE; 1090 1091 jobject drag_image = dnd_source_get_data("application/x-java-drag-image"); 1092 1093 if (drag_image) { 1094 jbyteArray byteArray = (jbyteArray) mainEnv->CallObjectMethod(drag_image, jByteBufferArray); 1095 if (!EXCEPTION_OCCURED(mainEnv)) { 1096 1097 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); 1098 jsize nraw = mainEnv->GetArrayLength(byteArray); 1099 1100 int w = 0, h = 0; 1101 int whsz = sizeof(jint) * 2; // Pixels are stored right after two ints 1102 // in this byteArray: width and height 1103 if (nraw > whsz) { 1104 jint* int_raw = (jint*) raw; 1105 w = BSWAP_32(int_raw[0]); 1106 h = BSWAP_32(int_raw[1]); 1107 1108 // We should have enough pixels for requested width and height 1109 if ((nraw - whsz) / 4 - w * h >= 0 ) { 1110 guchar* data = (guchar*) g_try_malloc0(nraw - whsz); 1111 if (data) { 1112 memcpy(data, (raw + whsz), nraw - whsz); 1113 pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, 1114 w, h, w * 4, (GdkPixbufDestroyNotify) g_free, NULL); 1115 } 1116 } 1117 } 1118 mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); 1119 } 1120 } 1121 1122 if (!GDK_IS_PIXBUF(pixbuf)) { 1123 jobject pixels = dnd_source_get_data("application/x-java-rawimage"); 1124 if (pixels) { 1125 is_raw = TRUE; 1126 mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf)); 1127 CHECK_JNI_EXCEPTION_RET(mainEnv, NULL) 1128 } 1129 } 1130 1131 if (!GDK_IS_PIXBUF(pixbuf)) { 1132 return NULL; 1133 } 1134 1135 int w = gdk_pixbuf_get_width(pixbuf); 1136 int h = gdk_pixbuf_get_height(pixbuf); 1137 1138 if (w > DRAG_IMAGE_MAX_WIDTH || h > DRAG_IMAGE_MAX_HEIGH) { 1139 double rw = DRAG_IMAGE_MAX_WIDTH / (double)w; 1140 double rh = DRAG_IMAGE_MAX_HEIGH / (double)h; 1141 double r = MIN(rw, rh); 1142 1143 1144 int new_w = w * r; 1145 int new_h = h * r; 1146 1147 w = new_w; 1148 h = new_h; 1149 1150 GdkPixbuf *tmp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, new_w, new_h, GDK_INTERP_TILES); 1151 g_object_unref(pixbuf); 1152 if (!GDK_IS_PIXBUF(tmp_pixbuf)) { 1153 return NULL; 1154 } 1155 pixbuf = tmp_pixbuf; 1156 } 1157 1158 *is_raw_image = is_raw; 1159 *width = w; 1160 *height = h; 1161 1162 return pixbuf; 1163 } 1164 1165 void DragView::set_drag_view() { 1166 reset_drag_view(); 1167 1168 gboolean is_raw_image = FALSE; 1169 gint w = 0, h = 0; 1170 GdkPixbuf* pixbuf = get_drag_image(&is_raw_image, &w, &h); 1171 1172 if (GDK_IS_PIXBUF(pixbuf)) { 1173 gint offset_x = w / 2; 1174 gint offset_y = h / 2; 1175 1176 gboolean is_offset_set = get_drag_image_offset(&offset_x, &offset_y); 1177 1178 DragView::view = new DragView::View(pixbuf, is_raw_image, is_offset_set, offset_x, offset_y); 1179 } 1180 } 1181 void DragView::move(gint x, gint y) { 1182 if (view) { 1183 view->move(x, y); 1184 } 1185 } 1186 1187 static void on_screen_changed(GtkWidget *widget, GdkScreen *previous_screen, gpointer view) { 1188 (void)widget; 1189 (void)previous_screen; 1190 1191 ((DragView::View*) view)->screen_changed(); 1192 } 1193 1194 static gboolean on_expose(GtkWidget *widget, GdkEventExpose *event, gpointer view) { 1195 (void)widget; 1196 (void)event; 1197 1198 ((DragView::View*) view)->expose(); 1199 return FALSE; 1200 } 1201 1202 DragView::View::View(GdkPixbuf* _pixbuf, gboolean _is_raw_image, 1203 gboolean _is_offset_set, gint _offset_x, gint _offset_y) : 1204 pixbuf(_pixbuf), 1205 is_raw_image(_is_raw_image), 1206 is_offset_set(_is_offset_set), 1207 offset_x(_offset_x), 1208 offset_y(_offset_y) 1209 { 1210 width = gdk_pixbuf_get_width(pixbuf); 1211 height = gdk_pixbuf_get_height(pixbuf); 1212 1213 widget = gtk_window_new(GTK_WINDOW_POPUP); 1214 gtk_window_set_type_hint(GTK_WINDOW(widget), GDK_WINDOW_TYPE_HINT_DND); 1215 1216 screen_changed(); 1217 1218 gtk_widget_realize(widget); 1219 1220 gtk_widget_set_app_paintable(widget, TRUE); 1221 1222 g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(on_expose), this); 1223 g_signal_connect(G_OBJECT(widget), "screen-changed", G_CALLBACK(on_screen_changed), this); 1224 1225 gtk_widget_set_size_request(widget, width, height); 1226 1227 gtk_window_set_decorated(GTK_WINDOW(widget), FALSE); 1228 gtk_window_move(GTK_WINDOW(widget), -10000, -10000); 1229 gtk_window_set_opacity(GTK_WINDOW(widget), .7); 1230 gtk_widget_show_all(widget); 1231 } 1232 1233 void DragView::View::screen_changed() { 1234 GdkScreen *screen = gtk_widget_get_screen(widget); 1235 glass_configure_window_transparency(widget, true); 1236 1237 if (!gdk_screen_is_composited(screen)) { 1238 if (!is_offset_set) { 1239 offset_x = 1; 1240 offset_y = 1; 1241 } 1242 } 1243 } 1244 1245 void DragView::View::expose() { 1246 cairo_t *context = gdk_cairo_create(gtk_widget_get_window(widget)); 1247 1248 cairo_surface_t* cairo_surface; 1249 1250 guchar* pixels = is_raw_image 1251 ? (guchar*) convert_BGRA_to_RGBA((const int*) gdk_pixbuf_get_pixels(pixbuf), 1252 gdk_pixbuf_get_rowstride(pixbuf), 1253 height) 1254 : gdk_pixbuf_get_pixels(pixbuf); 1255 1256 cairo_surface = cairo_image_surface_create_for_data( 1257 pixels, 1258 CAIRO_FORMAT_ARGB32, 1259 width, height, width * 4); 1260 1261 cairo_set_source_surface(context, cairo_surface, 0, 0); 1262 cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); 1263 cairo_paint(context); 1264 1265 if (is_raw_image) { 1266 g_free(pixels); 1267 } 1268 cairo_destroy(context); 1269 cairo_surface_destroy(cairo_surface); 1270 } 1271 1272 void DragView::View::move(gint x, gint y) { 1273 if (!gtk_events_pending()) { // avoid sluggish window move 1274 gtk_window_move(GTK_WINDOW(widget), x - offset_x, y - offset_y); 1275 } 1276 } 1277 1278 DragView::View::~View() { 1279 if (widget) { 1280 gtk_widget_destroy(widget); 1281 widget == NULL; 1282 } 1283 if (pixbuf) { 1284 g_object_unref(pixbuf); 1285 pixbuf == NULL; 1286 } 1287 } 1288