Bug 567371 - [GTK4] Removal of blocking dialog functions

- Changed MessageBox's handling of dialog startup & response return.
Requires changing the async "response" signal to a blocking call named
waitForResponse.

- Swapped where the title and secondary messages are set. This allows
the GTK3 and GTK4 code for this the same. This is done through the use
of gtk_message_dialog_format_secondary_text.

- Added response signal

Change-Id: I89930ff303ffe975969ef47921ff2e9a2845c154
Signed-off-by: Paul D'Pong <sdamrong@redhat.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
index 43cbc89..5149c30 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
@@ -7782,6 +7782,23 @@
 }
 #endif
 
+#ifndef NO_gtk_1message_1dialog_1format_1secondary_1text
+JNIEXPORT void JNICALL GTK_NATIVE(gtk_1message_1dialog_1format_1secondary_1text)
+	(JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1, jbyteArray arg2)
+{
+	jbyte *lparg1=NULL;
+	jbyte *lparg2=NULL;
+	GTK_NATIVE_ENTER(env, that, gtk_1message_1dialog_1format_1secondary_1text_FUNC);
+	if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail;
+	if (arg2) if ((lparg2 = (*env)->GetByteArrayElements(env, arg2, NULL)) == NULL) goto fail;
+	gtk_message_dialog_format_secondary_text((GtkMessageDialog *)arg0, (const gchar *)lparg1, (const gchar *)lparg2);
+fail:
+	if (arg2 && lparg2) (*env)->ReleaseByteArrayElements(env, arg2, lparg2, 0);
+	if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0);
+	GTK_NATIVE_EXIT(env, that, gtk_1message_1dialog_1format_1secondary_1text_FUNC);
+}
+#endif
+
 #ifndef NO_gtk_1message_1dialog_1new
 JNIEXPORT jlong JNICALL GTK_NATIVE(gtk_1message_1dialog_1new)
 	(JNIEnv *env, jclass that, jlong arg0, jint arg1, jint arg2, jint arg3, jbyteArray arg4, jbyteArray arg5)
@@ -13680,6 +13697,18 @@
 }
 #endif
 
+#ifndef NO_gtk_1window_1get_1icon_1name
+JNIEXPORT jlong JNICALL GTK_NATIVE(gtk_1window_1get_1icon_1name)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	jlong rc = 0;
+	GTK_NATIVE_ENTER(env, that, gtk_1window_1get_1icon_1name_FUNC);
+	rc = (jlong)gtk_window_get_icon_name((GtkWindow *)arg0);
+	GTK_NATIVE_EXIT(env, that, gtk_1window_1get_1icon_1name_FUNC);
+	return rc;
+}
+#endif
+
 #ifndef NO_gtk_1window_1get_1mnemonic_1modifier
 JNIEXPORT jint JNICALL GTK_NATIVE(gtk_1window_1get_1mnemonic_1modifier)
 	(JNIEnv *env, jclass that, jlong arg0)
@@ -13961,6 +13990,16 @@
 }
 #endif
 
+#ifndef NO_gtk_1window_1set_1icon_1name
+JNIEXPORT void JNICALL GTK_NATIVE(gtk_1window_1set_1icon_1name)
+	(JNIEnv *env, jclass that, jlong arg0, jlong arg1)
+{
+	GTK_NATIVE_ENTER(env, that, gtk_1window_1set_1icon_1name_FUNC);
+	gtk_window_set_icon_name((GtkWindow *)arg0, (const char *)arg1);
+	GTK_NATIVE_EXIT(env, that, gtk_1window_1set_1icon_1name_FUNC);
+}
+#endif
+
 #ifndef NO_gtk_1window_1set_1keep_1above
 JNIEXPORT void JNICALL GTK_NATIVE(gtk_1window_1set_1keep_1above)
 	(JNIEnv *env, jclass that, jlong arg0, jboolean arg1)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
index 9cab8d3..671dbe8 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
@@ -605,6 +605,7 @@
 	"gtk_1menu_1shell_1insert",
 	"gtk_1menu_1shell_1set_1take_1focus",
 	"gtk_1menu_1tool_1button_1new",
+	"gtk_1message_1dialog_1format_1secondary_1text",
 	"gtk_1message_1dialog_1new",
 	"gtk_1native_1dialog_1run",
 	"gtk_1native_1get_1surface",
