1/*
2 * Copyright (c) 2010, 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
26#include <jni.h>
27#include <stdio.h>
28#include <jni_util.h>
29#include <string.h>
30#include <X11/X.h>
31#include "gtk_interface.h"
32#include "sun_awt_X11_GtkFileDialogPeer.h"
33#include "java_awt_FileDialog.h"
34#include "debug_assert.h"
35
36typedef void GtkWidget;
37static JavaVM *jvm;
38
39/* To cache some method IDs */
40static jmethodID filenameFilterCallbackMethodID = NULL;
41static jmethodID setFileInternalMethodID = NULL;
42static jfieldID widgetFieldID = NULL;
43static jmethodID setWindowMethodID = NULL;
44
45JNIEXPORT void JNICALL Java_sun_awt_X11_GtkFileDialogPeer_initIDs
46(JNIEnv *env, jclass cx)
47{
48 filenameFilterCallbackMethodID = (*env)->GetMethodID(env, cx,
49 "filenameFilterCallback", "(Ljava/lang/String;)Z");
50 DASSERT(filenameFilterCallbackMethodID != NULL);
51 CHECK_NULL(filenameFilterCallbackMethodID);
52
53 setFileInternalMethodID = (*env)->GetMethodID(env, cx,
54 "setFileInternal", "(Ljava/lang/String;[Ljava/lang/String;)V");
55 DASSERT(setFileInternalMethodID != NULL);
56 CHECK_NULL(setFileInternalMethodID);
57
58 widgetFieldID = (*env)->GetFieldID(env, cx, "widget", "J");
59 DASSERT(widgetFieldID != NULL);
60 CHECK_NULL(widgetFieldID);
61
62 setWindowMethodID = (*env)->GetMethodID(env, cx, "setWindow", "(J)Z");
63 DASSERT(setWindowMethodID != NULL);
64}
65
66static gboolean filenameFilterCallback(const GtkFileFilterInfo * filter_info, gpointer obj)
67{
68 JNIEnv *env;
69 jstring filename;
70
71 env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
72
73 filename = (*env)->NewStringUTF(env, filter_info->filename);
74 JNU_CHECK_EXCEPTION_RETURN(env, FALSE);
75
76 return (*env)->CallBooleanMethod(env, obj, filenameFilterCallbackMethodID,
77 filename);
78}
79
80static void quit(JNIEnv * env, jobject jpeer, gboolean isSignalHandler)
81{
82 jthrowable pendingException;
83 if (pendingException = (*env)->ExceptionOccurred(env)) {
84 (*env)->ExceptionClear(env);
85 }
86
87 GtkWidget * dialog = (GtkWidget*)jlong_to_ptr(
88 (*env)->GetLongField(env, jpeer, widgetFieldID));
89
90 if (dialog != NULL)
91 {
92 // Callbacks from GTK signals are made within the GTK lock
93 // So, within a signal handler there is no need to call
94 // gdk_threads_enter() / gtk->gdk_threads_leave()
95 if (!isSignalHandler) {
96 gtk->gdk_threads_enter();
97 }
98
99 gtk->gtk_widget_hide (dialog);
100 gtk->gtk_widget_destroy (dialog);
101
102 gtk->gtk_main_quit ();
103
104 (*env)->SetLongField(env, jpeer, widgetFieldID, 0);
105
106 if (!isSignalHandler) {
107 gtk->gdk_threads_leave();
108 }
109 }
110
111 if (pendingException) {
112 (*env)->Throw(env, pendingException);
113 }
114}
115
116/*
117 * Class: sun_awt_X11_GtkFileDialogPeer
118 * Method: quit
119 * Signature: ()V
120 */
121JNIEXPORT void JNICALL Java_sun_awt_X11_GtkFileDialogPeer_quit
122(JNIEnv * env, jobject jpeer)
123{
124 quit(env, jpeer, FALSE);
125}
126
127/*
128 * Class: sun_awt_X11_GtkFileDialogPeer
129 * Method: toFront
130 * Signature: ()V
131 */
132JNIEXPORT void JNICALL Java_sun_awt_X11_GtkFileDialogPeer_toFront
133(JNIEnv * env, jobject jpeer)
134{
135 GtkWidget * dialog;
136
137 gtk->gdk_threads_enter();
138
139 dialog = (GtkWidget*)jlong_to_ptr(
140 (*env)->GetLongField(env, jpeer, widgetFieldID));
141
142 if (dialog != NULL) {
143 gtk->gtk_window_present((GtkWindow*)dialog);
144 }
145
146 gtk->gdk_threads_leave();
147}
148
149/*
150 * Class: sun_awt_X11_GtkFileDialogPeer
151 * Method: setBounds
152 * Signature: (IIIII)V
153 */
154JNIEXPORT void JNICALL Java_sun_awt_X11_GtkFileDialogPeer_setBounds
155(JNIEnv * env, jobject jpeer, jint x, jint y, jint width, jint height, jint op)
156{
157 GtkWindow* dialog;
158
159 gtk->gdk_threads_enter();
160
161 dialog = (GtkWindow*)jlong_to_ptr(
162 (*env)->GetLongField(env, jpeer, widgetFieldID));
163
164 if (dialog != NULL) {
165 if (x >= 0 && y >= 0) {
166 gtk->gtk_window_move(dialog, (gint)x, (gint)y);
167 }
168 if (width > 0 && height > 0) {
169 gtk->gtk_window_resize(dialog, (gint)width, (gint)height);
170 }
171 }
172
173 gtk->gdk_threads_leave();
174}
175
176/*
177 * baseDir should be freed by user.
178 */
179static gboolean isFromSameDirectory(GSList* list, gchar** baseDir) {
180
181 GSList *it = list;
182 gchar* prevDir = NULL;
183 gboolean isAllDirsSame = TRUE;
184
185 while (it) {
186 gchar* dir = gtk->g_path_get_dirname((gchar*) it->data);
187
188 if (prevDir && strcmp(prevDir, dir) != 0) {
189 isAllDirsSame = FALSE;
190 gtk->g_free(dir);
191 break;
192 }
193
194 if (!prevDir) {
195 prevDir = strdup(dir);
196 }
197 gtk->g_free(dir);
198
199 it = it->next;
200 }
201
202 if (isAllDirsSame) {
203 *baseDir = prevDir;
204 } else {
205 free(prevDir);
206 *baseDir = strdup("/");
207 }
208
209 return isAllDirsSame;
210}
211
212/**
213 * Convert a GSList to an array of filenames
214 */
215static jobjectArray toFilenamesArray(JNIEnv *env, GSList* list, jstring* jcurrent_folder)
216{
217 jstring str;
218 jclass stringCls;
219 GSList *iterator;
220 jobjectArray array;
221 int i;
222 gchar* entry;
223 gchar * baseDir;
224 gboolean isFromSameDir;
225
226 if (list == NULL) {
227 return NULL;
228 }
229
230 stringCls = (*env)->FindClass(env, "java/lang/String");
231 if (stringCls == NULL) {
232 (*env)->ExceptionClear(env);
233 JNU_ThrowInternalError(env, "Could not get java.lang.String class");
234 return NULL;
235 }
236
237 array = (*env)->NewObjectArray(env, gtk->gtk_g_slist_length(list), stringCls, NULL);
238 if (array == NULL) {
239 (*env)->ExceptionClear(env);
240 JNU_ThrowInternalError(env, "Could not instantiate array files array");
241 return NULL;
242 }
243
244 isFromSameDir = isFromSameDirectory(list, &baseDir);
245
246 *jcurrent_folder = (*env)->NewStringUTF(env, baseDir);
247 if (*jcurrent_folder == NULL) {
248 free(baseDir);
249 return NULL;
250 }
251
252 for (iterator = list, i=0;
253 iterator;
254 iterator = iterator->next, i++) {
255
256 entry = (gchar*) iterator->data;
257
258 if (isFromSameDir) {
259 entry = strrchr(entry, '/') + 1;
260 } else if (entry[0] == '/') {
261 entry++;
262 }
263
264 str = (*env)->NewStringUTF(env, entry);
265 if((*env)->ExceptionCheck(env)){
266 break;
267 }
268 if (str) {
269 (*env)->SetObjectArrayElement(env, array, i, str);
270 if((*env)->ExceptionCheck(env)){
271 break;
272 }
273 }
274 }
275
276 free(baseDir);
277 return array;
278}
279
280static void handle_response(GtkWidget* aDialog, gint responseId, gpointer obj)
281{
282 JNIEnv *env;
283 GSList *filenames;
284 jstring jcurrent_folder = NULL;
285 jobjectArray jfilenames;
286
287 env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
288 filenames = NULL;
289
290 if (responseId == GTK_RESPONSE_ACCEPT) {
291 filenames = gtk->gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(aDialog));
292 }
293
294 jfilenames = toFilenamesArray(env, filenames, &jcurrent_folder);
295
296 if (!(*env)->ExceptionCheck(env)) {
297 (*env)->CallVoidMethod(env, obj, setFileInternalMethodID,
298 jcurrent_folder, jfilenames);
299 }
300
301 quit(env, (jobject)obj, TRUE);
302}
303
304/*
305 * Class: sun_awt_X11_GtkFileDialogPeer
306 * Method: run
307 * Signature: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/io/FilenameFilter;ZII)V
308 */
309JNIEXPORT void JNICALL
310Java_sun_awt_X11_GtkFileDialogPeer_run(JNIEnv * env, jobject jpeer,
311 jstring jtitle, jint mode, jstring jdir, jstring jfile,
312 jobject jfilter, jboolean multiple, int x, int y)
313{
314 GtkWidget *dialog = NULL;
315 GtkFileFilter *filter;
316
317 if (jvm == NULL) {
318 (*env)->GetJavaVM(env, &jvm);
319 JNU_CHECK_EXCEPTION(env);
320 }
321
322 gtk->gdk_threads_enter();
323
324 const char *title = jtitle == NULL? "": (*env)->GetStringUTFChars(env, jtitle, 0);
325 if (title == NULL) {
326 (*env)->ExceptionClear(env);
327 JNU_ThrowOutOfMemoryError(env, "Could not get title");
328 return;
329 }
330
331 if (mode == java_awt_FileDialog_SAVE) {
332 /* Save action */
333 dialog = gtk->gtk_file_chooser_dialog_new(title, NULL,
334 GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL,
335 GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
336 }
337 else {
338 /* Default action OPEN */
339 dialog = gtk->gtk_file_chooser_dialog_new(title, NULL,
340 GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL,
341 GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
342
343 /* Set multiple selection mode, that is allowed only in OPEN action */
344 if (multiple) {
345 gtk->gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog),
346 multiple);
347 }
348 }
349
350 if (jtitle != NULL) {
351 (*env)->ReleaseStringUTFChars(env, jtitle, title);
352 }
353
354 /* Set the directory */
355 if (jdir != NULL) {
356 const char *dir = (*env)->GetStringUTFChars(env, jdir, 0);
357 if (dir == NULL) {
358 (*env)->ExceptionClear(env);
359 JNU_ThrowOutOfMemoryError(env, "Could not get dir");
360 return;
361 }
362 gtk->gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), dir);
363 (*env)->ReleaseStringUTFChars(env, jdir, dir);
364 }
365
366 /* Set the filename */
367 if (jfile != NULL) {
368 const char *filename = (*env)->GetStringUTFChars(env, jfile, 0);
369 if (filename == NULL) {
370 (*env)->ExceptionClear(env);
371 JNU_ThrowOutOfMemoryError(env, "Could not get filename");
372 return;
373 }
374 if (mode == java_awt_FileDialog_SAVE) {
375 gtk->gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);
376 } else {
377 gtk->gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), filename);
378 }
379 (*env)->ReleaseStringUTFChars(env, jfile, filename);
380 }
381
382 /* Set the file filter */
383 if (jfilter != NULL) {
384 filter = gtk->gtk_file_filter_new();
385 gtk->gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME,
386 filenameFilterCallback, jpeer, NULL);
387 gtk->gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
388 }
389
390 /* Other Properties */
391 if (gtk->gtk_check_version(2, 8, 0) == NULL ||
392 gtk->gtk_check_version(3, 0, 0) == NULL) {
393 gtk->gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(
394 dialog), TRUE);
395 }
396
397 /* Set the initial location */
398 if (x >= 0 && y >= 0) {
399 gtk->gtk_window_move((GtkWindow*)dialog, (gint)x, (gint)y);
400
401 // NOTE: it doesn't set the initial size for the file chooser
402 // as it seems like the file chooser overrides the size internally
403 }
404
405 gtk->g_signal_connect_data(dialog, "response", G_CALLBACK(
406 handle_response), jpeer, 0, 0);
407
408 (*env)->SetLongField(env, jpeer, widgetFieldID, ptr_to_jlong(dialog));
409
410 gtk->gtk_widget_show(dialog);
411
412 XID xid = gtk->gdk_x11_drawable_get_xid(gtk->get_window(dialog));
413 if( (*env)->CallBooleanMethod(env, jpeer, setWindowMethodID, xid) ) {
414 gtk->gtk_main();
415 }
416
417 gtk->gdk_threads_leave();
418}
419
420