1 /* 2 * Copyright (c) 2011, 2016, 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 EXCEPTION_OCCURED(env); 449 result = env->NewObject(jGtkPixelsCls, jGtkPixelsInit, w, h, buffer); 450 EXCEPTION_OCCURED(env); 451 452 g_object_unref(buf); 453 g_free(data); // data from convert_BGRA_to_RGBA 454 } 455 g_object_unref(stream); 456 } 457 ++cur_target; 458 } 459 return result; 460 } 461 462 static jobject dnd_target_get_raw(JNIEnv *env, GdkAtom target, gboolean string_data) 463 { 464 selection_data_ctx ctx; 465 jobject result = NULL; 466 if (dnd_target_receive_data(env, target, &ctx)) { 467 if (string_data) { 468 result = env->NewStringUTF((char *)ctx.data); 469 EXCEPTION_OCCURED(env); 470 } else { 471 jsize length = ctx.length * (ctx.format / 8); 472 jbyteArray array = env->NewByteArray(length); 473 EXCEPTION_OCCURED(env); 474 env->SetByteArrayRegion(array, 0, length, (const jbyte*)ctx.data); 475 EXCEPTION_OCCURED(env); 476 result = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, array); 477 EXCEPTION_OCCURED(env); 478 } 479 } 480 g_free(ctx.data); 481 return result; 482 } 483 484 jobject dnd_target_get_data(JNIEnv *env, jstring mime) 485 { 486 if (check_state_in_drag(env)) { 487 return NULL; 488 } 489 const char *cmime = env->GetStringUTFChars(mime, NULL); 490 jobject ret = NULL; 491 492 init_target_atoms(); 493 494 if (g_strcmp0(cmime, "text/plain") == 0) { 495 ret = dnd_target_get_string(env); 496 } else if (g_strcmp0(cmime, "text/uri-list") == 0) { 497 ret = dnd_target_get_list(env, FALSE); 498 } else if (g_str_has_prefix(cmime, "text/")) { 499 ret = dnd_target_get_raw(env, gdk_atom_intern(cmime, FALSE), TRUE); 500 } else if (g_strcmp0(cmime, "application/x-java-file-list") == 0) { 501 ret = dnd_target_get_list(env, TRUE); 502 } else if (g_strcmp0(cmime, "application/x-java-rawimage") == 0 ) { 503 ret = dnd_target_get_image(env); 504 } else { 505 ret = dnd_target_get_raw(env, gdk_atom_intern(cmime, FALSE), FALSE); 506 } 507 LOG_EXCEPTION(env) 508 env->ReleaseStringUTFChars(mime, cmime); 509 510 return ret; 511 } 512 513 /************************* SOURCE *********************************************/ 514 515 516 static GdkWindow *dnd_window = NULL; 517 static jint dnd_performed_action; 518 519 const char * const SOURCE_DND_CONTEXT = "fx-dnd-context"; 520 const char * const SOURCE_DND_DATA = "fx-dnd-data"; 521 const char * const SOURCE_DND_ACTIONS = "fx-dnd-actions"; 522 523 static GdkWindow* get_dnd_window() 524 { 525 if (dnd_window == NULL) { 526 GdkWindowAttr attr; 527 memset(&attr, 0, sizeof (GdkWindowAttr)); 528 attr.override_redirect = TRUE; 529 attr.window_type = GDK_WINDOW_TEMP; 530 attr.type_hint = GDK_WINDOW_TYPE_HINT_UTILITY; 531 attr.wclass = GDK_INPUT_OUTPUT; 532 attr.event_mask = GDK_ALL_EVENTS_MASK; 533 dnd_window = gdk_window_new(NULL, &attr, GDK_WA_NOREDIR | GDK_WA_TYPE_HINT); 534 535 gdk_window_move(dnd_window, -100, -100); 536 gdk_window_resize(dnd_window, 1, 1); 537 gdk_window_show(dnd_window); 538 } 539 return dnd_window; 540 } 541 542 static void dnd_set_performed_action(jint performed_action) { 543 dnd_performed_action = performed_action; 544 } 545 546 static jint dnd_get_performed_action() { 547 return dnd_performed_action; 548 } 549 550 static void dnd_pointer_grab(GdkCursor *cursor) 551 { 552 glass_gdk_master_pointer_grab(dnd_window, cursor); 553 } 554 555 static GdkDragContext *get_drag_context() { 556 GdkDragContext *ctx; 557 ctx = (GdkDragContext*)g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_CONTEXT); 558 return ctx; 559 } 560 561 static gboolean dnd_finish_callback() { 562 if (dnd_window) { 563 dnd_set_performed_action( 564 translate_gdk_action_to_glass( 565 gdk_drag_context_get_selected_action( 566 get_drag_context()))); 567 568 gdk_window_destroy(dnd_window); 569 dnd_window = NULL; 570 DragView::reset_drag_view(); 571 } 572 573 return FALSE; 574 } 575 576 gboolean is_in_drag() 577 { 578 return dnd_window != NULL; 579 } 580 581 static void determine_actions(guint state, GdkDragAction *action, GdkDragAction *possible_actions) 582 { 583 GdkDragAction suggested = static_cast<GdkDragAction>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_ACTIONS))); 584 585 if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { 586 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK) && (suggested & GDK_ACTION_LINK)) { 587 *action = *possible_actions = GDK_ACTION_LINK; 588 return; 589 } else if ((state & GDK_SHIFT_MASK) && (suggested & GDK_ACTION_MOVE)) { 590 *action = *possible_actions = GDK_ACTION_MOVE; 591 return; 592 } else if (suggested & GDK_ACTION_COPY){ 593 *action = *possible_actions = GDK_ACTION_COPY; 594 return; 595 } 596 } 597 598 *possible_actions = suggested; 599 600 if (suggested & GDK_ACTION_COPY) { 601 *action = GDK_ACTION_COPY; 602 } else if (suggested & GDK_ACTION_MOVE) { 603 *action = GDK_ACTION_MOVE; 604 } else if (suggested & GDK_ACTION_LINK) { 605 *action = GDK_ACTION_LINK; 606 } else { 607 *action = static_cast<GdkDragAction>(0); 608 } 609 } 610 611 static jobject dnd_source_get_data(const char *key) 612 { 613 jobject data = (jobject)g_object_get_data(G_OBJECT(dnd_window), SOURCE_DND_DATA); 614 jstring string = mainEnv->NewStringUTF(key); 615 EXCEPTION_OCCURED(mainEnv); 616 jobject result = mainEnv->CallObjectMethod(data, jMapGet, string, NULL); 617 618 return (EXCEPTION_OCCURED(mainEnv)) ? NULL : result; 619 } 620 621 static gboolean dnd_source_set_utf8_string(GdkWindow *requestor, GdkAtom property) 622 { 623 jstring string = (jstring)dnd_source_get_data("text/plain"); 624 if (!string) { 625 return FALSE; 626 } 627 628 const char *cstring = mainEnv->GetStringUTFChars(string, NULL); 629 if (!cstring) { 630 return FALSE; 631 } 632 gint size = strlen(cstring); 633 634 gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING, 635 8, GDK_PROP_MODE_REPLACE, (guchar *)cstring, size); 636 637 mainEnv->ReleaseStringUTFChars(string, cstring); 638 return TRUE; 639 } 640 641 static gboolean dnd_source_set_string(GdkWindow *requestor, GdkAtom property) 642 { 643 jstring string = (jstring)dnd_source_get_data("text/plain"); 644 if (!string) { 645 return FALSE; 646 } 647 648 gboolean is_data_set = FALSE; 649 const char *cstring = mainEnv->GetStringUTFChars(string, NULL); 650 if (cstring) { 651 gchar *res_str = g_convert((gchar *)cstring, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL); 652 653 if (res_str) { 654 gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING, 655 8, GDK_PROP_MODE_REPLACE, (guchar *)res_str, strlen(res_str)); 656 g_free(res_str); 657 is_data_set = TRUE; 658 } 659 660 mainEnv->ReleaseStringUTFChars(string, cstring); 661 } 662 return is_data_set; 663 } 664 665 static gboolean dnd_source_set_image(GdkWindow *requestor, GdkAtom property, GdkAtom target) 666 { 667 jobject pixels = dnd_source_get_data("application/x-java-rawimage"); 668 if (!pixels) { 669 return FALSE; 670 } 671 672 gchar *buffer; 673 gsize size; 674 const char * type; 675 GdkPixbuf *pixbuf = NULL; 676 gboolean result = FALSE; 677 678 if (target == TARGET_MIME_PNG_ATOM) { 679 type = "png"; 680 } else if (target == TARGET_MIME_JPEG_ATOM) { 681 type = "jpeg"; 682 } else if (target == TARGET_MIME_TIFF_ATOM) { 683 type = "tiff"; 684 } else if (target == TARGET_MIME_BMP_ATOM) { 685 type = "bmp"; 686 } else { 687 return FALSE; 688 } 689 690 mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf)); 691 692 if (!EXCEPTION_OCCURED(mainEnv) 693 && gdk_pixbuf_save_to_buffer(pixbuf, &buffer, &size, type, NULL, NULL)) { 694 gdk_property_change(requestor, property, target, 695 8, GDK_PROP_MODE_REPLACE, (guchar *)buffer, size); 696 result = TRUE; 697 } 698 g_object_unref(pixbuf); 699 return result; 700 } 701 702 static gboolean dnd_source_set_uri_list(GdkWindow *requestor, GdkAtom property) 703 { 704 const gchar* url = NULL; 705 jstring jurl = NULL; 706 707 jobjectArray files_array = NULL; 708 gsize files_cnt = 0; 709 710 if (jurl = (jstring) dnd_source_get_data("text/uri-list")) { 711 url = mainEnv->GetStringUTFChars(jurl, NULL); 712 } 713 714 if (files_array = (jobjectArray) dnd_source_get_data("application/x-java-file-list")) { 715 files_cnt = mainEnv->GetArrayLength(files_array); 716 } 717 if (!url && !files_cnt) { 718 return FALSE; 719 } 720 721 GString* res = g_string_new (NULL); //http://www.ietf.org/rfc/rfc2483.txt 722 723 if (files_cnt > 0) { 724 for (gsize i = 0; i < files_cnt; ++i) { 725 jstring string = (jstring) mainEnv->GetObjectArrayElement(files_array, i); 726 EXCEPTION_OCCURED(mainEnv); 727 const gchar* file = mainEnv->GetStringUTFChars(string, NULL); 728 gchar* uri = g_filename_to_uri(file, NULL, NULL); 729 730 g_string_append(res, uri); 731 g_string_append(res, URI_LIST_LINE_BREAK); 732 733 g_free(uri); 734 mainEnv->ReleaseStringUTFChars(string, file); 735 } 736 } 737 if (url) { 738 g_string_append(res, url); 739 g_string_append(res, URI_LIST_LINE_BREAK); 740 mainEnv->ReleaseStringUTFChars(jurl, url); 741 } 742 743 gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING, 744 8, GDK_PROP_MODE_REPLACE, (guchar *) res->str, res->len); 745 746 g_string_free(res, TRUE); 747 return TRUE; 748 } 749 750 static gboolean dnd_source_set_raw(GdkWindow *requestor, GdkAtom property, GdkAtom target) 751 { 752 gchar *target_name = gdk_atom_name(target); 753 jobject data = dnd_source_get_data(target_name); 754 gboolean is_data_set = FALSE; 755 if (data) { 756 if (mainEnv->IsInstanceOf(data, jStringCls)) { 757 const char *cstring = mainEnv->GetStringUTFChars((jstring)data, NULL); 758 if (cstring) { 759 gdk_property_change(requestor, property, GDK_SELECTION_TYPE_STRING, 760 8, GDK_PROP_MODE_REPLACE, (guchar *) cstring, strlen(cstring)); 761 762 mainEnv->ReleaseStringUTFChars((jstring)data, cstring); 763 is_data_set = TRUE; 764 } 765 } else if (mainEnv->IsInstanceOf(data, jByteBufferCls)) { 766 jbyteArray byteArray = (jbyteArray)mainEnv->CallObjectMethod(data, jByteBufferArray); 767 if (!EXCEPTION_OCCURED(mainEnv)) { 768 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); 769 if (raw) { 770 jsize nraw = mainEnv->GetArrayLength(byteArray); 771 772 gdk_property_change(requestor, property, target, 773 8, GDK_PROP_MODE_REPLACE, (guchar *) raw, nraw); 774 775 mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); 776 is_data_set = TRUE; 777 } 778 } 779 } 780 } 781 782 g_free(target_name); 783 return is_data_set; 784 } 785 786 static void process_dnd_source_selection_req(GdkWindow *window, GdkEventSelection* event) 787 { 788 (void)window; 789 790 #ifdef GLASS_GTK3 791 GdkWindow *requestor = (event->requestor); 792 #else 793 GdkWindow *requestor = 794 gdk_x11_window_foreign_new_for_display(gdk_display_get_default(), event->requestor); 795 #endif 796 797 gboolean is_data_set = FALSE; 798 if (event->target == TARGET_UTF8_STRING_ATOM 799 || event->target == TARGET_MIME_TEXT_PLAIN_ATOM) { 800 is_data_set = dnd_source_set_utf8_string(requestor, event->property); 801 } else if (event->target == TARGET_STRING_ATOM) { 802 is_data_set = dnd_source_set_string(requestor, event->property); 803 // } else if (event->target == TARGET_COMPOUND_TEXT_ATOM) { // XXX compound text 804 } else if (target_is_image(event->target)) { 805 is_data_set = dnd_source_set_image(requestor, event->property, event->target); 806 } else if (event->target == TARGET_MIME_URI_LIST_ATOM) { 807 is_data_set = dnd_source_set_uri_list(requestor, event->property); 808 } else { 809 is_data_set = dnd_source_set_raw(requestor, event->property, event->target); 810 } 811 812 gdk_selection_send_notify(event->requestor, event->selection, event->target, 813 (is_data_set) ? event->property : GDK_NONE, event->time); 814 } 815 816 static void process_dnd_source_mouse_release(GdkWindow *window, GdkEventButton *event) { 817 (void)window; 818 (void)event; 819 820 glass_gdk_master_pointer_ungrab(); 821 822 if (gdk_drag_context_get_selected_action(get_drag_context())) { 823 gdk_drag_drop(get_drag_context(), GDK_CURRENT_TIME); 824 } else { 825 gdk_drag_abort(get_drag_context(), GDK_CURRENT_TIME); 826 /* let the gdk_drag_abort messages handled before finish */ 827 gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL); 828 } 829 } 830 831 static void process_drag_motion(gint x_root, gint y_root, guint state) 832 { 833 DragView::move(x_root, y_root); 834 835 GdkWindow *dest_window; 836 GdkDragProtocol prot; 837 838 gdk_drag_find_window_for_screen(get_drag_context(), NULL, gdk_screen_get_default(), 839 x_root, y_root, &dest_window, &prot); 840 841 if (prot != GDK_DRAG_PROTO_NONE) { 842 GdkDragAction action, possible_actions; 843 determine_actions(state, &action, &possible_actions); 844 gdk_drag_motion(get_drag_context(), dest_window, prot, x_root, y_root, 845 action, possible_actions, GDK_CURRENT_TIME); 846 } 847 } 848 849 static void process_dnd_source_mouse_motion(GdkWindow *window, GdkEventMotion *event) 850 { 851 (void)window; 852 853 process_drag_motion(event->x_root, event->y_root, event->state); 854 } 855 856 static void process_dnd_source_key_press_release(GdkWindow *window, GdkEventKey *event) 857 { 858 (void)window; 859 860 if (event->is_modifier) { 861 guint state = event->state; 862 guint new_mod = 0; 863 gint x,y; 864 if (event->keyval == GLASS_GDK_KEY_CONSTANT(Control_L) || 865 event->keyval == GLASS_GDK_KEY_CONSTANT(Control_R)) { 866 new_mod = GDK_CONTROL_MASK; 867 } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_L) || 868 event->keyval == GLASS_GDK_KEY_CONSTANT(Alt_R)) { 869 new_mod = GDK_MOD1_MASK; 870 } else if (event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_L) || 871 event->keyval == GLASS_GDK_KEY_CONSTANT(Shift_R)) { 872 new_mod = GDK_SHIFT_MASK; 873 } 874 875 if (event->type == GDK_KEY_PRESS) { 876 state |= new_mod; 877 } else { 878 state ^= new_mod; 879 } 880 881 glass_gdk_master_pointer_get_position(&x, &y); 882 process_drag_motion(x, y, state); 883 884 } 885 } 886 887 static void process_dnd_source_drag_status(GdkWindow *window, GdkEventDND *event) 888 { 889 (void)window; 890 891 GdkDragAction selected = gdk_drag_context_get_selected_action(event->context); 892 GdkCursor* cursor; 893 894 if (selected & GDK_ACTION_COPY) { 895 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-copy"); 896 if (cursor == NULL) { 897 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "copy"); 898 } 899 } else if (selected & (GDK_ACTION_MOVE | GDK_ACTION_PRIVATE)) { 900 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-move"); 901 if (cursor == NULL) { 902 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "move"); 903 } 904 if (cursor == NULL) { 905 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "closedhand"); 906 } 907 } else if (selected & GDK_ACTION_LINK) { 908 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-link"); 909 if (cursor == NULL) { 910 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "link"); 911 } 912 } else { 913 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-no-drop"); 914 if (cursor == NULL) { 915 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "no-drop"); 916 } 917 if (cursor == NULL) { 918 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "not-allowed"); 919 } 920 if (cursor == NULL) { 921 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "forbidden"); 922 } 923 if (cursor == NULL) { 924 cursor = gdk_cursor_new_from_name(gdk_display_get_default(), "dnd-none"); 925 } 926 } 927 if (cursor == NULL) { 928 cursor = gdk_cursor_new(GDK_LEFT_PTR); 929 } 930 931 dnd_pointer_grab(cursor); 932 } 933 934 static void process_dnd_source_drop_finished(GdkWindow *window, GdkEventDND *event) 935 { 936 (void)window; 937 (void)event; 938 939 gdk_threads_add_idle((GSourceFunc) dnd_finish_callback, NULL); 940 } 941 942 void process_dnd_source(GdkWindow *window, GdkEvent *event) { 943 switch(event->type) { 944 case GDK_MOTION_NOTIFY: 945 process_dnd_source_mouse_motion(window, &event->motion); 946 break; 947 case GDK_BUTTON_RELEASE: 948 process_dnd_source_mouse_release(window, &event->button); 949 break; 950 case GDK_DRAG_STATUS: 951 process_dnd_source_drag_status(window, &event->dnd); 952 break; 953 case GDK_DROP_FINISHED: 954 process_dnd_source_drop_finished(window, &event->dnd); 955 break; 956 case GDK_KEY_PRESS: 957 case GDK_KEY_RELEASE: 958 process_dnd_source_key_press_release(window, &event->key); 959 break; 960 case GDK_DRAG_ENTER: 961 gdk_selection_owner_set(dnd_window, gdk_drag_get_selection(get_drag_context()), GDK_CURRENT_TIME, FALSE); 962 break; 963 case GDK_SELECTION_REQUEST: 964 process_dnd_source_selection_req(window, &event->selection); 965 break; 966 default: 967 break; 968 } 969 } 970 971 static void add_target_from_jstring(JNIEnv *env, GList **list, jstring string) 972 { 973 const char *gstring = env->GetStringUTFChars(string, NULL); 974 if (g_strcmp0(gstring, "text/plain") == 0) { 975 *list = g_list_append(*list, TARGET_UTF8_STRING_ATOM); 976 *list = g_list_append(*list, TARGET_MIME_TEXT_PLAIN_ATOM); 977 *list = g_list_append(*list, TARGET_STRING_ATOM); 978 //*list = g_list_append(list, TARGET_COMPOUND_TEXT_ATOM); 979 } else if (g_strcmp0(gstring, "application/x-java-rawimage") == 0) { 980 *list = g_list_append(*list, TARGET_MIME_PNG_ATOM); 981 *list = g_list_append(*list, TARGET_MIME_JPEG_ATOM); 982 *list = g_list_append(*list, TARGET_MIME_TIFF_ATOM); 983 *list = g_list_append(*list, TARGET_MIME_BMP_ATOM); 984 } else if (g_strcmp0(gstring, "application/x-java-file-list") == 0) { 985 *list = g_list_append(*list, TARGET_MIME_URI_LIST_ATOM); 986 } else { 987 *list = g_list_append(*list, gdk_atom_intern(gstring, FALSE)); 988 } 989 env->ReleaseStringUTFChars(string, gstring); 990 991 } 992 993 static GList* data_to_targets(JNIEnv *env, jobject data) 994 { 995 jobject keys; 996 jobject keysIterator; 997 jstring next; 998 999 GList *list = NULL; 1000 1001 init_target_atoms(); 1002 1003 keys = env->CallObjectMethod(data, jMapKeySet, NULL); 1004 JNI_EXCEPTION_TO_CPP(env) 1005 keysIterator = env->CallObjectMethod(keys, jIterableIterator, NULL); 1006 JNI_EXCEPTION_TO_CPP(env) 1007 while (env->CallBooleanMethod(keysIterator, jIteratorHasNext) == JNI_TRUE) { 1008 next = (jstring)env->CallObjectMethod(keysIterator, jIteratorNext, NULL); 1009 JNI_EXCEPTION_TO_CPP(env) 1010 add_target_from_jstring(env, &list, next); 1011 } 1012 return list; 1013 } 1014 1015 static void dnd_source_push_data(JNIEnv *env, jobject data, jint supported) 1016 { 1017 GdkWindow *src_window = get_dnd_window(); 1018 GList *targets; 1019 GdkDragContext *ctx; 1020 1021 if (supported == 0) { 1022 return; // No supported actions, do nothing 1023 } 1024 1025 targets = data_to_targets(env, data); 1026 1027 data = env->NewGlobalRef(data); 1028 1029 g_object_set_data_full(G_OBJECT(src_window), SOURCE_DND_DATA, data, clear_global_ref); 1030 g_object_set_data(G_OBJECT(src_window), SOURCE_DND_ACTIONS, (gpointer)translate_glass_action_to_gdk(supported)); 1031 1032 DragView::set_drag_view(); 1033 1034 ctx = gdk_drag_begin(src_window, targets); 1035 1036 g_list_free(targets); 1037 1038 g_object_set_data(G_OBJECT(src_window), SOURCE_DND_CONTEXT, ctx); 1039 1040 dnd_pointer_grab(NULL); 1041 1042 is_dnd_owner = TRUE; 1043 } 1044 1045 jint execute_dnd(JNIEnv *env, jobject data, jint supported) { 1046 try { 1047 dnd_source_push_data(env, data, supported); 1048 } catch (jni_exception&) { 1049 return 0; 1050 } 1051 1052 while (is_in_drag()) { 1053 gtk_main_iteration(); 1054 } 1055 1056 return dnd_get_performed_action(); 1057 } 1058 1059 /******************** DRAG VIEW ***************************/ 1060 DragView::View* DragView::view = NULL; 1061 1062 void DragView::reset_drag_view() { 1063 delete view; 1064 view = NULL; 1065 } 1066 1067 gboolean DragView::get_drag_image_offset(int* x, int* y) { 1068 gboolean offset_set = FALSE; 1069 jobject bb = dnd_source_get_data("application/x-java-drag-image-offset"); 1070 if (bb) { 1071 jbyteArray byteArray = (jbyteArray)mainEnv->CallObjectMethod(bb, jByteBufferArray); 1072 if (!EXCEPTION_OCCURED(mainEnv)) { 1073 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); 1074 jsize nraw = mainEnv->GetArrayLength(byteArray); 1075 1076 if ((size_t) nraw >= sizeof(jint) * 2) { 1077 jint* r = (jint*) raw; 1078 *x = BSWAP_32(r[0]); 1079 *y = BSWAP_32(r[1]); 1080 offset_set = TRUE; 1081 } 1082 1083 mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); 1084 } 1085 } 1086 return offset_set; 1087 } 1088 1089 GdkPixbuf* DragView::get_drag_image(gboolean* is_raw_image, gint* width, gint* height) { 1090 GdkPixbuf *pixbuf = NULL; 1091 gboolean is_raw = FALSE; 1092 1093 jobject drag_image = dnd_source_get_data("application/x-java-drag-image"); 1094 1095 if (drag_image) { 1096 jbyteArray byteArray = (jbyteArray) mainEnv->CallObjectMethod(drag_image, jByteBufferArray); 1097 if (!EXCEPTION_OCCURED(mainEnv)) { 1098 1099 jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); 1100 jsize nraw = mainEnv->GetArrayLength(byteArray); 1101 1102 int w = 0, h = 0; 1103 int whsz = sizeof(jint) * 2; // Pixels are stored right after two ints 1104 // in this byteArray: width and height 1105 if (nraw > whsz) { 1106 jint* int_raw = (jint*) raw; 1107 w = BSWAP_32(int_raw[0]); 1108 h = BSWAP_32(int_raw[1]); 1109 1110 // We should have enough pixels for requested width and height 1111 if ((nraw - whsz) / 4 - w * h >= 0 ) { 1112 guchar* data = (guchar*) g_try_malloc0(nraw - whsz); 1113 if (data) { 1114 memcpy(data, (raw + whsz), nraw - whsz); 1115 pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, 1116 w, h, w * 4, (GdkPixbufDestroyNotify) g_free, NULL); 1117 } 1118 } 1119 } 1120 mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); 1121 } 1122 } 1123 1124 if (!GDK_IS_PIXBUF(pixbuf)) { 1125 jobject pixels = dnd_source_get_data("application/x-java-rawimage"); 1126 if (pixels) { 1127 is_raw = TRUE; 1128 mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf)); 1129 CHECK_JNI_EXCEPTION_RET(mainEnv, NULL) 1130 } 1131 } 1132 1133 if (!GDK_IS_PIXBUF(pixbuf)) { 1134 return NULL; 1135 } 1136 1137 int w = gdk_pixbuf_get_width(pixbuf); 1138 int h = gdk_pixbuf_get_height(pixbuf); 1139 1140 if (w > DRAG_IMAGE_MAX_WIDTH || h > DRAG_IMAGE_MAX_HEIGH) { 1141 double rw = DRAG_IMAGE_MAX_WIDTH / (double)w; 1142 double rh = DRAG_IMAGE_MAX_HEIGH / (double)h; 1143 double r = MIN(rw, rh); 1144 1145 1146 int new_w = w * r; 1147 int new_h = h * r; 1148 1149 w = new_w; 1150 h = new_h; 1151 1152 GdkPixbuf *tmp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, new_w, new_h, GDK_INTERP_TILES); 1153 g_object_unref(pixbuf); 1154 if (!GDK_IS_PIXBUF(tmp_pixbuf)) { 1155 return NULL; 1156 } 1157 pixbuf = tmp_pixbuf; 1158 } 1159 1160 *is_raw_image = is_raw; 1161 *width = w; 1162 *height = h; 1163 1164 return pixbuf; 1165 } 1166 1167 void DragView::set_drag_view() { 1168 reset_drag_view(); 1169 1170 gboolean is_raw_image = FALSE; 1171 gint w = 0, h = 0; 1172 GdkPixbuf* pixbuf = get_drag_image(&is_raw_image, &w, &h); 1173 1174 if (GDK_IS_PIXBUF(pixbuf)) { 1175 gint offset_x = w / 2; 1176 gint offset_y = h / 2; 1177 1178 gboolean is_offset_set = get_drag_image_offset(&offset_x, &offset_y); 1179 1180 DragView::view = new DragView::View(pixbuf, is_raw_image, is_offset_set, offset_x, offset_y); 1181 } 1182 } 1183 void DragView::move(gint x, gint y) { 1184 if (view) { 1185 view->move(x, y); 1186 } 1187 } 1188 1189 static void on_screen_changed(GtkWidget *widget, GdkScreen *previous_screen, gpointer view) { 1190 (void)widget; 1191 (void)previous_screen; 1192 1193 ((DragView::View*) view)->screen_changed(); 1194 } 1195 1196 static gboolean on_expose(GtkWidget *widget, GdkEventExpose *event, gpointer view) { 1197 (void)widget; 1198 (void)event; 1199 1200 ((DragView::View*) view)->expose(); 1201 return FALSE; 1202 } 1203 1204 DragView::View::View(GdkPixbuf* _pixbuf, gboolean _is_raw_image, 1205 gboolean _is_offset_set, gint _offset_x, gint _offset_y) : 1206 pixbuf(_pixbuf), 1207 is_raw_image(_is_raw_image), 1208 is_offset_set(_is_offset_set), 1209 offset_x(_offset_x), 1210 offset_y(_offset_y) 1211 { 1212 width = gdk_pixbuf_get_width(pixbuf); 1213 height = gdk_pixbuf_get_height(pixbuf); 1214 1215 widget = gtk_window_new(GTK_WINDOW_POPUP); 1216 gtk_window_set_type_hint(GTK_WINDOW(widget), GDK_WINDOW_TYPE_HINT_DND); 1217 1218 screen_changed(); 1219 1220 gtk_widget_realize(widget); 1221 1222 gtk_widget_set_app_paintable(widget, TRUE); 1223 1224 g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(on_expose), this); 1225 g_signal_connect(G_OBJECT(widget), "screen-changed", G_CALLBACK(on_screen_changed), this); 1226 1227 gtk_widget_set_size_request(widget, width, height); 1228 1229 gtk_window_set_decorated(GTK_WINDOW(widget), FALSE); 1230 gtk_window_move(GTK_WINDOW(widget), -10000, -10000); 1231 gtk_window_set_opacity(GTK_WINDOW(widget), .7); 1232 gtk_widget_show_all(widget); 1233 } 1234 1235 void DragView::View::screen_changed() { 1236 GdkScreen *screen = gtk_widget_get_screen(widget); 1237 1238 glass_configure_window_transparency(widget, true); 1239 1240 if (!gdk_screen_is_composited(screen)) { 1241 if (!is_offset_set) { 1242 offset_x = 1; 1243 offset_y = 1; 1244 } 1245 } 1246 } 1247 1248 void DragView::View::expose() { 1249 cairo_t *context = gdk_cairo_create(gtk_widget_get_window(widget)); 1250 1251 cairo_surface_t* cairo_surface; 1252 1253 guchar* pixels = is_raw_image 1254 ? (guchar*) convert_BGRA_to_RGBA((const int*) gdk_pixbuf_get_pixels(pixbuf), 1255 gdk_pixbuf_get_rowstride(pixbuf), 1256 height) 1257 : gdk_pixbuf_get_pixels(pixbuf); 1258 1259 cairo_surface = cairo_image_surface_create_for_data( 1260 pixels, 1261 CAIRO_FORMAT_ARGB32, 1262 width, height, width * 4); 1263 1264 cairo_set_source_surface(context, cairo_surface, 0, 0); 1265 cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); 1266 cairo_paint(context); 1267 1268 if (is_raw_image) { 1269 g_free(pixels); 1270 } 1271 cairo_destroy(context); 1272 cairo_surface_destroy(cairo_surface); 1273 } 1274 1275 void DragView::View::move(gint x, gint y) { 1276 if (!gtk_events_pending()) { // avoid sluggish window move 1277 gtk_window_move(GTK_WINDOW(widget), x - offset_x, y - offset_y); 1278 } 1279 } 1280 1281 DragView::View::~View() { 1282 if (widget) { 1283 gtk_widget_destroy(widget); 1284 widget == NULL; 1285 } 1286 if (pixbuf) { 1287 g_object_unref(pixbuf); 1288 pixbuf == NULL; 1289 } 1290 } 1291