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