From b9eebf5891696c6df08857b0c0c7e40fe866a049 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Wed, 18 Mar 2026 14:06:38 +0100 Subject: [PATCH] Fix CTabFolder topRight WRAP overflow triggering too aggressively The topRight control with SWT.RIGHT | SWT.WRAP was wrapping to a second row even when sufficient horizontal space was available. This was most visible on GTK where controls like Text widgets are slightly taller than the tab height due to theme padding. Two changes fix this: 1. updateTabHeight: Include WRAP controls in the tab height calculation. Previously WRAP controls were excluded, so the tab row did not grow to accommodate them. This caused a height-based overflow check in computeControlBounds to trigger, forcing the control below the tabs even when it fit horizontally. 2. computeControlBounds: Use <= instead of < when checking if a WRAP control fits the available width, so controls that exactly fit are not unnecessarily overflowed. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/3138 --- .../org/eclipse/swt/custom/CTabFolder.java | 4 +- .../Bug3138_CTabFolderWrapDiagnostic.java | 142 ++++++++++++++++++ ...est_org_eclipse_swt_custom_CTabFolder.java | 81 ++++++++++ 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug3138_CTabFolderWrapDiagnostic.java diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java index d35d799bb0..98efb4610a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java @@ -591,7 +591,7 @@ Rectangle[] computeControlBounds (Point size, boolean[][] position) { rects[i].height = getControlHeight(ctrlSize); rects[i].x = x; rects[i].y = getControlY(size, rects, borderBottom, borderTop, i); - } else if (((alignment & (SWT.WRAP)) != 0 && ctrlSize.x < availableWidth)) { + } else if ((alignment & SWT.WRAP) != 0 && ctrlSize.x <= availableWidth) { x -= ctrlSize.x; rects[i].width = ctrlSize.x; rects[i].height = getControlHeight(ctrlSize); @@ -3924,7 +3924,7 @@ boolean updateTabHeight(boolean force){ gc.dispose(); if (fixedTabHeight == SWT.DEFAULT && controls != null && controls.length > 0) { for (int i = 0; i < controls.length; i++) { - if ((controlAlignments[i] & SWT.WRAP) == 0 && !controls[i].isDisposed() && controls[i].getVisible()) { + if (!controls[i].isDisposed() && controls[i].getVisible()) { int topHeight = controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT).y; topHeight += renderer.computeTrim(CTabFolderRenderer.PART_HEADER, SWT.NONE, 0,0,0,0).height + 1; tabHeight = Math.max(topHeight, tabHeight); diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug3138_CTabFolderWrapDiagnostic.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug3138_CTabFolderWrapDiagnostic.java new file mode 100644 index 0000000000..49357a25d6 --- /dev/null +++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Bug3138_CTabFolderWrapDiagnostic.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * 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 + *******************************************************************************/ +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.graphics.Point; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +/** + * Diagnostic snippet for CTabFolder topRight WRAP overflow on GTK. + * + * Reproduces the issue where setTopRight(control, SWT.RIGHT | SWT.WRAP) + * wraps to a second row even when there is sufficient horizontal space. + * This particularly affects composites containing a Text widget (search field). + * + * Run this and observe: + * 1. Whether the toolbar+text renders inline or wrapped below the tabs + * 2. The computed sizes printed to stdout + * 3. Compare behavior with SWT.RIGHT only (no WRAP) vs SWT.RIGHT | SWT.WRAP + * + * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3138 + */ +public class Bug3138_CTabFolderWrapDiagnostic { + + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setSize(800, 400); + shell.setText("Bug 3138 - CTabFolder WRAP Diagnostic"); + shell.setLayout(new GridLayout(1, false)); + + // --- Case 1: SWT.RIGHT | SWT.WRAP (the problematic case) --- + createTestCase(shell, "SWT.RIGHT | SWT.WRAP", SWT.RIGHT | SWT.WRAP); + + // --- Case 2: SWT.RIGHT only (works but never wraps) --- + createTestCase(shell, "SWT.RIGHT only", SWT.RIGHT); + + shell.open(); + + // Log sizes after layout + shell.getDisplay().asyncExec(() -> { + System.out.println("=== Shell size: " + shell.getSize() + " ==="); + System.out.println(); + }); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } + + private static void createTestCase(Composite parent, String label, int alignment) { + Group group = new Group(parent, SWT.NONE); + group.setText(label); + group.setLayout(new FillLayout()); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + CTabFolder folder = new CTabFolder(group, SWT.BORDER); + + // Create tabs similar to Eclipse views + for (int i = 1; i <= 3; i++) { + CTabItem item = new CTabItem(folder, SWT.CLOSE); + item.setText("Tab " + i); + Label content = new Label(folder, SWT.NONE); + content.setText("Content for tab " + i); + item.setControl(content); + } + folder.setSelection(0); + + // Create topRight composite mimicking Eclipse view toolbar with search field + Composite topRight = new Composite(folder, SWT.NONE); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + topRight.setLayout(gl); + + // ToolBar with a few buttons + ToolBar toolbar = new ToolBar(topRight, SWT.FLAT); + for (int i = 0; i < 3; i++) { + ToolItem ti = new ToolItem(toolbar, SWT.PUSH); + ti.setText("B" + i); + } + + // Text widget (search field) - this is the key contributor to inflated size + Text searchText = new Text(topRight, SWT.BORDER | SWT.SEARCH); + searchText.setMessage("Filter..."); + searchText.setLayoutData(new GridData(100, SWT.DEFAULT)); + + folder.setTopRight(topRight, alignment); + + // Diagnostic logging + parent.getDisplay().asyncExec(() -> { + if (topRight.isDisposed()) return; + + Point topRightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point toolbarSize = toolbar.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point textSize = searchText.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point textSize100 = searchText.computeSize(100, SWT.DEFAULT); + + System.out.println("--- " + label + " ---"); + System.out.println(" topRight.computeSize(DEFAULT,DEFAULT) = " + topRightSize); + System.out.println(" toolbar.computeSize(DEFAULT,DEFAULT) = " + toolbarSize); + System.out.println(" text.computeSize(DEFAULT,DEFAULT) = " + textSize); + System.out.println(" text.computeSize(100,DEFAULT) = " + textSize100); + System.out.println(" folder.getSize() = " + folder.getSize()); + + // Compute what CTabFolder sees as available width + int folderWidth = folder.getSize().x; + int itemWidth = 0; + for (CTabItem item : folder.getItems()) { + itemWidth += item.getBounds().width; + } + System.out.println(" total tab item width = " + itemWidth); + System.out.println(" remaining width (folder - items) = " + (folderWidth - itemWidth)); + System.out.println(" topRight preferred vs remaining = " + topRightSize.x + + " vs " + (folderWidth - itemWidth)); + System.out.println(" WOULD OVERFLOW? = " + (topRightSize.x >= (folderWidth - itemWidth))); + System.out.println(); + }); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java index c31a31752e..7e69dbce11 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java @@ -435,6 +435,87 @@ public void test_childControlOverlap() { setTopRightAndCheckOverlap.accept(null, 0); } +/** + * Test that a topRight control with SWT.RIGHT | SWT.WRAP renders inline + * when there is sufficient horizontal space, and wraps to a second row + * when space is tight. Verifies that the control is not clipped vertically + * when inline (tab row must grow to accommodate taller controls like Text). + * + * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3138 + */ +@Test +public void test_topRightWrapOverflow() { + makeCleanEnvironment(SWT.BORDER); + shell.setSize(800, 400); + shell.setLayout(new FillLayout()); + + for (int i = 1; i <= 3; i++) { + CTabItem item = new CTabItem(ctabFolder, SWT.CLOSE); + item.setText("Tab " + i); + Label content = new Label(ctabFolder, SWT.NONE); + content.setText("Content " + i); + item.setControl(content); + } + ctabFolder.setSelection(0); + + // Create a topRight composite with a ToolBar and a Text (search field), + // similar to Eclipse view toolbars (e.g., EGit Staging View) + Composite topRight = new Composite(ctabFolder, SWT.NONE); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + topRight.setLayout(gl); + + ToolBar toolbar = new ToolBar(topRight, SWT.FLAT); + for (int i = 0; i < 3; i++) { + ToolItem ti = new ToolItem(toolbar, SWT.PUSH); + ti.setText("B" + i); + } + Text searchText = new Text(topRight, SWT.BORDER | SWT.SEARCH); + searchText.setMessage("Filter..."); + searchText.setLayoutData(new GridData(80, SWT.DEFAULT)); + + ctabFolder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP); + + SwtTestUtil.openShell(shell); + processEvents(); + + // With 800px shell, there is plenty of space — topRight must be inline + Rectangle topRightBounds = topRight.getBounds(); + CTabItem firstTab = ctabFolder.getItem(0); + Rectangle tabBounds = firstTab.getBounds(); + + // topRight should be on the same row as the tabs (y positions overlap) + assertTrue(topRightBounds.y < tabBounds.y + tabBounds.height, + "topRight should be on the same row as tabs when there is enough space. " + + "topRight.y=" + topRightBounds.y + " tab.bottom=" + (tabBounds.y + tabBounds.height)); + + // topRight must not be clipped: its full height should be visible within the folder + Rectangle folderBounds = ctabFolder.getBounds(); + assertTrue(topRightBounds.y >= 0, + "topRight must not be clipped at the top. topRight.y=" + topRightBounds.y); + assertTrue(topRightBounds.y + topRightBounds.height <= folderBounds.height, + "topRight must not be clipped at the bottom"); + + // Now shrink the shell so there is not enough space — topRight should wrap + int totalTabWidth = 0; + for (CTabItem item : ctabFolder.getItems()) { + totalTabWidth += item.getBounds().width; + } + int topRightWidth = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + // Make shell narrow enough that tabs + topRight don't fit + shell.setSize(totalTabWidth + topRightWidth / 2, 400); + processEvents(); + + topRightBounds = topRight.getBounds(); + tabBounds = ctabFolder.getItem(0).getBounds(); + + // topRight should now be below the tab row (wrapped) + assertTrue(topRightBounds.y >= tabBounds.y + tabBounds.height, + "topRight should wrap below tabs when space is tight. " + + "topRight.y=" + topRightBounds.y + " tab.bottom=" + (tabBounds.y + tabBounds.height)); +} + /** * Min/max and chevron icon can appear below tab row. * Test for bug 499215, 533582.