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