Skip to content

Commit

Permalink
[GTK4] Migrate DirectoryDialog from GtkFileChooser to GtkFileDialog
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ptziegler committed Aug 26, 2024
1 parent 32b31fb commit 44b9e93
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 19 deletions.
48 changes: 48 additions & 0 deletions bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 *) */
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,6 +14,7 @@
package org.eclipse.swt.internal;

import java.lang.reflect.*;
import java.util.function.*;

import org.eclipse.swt.internal.gtk.*;
import org.eclipse.swt.widgets.*;
Expand All @@ -26,8 +27,40 @@
*/

public class SyncDialogUtil {
static int responseID;
static long responseID;
static Callback dialogResponseCallback;
static Consumer<Long> dialogResponseFinish;

/**
* This method implements the {@code AsyncReadyCallback} mechanism that is
* used in GTK4. Most operations within GTK4 are executed asynchronously,
* where the user is given an 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.<br>
* 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 pointer for the {@code AsyncReadyCallback} object From
* within the callback routine, the {@code asyncFinish} consumer is called,
* receiving the {@code AsyncResult} value of the callback as argument.<br>
* 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 void run(Display display, Consumer<Callback> asyncOpen, Consumer<Long> asyncFinish) {
initializeResponseCallback();

dialogResponseFinish = asyncFinish;
asyncOpen.accept(dialogResponseCallback);

while (!display.isDisposed()) {
if (responseID != -1) {
break;
}
display.readAndDispatch();
}

disposeResponseCallback();
}

/**
* A blocking call that waits for the handling of the signal before returning
Expand All @@ -53,21 +86,22 @@ static public int run(Display display, long handle, boolean isNativeDialog) {
}

disposeResponseCallback();
return responseID;
return (int) responseID;
}

/**
* Initializes the response callback and resets the responseID of the dialog to the default value.
* 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});
responseID = -1;
}

static void disposeResponseCallback() {
dialogResponseCallback.dispose();
dialogResponseCallback = null;
dialogResponseFinish = null;
}

/**
Expand All @@ -77,7 +111,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) {
static void dialogResponseProc(long dialog, long response_id, long user_data) {
if (dialogResponseFinish != null) {
dialogResponseFinish.accept(response_id);
}
responseID = response_id;
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +16,7 @@

import java.io.*;
import java.util.*;
import java.util.function.*;

import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
Expand Down Expand Up @@ -166,7 +167,11 @@ Optional<String> openNativeChooserDialog () {
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);
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) {
Expand All @@ -186,7 +191,7 @@ Optional<String> 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);
Expand All @@ -206,25 +211,30 @@ Optional<String> openNativeChooserDialog () {
hookId = OS.g_signal_add_emission_hook (signalId, 0, display.emissionProc, handle, 0);
}

int response;
int[] response = new int[1];
long[] file = new long[1];
if (GTK.GTK4) {
response = SyncDialogUtil.run(display, handle, true);
long dialogHandle = handle;
Consumer<Callback> dialogCallback = callback -> GTK4.gtk_file_dialog_select_folder(dialogHandle, shellHandle, 0, callback.getAddress(), 0);
SyncDialogUtil.run(display, dialogCallback, (asyncResult) -> {
file[0] = GTK4.gtk_file_dialog_select_folder_finish(dialogHandle, asyncResult, null);
response[0] = GTK.GTK_RESPONSE_ACCEPT;
});
} else {
display.externalEventLoop = true;
display.sendPreExternalEventDispatchEvent ();
response = GTK3.gtk_native_dialog_run (handle);
response[0] = GTK3.gtk_native_dialog_run (handle);
display.externalEventLoop = false;
display.sendPostExternalEventDispatchEvent ();
}

if ((style & SWT.RIGHT_TO_LEFT) != 0) {
OS.g_signal_remove_emission_hook (signalId, hookId);
}
if (response == GTK.GTK_RESPONSE_ACCEPT) {
if (response[0] == 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);
path = OS.g_file_get_path(file[0]);
} else {
path = GTK3.gtk_file_chooser_get_filename (handle);
}
Expand All @@ -248,14 +258,14 @@ Optional<String> openNativeChooserDialog () {
}
}
}

Optional<String> result = Optional.empty();
if (response == GTK.GTK_RESPONSE_ACCEPT) {
if (response[0] == GTK.GTK_RESPONSE_ACCEPT) {
result = Optional.ofNullable(selectedPath);
}
display.removeIdleProc ();
OS.g_object_unref(handle);
if (result.isPresent() || response == GTK.GTK_RESPONSE_CANCEL) {
if (result.isPresent() || response[0] == GTK.GTK_RESPONSE_CANCEL) {
return result;
}
throw new SWTException(SWT.ERROR_INVALID_RETURN_VALUE);
Expand Down

0 comments on commit 44b9e93

Please sign in to comment.