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