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