From d4e0a6bcf2c1bf5171793810b5d3de22d9d623ce Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Mon, 26 Aug 2024 19:36:20 +0200 Subject: [PATCH] [GTK4] Migrate DirectoryDialog from GtkFileChooser to GtkFileDialog The FileChooser has been marked as deprecated with GTK 4.10 with the FileDialog being its replacement. Using this new API poses a challenge, as it requires the usage of the AsyncReadyCallback mechanism in order to respond to the closure of the dialog. Once the dialog has been opened via gtk_file_dialog_select_folder(), it is necessary to call gtk_file_dialog_select_folder_finish() from within the callback method. This method also returns the selected folder, which has to be cached until after the dialog has been closed. Following native methods have been added to GTK4: - gtk_file_dialog_new - gtk_file_dialog_select_folder - gtk_file_dialog_select_folder_finish - gtk_file_dialog_set_initial_folder --- .../Eclipse SWT PI/gtk/library/gtk4.c | 48 +++++++++++++++++ .../Eclipse SWT PI/gtk/library/gtk4_stats.h | 4 ++ .../org/eclipse/swt/internal/gtk4/GTK4.java | 26 +++++++++- .../eclipse/swt/internal/SyncDialogUtil.java | 52 +++++++++++++++++-- .../eclipse/swt/widgets/DirectoryDialog.java | 19 ++++--- 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c index e7332024547..1c9ad0e7871 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c @@ -704,6 +704,54 @@ JNIEXPORT jboolean JNICALL GTK4_NATIVE(gtk_1file_1chooser_1set_1file) } #endif +#ifndef NO_gtk_1file_1dialog_1new +JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1file_1dialog_1new) + (JNIEnv *env, jclass that) +{ + jlong rc = 0; + GTK4_NATIVE_ENTER(env, that, gtk_1file_1dialog_1new_FUNC); + rc = (jlong)gtk_file_dialog_new(); + GTK4_NATIVE_EXIT(env, that, gtk_1file_1dialog_1new_FUNC); + return rc; +} +#endif + +#ifndef NO_gtk_1file_1dialog_1select_1folder +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1file_1dialog_1select_1folder) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlong arg2, jlong arg3, jlong arg4) +{ + GTK4_NATIVE_ENTER(env, that, gtk_1file_1dialog_1select_1folder_FUNC); + gtk_file_dialog_select_folder((GtkFileDialog *)arg0, (GtkWindow *)arg1, (GCancellable *)arg2, (GAsyncReadyCallback)arg3, (gpointer)arg4); + GTK4_NATIVE_EXIT(env, that, gtk_1file_1dialog_1select_1folder_FUNC); +} +#endif + +#ifndef NO_gtk_1file_1dialog_1select_1folder_1finish +JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1file_1dialog_1select_1folder_1finish) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlongArray arg2) +{ + jlong *lparg2=NULL; + jlong rc = 0; + GTK4_NATIVE_ENTER(env, that, gtk_1file_1dialog_1select_1folder_1finish_FUNC); + if (arg2) if ((lparg2 = (*env)->GetLongArrayElements(env, arg2, NULL)) == NULL) goto fail; + rc = (jlong)gtk_file_dialog_select_folder_finish((GtkFileDialog *)arg0, (GAsyncResult *)arg1, (GError *)lparg2); +fail: + if (arg2 && lparg2) (*env)->ReleaseLongArrayElements(env, arg2, lparg2, 0); + GTK4_NATIVE_EXIT(env, that, gtk_1file_1dialog_1select_1folder_1finish_FUNC); + return rc; +} +#endif + +#ifndef NO_gtk_1file_1dialog_1set_1initial_1folder +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1file_1dialog_1set_1initial_1folder) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + GTK4_NATIVE_ENTER(env, that, gtk_1file_1dialog_1set_1initial_1folder_FUNC); + gtk_file_dialog_set_initial_folder((GtkFileDialog *)arg0, (GFile *)arg1); + GTK4_NATIVE_EXIT(env, that, gtk_1file_1dialog_1set_1initial_1folder_FUNC); +} +#endif + #ifndef NO_gtk_1frame_1set_1child JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1frame_1set_1child) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h index 6d5b896ecf4..de0d695c1f0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h @@ -81,6 +81,10 @@ typedef enum { gtk_1file_1chooser_1get_1files_FUNC, gtk_1file_1chooser_1set_1current_1folder_FUNC, gtk_1file_1chooser_1set_1file_FUNC, + gtk_1file_1dialog_1new_FUNC, + gtk_1file_1dialog_1select_1folder_FUNC, + gtk_1file_1dialog_1select_1folder_1finish_FUNC, + gtk_1file_1dialog_1set_1initial_1folder_FUNC, gtk_1frame_1set_1child_FUNC, gtk_1gesture_1click_1new_FUNC, gtk_1gesture_1drag_1new_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java index ef59930c698..1a9fa6dbb9a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 Syntevo and others. + * Copyright (c) 2021, 2024 Syntevo and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -191,7 +191,29 @@ public class GTK4 { * @param error cast=(GError **) */ public static final native boolean gtk_file_chooser_set_file(long chooser, long file, long error); - + + /* GtkFileDialog */ + public static final native long gtk_file_dialog_new(); + /** + * @param self cast=(GtkFileDialog *) + * @param parent cast=(GtkWindow *) + * @param cancellable cast=(GCancellable *) + * @param callback cast=(GAsyncReadyCallback) + * @param user_data cast=(gpointer) + */ + public static final native void gtk_file_dialog_select_folder(long self, long parent, long cancellable, long callback, long user_data); + /** + * @param self cast=(GtkFileDialog *) + * @param result cast=(GAsyncResult *) + * @param error cast=(GError *) + */ + public static final native long gtk_file_dialog_select_folder_finish(long self, long result, long[] error); + /** + * @param self cast=(GtkFileDialog *) + * @param folder cast=(GFile *) + */ + public static final native void gtk_file_dialog_set_initial_folder(long self, long folder); + /* GtkScrolledWindow */ public static final native long gtk_scrolled_window_new(); /** @param scrolled_window cast=(GtkScrolledWindow *) */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/SyncDialogUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/SyncDialogUtil.java index e7596afcf31..5464d37f000 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/SyncDialogUtil.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/SyncDialogUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 Red Hat Inc. and others. + * Copyright (c) 2020, 2024 Red Hat Inc. and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,7 +14,9 @@ package org.eclipse.swt.internal; import java.lang.reflect.*; +import java.util.function.*; +import org.eclipse.swt.*; import org.eclipse.swt.internal.gtk.*; import org.eclipse.swt.widgets.*; @@ -28,6 +30,41 @@ public class SyncDialogUtil { static int responseID; static Callback dialogResponseCallback; + static Function dialogAsyncFinish; + static Long dialogAsyncValue; + + /** + * This method implements the {@code AsyncReadyCallback} mechanism that is + * used in GTK4. Most operations within GTK4 are executed asynchronously, + * where the user is given the option to respond to the completion of such + * an operation via a callback method, in order to e.g. process the result + * or to apply additional cleanup tasks.
+ * When calling this method, the asynchronous operation is initiated via the + * {code asyncOpen} parameter. Callers have to ensure that the callback + * address is used as argument for the {@code AsyncReadyCallback} parameter. + * From within the callback routine, the {@code asyncFinish} function is + * called, receiving the {@code AsyncResult} of the callback as argument and + * returning the {@code long} value of the callback function.
+ * This method blocks until the callback method has been called. It is + * therefore essential that callers use the address of the {@link Callback} + * as address for the {@code AsyncReadyCallback} object. + */ + static public long run(Display display, Consumer asyncOpen, Function asyncFinish) { + initializeResponseCallback(); + + dialogAsyncFinish = asyncFinish; + asyncOpen.accept(dialogResponseCallback.getAddress()); + + while (!display.isDisposed()) { + if (dialogAsyncValue != null) { + break; + } + display.readAndDispatch(); + } + + disposeResponseCallback(); + return dialogAsyncValue; + } /** * A blocking call that waits for the handling of the signal before returning @@ -53,7 +90,7 @@ static public int run(Display display, long handle, boolean isNativeDialog) { } disposeResponseCallback(); - return responseID; + return (int) responseID; } /** @@ -61,13 +98,15 @@ static public int run(Display display, long handle, boolean isNativeDialog) { * This function should be called before connect the dialog to the "response" signal, as this sets up the callback. */ static void initializeResponseCallback() { - dialogResponseCallback = new Callback(SyncDialogUtil.class, "dialogResponseProc", void.class, new Type[] {long.class, int.class, long.class}); + dialogResponseCallback = new Callback(SyncDialogUtil.class, "dialogResponseProc", void.class, new Type[] {long.class, long.class, long.class}); + dialogAsyncValue = null; responseID = -1; } static void disposeResponseCallback() { dialogResponseCallback.dispose(); dialogResponseCallback = null; + dialogAsyncFinish = null; } /** @@ -77,7 +116,10 @@ static void disposeResponseCallback() { * * Note: Native dialogs are platform dialogs that don't use GtkDialog or GtkWindow. */ - static void dialogResponseProc(long dialog, int response_id, long user_data) { - responseID = response_id; + static void dialogResponseProc(long dialog, long response_id, long user_data) { + if (dialogAsyncFinish != null) { + dialogAsyncValue = dialogAsyncFinish.apply(response_id); + } + responseID = (int) response_id; } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/DirectoryDialog.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/DirectoryDialog.java index e41896a07d8..7f58b6663e0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/DirectoryDialog.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/DirectoryDialog.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -165,8 +165,12 @@ Optional openNativeChooserDialog () { byte [] titleBytes = Converter.wcsToMbcs (title, true); long shellHandle = parent.topHandle (); Display display = parent != null ? parent.getDisplay (): Display.getCurrent (); - long handle = 0; - handle = GTK.gtk_file_chooser_native_new(titleBytes, shellHandle, GTK.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, null, null); + long handle; + if (GTK.GTK4) { + handle = GTK4.gtk_file_dialog_new(); + } else { + handle = GTK.gtk_file_chooser_native_new(titleBytes, shellHandle, GTK.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, null, null); + } if (handle == 0) error (SWT.ERROR_NO_HANDLES); if (filterPath != null && filterPath.length () > 0) { @@ -186,7 +190,7 @@ Optional openNativeChooserDialog () { if (ptr != 0) { if (GTK.GTK4) { long file = OS.g_file_new_for_path(buffer); - GTK4.gtk_file_chooser_set_current_folder (handle, file, 0); + GTK4.gtk_file_dialog_set_initial_folder (handle, file); OS.g_object_unref(file); } else { GTK3.gtk_file_chooser_set_current_folder (handle, ptr); @@ -207,8 +211,12 @@ Optional openNativeChooserDialog () { } int response; + long file = 0; if (GTK.GTK4) { - response = SyncDialogUtil.run(display, handle, true); + file = SyncDialogUtil.run(display, + asyncCallback -> GTK4.gtk_file_dialog_select_folder(handle, shellHandle, 0, asyncCallback, 0), + asyncResult -> GTK4.gtk_file_dialog_select_folder_finish(handle, asyncResult, null)); + response = file != 0 ? GTK.GTK_RESPONSE_ACCEPT : GTK.GTK_RESPONSE_CANCEL; } else { display.externalEventLoop = true; display.sendPreExternalEventDispatchEvent (); @@ -223,7 +231,6 @@ Optional openNativeChooserDialog () { if (response == GTK.GTK_RESPONSE_ACCEPT) { long path; if (GTK.GTK4) { - long file = GTK4.gtk_file_chooser_get_file (handle); path = OS.g_file_get_path(file); } else { path = GTK3.gtk_file_chooser_get_filename (handle);