Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 544188

Summary: Inadequate width for event bounds in OwnerDrawLabelProvider.paint()
Product: [Eclipse Project] Platform Reporter: Simeon Andreev <simeon.danailov.andreev>
Component: UIAssignee: Platform-UI-Inbox <Platform-UI-Inbox>
Status: CLOSED WONTFIX QA Contact:
Severity: normal    
Priority: P3 CC: ericwill, loskutov, xixiyan
Version: 4.9   
Target Milestone: ---   
Hardware: PC   
OS: Linux   
See Also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=531882
Whiteboard:
Attachments:
Description Flags
Snippet to reproduce the problem with. Run and observe no change in paint event bounds despite attempt to do so in measure method. none

Description Simeon Andreev CLA 2019-02-06 10:58:26 EST
Created attachment 277476 [details]
Snippet to reproduce the problem with. Run and observe no change in paint event bounds despite attempt to do so in measure method.

Broken in:

Eclipse SDK
Version: 4.9
Build id: I20180716-2000

Not broken in:

Eclipse SDK
Version: Photon (4.8)
Build id: I20180611-0500

Snippet: "TestOwnerDrawLabelProvider.java"

Observe standard output, normally (in 4.8 and below) its e.g.:

Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 80, 21}

Observe that the paint has bounds with width 80 after the measure method is ran. The measure method sets the width 80, this width then "arrives" at the paint event.

In 4.9 the output is instead e.g.:

Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Measure: Rectangle {0, 0, 4, 19}
Erase: Rectangle {0, 0, 383, 21}
Paint: Rectangle {2, 0, 4, 21}

Observe that the paint event bounds width remains equal to 4.

This breaks painting in the OwnerDrawLabelProvider that depends on the measurement done in the measure method. E.g. StyledCellLabelProvider.measure(Event, Object) adjusts the width of the event, this would not function in 4.9 and above.
Comment 1 Simeon Andreev CLA 2019-02-06 11:00:15 EST
The bug is still seen in:

Eclipse SDK
Version: 2019-03 (4.11)
Build id: I20190108-1800

The workaround in our product is to hard-code paint width, which is not nice.
Comment 2 Simeon Andreev CLA 2019-02-07 04:02:10 EST
Looks like in org.eclipse.swt.widgets.Table.rendererRender(long, long, long, long, long, long, long, long) there is a call to org.eclipse.swt.widgets.Widget.gtk_cell_renderer_get_preferred_size(long, long, int[], int[]). This used to return the bounds set by the measure call, but no longer does.

This is a regression from the fix for bug 531882:

https://git.eclipse.org/c/platform/eclipse.platform.swt.git/commit/?id=90b11d0439eaf39696ee5c80da07e42a77c9ce34

If I revert the following change, the behaviour is as it was in 4.8:

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 ce861fcdb5..405b605468 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        
@@ -2868,7 +2868,7 @@ void sendMeasureEvent (long /*int*/ cell, long /*int*/ width, long /*int*/ heigh
                        if (contentHeight [0] < rect.height) contentHeight [0] = rect.height;
                        if (width != 0) C.memmove (width, contentWidth, 4);
                        if (height != 0) C.memmove (height, contentHeight, 4);
-                       GTK.gtk_cell_renderer_set_fixed_size (cell, -1, contentHeight [0]);
+                       GTK.gtk_cell_renderer_set_fixed_size (cell, contentWidth [0], contentHeight [0]);
                }
        }
 }
Comment 3 Simeon Andreev CLA 2019-02-07 04:09:16 EST
Hi Xi,

what is the reason for the following change?

https://git.eclipse.org/c/platform/eclipse.platform.swt.git/diff/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Table.java?id=90b11d0439eaf39696ee5c80da07e42a77c9ce34

See the attached snippet, its no longer possible to set a specific width during OwnerDrawLabelProvider.measure() event.

E.g. see example in:

http://www.vogella.com/tutorials/EclipseJFaceTableAdvanced/article.html#styledcelllabelprovider-and-ownerdrawlabelprovider

