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
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;
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 }
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
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)) {
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;
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 }
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);
|
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
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;
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 }
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
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)) {
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;
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 }
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);
|