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