@@ -1055,6 +1056,7 @@
 	"gtk_1window_1get_1focus",
 	"gtk_1window_1get_1group",
 	"gtk_1window_1get_1icon_1list",
+	"gtk_1window_1get_1icon_1name",
 	"gtk_1window_1get_1mnemonic_1modifier",
 	"gtk_1window_1get_1modal",
 	"gtk_1window_1get_1position",
@@ -1080,6 +1082,7 @@
 	"gtk_1window_1set_1destroy_1with_1parent",
 	"gtk_1window_1set_1geometry_1hints",
 	"gtk_1window_1set_1icon_1list",
+	"gtk_1window_1set_1icon_1name",
 	"gtk_1window_1set_1keep_1above",
 	"gtk_1window_1set_1modal",
 	"gtk_1window_1set_1resizable",
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
index 1f5b488..91614f6 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
@@ -603,6 +603,7 @@
 	gtk_1menu_1shell_1insert_FUNC,
 	gtk_1menu_1shell_1set_1take_1focus_FUNC,
 	gtk_1menu_1tool_1button_1new_FUNC,
+	gtk_1message_1dialog_1format_1secondary_1text_FUNC,
 	gtk_1message_1dialog_1new_FUNC,
 	gtk_1native_1dialog_1run_FUNC,
 	gtk_1native_1get_1surface_FUNC,
@@ -1053,6 +1054,7 @@
 	gtk_1window_1get_1focus_FUNC,
 	gtk_1window_1get_1group_FUNC,
 	gtk_1window_1get_1icon_1list_FUNC,
+	gtk_1window_1get_1icon_1name_FUNC,
 	gtk_1window_1get_1mnemonic_1modifier_FUNC,
 	gtk_1window_1get_1modal_FUNC,
 	gtk_1window_1get_1position_FUNC,
@@ -1078,6 +1080,7 @@
 	gtk_1window_1set_1destroy_1with_1parent_FUNC,
 	gtk_1window_1set_1geometry_1hints_FUNC,
 	gtk_1window_1set_1icon_1list_FUNC,
+	gtk_1window_1set_1icon_1name_FUNC,
 	gtk_1window_1set_1keep_1above_FUNC,
 	gtk_1window_1set_1modal_FUNC,
 	gtk_1window_1set_1resizable_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java
index d658fd4..63a50ca 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java
@@ -1842,6 +1842,7 @@
 	public static final native long gtk_radio_menu_item_new(long group);
 	public static final native long gtk_separator_menu_item_new();
 
+
 	/* GtkPopover */
 	/** @param popover cast=(GtkPopover *) */
 	public static final native void gtk_popover_popdown(long popover);
@@ -1858,9 +1859,12 @@
 	 * @param model cast=(GMenuModel *) */
 	public static final native long gtk_popover_menu_new_from_model(long model);
 
+
 	/* GtkMenuButton */
 	public static final native long gtk_menu_button_new();
 
+
+	/* GtkMessageDialog */
 	/**
 	 * @param parent cast=(GtkWindow *)
 	 * @param flags cast=(GtkDialogFlags)
@@ -1870,6 +1874,14 @@
 	 * @param arg cast=(const gchar *)
 	 */
 	public static final native long gtk_message_dialog_new(long parent, int flags, int type, int buttons, byte[] message_format, byte[] arg);
+	/**
+	 * @param message_dialog cast=(GtkMessageDialog *)
+	 * @param message_format cast=(const gchar *)
+	 * @param arg cast=(const gchar *)
+	 */
+	public static final native void gtk_message_dialog_format_secondary_text(long message_dialog, byte[] message_format, byte[] arg);
+
+
 	/** @param dialog cast=(GtkNativeDialog *) */
 	public static final native int gtk_native_dialog_run(long dialog);
 	/** @param notebook cast=(GtkNotebook *) */
@@ -3881,4 +3893,11 @@
 	public static final native void gtk_window_set_child(long window, long child);
 	/** @param window cast=(GtkWindow *) */
 	public static final native void gtk_window_destroy(long window);
+	/** @param window cast=(GtkWindow *) */
+	public static final native long gtk_window_get_icon_name(long window);
+	/**
+	 * @param window cast=(GtkWindow *)
+	 * @param name cast=(const char *)
+	 * */
+	public static final native void gtk_window_set_icon_name(long window, long name);
 }
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
index 1eb8e6b..0e69e50 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
@@ -361,6 +361,7 @@
 	public static final byte[] window_state_event = ascii("window-state-event");
 	public static final byte[] notify_state = ascii("notify::state");
 	public static final byte[] notify_theme_change = ascii("notify::gtk-application-prefer-dark-theme");
