Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimeon Andreev2018-03-20 22:39:17 +0000
committerAndrey Loskutov2018-04-06 13:46:31 +0000
commitd7ce5978d32a9dd4bf77dfa77e4b1c696992af4a (patch)
tree9bcac037e565f993c35890507e0797588f2a4600
parentd0fd89b3ff82408d3222b5b8dffada99a3471f65 (diff)
downloadeclipse.platform.swt-d7ce5978d32a9dd4bf77dfa77e4b1c696992af4a.tar.gz
eclipse.platform.swt-d7ce5978d32a9dd4bf77dfa77e4b1c696992af4a.tar.xz
eclipse.platform.swt-d7ce5978d32a9dd4bf77dfa77e4b1c696992af4a.zip
Bug 531667 - GTK drawing and clipping issues after Control.print(GC) fix
With the fix for bug 531667, at least the following regressions were introduced: 1. and 2. CTabFolder with SWT.RIGHT_TO_LEFT flag is not drawn and code using GC.setTransform()/GC.getTransform() was broken in some cases. E.g. caused drawing artifacts in tools based on GEF, such as figures drawn with wrong coordinates and starting with 0,0 in a global window space. We observe these regressions, since the Cairo transformation matrix is overwritten on some occasions, causing Cairo to lose the current (x,y) translation relative to the widget parents. 3. Further drawing artifacts are seen when scrolling in a GEF editor, or when resizing neighboring part stacks so that they land over specific GEF editor elements (e.g. LED). Namely, some elements are painted over neighboring composites (e.g. ports of a LED). This is caused by bad clipping set by client code and GC.setClipping(0) removing current Cairo clipping. Since GC is able to to draw at any point in the shell after the fix for bug 531667 (as the Cairo handle is shared by all composites), this results in client code being able to draw over other unrelated composites. 4. Bug 478618 was re-introduced on GTK > 3.20, i.e. ControlDecoration of e.g. text fields would paint its error decoration at a wrong location. In result, two decorations per ControlDecoration are painted; one of which is misplaced while the other will not be repainted on correcting input. On GTK 3.14 the ControlDecorations aren't drawn at all independently from bug 531667 or this patch, see bug 533241. We resolve the first two issues by remembering the original Cairo transformation matrix in GC and applying this matrix on every transform set by the client code. The third regression is fixed by remembering the original Cairo clipping and intersecting the client clipping with it. The last regression in ControlDecoration is fixed by applying the patch for bug 478618 also for GTK 3.20 and above. For GTK 3.14 see bug 533241, because this is not a regression from the Control.print(GC) fix. Manual bug snippets are provided for all of the regressions. Change-Id: I1bcc3c72d14fc37b05c51ff6f0845667a95d160c Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com> Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java172
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Group.java2
-rw-r--r--tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted.java71
-rw-r--r--tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted_nested.java90
-rw-r--r--tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_GC_transform_is_wrong.java155
-rw-r--r--tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_Group_drawing_with_paint_listener_is_wrong.java75
-rw-r--r--tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_PaintListener_paints_despite_empty_GC_clipping.java180
7 files changed, 729 insertions, 16 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java
index bccf08f31a..b475cec1a9 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java
@@ -74,6 +74,23 @@ public final class GC extends Resource {
Drawable drawable;
GCData data;
+ /**
+ * The current Cairo matrix, which positions widgets in the shell.
+ * Client transformations come on top of this matrix.
+ */
+ private double[] cairoTransformationMatrix;
+
+ /**
+ * Tracks the last transformation with which {@link #setTransform(Transform)} was called,
+ * so that we can answer clients of {@link #getTransform(Transform)}.
+ */
+ private double[] currentTransform;
+
+ /**
+ * Original clipping set on this GC
+ */
+ private Rectangle clipping;
+
final static int FOREGROUND = 1 << 0;
final static int BACKGROUND = 1 << 1;
final static int FONT = 1 << 2;
@@ -2482,10 +2499,23 @@ public void getTransform(Transform transform) {
if (transform.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
long /*int*/ cairo = data.cairo;
if (cairo != 0) {
- Cairo.cairo_get_matrix(cairo, transform.handle);
- double[] identity = identity();
- Cairo.cairo_matrix_invert(identity);
- Cairo.cairo_matrix_multiply(transform.handle, transform.handle, identity);
+ /*
+ * The client wants to know the relative transformation they set for their widgets.
+ * They do not want to know about the global coordinates of their widget, which is contained in Cairo.cairo_get_matrix().
+ * So we return whatever the client specified with setTransform.
+ */
+ if (GTK.GTK_VERSION >= OS.VERSION (3, 14, 0)) {
+ if (currentTransform != null) {
+ transform.handle = currentTransform.clone();
+ } else {
+ transform.handle = new double[] { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
+ }
+ } else {
+ Cairo.cairo_get_matrix(cairo, transform.handle);
+ double[] identity = identity();
+ Cairo.cairo_matrix_invert(identity);
+ Cairo.cairo_matrix_multiply(transform.handle, transform.handle, identity);
+ }
} else {
transform.setElements(1, 0, 0, 1, 0, 0);
}
@@ -2570,10 +2600,21 @@ void init(Drawable drawable, GCData data, long /*int*/ gdkGC) {
Cairo.cairo_set_fill_rule(cairo, Cairo.CAIRO_FILL_RULE_EVEN_ODD);
data.state &= ~(BACKGROUND | FOREGROUND | FONT | LINE_WIDTH | LINE_CAP | LINE_JOIN | LINE_STYLE | DRAW_OFFSET);
setClipping(data.clipRgn);
+ initCairo();
if ((data.style & SWT.MIRRORED) != 0) {
- initCairo();
- Cairo.cairo_set_matrix(data.cairo, identity());
+ // Don't overwrite the Cairo transformation matrix in GTK 3.14 and above; it contains a translation relative to the parent widget.
+ if (GTK.GTK_VERSION >= OS.VERSION (3, 14, 0)) {
+ int[] w = new int[1], h = new int[1];
+ getSize(w, h);
+ Cairo.cairo_translate(cairo, w[0], 0);
+ Cairo.cairo_scale(cairo, -1.0, 1.0);
+ } else {
+ Cairo.cairo_set_matrix(data.cairo, identity());
+ }
}
+ if (cairoTransformationMatrix == null) cairoTransformationMatrix = new double[6];
+ Cairo.cairo_get_matrix(data.cairo, cairoTransformationMatrix);
+ clipping = getClipping();
}
void initCairo() {
@@ -2681,7 +2722,7 @@ public void setAdvanced(boolean advanced) {
setAlpha(0xFF);
setAntialias(SWT.DEFAULT);
setBackgroundPattern(null);
- setClipping(0);
+ resetClipping();
setForegroundPattern(null);
setInterpolation(SWT.DEFAULT);
setTextAntialias(SWT.DEFAULT);
@@ -2883,11 +2924,78 @@ void setCairoClip(long /*int*/ damageRgn, long /*int*/ clipRgn) {
Cairo.cairo_set_matrix(cairo, matrix);
}
if (clipRgn != 0) {
- setCairoRegion(cairo, clipRgn);
+ long /*int*/ clipRgnCopy = GDK.gdk_region_new();
+ GDK.gdk_region_union(clipRgnCopy, clipRgn);
+
+ /*
+ * Bug 531667: widgets paint over other widgets
+ *
+ * The Cairo handle is shared by all widgets, but GC.setClipping allows global clipping changes.
+ * So we intersect whatever the client sets with the initial GC clipping.
+ */
+ if (GTK.GTK_VERSION >= OS.VERSION(3, 14, 0)) {
+ limitClipping(clipRgnCopy);
+ }
+
+ setCairoRegion(cairo, clipRgnCopy);
Cairo.cairo_clip(cairo);
+ GDK.gdk_region_destroy(clipRgnCopy);
}
}
+/**
+ * Intersects given clipping with original clipping of this context, so
+ * that resulting clip does not allow to paint outside of the GC bounds.
+ */
+private void limitClipping(long /*int*/ gcClipping) {
+ Region clippingRegion = new Region();
+ if (currentTransform != null) {
+ // we want to apply GC clipping stored in init() as is, so we invert user transformations that may distort it
+ double[] invertedCurrentTransform = currentTransform.clone();
+ Cairo.cairo_matrix_invert(invertedCurrentTransform);
+ int[] clippingWithoutUserTransform = transformRectangle(invertedCurrentTransform, clipping);
+ clippingRegion.add(clippingWithoutUserTransform);
+ GDK.gdk_region_intersect(gcClipping, clippingRegion.handle);
+ } else {
+ clippingRegion.add(clipping);
+ }
+ GDK.gdk_region_intersect(gcClipping, clippingRegion.handle);
+ clippingRegion.dispose();
+}
+
+/**
+ * Transforms rectangle with given matrix
+ *
+ * @return transformed rectangle corner coordinates, with x,y order of points.
+ */
+private static int[] transformRectangle(double[] affineTransformation, Rectangle rectangle) {
+ Point[] endPoints = {
+ new Point(rectangle.x , rectangle.y ),
+ new Point(rectangle.x + rectangle.width, rectangle.y ),
+ new Point(rectangle.x + rectangle.width, rectangle.y + rectangle.height),
+ new Point(rectangle.x , rectangle.y + rectangle.height),
+ };
+ return transformPoints(affineTransformation, endPoints);
+}
+
+/**
+ * Transforms x,y coordinate pairs with given matrix
+ *
+ * @return transformed x,y coordinates.
+ */
+private static int[] transformPoints(double[] transformation, Point[] points) {
+ int[] transformedPoints = new int[points.length * 2];
+ double[] px = new double[1], py = new double[1];
+ for (int i = 0; i < points.length; ++i) {
+ px[0] = points[i].x;
+ py[0] = points[i].y;
+ Cairo.cairo_matrix_transform_point(transformation, px, py);
+ transformedPoints[(i * 2) + 0] = (int) Math.round(px[0]);
+ transformedPoints[(i * 2) + 1] = (int) Math.round(py[0]);
+ }
+ return transformedPoints;
+}
+
void setClipping(long /*int*/ clipRgn) {
long /*int*/ cairo = data.cairo;
if (clipRgn == 0) {
@@ -2974,7 +3082,7 @@ void setClippingInPixels(int x, int y, int width, int height) {
public void setClipping(Path path) {
if (handle == 0) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
if (path != null && path.isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
- setClipping(0);
+ resetClipping();
if (path != null) {
initCairo();
long /*int*/ cairo = data.cairo;
@@ -3008,11 +3116,26 @@ void setClippingInPixels(Rectangle rect) {
return; //FIXME: This is an atrocious hack for bug 446075
}
if (rect == null) {
- setClipping(0);
+ resetClipping();
} else {
setClippingInPixels(rect.x, rect.y, rect.width, rect.height);
}
}
+
+private void resetClipping() {
+ if (GTK.GTK_VERSION >= OS.VERSION(3, 14, 0)) {
+ /*
+ * Bug 531667: widgets paint over other widgets
+ *
+ * The Cairo handle is shared by all widgets, and GC.setClipping(0) allows painting outside the current GC area.
+ * So if we reset any custom clipping we still want to restrict GC operations with the initial GC clipping.
+ */
+ setClipping(clipping);
+ } else {
+ setClipping(0);
+ }
+}
+
/**
* Sets the area of the receiver which can be changed
* by drawing operations to the region specified
@@ -3032,7 +3155,11 @@ void setClippingInPixels(Rectangle rect) {
public void setClipping(Region region) {
if (handle == 0) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
if (region != null && region.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
- setClipping(region != null ? region.handle : 0);
+ if (region != null) {
+ setClipping(region.handle);
+ } else {
+ resetClipping();
+ }
}
/**
@@ -3600,11 +3727,26 @@ public void setTransform(Transform transform) {
if (data.cairo == 0 && transform == null) return;
initCairo();
long /*int*/ cairo = data.cairo;
- double[] identity = identity();
- if (transform != null) {
- Cairo.cairo_matrix_multiply(identity, transform.handle, identity);
+ if (GTK.GTK_VERSION >= OS.VERSION (3, 14, 0)) {
+ // Re-set the original Cairo transformation matrix: it contains a translation relative to the parent widget.
+ if (currentTransform != null) {
+ Cairo.cairo_set_matrix(cairo, cairoTransformationMatrix);
+ currentTransform = null;
+ }
+ // Apply user transform on top of the current transformation matrix (and remember it)
+ if (transform != null) {
+ currentTransform = transform.handle.clone();
+ double[] transformMatrix = identity();
+ Cairo.cairo_matrix_multiply(transformMatrix, transform.handle, transformMatrix);
+ Cairo.cairo_transform(cairo, transformMatrix);
+ }
+ } else {
+ double[] identity = identity();
+ if (transform != null) {
+ Cairo.cairo_matrix_multiply(identity, transform.handle, identity);
+ }
+ Cairo.cairo_set_matrix(cairo, identity);
}
- Cairo.cairo_set_matrix(cairo, identity);
data.state &= ~DRAW_OFFSET;
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Group.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Group.java
index c88f0306db..5469a776b0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Group.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Group.java
@@ -477,7 +477,7 @@ int setBounds(int x, int y, int width, int height, boolean move, boolean resize)
@Override
long /*int*/ paintHandle() {
- if (GTK.GTK3) {
+ if (GTK.GTK3 && GTK.GTK_VERSION < OS.VERSION(3, 20, 0)) {
return super.paintHandle();
}
else {
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted.java
new file mode 100644
index 0000000000..e2b0b56cc4
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others. All rights reserved.
+ * The contents of this file are made available under the terms
+ * of the GNU Lesser General Public License (LGPL) Version 2.1 that
+ * accompanies this distribution (lgpl-v21.txt). The LGPL is also
+ * available at http://www.gnu.org/licenses/lgpl.html. If the version
+ * of the LGPL at http://www.gnu.org is different to the version of
+ * the LGPL accompanying this distribution and there is any conflict
+ * between the two license versions, the terms of the LGPL accompanying
+ * this distribution shall govern.
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Description: {@link CTabItem} in {@link CTabFolder} are not drawn if {@link CTabFolder} is placed
+ * below some other widget and is using {@link SWT#RIGHT_TO_LEFT}.
+ * Steps to reproduce:
+ * <ol>
+ * <li>Run the snippet.</li>
+ * <li>Observe tabs of tab folder.</li>
+ * </ol>
+ * Expected results: The tabs of the tab folder are seen.
+ * Actual results: The tabs of the tab folder are not seen.
+ */
+public class Bug531667_CTabFolder_right_to_left_tabs_are_not_painted {
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(400, 300);
+ shell.setLayout(new FillLayout(SWT.VERTICAL));
+ shell.setText("Bug 531667: tab painting is broken");
+
+ Composite c = new Composite(shell, SWT.NONE);
+ c.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
+
+ CTabFolder tabFolder = new CTabFolder(shell, SWT.NONE | SWT.RIGHT_TO_LEFT);
+ createTab(tabFolder, "Tab 1", SWT.COLOR_GREEN);
+ createTab(tabFolder, "Tab 2", SWT.COLOR_YELLOW);
+
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ }
+
+ private static CTabItem createTab(CTabFolder parent, String text, int color) {
+ CTabItem tab1 = new CTabItem(parent, SWT.NONE);
+ tab1.setText(text);
+ Composite comp = new Composite(parent, SWT.NONE);
+ comp.setBackground(comp.getDisplay().getSystemColor(color));
+ tab1.setControl(comp);
+ return tab1;
+ }
+
+}
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted_nested.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted_nested.java
new file mode 100644
index 0000000000..98217c9cf2
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_CTabFolder_right_to_left_tabs_are_not_painted_nested.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others. All rights reserved.
+ * The contents of this file are made available under the terms
+ * of the GNU Lesser General Public License (LGPL) Version 2.1 that
+ * accompanies this distribution (lgpl-v21.txt). The LGPL is also
+ * available at http://www.gnu.org/licenses/lgpl.html. If the version
+ * of the LGPL at http://www.gnu.org is different to the version of
+ * the LGPL accompanying this distribution and there is any conflict
+ * between the two license versions, the terms of the LGPL accompanying
+ * this distribution shall govern.
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Description: {@link CTabItem} in {@link CTabFolder} are not drawn if {@link CTabFolder} is placed
+ * below some other widget and is using {@link SWT#RIGHT_TO_LEFT}.
+ * Steps to reproduce:
+ * <ol>
+ * <li>Run the snippet.</li>
+ * <li>Observe tabs of tab folder.</li>
+ * </ol>
+ * Expected results: The tabs of the tab folders are seen.
+ * Actual results: The tabs of the tab folders are not seen.
+ */
+public class Bug531667_CTabFolder_right_to_left_tabs_are_not_painted_nested {
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(400, 300);
+ shell.setLayout(new FillLayout(SWT.VERTICAL));
+ shell.setText("Bug 531667: tab painting is broken");
+
+ Composite c = new Composite(shell, SWT.NONE);
+ c.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
+
+ Composite c1 = new Composite(shell, SWT.NONE);
+ c1.setLayout(new FillLayout(SWT.HORIZONTAL));
+ Composite c2 = new Composite(shell, SWT.NONE);
+ c2.setLayout(new FillLayout(SWT.HORIZONTAL));
+
+ Composite c3 = new Composite(c1, SWT.NONE);
+ c3.setLayout(new FillLayout());
+ Composite c4 = new Composite(c1, SWT.NONE);
+ c4.setLayout(new FillLayout());
+ Composite c5 = new Composite(c2, SWT.NONE);
+ c5.setLayout(new FillLayout());
+ Composite c6 = new Composite(c2, SWT.NONE);
+ c6.setLayout(new FillLayout());
+
+ Composite[] co = { c4, c3, c5, c6 };
+ for (int i = 0; i < co.length; ++i) {
+ Composite comp = co[i];
+ int alignmentStyle = i % 2 == 0 ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
+ CTabFolder tabFolder = new CTabFolder(comp, alignmentStyle);
+ createTab(tabFolder, "Tab 1", SWT.COLOR_GREEN);
+ createTab(tabFolder, "Tab 2", SWT.COLOR_YELLOW);
+ }
+
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ }
+
+ private static CTabItem createTab(CTabFolder parent, String text, int color) {
+ CTabItem tab1 = new CTabItem(parent, SWT.NONE);
+ tab1.setText(text);
+ Composite comp = new Composite(parent, SWT.NONE);
+ comp.setBackground(comp.getDisplay().getSystemColor(color));
+ tab1.setControl(comp);
+ return tab1;
+ }
+
+}
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_GC_transform_is_wrong.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_GC_transform_is_wrong.java
new file mode 100644
index 0000000000..f923d9659f
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_GC_transform_is_wrong.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others. All rights reserved.
+ * The contents of this file are made available under the terms
+ * of the GNU Lesser General Public License (LGPL) Version 2.1 that
+ * accompanies this distribution (lgpl-v21.txt). The LGPL is also
+ * available at http://www.gnu.org/licenses/lgpl.html. If the version
+ * of the LGPL at http://www.gnu.org is different to the version of
+ * the LGPL accompanying this distribution and there is any conflict
+ * between the two license versions, the terms of the LGPL accompanying
+ * this distribution shall govern.
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.swt.tests.gtk.snippets;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.graphics.Transform;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Description: {@link GC#setTransform(Transform)} is wrong. Steps to reproduce:
+ * <ol>
+ * <li>Run the snippet.</li>
+ * <li>Resize the shell, so that figures are clipped by the composite.</li>
+ * </ol>
+ * Expected results: Each square in the composite has a fully visible red figure, which is contained in its composite.
+ * Actual results: Only the top left square has a visible figure, the figure is drawn over other composites on resize.
+ */
+public class Bug531667_GC_transform_is_wrong {
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(400, 400);
+ shell.setText("Bug 531667 GC transform is wrong");
+ shell.setLayout(new FillLayout());
+ Composite main = new Composite(shell, SWT.NONE);
+ main.setLayout(new FillLayout());
+
+ Composite[] squares = showCaseTransform(main);
+ for (Composite square : squares) {
+ square.layout();
+ }
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ }
+
+ private static Composite[] showCaseTransform(Composite main) {
+ Composite composite = new Composite(main, SWT.NONE);
+ composite.setLayout(new FillLayout());
+
+ Composite left = new Composite(composite, SWT.NONE);
+ left.setLayout(new FillLayout(SWT.VERTICAL));
+ Composite upperLeft = new Composite(left, SWT.BORDER);
+ Composite bottomLeft = new Composite(left, SWT.BORDER);
+
+ Composite right = new Composite(composite, SWT.NONE);
+ right.setLayout(new FillLayout(SWT.VERTICAL));
+ Composite upperRight = new Composite(right, SWT.BORDER);
+ Composite bottomRight = new Composite(right, SWT.BORDER);
+
+ Consumer<Transform> upperLeftTransform = t -> transform(t, 100.f, 25.f, 1.5f, 1.25f, 45.f);
+ Consumer<Transform> upperRightTransform = t -> transform(t, 150.f, 25.f, 1.f, 1.5f, 75.f);
+ Consumer<Transform> bottomLeftTransform = t -> transform(t, 50.f, 25.f, 0.75f, 0.5f, 15.f);
+ Consumer<Transform> bottomRightTransform = t -> transform(t, 150.f, 75.f, 0.5f, 0.75f, 45.f);
+
+ Composite[] squares = { upperLeft, upperRight, bottomLeft, bottomRight };
+ List<Consumer<Transform>> transformations = Arrays.asList(upperLeftTransform, upperRightTransform,
+ bottomLeftTransform, bottomRightTransform);
+
+ for (int i = 0; i < squares.length; ++i) {
+ Composite square = squares[i];
+ square.setLayout(new FillLayout(SWT.VERTICAL));
+
+ Consumer<Transform> transformation = transformations.get(i);
+ PaintListener paint = new TransformSquare(transformation);
+ Canvas canvas = new Canvas(square, SWT.BORDER);
+ canvas.addPaintListener(paint);
+ }
+
+ return squares;
+ }
+
+ private static GC gc(PaintEvent event) {
+ if (event != null) {
+ return event.gc;
+ }
+ return null;
+ }
+
+ private static void transform(Transform transform, float translateX, float translateY, float scaleX, float scaleY, float rotationAngle) {
+ transform.translate(translateX, translateY);
+ transform.scale(scaleX, scaleY);
+ transform.rotate(rotationAngle);
+ }
+
+ private static class TransformSquare implements PaintListener {
+
+ private final Consumer<Transform> transformation;
+
+ TransformSquare(Consumer<Transform> transformation) {
+ this.transformation = transformation;
+ }
+
+ @Override
+ public void paintControl(PaintEvent event) {
+ GC gc = gc(event);
+
+ Device device = gc.getDevice();
+ Transform transform = new Transform(device);
+ transformation.accept(transform);
+
+ // set / get the transformation a few times, to see that this has no unexpected effect
+ gc.setTransform(transform);
+ gc.getTransform(transform);
+ gc.setTransform(transform);
+
+ // set some clipping to ensure we didn't break something clipping+transform related
+ Region region = new Region();
+ region.add(new Rectangle(0, 0, 200, 200));
+ region.subtract(new Rectangle(50, 50, 50, 50));
+ gc.setClipping(region);
+
+ int width = 80;
+ int height = 80;
+ gc.setAlpha(155);
+ gc.setBackground(device.getSystemColor(SWT.COLOR_RED));
+ int[] points = { 0, 0, width, 0, width, height, 0, height };
+ gc.fillPolygon(points);
+
+ gc.setTransform(null);
+ }
+ }
+}
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_Group_drawing_with_paint_listener_is_wrong.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_Group_drawing_with_paint_listener_is_wrong.java
new file mode 100644
index 0000000000..f4850eefe4
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_Group_drawing_with_paint_listener_is_wrong.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others. All rights reserved.
+ * The contents of this file are made available under the terms
+ * of the GNU Lesser General Public License (LGPL) Version 2.1 that
+ * accompanies this distribution (lgpl-v21.txt). The LGPL is also
+ * available at http://www.gnu.org/licenses/lgpl.html. If the version
+ * of the LGPL at http://www.gnu.org is different to the version of
+ * the LGPL accompanying this distribution and there is any conflict
+ * between the two license versions, the terms of the LGPL accompanying
+ * this distribution shall govern.
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Description: Drawing an image with a paint listener of a {@link Group} yields wrong results,
+ * namely the drawing is done in the group area and not in client area of the group.
+ * This causes problems e.g. with JFace ControlDecoration.
+ * Steps to reproduce:
+ * <ol>
+ * <li>Run the snippet.</li>
+ * </ol>
+ * Expected results: The shell contains a group, with a square fully within the group area.
+ * Actual results: The shell contains a group, with a square painted partially outside of the group area.
+ */
+public class Bug531667_Group_drawing_with_paint_listener_is_wrong {
+
+ public static void main(String[] args) {
+ final Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setLayout(new FillLayout());
+ shell.setSize(200, 200);
+ shell.setText("Bug 531667 paint inside a Group is wrong");
+
+ final Group group = new Group(shell, SWT.NONE);
+ group.setText("some group");
+ group.setLayout(new FillLayout());
+
+ final Image image = new Image(display, 40, 40);
+ GC gc = new GC(image);
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));
+ gc.fillRectangle(0, 0, 40, 40);
+ gc.dispose();
+
+ class DrawSquare implements PaintListener {
+ @Override
+ public void paintControl(PaintEvent e) {
+ GC gc = e.gc;
+ gc.drawImage(image, 0, 0);
+ }
+ }
+ group.addPaintListener(new DrawSquare());
+
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ }
+}
diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_PaintListener_paints_despite_empty_GC_clipping.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_PaintListener_paints_despite_empty_GC_clipping.java
new file mode 100644
index 0000000000..64f4d7ac4e
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug531667_PaintListener_paints_despite_empty_GC_clipping.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others. All rights reserved.
+ * The contents of this file are made available under the terms
+ * of the GNU Lesser General Public License (LGPL) Version 2.1 that
+ * accompanies this distribution (lgpl-v21.txt). The LGPL is also
+ * available at http://www.gnu.org/licenses/lgpl.html. If the version
+ * of the LGPL at http://www.gnu.org is different to the version of
+ * the LGPL accompanying this distribution and there is any conflict
+ * between the two license versions, the terms of the LGPL accompanying
+ * this distribution shall govern.
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.gtk.snippets;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Path;
+import org.eclipse.swt.graphics.PathData;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Description: {@link Canvas#print(GC)} does not print the canvas.
+ * Steps to reproduce:
+ * <ol>
+ * <li>Run the snippet.</li>
+ * </ol>
+ * Expected results: The are no red figures drawn.
+ * Actual results: Red figures are drawn on each of the bottom composites,
+ * resulted from drawing on top of other composites due to bad clipping.
+ */
+public class Bug531667_PaintListener_paints_despite_empty_GC_clipping {
+
+ public static void main(String[] args) {
+ final Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(500, 250);
+ shell.setText("Bug 531667 paint listener paints despite empty clipping");
+ shell.setLayout(new FillLayout());
+
+ /*
+ * Some paint listeners which will paint a figure on their own composite,
+ * then set clipping in the area of another composite and try to draw over it.
+ * We should be seeing only figures in the upper composites.
+ */
+
+ class RectangleClipping implements BiConsumer<GC, Rectangle> {
+ @Override
+ public void accept(GC gc, Rectangle boundsOfComposite2) {
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ Rectangle rectangle = new Rectangle(0, 0, 50, 50);
+ gc.setClipping(rectangle);
+ gc.fillRectangle(0, 0, 200, 200);
+
+ Rectangle rectangleInComposite2 = new Rectangle(boundsOfComposite2.x, boundsOfComposite2.y, 50, 50);
+ gc.setClipping(rectangleInComposite2);
+
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));
+ gc.fillRectangle(rectangleInComposite2);
+ }
+ }
+
+ class BoundsClipping implements BiConsumer<GC, Rectangle> {
+ @Override
+ public void accept(GC gc, Rectangle boundsOfComposite2) {
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ gc.setClipping(50, 50, 50, 50);
+ gc.fillRectangle(0, 0, 200, 200);
+
+ gc.setClipping(boundsOfComposite2.x, boundsOfComposite2.y, 100, 100);
+
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));
+ gc.fillRectangle(boundsOfComposite2.x + 50, boundsOfComposite2.y + 50, 50, 50);
+ }
+ }
+
+ class PathClipping implements BiConsumer<GC, Rectangle> {
+ @Override
+ public void accept(GC gc, Rectangle boundsOfComposite2) {
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ Rectangle clipping = gc.getClipping();
+ Path path = triangle(0, 0, 100, display);
+ gc.setClipping(path);
+ gc.fillRectangle(0, 0, 100, 100);
+ gc.setClipping(clipping);
+
+ Rectangle rectangle = new Rectangle(boundsOfComposite2.x, boundsOfComposite2.y, 100, 100);
+ Path pathInComposite22 = triangle(rectangle.x, rectangle.y, 100, display);
+ gc.setClipping(pathInComposite22);
+
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));
+ gc.fillPath(pathInComposite22);
+ }
+ }
+
+ class RegionClipping implements BiConsumer<GC, Rectangle> {
+ @Override
+ public void accept(GC gc, Rectangle boundsOfComposite2) {
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ Region region = new Region();
+ region.add(new Rectangle(0, 0, 50, 50));
+ region.add(new Rectangle(50, 50, 50, 50));
+ gc.setClipping(region);
+ gc.fillRectangle(0, 0, 200, 200);
+
+ region.translate(boundsOfComposite2.x, boundsOfComposite2.y);
+ gc.setClipping(region);
+
+ gc.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));
+ gc.fillRectangle(boundsOfComposite2);
+ }
+ }
+
+ List<BiConsumer<GC, Rectangle> > paints = Arrays.asList(
+ new RectangleClipping(),
+ new BoundsClipping(),
+ new PathClipping(),
+ new RegionClipping()
+ );
+
+ for (BiConsumer<GC, Rectangle> paint : paints) {
+ paintedComposite(shell, paint);
+ }
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) display.sleep();
+ }
+ display.dispose();
+ }
+
+ private static void paintedComposite(Shell shell, final BiConsumer<GC, Rectangle> paint) {
+ Composite composite = new Composite(shell, SWT.BORDER);
+ composite.setLayout(new FillLayout(SWT.VERTICAL));
+
+ Composite composite1 = new Composite(composite, SWT.BORDER);
+ Composite composite2 = new Composite(composite, SWT.BORDER);
+ composite2.setLayout(new FillLayout(SWT.VERTICAL));
+
+ class PaintComposites implements PaintListener {
+ @Override
+ public void paintControl(PaintEvent e) {
+ e.gc.setAlpha(125);
+ paint.accept(e.gc, composite2.getBounds());
+ }
+ }
+
+ composite1.addPaintListener(new PaintComposites());
+
+
+ new Composite(composite2, SWT.NONE);
+ Label label21 = new Label(composite2, SWT.NONE);
+ label21.setText(paint.getClass().getSimpleName());
+ Label label22 = new Label(composite2, SWT.NONE);
+ label22.setText("should be empty");
+
+ }
+
+ private static Path triangle(int x, int y, int size, Display display) {
+ PathData pathData = new PathData();
+ pathData.types = new byte[] { SWT.PATH_MOVE_TO, SWT.PATH_LINE_TO, SWT.PATH_LINE_TO, SWT.PATH_CLOSE };
+ pathData.points = new float[] { x + 0.f, y + 0.f, x + size, y + 0.f, x + (size / 2), y + size };
+ Path path = new Path(display, pathData);
+ return path;
+ }
+} \ No newline at end of file

Back to the top