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