7c442c1
From 272bf0d8ebce8f12e9bb0de2c389d3a18d319afb Mon Sep 17 00:00:00 2001
7c442c1
From: Eric Williams
7c442c1
Date: Wed, 28 Feb 2018 16:10:48 -0500
7c442c1
Subject: Bug 431423: [GTK3] Context menu should appear by pointer when invoked
7c442c1
 via mouse
7c442c1
7c442c1
Some context menus are positioned incorrectly when the size (number of
7c442c1
items) of the menu changes between clicks.
7c442c1
7c442c1
The cause of this bug is due to the way SWT populates its menus: often
7c442c1
it is done asynchronously outside of SWT based on an SWT.Show listener.
7c442c1
The result is that the menu items are added/removed just before showing,
7c442c1
and GTK sometimes doesn't have enough time to properly adjust the size
7c442c1
of the menu internally. Specifically, the GdkWindow of the toplevel
7c442c1
widget (GtkWindow) for the menu lags in height. Since GTK thinks the
7c442c1
menu is taller/shorter than it is, the menu is positioned at the wrong
7c442c1
y-coordinate.
7c442c1
7c442c1
The fix is to check if the number of menu items has changed from one
7c442c1
pop-up to the next. If so, calculate the preferred height of the menu
7c442c1
and resize the GdkWindow of the menu's toplevel GtkWindow to reflect
7c442c1
this change. This way the internal sizing calculations in GTK will be
7c442c1
accurate and the menu will be positioned correctly.
7c442c1
7c442c1
The bug is fixed on GTK3.22 with X11 only, as this uses the new GTK3.22
7c442c1
GtkMenu API. Older GTK3 versions use the outdated GtkMenu API and fixing
7c442c1
these cases would not be worth the effort. Wayland is not affected by
7c442c1
this bug.
7c442c1
7c442c1
Reliable steps to reproduce the issue can be found here:
7c442c1
https://bugs.eclipse.org/bugs/show_bug.cgi?id=431423#c19
7c442c1
7c442c1
Tested JFace/SWT menu snippets, AllNonBrowser JUnit tests, and
7c442c1
ControlExample on GTK3.22 with X11.
7c442c1
7c442c1
Change-Id: I392983438cc32b9455c53cc464626294f3ec72f9
7c442c1
Signed-off-by: Eric Williams <ericwill@redhat.com>---
7c442c1
 .../Eclipse SWT PI/gtk/library/os.c                | 25 ++++++++++++
7c442c1
 .../Eclipse SWT PI/gtk/library/os_stats.c          |  1 +
7c442c1
 .../Eclipse SWT PI/gtk/library/os_stats.h          |  1 +
7c442c1
 .../gtk/org/eclipse/swt/internal/gtk/OS.java      | 12 ++++++
7c442c1
 .../gtk/org/eclipse/swt/widgets/Menu.java          | 46 ++++++++++++++++++++++
7c442c1
 5 files changed, 85 insertions(+)
7c442c1
7c442c1
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
7c442c1
index 428d448..7cddab1 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c	
7c442c1
@@ -16214,6 +16214,31 @@ JNIEXPORT jintLong JNICALL GTK_NATIVE(_1gtk_1widget_1get_1parent_1window)
7c442c1
 }
7c442c1
 #endif
7c442c1
 
7c442c1
+#ifndef NO__1gtk_1widget_1get_1preferred_1height
7c442c1
+JNIEXPORT void JNICALL OS_NATIVE(_1gtk_1widget_1get_1preferred_1height)
7c442c1
+	(JNIEnv *env, jclass that, jintLong arg0, jintArray arg1, jintArray arg2)
7c442c1
+{
7c442c1
+	jint *lparg1=NULL;
7c442c1
+	jint *lparg2=NULL;
7c442c1
+	OS_NATIVE_ENTER(env, that, _1gtk_1widget_1get_1preferred_1height_FUNC);
7c442c1
+	if (arg1) if ((lparg1 = (*env)->GetIntArrayElements(env, arg1, NULL)) == NULL) goto fail;
7c442c1
+	if (arg2) if ((lparg2 = (*env)->GetIntArrayElements(env, arg2, NULL)) == NULL) goto fail;
7c442c1
+/*
7c442c1
+	gtk_widget_get_preferred_height(arg0, lparg1, lparg2);
7c442c1
+*/
7c442c1
+	{
7c442c1
+		OS_LOAD_FUNCTION(fp, gtk_widget_get_preferred_height)
7c442c1
+		if (fp) {
7c442c1
+			((void (CALLING_CONVENTION*)(jintLong, jint *, jint *))fp)(arg0, lparg1, lparg2);
7c442c1
+		}
7c442c1
+	}
7c442c1
+fail:
7c442c1
+	if (arg2 && lparg2) (*env)->ReleaseIntArrayElements(env, arg2, lparg2, 0);
7c442c1
+	if (arg1 && lparg1) (*env)->ReleaseIntArrayElements(env, arg1, lparg1, 0);
7c442c1
+	OS_NATIVE_EXIT(env, that, _1gtk_1widget_1get_1preferred_1height_FUNC);
7c442c1
+}
7c442c1
+#endif
7c442c1
+
7c442c1
 #ifndef NO__1gtk_1widget_1get_1preferred_1height_1for_1width
