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