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