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