Bug 546248: [GTK3] Composite receives to much repaint events

As of GTK3.16, GTK will send extra draw events to SwtFixed instances
containing overlay scrollbars. This happens when the mouse leaves
an SwtFixed instance that has overlay scrollbars attached to it.

The fix is to check the dirty region in need of a redraw, and only
sent an SWT.Paint event if the actual SwtFixed instance is in need
of a redraw.

Tested on GTK3.24 on X11 and Wayland. No AllNonBrowser JUnit tests
fail.

Change-Id: I958b4c16020a14f6f24e6e3d1ca67aba2ebec329
Signed-off-by: Eric Williams <ericwill@redhat.com>
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 813c4b0..8715917 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
@@ -717,6 +717,11 @@
 	 */
 	public static final String GTK_THEME_SET_NAME;
 	/**
+	 * True iff overlay scrolling has been disabled via GTK_OVERLAY_SCROLLING=0.
+	 * See bug 546248.
+	 */
+	public static final boolean GTK_OVERLAY_SCROLLING_DISABLED;
+	/**
 	 * True if SWT is running on the GNOME desktop environment.
 	 */
 	public static final boolean isGNOME;
@@ -769,6 +774,14 @@
 		GTK_THEME_SET = gtkThemeSet;
 		GTK_THEME_SET_NAME = gtkThemeName;
 
+		String scrollingProperty = "GTK_OVERLAY_SCROLLING";
+		String scrollingCheck = getEnvironmentalVariable(scrollingProperty);
+		boolean scrollingDisabled = false;
+		if (scrollingCheck != null && scrollingCheck.equals("0")) {
+			scrollingDisabled = true;
+		}
+		GTK_OVERLAY_SCROLLING_DISABLED = scrollingDisabled;
+
 		Map<String, String> env = System.getenv();
 		String desktopEnvironment = env.get("XDG_CURRENT_DESKTOP");
 		boolean gnomeDetected = false;
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Scrollable.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Scrollable.java
index d4a8252..2344fbe 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Scrollable.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Scrollable.java
@@ -304,6 +304,45 @@
 }
 
 @Override
+long gtk_draw (long widget, long cairo) {
+	/*
+	 * Draw events destined for an SwtFixed instance will sometimes
+	 * only be redrawing the scrollbars attached to it. GTK will send many
+	 * draw events to an SwtFixed instance if:
+	 *   1) that instance has overlay scrollbars attached to it, and
+	 *   2) the mouse has just left (leave-notify) that SwtFixed widget.
+	 *
+	 * Such extra draw events cause extra SWT.Paint events to be sent and
+	 * reduce performance. The fix is to check if the dirty region in need
+	 * of a redraw is the same region that the scroll bars occupy, and ignore
+	 * draw events that target such cases. See bug 546248.
+	 */
+	boolean overlayScrolling = !OS.GTK_OVERLAY_SCROLLING_DISABLED &&
+			GTK.GTK_VERSION >= OS.VERSION(3, 16, 0);
+	if (overlayScrolling && OS.G_OBJECT_TYPE(widget) == OS.swt_fixed_get_type()) {
+		if ((style & SWT.V_SCROLL) != 0 && verticalBar != null) {
+			GtkAllocation verticalBarAlloc = new GtkAllocation();
+			GTK.gtk_widget_get_allocation(verticalBar.handle, verticalBarAlloc);
+			GdkRectangle rect = new GdkRectangle();
+			GDK.gdk_cairo_get_clip_rectangle(cairo, rect);
+			if (rect.width == verticalBarAlloc.width && rect.height == verticalBarAlloc.height) {
+				return 0;
+			}
+		}
+		if ((style & SWT.H_SCROLL) != 0 && horizontalBar != null) {
+			GtkAllocation horizontalBarAlloc = new GtkAllocation();
+			GTK.gtk_widget_get_allocation(horizontalBar.handle, horizontalBarAlloc);
+			GdkRectangle rect = new GdkRectangle();
+			GDK.gdk_cairo_get_clip_rectangle(cairo, rect);
+			if (rect.width == horizontalBarAlloc.width && rect.height == horizontalBarAlloc.height) {
+				return 0;
+			}
+		}
+	}
+	return super.gtk_draw(widget, cairo);
+}
+
+@Override
 long gtk_scroll_event (long widget, long eventPtr) {
 	long result = super.gtk_scroll_event (widget, eventPtr);
 
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug546248_CompositeTooManyPaintEvents.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug546248_CompositeTooManyPaintEvents.java
new file mode 100644
index 0000000..c4c7f23
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug546248_CompositeTooManyPaintEvents.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Thomas Singer and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Thomas Singer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+
+public class Bug546248_CompositeTooManyPaintEvents {
+
+	public static void main(String[] args) {
+		final Display display = new Display();
+
+		final StringBuilder buffer = new StringBuilder();
+
+		final int period = 250;
+		display.timerExec(period, new Runnable() {
+			@Override
+			public void run() {
+				if (buffer.length() > 0) {
+					System.out.println(DateFormat.getTimeInstance().format(new Date()) + ": " + buffer);
+					buffer.setLength(0);
+				}
+				if (!display.isDisposed()) {
+					display.timerExec(period, this);
+				}
+			}
+		});
+
+		final Shell shell = new Shell(display);
+		shell.setLayout(new FillLayout());
+
+		final Composite composite = new Composite(shell, SWT.DOUBLE_BUFFERED | SWT.V_SCROLL);
+		final Listener listener = event -> {
+			if (event.type == SWT.Paint) {
+				buffer.append('p');
+				event.gc.drawText("Hello world", 0, 0);
+			}
+			else if (event.type == SWT.MouseMove) {
+				buffer.append('m');
+			}
+			else if (event.type == SWT.MouseHover) {
+				buffer.append('h');
+			}
+		};
+		composite.addListener(SWT.Paint, listener);
+		composite.addListener(SWT.MouseMove, listener);
+		composite.addListener(SWT.MouseHover, listener);
+
+		shell.setSize(400, 300);
+		shell.open();
+
+		while (!shell.isDisposed()) {
+			if (!display.readAndDispatch()) {
+				display.sleep();
+			}
+		}
+
+		display.dispose();
+	}
+}