7c442c1
 JNIEXPORT void JNICALL OS_NATIVE(_1gtk_1widget_1get_1preferred_1height_1for_1width)
7c442c1
 	(JNIEnv *env, jclass that, jintLong arg0, jint arg1, jintArray arg2, jintArray arg3)
7c442c1
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_custom.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_custom.h
7c442c1
index ddb326e..43acdb3 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_custom.h	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_custom.h	
7c442c1
@@ -296,6 +296,7 @@
7c442c1
 #define gtk_widget_override_color_LIB LIB_GTK
7c442c1
 #define gtk_widget_override_background_color_LIB LIB_GTK
7c442c1
 #define gtk_widget_override_font_LIB LIB_GTK
7c442c1
+#define gtk_widget_get_preferred_height_LIB LIB_GTK
7c442c1
 #define gtk_widget_get_preferred_height_for_width_LIB LIB_GTK
7c442c1
 #define gtk_widget_get_preferred_width_for_height_LIB LIB_GTK
7c442c1
 #define gtk_style_context_get_font_LIB LIB_GTK
7c442c1
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
7c442c1
index ddb326e..43acdb3 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c	
7c442c1
@@ -1321,6 +1321,7 @@ char * GTK_nativeFunctionNames[] = {
7c442c1
 	"_1gtk_1widget_1get_1pango_1context",
7c442c1
 	"_1gtk_1widget_1get_1parent",
7c442c1
 	"_1gtk_1widget_1get_1parent_1window",
7c442c1
+	"_1gtk_1widget_1get_1preferred_1height",
7c442c1
 	"_1gtk_1widget_1get_1preferred_1height_1for_1width",
7c442c1
 	"_1gtk_1widget_1get_1preferred_1size",
7c442c1
 	"_1gtk_1widget_1get_1preferred_1width_1for_1height",
7c442c1
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
7c442c1
index f8fd88f..2038eea 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h	
7c442c1
@@ -1331,6 +1331,7 @@ typedef enum {
7c442c1
 	_1gtk_1widget_1get_1pango_1context_FUNC,
7c442c1
 	_1gtk_1widget_1get_1parent_FUNC,
7c442c1
 	_1gtk_1widget_1get_1parent_1window_FUNC,
7c442c1
+	_1gtk_1widget_1get_1preferred_1height_FUNC,
7c442c1
 	_1gtk_1widget_1get_1preferred_1height_1for_1width_FUNC,
7c442c1
 	_1gtk_1widget_1get_1preferred_1size_FUNC,
7c442c1
 	_1gtk_1widget_1get_1preferred_1width_1for_1height_FUNC,
7c442c1
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
7c442c1
index a343cfe..a324248 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java	
7c442c1
@@ -6388,6 +6388,18 @@ public class GTK extends OS {
7c442c1
 /**
7c442c1
  * @method flags=dynamic
7c442c1
  */
7c442c1
+public static final native void _gtk_widget_get_preferred_height(long /*int*/ widget, int[] minimum_size, int[] natural_size);
7c442c1
+public static final void gtk_widget_get_preferred_height(long /*int*/ widget, int[] minimum_size, int[] natural_size) {
7c442c1
+	lock.lock();
7c442c1
+	try {
7c442c1
+		_gtk_widget_get_preferred_height(widget, minimum_size, natural_size);
7c442c1
+	} finally {
7c442c1
+		lock.unlock();
7c442c1
+	}
7c442c1
+}
7c442c1
+/**
7c442c1
+ * @method flags=dynamic
7c442c1
+ */
7c442c1
 public static final native void _gtk_widget_get_preferred_width_for_height(long /*int*/ widget, int height, int[] minimum_size, int[] natural_size);
7c442c1
 public static final void gtk_widget_get_preferred_width_for_height(long /*int*/ widget, int height, int[] minimum_size, int[] natural_size) {
7c442c1
 	lock.lock();
7c442c1
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java
7c442c1
index d6ddbbc..6620fbc 100644
7c442c1
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java	
7c442c1
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java	
7c442c1
@@ -45,6 +45,7 @@ public class Menu extends Widget {
7c442c1
 	Decorations parent;
7c442c1
 	long /*int*/ imItem, imSeparator, imHandle;
7c442c1
 	ImageList imageList;
7c442c1
+	int poppedUpCount;
7c442c1
 
7c442c1
 /**
7c442c1
  * Constructs a new instance of this class given its parent,
7c442c1
@@ -227,7 +228,9 @@
7c442c1
 			* will fail.  The fix is to ensure that the timestamp of the last
7c442c1
 			* event processed is used.
7c442c1
 			*/
7c442c1
+			verifyMenuPosition(getItemCount());
7c442c1
 			OS.gtk_menu_popup (handle, 0, 0, address, data, 0, display.getLastEventTime ());
7c442c1
+			poppedUpCount = getItemCount();
7c442c1
 		} else {
7c442c1
 			sendEvent (SWT.Hide);
7c442c1
 		}
7c442c1
@@ -1069,6 +1072,49 @@ void setOrientation (boolean create) {
7c442c1
 }
7c442c1
 
7c442c1
 /**
7c442c1
+ * Feature in GTK3 on X11: context menus in SWT are populated
7c442c1
+ * dynamically, sometimes asynchronously outside of SWT
7c442c1
+ * (i.e. in Platform UI). This means that items are added and
7c442c1
+ * removed just before the menu is shown. This method of
7c442c1
+ * changing the menu content can sometimes cause sizing issues
7c442c1
+ * internally in GTK, specifically with the height of the
7c442c1
+ * toplevel GdkWindow. 

7c442c1
+ *
7c442c1
+ * The fix is to cache the number of items popped up previously,
7c442c1
+ * and if the number of items in the current menu (to be popped up)
7c442c1
+ * is different, then:
    7c442c1
    + *     
  • get the preferred height of the menu
  • 7c442c1
    + *     
  • set the toplevel GdkWindow to that height
  • 7c442c1
    + *
    7c442c1
    + * @param itemCount the current number of items in the menu, just
    7c442c1
    + * before it's about to be shown/popped-up
    7c442c1
    + */
    7c442c1
    +void verifyMenuPosition (int itemCount) {
    7c442c1
    +	if (OS.GTK3 && OS.isX11()) {
    7c442c1
    +		if (itemCount != poppedUpCount && poppedUpCount != 0) {
    7c442c1
    +			int [] naturalHeight = new int [1];
    7c442c1
    +			/*
    7c442c1
    +			 * We need to "show" the menu before fetching the preferred height.
    7c442c1
    +			 * Note, this does not actually pop-up the menu.
    7c442c1
    +			 */
    7c442c1
    +			OS.gtk_widget_show(handle);
    7c442c1
    +			/*
    7c442c1
    +			 * Menus are height-for-width only: use gtk_widget_get_preferred_height()
    7c442c1
    +			 * instead of gtk_widget_get_preferred_size().
    7c442c1
    +			 */
    7c442c1
    +			OS.gtk_widget_get_preferred_height(handle, null, naturalHeight);
    7c442c1
    +			if (naturalHeight[0] > 0) {
    7c442c1
    +				long /*int*/ topLevelWidget = OS.gtk_widget_get_toplevel(handle);
    7c442c1
    +				long /*int*/ topLevelWindow = OS.gtk_widget_get_window(topLevelWidget);
    7c442c1
    +				int width = OS.gdk_window_get_width(topLevelWindow);
    7c442c1
    +				OS.gdk_window_resize(topLevelWindow, width, naturalHeight[0]);
    7c442c1
    +			}
    7c442c1
    +		}
    7c442c1
    +	}
    7c442c1
    +	return;
    7c442c1
    +}
    7c442c1
    +
    7c442c1
    +/**
    7c442c1
      * Marks the receiver as visible if the argument is true,
    7c442c1
      * and marks it invisible otherwise.
    7c442c1
      *