+	public static final byte[] response = ascii("response");
 
 	/** Properties */
 	public static final byte[] active = ascii("active");
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MessageBox.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MessageBox.java
index 35b7f2f..6c369ff 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MessageBox.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MessageBox.java
@@ -14,6 +14,8 @@
 package org.eclipse.swt.widgets;
 
 
+import java.lang.reflect.*;
+
 import org.eclipse.swt.*;
 import org.eclipse.swt.internal.*;
 import org.eclipse.swt.internal.gtk.*;
@@ -45,6 +47,10 @@
 
 	String message = "";
 	long handle;
+
+	/* GTK4 fields used to transform async response callback synchronous */
+	int responseID;
+	Callback dialogResponseCallback;
 /**
  * Constructs a new instance of this class given only its parent.
  *
@@ -153,65 +159,82 @@
 	if ((style & (SWT.ICON_QUESTION)) != 0) messageType = GTK.GTK_MESSAGE_QUESTION;
 	if ((style & (SWT.ICON_ERROR)) != 0)    messageType = GTK.GTK_MESSAGE_ERROR;
 
-	byte [] format = Converter.wcsToMbcs ("%s", true);
-	byte [] buffer = Converter.wcsToMbcs (message, true);
+	byte[] format = Converter.wcsToMbcs("%s", true);
+	byte[] buffer = Converter.wcsToMbcs(title, true);
 	handle = GTK.gtk_message_dialog_new(parentHandle, dialogFlags, messageType, 0, format, buffer);
 	if (handle == 0) error(SWT.ERROR_NO_HANDLES);
+
+	// Copy parent's icon
 	if (parentHandle != 0) {
-		long pixbufs = GTK.gtk_window_get_icon_list (parentHandle);
-		if (pixbufs != 0) {
-			GTK.gtk_window_set_icon_list (handle, pixbufs);
-			OS.g_list_free (pixbufs);
+		if (GTK.GTK4) {
+			long iconName = GTK.gtk_window_get_icon_name(parentHandle);
+			if (iconName != 0) {
+				GTK.gtk_window_set_icon_name(handle, iconName);
+			}
+		} else {
+			long pixbufs = GTK.gtk_window_get_icon_list(parentHandle);
+			if (pixbufs != 0) {
+				GTK.gtk_window_set_icon_list(handle, pixbufs);
+				OS.g_list_free (pixbufs);
+			}
 		}
 	}
-	Display display = parent != null ? parent.getDisplay (): Display.getCurrent ();
-	createButtons (display.getDismissalAlignment ());
-	buffer = Converter.wcsToMbcs(title, true);
-	GTK.gtk_window_set_title(handle,buffer);
-	display.addIdleProc ();
-	Dialog oldModal = null;
-	/*
-	* In order to allow the dialog to be modal of it's
-	* parent shells, it is required to assign the
-	* dialog to the same window group as of the shells.
-	*/
-	long group = GTK.gtk_window_get_group(0);
-	GTK.gtk_window_group_add_window (group, handle);
 
