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