Bug 577878 - [GTK] Table/Tree with SWT.PaintItem don't show rows when
dragging them

In first version of the patch, I overlooked the case where a column
contains multiple renderers. For example, when '.setImage()' was used.

Change-Id: I50c7171811737057e5d1b70b8a2a0b7ce229c998
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.swt/+/192203
Tested-by: Andrey Loskutov <loskutov@gmx.de>
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java
index 35a1834..0f41e63 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java
@@ -2962,8 +2962,28 @@
 		}
 	}
 
-	GdkRectangle backround_area_rect = new GdkRectangle ();
-	OS.memmove (backround_area_rect, background_area, GdkRectangle.sizeof);
+	GdkRectangle rendererRect = new GdkRectangle ();
+	GdkRectangle columnRect = new GdkRectangle ();
+	{
+		/*
+		 * SWT creates multiple renderers (kind of sub-columns) per column.
+		 * For example: one for checkbox, one for image, one for text.
+		 * 'background_area' argument in this function is area of currently
+		 * painted renderer. However, for SWT.EraseItem and SWT.PaintItem,
+		 * SWT wants entire column's area along with the event. There's api
+		 * 'gtk_tree_view_get_background_area()' but it calculates item's
+		 * rect in control, which will be have wrong Y if item is rendered
+		 * separately (for example, for drag image).
+		 * The workaround is to take X range from api and Y range from argument.
+		 */
+		OS.memmove (rendererRect, background_area, GdkRectangle.sizeof);
+
+		long path = GTK.gtk_tree_model_get_path (modelHandle, iter);
+		GTK.gtk_tree_view_get_background_area (handle, path, columnHandle, columnRect);
+		GTK.gtk_tree_path_free (path);
+
+		columnRect.y = rendererRect.y;
+	}
 
 	if (item != null) {
 		if (GTK.GTK_IS_CELL_RENDERER_TOGGLE (cell) || (columnIndex != 0 || (style & SWT.CHECK) == 0)) {
@@ -2984,7 +3004,7 @@
 				if ((flags & GTK.GTK_CELL_RENDERER_FOCUSED) != 0) drawState |= SWT.FOCUSED;
 			}
 
-			Rectangle rect = backround_area_rect.toRectangle ();
+			Rectangle rect = columnRect.toRectangle ();
 			if ((drawState & SWT.SELECTED) == 0) {
 				if ((state & PARENT_BACKGROUND) != 0 || backgroundImage != null) {
 					Control control = findBackgroundControl ();
@@ -3075,7 +3095,7 @@
 	if ((drawState & SWT.BACKGROUND) != 0 && (drawState & SWT.SELECTED) == 0) {
 		GC gc = getGC(cr);
 		gc.setBackground (item.getBackground (columnIndex));
-		gc.fillRectangle (DPIUtil.autoScaleDown (backround_area_rect.toRectangle ()));
+		gc.fillRectangle (DPIUtil.autoScaleDown (rendererRect.toRectangle ()));
 		gc.dispose ();
 	}
 	if ((drawState & SWT.FOREGROUND) != 0 || GTK.GTK_IS_CELL_RENDERER_TOGGLE (cell)) {
@@ -3101,7 +3121,7 @@
 		if (GTK.GTK_IS_CELL_RENDERER_TEXT (cell)) {
 			if (hooks (SWT.PaintItem)) {
 				if (wasSelected) drawState |= SWT.SELECTED;
-				Rectangle rect = backround_area_rect.toRectangle ();
+				Rectangle rect = columnRect.toRectangle ();
 				ignoreSize = true;
 				int [] contentX = new int [1], contentWidth = new int [1];
 				gtk_cell_renderer_get_preferred_size (cell, handle, contentWidth, null);
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java
index a5fda90..215f2ee 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java
@@ -3169,8 +3169,28 @@
 		}
 	}
 
-	GdkRectangle backround_area_rect = new GdkRectangle ();
-	OS.memmove (backround_area_rect, background_area, GdkRectangle.sizeof);
+	GdkRectangle rendererRect = new GdkRectangle ();
+	GdkRectangle columnRect = new GdkRectangle ();
+	{
+		/*
+		 * SWT creates multiple renderers (kind of sub-columns) per column.
+		 * For example: one for checkbox, one for image, one for text.
+		 * 'background_area' argument in this function is area of currently
+		 * painted renderer. However, for SWT.EraseItem and SWT.PaintItem,
+		 * SWT wants entire column's area along with the event. There's api
+		 * 'gtk_tree_view_get_background_area()' but it calculates item's
+		 * rect in control, which will be have wrong Y if item is rendered
+		 * separately (for example, for drag image).
+		 * The workaround is to take X range from api and Y range from argument.
+		 */
+		OS.memmove (rendererRect, background_area, GdkRectangle.sizeof);
+
+		long path = GTK.gtk_tree_model_get_path (modelHandle, iter);
+		GTK.gtk_tree_view_get_background_area (handle, path, columnHandle, columnRect);
+		GTK.gtk_tree_path_free (path);
+
+		columnRect.y = rendererRect.y;
+	}
 
 	if (item != null) {
 		if (GTK.GTK_IS_CELL_RENDERER_TOGGLE (cell) || ( columnIndex != 0 || (style & SWT.CHECK) == 0)) {
@@ -3191,7 +3211,7 @@
 				if ((flags & GTK.GTK_CELL_RENDERER_FOCUSED) != 0) drawState |= SWT.FOCUSED;
 			}
 
-			Rectangle rect = backround_area_rect.toRectangle ();
+			Rectangle rect = columnRect.toRectangle ();
 			// Use the x and width information from the Cairo context. See bug 535124.
 			if (cr != 0) {
 				GdkRectangle r2 = new GdkRectangle ();
@@ -3278,7 +3298,7 @@
 	if ((drawState & SWT.BACKGROUND) != 0 && (drawState & SWT.SELECTED) == 0) {
 		GC gc = getGC(cr);
 		gc.setBackground (item.getBackground (columnIndex));
-		gc.fillRectangle (DPIUtil.autoScaleDown (backround_area_rect.toRectangle ()));
+		gc.fillRectangle (DPIUtil.autoScaleDown (rendererRect.toRectangle ()));
 		gc.dispose ();
 	}
 	if ((drawState & SWT.FOREGROUND) != 0 || GTK.GTK_IS_CELL_RENDERER_TOGGLE (cell)) {
@@ -3304,7 +3324,7 @@
 		if (GTK.GTK_IS_CELL_RENDERER_TEXT (cell)) {
 			if (hooks (SWT.PaintItem)) {
 				if (wasSelected) drawState |= SWT.SELECTED;
-				Rectangle rect = backround_area_rect.toRectangle ();
+				Rectangle rect = columnRect.toRectangle ();
 				ignoreSize = true;
 				int [] contentX = new int [1], contentWidth = new int [1];
 				gtk_cell_renderer_get_preferred_size (cell, handle, contentWidth, null);
diff --git a/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug577878_GTK_TreeTable_NoDragImageWithPaintItem.java b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug577878_GTK_TreeTable_NoDragImageWithPaintItem.java
index 2d65286..028e5c5 100644
--- a/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug577878_GTK_TreeTable_NoDragImageWithPaintItem.java
+++ b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug577878_GTK_TreeTable_NoDragImageWithPaintItem.java
@@ -16,23 +16,56 @@
 
 import org.eclipse.swt.*;
 import org.eclipse.swt.dnd.*;
+import org.eclipse.swt.graphics.*;
 import org.eclipse.swt.layout.*;
 import org.eclipse.swt.widgets.*;
 
 public final class Bug577878_GTK_TreeTable_NoDragImageWithPaintItem {
 	static final int NUM_ROWS = 100;
-	static final int NUM_COLS = 3;
+	static final int NUM_COLS = 2;
+	static final int IMG_CX   = 22;
+	static final int IMG_CY   = 16;
 
 	static String makeItemText(int iRow, int iCol) {
 		return "Item#" + iRow + ":" + iCol;
 	}
 
-	static void createTable(Composite parent, boolean isCustomPaint) {
-		Table control = new Table (parent, SWT.BORDER | SWT.V_SCROLL);
+	static Image makeItemImage(Device device, int iRow) {
+		Image image = new Image(device, IMG_CX, IMG_CY);
+		GC gc = new GC(image);
+		gc.setBackground(new Color(100,255,100));
+		gc.fillRectangle(0, 0, IMG_CX-1, IMG_CY-1);
+		gc.drawRectangle(0, 0, IMG_CX-1, IMG_CY-1);
+
+		String text = Integer.toString(iRow);
+		Point textSize = gc.stringExtent(text);
+		gc.drawText(text, (IMG_CX - textSize.x) / 2, (IMG_CY - textSize.y) / 2);
+
+		gc.dispose();
+		return image;
+	}
+
+	static void paintItem(Event event, Image image, int iRow) {
+		if (image != null) {
+			event.x += 2;
+			int y = event.y + (event.height - image.getBounds().height) / 2;
+			event.gc.drawImage(image, event.x, y);
+			event.x += image.getBounds().width;
+		}
+
+		event.x += 2;
+		String text = makeItemText(iRow, event.index);
+		Point textSize = event.gc.stringExtent(text);
+		int y = event.y + (event.height - textSize.y) / 2;
+		event.gc.drawString (text, event.x, y, true);
+	}
+
+	static void createTable(Composite parent, boolean isCustomPaint, boolean isImages) {
+		Table control = new Table (parent, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
 		control.setLayoutData (new GridData (SWT.FILL, SWT.FILL, true, true));
 
 		control.setHeaderVisible (true);
-		for (int iColumn = 0; iColumn < 3; iColumn++) {
+		for (int iColumn = 0; iColumn < NUM_COLS; iColumn++) {
 			TableColumn column = new TableColumn (control, 0);
 			column.setText ("Col#" + iColumn);
 			column.setWidth (120);
@@ -40,17 +73,26 @@
 
 		control.setItemCount (NUM_ROWS);
 
+		if (isImages) {
+			for (int iItem = 0; iItem < NUM_ROWS; iItem++) {
+				control.getItem(iItem).setImage(makeItemImage(parent.getDisplay(), iItem));
+			}
+		}
+
 		if (isCustomPaint) {
 			control.addListener (SWT.MeasureItem, e -> {
 				e.width = 120;
 				e.height = 20;
 			});
 
+			control.addListener (SWT.EraseItem, e -> {
+				e.detail &= ~SWT.FOREGROUND;
+			});
+
 			control.addListener (SWT.PaintItem, e -> {
 				TableItem item = (TableItem) e.item;
 				int iRow = item.getParent ().indexOf (item);
-				String text = makeItemText(iRow, e.index);
-				e.gc.drawString (text, e.x, e.y, true);
+				paintItem(e, item.getImage(), iRow);
 			});
 		} else {
 			for (int iRow = 0; iRow < NUM_ROWS; iRow++) {
@@ -65,12 +107,12 @@
 		dragSource.addDragListener (new DragSourceAdapter ());
 	}
 
-	static void createTree(Composite parent, boolean isCustomPaint) {
-		Tree control = new Tree (parent, SWT.BORDER | SWT.V_SCROLL);
+	static void createTree(Composite parent, boolean isCustomPaint, boolean isImages) {
+		Tree control = new Tree (parent, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
 		control.setLayoutData (new GridData (SWT.FILL, SWT.FILL, true, true));
 
 		control.setHeaderVisible (true);
-		for (int iColumn = 0; iColumn < 3; iColumn++) {
+		for (int iColumn = 0; iColumn < NUM_COLS; iColumn++) {
 			TreeColumn column = new TreeColumn (control, 0);
 			column.setText ("Col#" + iColumn);
 			column.setWidth (120);
@@ -78,18 +120,27 @@
 
 		control.setItemCount (NUM_ROWS);
 
+		if (isImages) {
+			for (int iItem = 0; iItem < NUM_ROWS; iItem++) {
+				control.getItem(iItem).setImage(makeItemImage(parent.getDisplay(), iItem));
+			}
+		}
+
 		if (isCustomPaint) {
 			control.addListener (SWT.MeasureItem, e -> {
 				e.width = 120;
 				e.height = 20;
 			});
 
+			control.addListener (SWT.EraseItem, e -> {
+				e.detail &= ~SWT.FOREGROUND;
+			});
+
 			control.addListener (SWT.PaintItem, e -> {
 				TreeItem item = (TreeItem) e.item;
 				TreeItem parentItem = item.getParentItem ();
 				int iRow = (parentItem != null) ? parentItem.indexOf (item) : item.getParent ().indexOf (item);
-				String text = makeItemText(iRow, e.index);
-				e.gc.drawString (text, e.x, e.y, true);
+				paintItem(e, item.getImage(), iRow);
 			});
 		} else {
 			for (int iRow = 0; iRow < NUM_ROWS; iRow++) {
@@ -120,24 +171,38 @@
 		hint.setText (
 			"1) Run on GTK\n" +
 			"2) Drag rows from regular Table/Tree - there will be a drag image\n" +
-			"3) Bug 577878: Table/Tree with SWT.PaintItem do not have drag image\n"
+			"3) Bug 577878: Table/Tree with SWT.PaintItem do not have drag image\n" +
+			"\n" +
+			"Patch v2:\n" +
+			"4) Bug 577878 v2: Tree/Table with image should not have empty space\n" +
+			"   to the left of the item\n" +
+			"5) Bug 579395: Tree with images and PaintItem overpaints expanders\n" +
+			"   Old bug, not fixed in this patch."
 		);
 
 		Composite composite = new Composite (shell, 0);
 		composite.setLayoutData (new GridData (SWT.FILL, SWT.FILL, true, true));
-		composite.setLayout (new GridLayout (2, true));
+		composite.setLayout (new GridLayout (4, true));
 
 		new Label(composite, 0).setText ("Table");
+		new Label(composite, 0).setText ("Table+Images");
 		new Label(composite, 0).setText ("Table+PaintItem");
-		createTable(composite, false);
-		createTable(composite, true);
+		new Label(composite, 0).setText ("Table+PaintItem+Images");
+		createTable(composite, false, false);
+		createTable(composite, false, true);
+		createTable(composite, true, false);
+		createTable(composite, true, true);
 
 		new Label(composite, 0).setText ("Tree");
+		new Label(composite, 0).setText ("Tree+Images");
 		new Label(composite, 0).setText ("Tree+PaintItem");
-		createTree(composite, false);
-		createTree(composite, true);
+		new Label(composite, 0).setText ("Tree+PaintItem+Images");
+		createTree(composite, false, false);
+		createTree(composite, false, true);
+		createTree(composite, true, false);
+		createTree(composite, true, true);
 
-		shell.setSize (800, 800);
+		shell.setSize (900, 600);
 		shell.open();
 
 		while (!shell.isDisposed()) {