-	if (GTK.gtk_window_get_modal (handle)) {
-		oldModal = display.getModalDialog ();
-		display.setModalDialog (this);
-	}
-	int signalId = 0;
-	long hookId = 0;
-	if ((style & SWT.RIGHT_TO_LEFT) != 0) {
-		signalId = OS.g_signal_lookup (OS.map, GTK.GTK_TYPE_WIDGET());
-		hookId = OS.g_signal_add_emission_hook (signalId, 0, display.emissionProc, handle, 0);
-	}
-	display.externalEventLoop = true;
-	display.sendPreExternalEventDispatchEvent ();
-	int response = GTK.gtk_dialog_run (handle);
-	/*
-	* This call to gdk_threads_leave() is a temporary work around
-	* to avoid deadlocks when gdk_threads_init() is called by native
-	* code outside of SWT (i.e AWT, etc). It ensures that the current
-	* thread leaves the GTK lock acquired by the function above.
-	*/
-	if (!GTK.GTK4) GDK.gdk_threads_leave();
-	display.externalEventLoop = false;
-	display.sendPostExternalEventDispatchEvent ();
-	if ((style & SWT.RIGHT_TO_LEFT) != 0) {
-		OS.g_signal_remove_emission_hook (signalId, hookId);
-	}
-	if (GTK.gtk_window_get_modal (handle)) {
-		display.setModalDialog (oldModal);
-	}
-	display.removeIdleProc ();
+	Display display = parent != null ? parent.getDisplay() : Display.getCurrent();
+	createButtons(display.getDismissalAlignment());
+	GTK.gtk_message_dialog_format_secondary_text(handle, format, Converter.javaStringToCString(message));
+
+	int response;
 	if (GTK.GTK4) {
-		GTK.gtk_window_destroy(handle);
+		initializeResponseCallback();
+		OS.g_signal_connect(handle, OS.response, dialogResponseCallback.getAddress(), 0);
+		GTK.gtk_widget_show(handle);
+
+		response = waitForResponse(display);
+		disposeResponseCallback();
 	} else {
+		display.addIdleProc ();
+		Dialog oldModal = null;
+		/*
+		* In order to allow the dialog to be modal of it's
+		* parent shells, it is required to assign the
+		* dialog to the same window group as of the shells.
+		*/
+		long group = GTK.gtk_window_get_group(0);
+		GTK.gtk_window_group_add_window (group, handle);
+
+		if (GTK.gtk_window_get_modal (handle)) {
+			oldModal = display.getModalDialog ();
+			display.setModalDialog (this);
+		}
+		int signalId = 0;
+		long hookId = 0;
+		if ((style & SWT.RIGHT_TO_LEFT) != 0) {
+			signalId = OS.g_signal_lookup (OS.map, GTK.GTK_TYPE_WIDGET());
+			hookId = OS.g_signal_add_emission_hook (signalId, 0, display.emissionProc, handle, 0);
+		}
+		display.externalEventLoop = true;
+		display.sendPreExternalEventDispatchEvent ();
+		response = GTK.gtk_dialog_run (handle);
+		/*
+		* This call to gdk_threads_leave() is a temporary work around
+		* to avoid deadlocks when gdk_threads_init() is called by native
+		* code outside of SWT (i.e AWT, etc). It ensures that the current
+		* thread leaves the GTK lock acquired by the function above.
+		*/
+		GDK.gdk_threads_leave();
+		display.externalEventLoop = false;
+		display.sendPostExternalEventDispatchEvent ();
+		if ((style & SWT.RIGHT_TO_LEFT) != 0) {
+			OS.g_signal_remove_emission_hook (signalId, hookId);
+		}
+		if (GTK.gtk_window_get_modal (handle)) {
+			display.setModalDialog (oldModal);
+		}
+		display.removeIdleProc ();
 		GTK.gtk_widget_destroy(handle);
 	}
+
 	return response;
 }
 
@@ -245,4 +268,55 @@
 	return style;
 }
 
+/**
+ * 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.
+ *
+ * Note: This function is only used in the GTK4 implementation due to the removal of the blocking call gtk_dialog_run. See bug 567371.
+ */
+private void initializeResponseCallback() {
+	dialogResponseCallback = new Callback(this, "dialogResponseProc", void.class, new Type[] {long.class, int.class, long.class});
+	responseID = -1;
+}
+
+/**
+ * Disposes the response callback.
+ * This function should be called after waitForResponse has returned, as this signifies the end of the signal handling.
+ *
+ * Note: This function is only used in the GTK4 implementation due to the removal of the blocking call gtk_dialog_run. See bug 567371.
+ * @see waitForResponse
+ */
+private void disposeResponseCallback() {
+	dialogResponseCallback.dispose();
+	dialogResponseCallback = null;
+}
+
+/**
+ * A blocking call that waits for the handling of the signal before returning.
+ *
+ * Note: This function is only used in the GTK4 implementation due to the removal of the blocking call gtk_dialog_run. See bug 567371.
+ *
+ * @return the response_id from the dialog presented to the user
+ */
+private int waitForResponse(Display display) {
+	while (!display.isDisposed()) {
+		boolean eventsDispatched = OS.g_main_context_iteration (0, false);
+		if (responseID != -1) {
+			break;
+		} else if (!eventsDispatched) {
+			display.sleep();
+		}
+	}
+
+	return responseID;
+}
+
+/**
+ * Callback function for the "response" signal in GtkDialog widgets.
+ * Destroys the dialog after a response is given.
+ */
+void dialogResponseProc(long dialog, int response_id, long user_date) {
+	responseID = response_id;
+	GTK.gtk_window_destroy(dialog);
+}
 }