Bug 577767 - [cocoa] Table/Tree sometimes have wrong scroll range
Some additional notes:
1) `autoresizingMask` is set by `-[NSTableView setAutoresizingMask:]`
which overrides value of 0 to 18 based on undocumented app config option
`NSTableViewEnforceAutoresizingMaskWhenInClipView`.
2) Tree's `NSOutlineView` inherits from `NSTableView`.
3) I measured performance of `-[NSTableView tile]` and it's good.
In a test with 10000 table items, performance is around 25000/sec on
my machine. Also, it doesn't call `MeasureItem` (which could turn to
be expensive).
Change-Id: I6611a7a9cdbd558590b3f8101603350d4576640c
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.swt/+/188859
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Sravan Kumar Lakkimsetti <sravankumarl@in.ibm.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java
index 54db2ab..e436f2d 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java
@@ -2676,6 +2676,22 @@
setScrollWidth (items, true);
}
+@Override
+void setFrameSize (long id, long sel, NSSize size) {
+ super.setFrameSize(id, sel, size);
+
+ /*
+ * Bug 577767: Since macOS 10.15, NSTableView has 'autoresizingMask'
+ * set to follow resizes of its NSClipView. This sometimes causes
+ * Table/Tree to have wrong scroll range (note that size of NSClipView
+ * is what you see and size of NSTableView is the size of entire
+ * content, this defines scroll range). The workaround is to recalc
+ * layout after resizing.
+ */
+ if ((scrollView != null) && (id == scrollView.id))
+ ((NSTableView)view).tile();
+}
+
/**
* Sets the header background color to the color specified
* by the argument, or to the default system color if the argument is null.
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java
index 42a3946..2a488f0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java
@@ -2984,6 +2984,22 @@
setScrollWidth ();
}
+@Override
+void setFrameSize (long id, long sel, NSSize size) {
+ super.setFrameSize(id, sel, size);
+
+ /*
+ * Bug 577767: Since macOS 10.15, NSTableView has 'autoresizingMask'
+ * set to follow resizes of its NSClipView. This sometimes causes
+ * Table/Tree to have wrong scroll range (note that size of NSClipView
+ * is what you see and size of NSTableView is the size of entire
+ * content, this defines scroll range). The workaround is to recalc
+ * layout after resizing.
+ */
+ if ((scrollView != null) && (id == scrollView.id))
+ ((NSTableView)view).tile();
+}
+
/**
* Sets the header background color to the color specified
* by the argument, or to the default system color if the argument is null.
diff --git a/tests/org.eclipse.swt.tests.cocoa/ManualTests/org/eclipse/swt/tests/cocoa/snippets/Bug577767_macOS_TableScrollsPastContent.java b/tests/org.eclipse.swt.tests.cocoa/ManualTests/org/eclipse/swt/tests/cocoa/snippets/Bug577767_macOS_TableScrollsPastContent.java
new file mode 100644
index 0000000..c9c53ca
--- /dev/null
+++ b/tests/org.eclipse.swt.tests.cocoa/ManualTests/org/eclipse/swt/tests/cocoa/snippets/Bug577767_macOS_TableScrollsPastContent.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Syntevo 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:
+ * Syntevo - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.swt.tests.cocoa.snippets;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public final class Bug577767_macOS_TableScrollsPastContent {
+ public static void testTable(Button button, boolean isVirtual) {
+ final Shell popup = new Shell (button.getDisplay (), SWT.ON_TOP | SWT.NO_FOCUS);
+ popup.setLayout (new FillLayout ());
+
+ int virtual = isVirtual ? SWT.VIRTUAL : 0;
+ Table table = new Table (popup, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | virtual);
+
+ // For the problem to occur, Table's item must not fit (by width)
+ // into initial size of Table. Since Bug 575259, initial size is
+ // 100x100 px.
+ table.addListener (SWT.MeasureItem, (arg) -> arg.width = 120);
+
+ // Give items some content that makes it easy to see item boundaries
+ table.addListener (SWT.PaintItem, (arg) -> {
+ TableItem item = (TableItem)arg.item;
+ int color = 3 + item.getParent ().indexOf (item) % 10;
+ Rectangle rect = arg.getBounds ();
+ rect.width = 50;
+ arg.gc.setBackground (button.getDisplay ().getSystemColor (color));
+ arg.gc.fillRectangle (rect);
+ });
+
+ table.setItemCount (20);
+
+ // VIRTUAL is a bit more resilient for some reason; but TableItem.getBounds()
+ // triggers the bug
+ if (isVirtual)
+ table.getItem (0).getBounds ();
+
+ // A way to get rid of popup for convenience of testing
+ table.addListener (SWT.MouseDown, e -> popup.dispose ());
+
+ popup.setLocation (button.toDisplay (new Point (20, 20)));
+ popup.setSize (200, 200);
+ popup.open ();
+ }
+
+ public static void testTree(Button button, boolean isVirtual) {
+ final Shell popup = new Shell (button.getDisplay (), SWT.ON_TOP | SWT.NO_FOCUS);
+ popup.setLayout (new FillLayout ());
+
+ int virtual = isVirtual ? SWT.VIRTUAL : 0;
+ Tree tree = new Tree (popup, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | virtual);
+
+ // For the problem to occur, Tree's item must not fit (by width)
+ // into initial size of Table. Since Bug 575259, initial size is
+ // 100x100 px.
+ tree.addListener (SWT.MeasureItem, (arg) -> arg.width = 120);
+
+ // Give items some content that makes it easy to see item boundaries
+ tree.addListener (SWT.PaintItem, (arg) -> {
+ TreeItem item = (TreeItem)arg.item;
+ int color = 3 + item.getParent ().indexOf (item) % 10;
+ Rectangle rect = arg.getBounds ();
+ rect.width = 50;
+ arg.gc.setBackground (button.getDisplay ().getSystemColor (color));
+ arg.gc.fillRectangle (rect);
+ });
+
+ tree.setItemCount (20);
+
+ // Needed for problem to occur in VIRTUAL Tree.
+ // Regular Tree doesn't have the problem for some reason.
+ if (isVirtual)
+ tree.getItem (0).getBounds ();
+
+ // A way to get rid of popup for convenience of testing
+ tree.addListener (SWT.MouseDown, e -> popup.dispose ());
+
+ popup.setLocation (button.toDisplay (new Point (20, 20)));
+ popup.setSize (200, 200);
+ popup.open ();
+ }
+
+ public static void main(String[] args) {
+ final Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setLayout (new GridLayout (1, true));
+
+ Label hint = new Label (shell, 0);
+ hint.setText(
+ "1) Use macOS 10.15+\n" +
+ "2) Click button to show popup\n" +
+ "3) Use scroller thumb or mouse wheel to scroll to the end\n" +
+ "4) There will be empty space at the end"
+ );
+
+ Button btnTestRegularTable = new Button(shell, SWT.PUSH);
+ btnTestRegularTable.setText ("Test regular Table");
+ btnTestRegularTable.addListener (SWT.Selection, e -> {
+ testTable (btnTestRegularTable, false);
+ });
+
+ Button btnTestVirtualTable = new Button(shell, SWT.PUSH);
+ btnTestVirtualTable.setText ("Test virtual Table");
+ btnTestVirtualTable.addListener (SWT.Selection, e -> {
+ testTable (btnTestVirtualTable, true);
+ });
+
+ Button btnTestRegularTree = new Button(shell, SWT.PUSH);
+ btnTestRegularTree.setText ("Test regular Tree");
+ btnTestRegularTree.addListener (SWT.Selection, e -> {
+ testTree (btnTestRegularTree, false);
+ });
+
+ Button btnTestVirtualTree = new Button(shell, SWT.PUSH);
+ btnTestVirtualTree.setText ("Test virtual Tree");
+ btnTestVirtualTree.addListener (SWT.Selection, e -> {
+ testTree (btnTestVirtualTree, true);
+ });
+
+ shell.pack ();
+ shell.open ();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+ display.dispose();
+ }
+}