In particular the example code here would not work as intended:

    colHelloAndIcon.setLabelProvider(new OwnerDrawLabelProvider() {
            @Override
            protected void measure(Event event, Object element) {
                Rectangle rectangle = ICON.getBounds();
                event.
                setBounds(new Rectangle(
                        event.x,
                        event.y,
                        rectangle.width + 200 ,
                        rectangle.height));
            }



Best regards and thanks,
Simeon
Comment 4 Xi Yan CLA 2019-02-07 12:12:24 EST
(In reply to Simeon Andreev from comment #3)
> Hi Xi,
> 
> what is the reason for the following change?
> 
> https://git.eclipse.org/c/platform/eclipse.platform.swt.git/diff/bundles/org.
> eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Table.
> java?id=90b11d0439eaf39696ee5c80da07e42a77c9ce34
> 
> See the attached snippet, its no longer possible to set a specific width
> during OwnerDrawLabelProvider.measure() event.

It was intended to match the behaviour on GTK2 and Mac where event.width in PaintItem stays the same regardless of what it was set in MeasureItem. i.e. The same event.width is send to both MeasureItem and PaintItem. In MeasureItem, the new modified event.width is only used once to set the size of the table column and never updated to be the one sent to PaintItem. 

I tried running the attached snippet on Mac and the paint event bound is always equal to 4 as well. Do you get the intended behaviour on a Mac using PaintItem? If so, there might be some other issues on GTK that I'm missing.
Comment 5 Simeon Andreev CLA 2019-02-08 03:29:53 EST
OK, I do see that behaviour in GTK2 is same as in Eclipse 4.9 and onward. 

Any idea why the paint event has width of only 4? This seems to be much smaller than the actual cell width.
Comment 6 Simeon Andreev CLA 2019-02-08 03:43:29 EST
Using the following example: https://wiki.eclipse.org/JFaceSnippets#Snippet010_-_Owner_Draw

I don't really see a difference when setting different width, neither in 4.8 nor in 4.11. Setting the height does change the paint event height and also the row height. But the width does nothing.
Comment 7 Xi Yan CLA 2019-02-08 10:26:29 EST
(In reply to Simeon Andreev from comment #5)
> OK, I do see that behaviour in GTK2 is same as in Eclipse 4.9 and onward. 
> 
> Any idea why the paint event has width of only 4? This seems to be much
> smaller than the actual cell width.

I assume that the paint event has width of 4 has something to do with the OwnerDrawLabelProvider in jface and its sending the initial 2px boarders when the table wasn't filled with anything. This doesn't happen with a plain SWT snippet though. If you look at 

https://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet230.java

The event.width sent to MeasureItem is 56 to account for the text set in the table, and in MeasureItem we add the image width to the column. 

(In reply to Simeon Andreev from comment #6)
> Using the following example:
> https://wiki.eclipse.org/JFaceSnippets#Snippet010_-_Owner_Draw
> 
> I don't really see a difference when setting different width, neither in 4.8
> nor in 4.11. Setting the height does change the paint event height and also
> the row height. But the width does nothing.

I'm not sure about what exactly does the measure function in OwnerDrawLabelProvider does, but it might not be the same as calling MeasureItem with a listener. I also don't see a difference when setting different width with in the snippet in both 4.8 and 4.11 and on Mac as well. The plain SWT snippet attached should does work though.
Comment 8 Simeon Andreev CLA 2019-02-08 10:36:06 EST
All right, thanks. I'll move back to jface and take a look at the differences between the SWT snippet and the JFace snippet. Its irritating that the paint event doesn't have full width.
Comment 9 Simeon Andreev CLA 2019-02-12 06:49:22 EST
Attaching an SWT listener to the viewers SWT table yields same event bounds as the ones seen in JFace:

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setSize(400, 200);
		shell.setLayout(new FillLayout());

		final TableViewer v = new TableViewer(shell);
		v.setLabelProvider(new OwnerDrawLabelProvider() {

			@Override
			protected void measure(Event event, Object element) {
				System.out.println("Measure JFace: " + event.getBounds());
				event.setBounds(defaultBounds);
			}

		    @Override
		    protected void erase(Event event, Object element) {
		    }
			
			@Override
			protected void paint(Event event, Object element) {
				System.out.println("Paint Face: " + event.getBounds());
			}

		});
		int m = 2;
		String[] contents = new String[m];
		for (int i = 0; i < m; ++i) {
			TableColumn column = new TableColumn(v.getTable(), SWT.NONE);
			column.setWidth(100);
			column.setText("column " + i);
			contents[i] = "content " + i;
		}
		v.getTable().setHeaderVisible(true);
		v.setContentProvider(new ArrayContentProvider());
		v.setInput(contents);

		Listener paintListener = event -> {
			switch(event.type) {
				case SWT.MeasureItem: {
					System.out.println("Measure SWT: " + event.getBounds());
					break;
				}
				case SWT.PaintItem: {
					System.out.println("Paint SWT: " + event.getBounds());
					break;
				}
			}
		};
		v.getTable().addListener(SWT.MeasureItem, paintListener);
		v.getTable().addListener(SWT.PaintItem, paintListener);
		
		shell.open();
		
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}

		display.dispose();
	}

Removing this call returns the event bounds to normal: v.setLabelProvider(new OwnerDrawLabelProvider() {

I'll check why that is.

Also note that the event bounds seem to depend on the length of text in the cell. Longer text results in longer event width. Maybe related.
Comment 10 Simeon Andreev CLA 2019-02-12 08:39:49 EST
Looks like setting the input after setting a CellLabelProvider results in the too-small event width.

Xi, any idea what changes the preferred cell width (since this is where the event width comes from)? I'm having trouble finding what operation of the label provider exactly changes this.
Comment 11 Simeon Andreev CLA 2019-02-12 10:01:10 EST
Looks like setting the TableViewer input *after* specifying a CellLabelProvider results in clearing the entire table:

"main" #1 prio=5 os_prio=0 tid=0x00007ffff004f800 nid=0x3f8b at breakpoint[0x00007ffff7fcc000]
   java.lang.Thread.State: RUNNABLE
        at org.eclipse.swt.internal.gtk.GTK.gtk_list_store_set(GTK.java:4023)
        at org.eclipse.swt.widgets.TableItem.clear(TableItem.java:227)
        at org.eclipse.swt.widgets.Table.clear(Table.java:377)
        at org.eclipse.jface.viewers.TableViewer.doClear(TableViewer.java:328)
        at org.eclipse.jface.viewers.AbstractTableViewer.internalRefreshAll(AbstractTableViewer.java:695)
        at org.eclipse.jface.viewers.AbstractTableViewer.internalRefresh(AbstractTableViewer.java:616)
        at org.eclipse.jface.viewers.AbstractTableViewer.internalRefresh(AbstractTableViewer.java:608)
        at org.eclipse.jface.viewers.AbstractTableViewer.lambda$0(AbstractTableViewer.java:570)
        at org.eclipse.jface.viewers.AbstractTableViewer$$Lambda$17/1843368112.run(Unknown Source)
        at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1449)
        at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1410)
        at org.eclipse.jface.viewers.AbstractTableViewer.inputChanged(AbstractTableViewer.java:570)
        at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:289)
        at org.eclipse.jface.viewers.StructuredViewer.setInput(StructuredViewer.java:1687)
        at jface.bugs.TestOwnerDrawLabelProvider.main(TestOwnerDrawLabelProvider.java:72)

I am guessing this makes the preferred cell width to equal 4 from GTK+ perspective, although both SWT and JFace have correct cell bounds.
Comment 12 Simeon Andreev CLA 2019-02-12 10:14:29 EST
Behaviour in Windows 10 is similar.

If I set the table viewer input before registering the label provider, I see drawing artefacts and "OK" event width.

If I set the table viewer input after registering the label provider, I see no drawing artefacts and 4 px event width (0 px event width on Windows 10).

I have no idea how the event bounds are supposed to be used to draw anything in such case. I do see that this snippet adds artificial 100 px to the event width then drawing flags:

https://git.eclipse.org/c/platform/eclipse.platform.ui.git/plain/examples/org.eclipse.jface.snippets/Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/Snippet010OwnerDraw.java


		@Override
		protected void drawFlag(Event event) {

			Rectangle bounds = event.getBounds();
			bounds.width += 100;

Seems odd at best.
Comment 13 Xi Yan CLA 2019-02-12 10:41:18 EST
(In reply to Simeon Andreev from comment #10)
> Looks like setting the input after setting a CellLabelProvider results in
> the too-small event width.
> 
> Xi, any idea what changes the preferred cell width (since this is where the
> event width comes from)? I'm having trouble finding what operation of the
> label provider exactly changes this.

The event width sent to PaintItem is send constantly in the rendererRender method in Table.java here:

https://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Table.java#n3141

And in GTK, the width is gotten from calling GTK.gtk_cell_renderer_get_preferred_size: 

https://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Table.java#n3083
Comment 14 Simeon Andreev CLA 2019-02-13 03:53:46 EST
Looks like this is the behaviour on Windows 10, GTK3 and GTK2. So I don't think its meaningful to change it. I've suggested the following workaround for our developers:

Rectangle textBounds = ((TableItem) event.item).getTextBounds(event.index);

This is very similar to what is done in: org.eclipse.jface.viewers.StyledCellLabelProvider

The feedback we got is that this is not nice, since its not independent of the tree/table type. The only other option I see is using the event.gc clipping. Both don't seem to be optimal, but I think they are preferable to touching API that has been as it is for quite some time.