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