Skip to content

Commit 53a4f88

Browse files
committed
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 #3138
1 parent de4908c commit 53a4f88

File tree

3 files changed

+225
-2
lines changed

3 files changed

+225
-2
lines changed

bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ Rectangle[] computeControlBounds (Point size, boolean[][] position) {
591591
rects[i].height = getControlHeight(ctrlSize);
592592
rects[i].x = x;
593593
rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
594-
} else if (((alignment & (SWT.WRAP)) != 0 && ctrlSize.x < availableWidth)) {
594+
} else if ((alignment & SWT.WRAP) != 0 && ctrlSize.x <= availableWidth) {
595595
x -= ctrlSize.x;
596596
rects[i].width = ctrlSize.x;
597597
rects[i].height = getControlHeight(ctrlSize);
@@ -3924,7 +3924,7 @@ boolean updateTabHeight(boolean force){
39243924
gc.dispose();
39253925
if (fixedTabHeight == SWT.DEFAULT && controls != null && controls.length > 0) {
39263926
for (int i = 0; i < controls.length; i++) {
3927-
if ((controlAlignments[i] & SWT.WRAP) == 0 && !controls[i].isDisposed() && controls[i].getVisible()) {
3927+
if (!controls[i].isDisposed() && controls[i].getVisible()) {
39283928
int topHeight = controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
39293929
topHeight += renderer.computeTrim(CTabFolderRenderer.PART_HEADER, SWT.NONE, 0,0,0,0).height + 1;
39303930
tabHeight = Math.max(topHeight, tabHeight);
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.swt.tests.gtk.snippets;
12+
13+
import org.eclipse.swt.SWT;
14+
import org.eclipse.swt.custom.CTabFolder;
15+
import org.eclipse.swt.custom.CTabItem;
16+
import org.eclipse.swt.graphics.Point;
17+
import org.eclipse.swt.layout.FillLayout;
18+
import org.eclipse.swt.layout.GridData;
19+
import org.eclipse.swt.layout.GridLayout;
20+
import org.eclipse.swt.widgets.Composite;
21+
import org.eclipse.swt.widgets.Display;
22+
import org.eclipse.swt.widgets.Group;
23+
import org.eclipse.swt.widgets.Label;
24+
import org.eclipse.swt.widgets.Shell;
25+
import org.eclipse.swt.widgets.Text;
26+
import org.eclipse.swt.widgets.ToolBar;
27+
import org.eclipse.swt.widgets.ToolItem;
28+
29+
/**
30+
* Diagnostic snippet for CTabFolder topRight WRAP overflow on GTK.
31+
*
32+
* Reproduces the issue where setTopRight(control, SWT.RIGHT | SWT.WRAP)
33+
* wraps to a second row even when there is sufficient horizontal space.
34+
* This particularly affects composites containing a Text widget (search field).
35+
*
36+
* Run this and observe:
37+
* 1. Whether the toolbar+text renders inline or wrapped below the tabs
38+
* 2. The computed sizes printed to stdout
39+
* 3. Compare behavior with SWT.RIGHT only (no WRAP) vs SWT.RIGHT | SWT.WRAP
40+
*
41+
* See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3138
42+
*/
43+
public class Bug3138_CTabFolderWrapDiagnostic {
44+
45+
public static void main(String[] args) {
46+
Display display = new Display();
47+
Shell shell = new Shell(display);
48+
shell.setSize(800, 400);
49+
shell.setText("Bug 3138 - CTabFolder WRAP Diagnostic");
50+
shell.setLayout(new GridLayout(1, false));
51+
52+
// --- Case 1: SWT.RIGHT | SWT.WRAP (the problematic case) ---
53+
createTestCase(shell, "SWT.RIGHT | SWT.WRAP", SWT.RIGHT | SWT.WRAP);
54+
55+
// --- Case 2: SWT.RIGHT only (works but never wraps) ---
56+
createTestCase(shell, "SWT.RIGHT only", SWT.RIGHT);
57+
58+
shell.open();
59+
60+
// Log sizes after layout
61+
shell.getDisplay().asyncExec(() -> {
62+
System.out.println("=== Shell size: " + shell.getSize() + " ===");
63+
System.out.println();
64+
});
65+
66+
while (!shell.isDisposed()) {
67+
if (!display.readAndDispatch())
68+
display.sleep();
69+
}
70+
display.dispose();
71+
}
72+
73+
private static void createTestCase(Composite parent, String label, int alignment) {
74+
Group group = new Group(parent, SWT.NONE);
75+
group.setText(label);
76+
group.setLayout(new FillLayout());
77+
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
78+
79+
CTabFolder folder = new CTabFolder(group, SWT.BORDER);
80+
81+
// Create tabs similar to Eclipse views
82+
for (int i = 1; i <= 3; i++) {
83+
CTabItem item = new CTabItem(folder, SWT.CLOSE);
84+
item.setText("Tab " + i);
85+
Label content = new Label(folder, SWT.NONE);
86+
content.setText("Content for tab " + i);
87+
item.setControl(content);
88+
}
89+
folder.setSelection(0);
90+
91+
// Create topRight composite mimicking Eclipse view toolbar with search field
92+
Composite topRight = new Composite(folder, SWT.NONE);
93+
GridLayout gl = new GridLayout(2, false);
94+
gl.marginHeight = 0;
95+
gl.marginWidth = 0;
96+
topRight.setLayout(gl);
97+
98+
// ToolBar with a few buttons
99+
ToolBar toolbar = new ToolBar(topRight, SWT.FLAT);
100+
for (int i = 0; i < 3; i++) {
101+
ToolItem ti = new ToolItem(toolbar, SWT.PUSH);
102+
ti.setText("B" + i);
103+
}
104+
105+
// Text widget (search field) - this is the key contributor to inflated size
106+
Text searchText = new Text(topRight, SWT.BORDER | SWT.SEARCH);
107+
searchText.setMessage("Filter...");
108+
searchText.setLayoutData(new GridData(100, SWT.DEFAULT));
109+
110+
folder.setTopRight(topRight, alignment);
111+
112+
// Diagnostic logging
113+
parent.getDisplay().asyncExec(() -> {
114+
if (topRight.isDisposed()) return;
115+
116+
Point topRightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
117+
Point toolbarSize = toolbar.computeSize(SWT.DEFAULT, SWT.DEFAULT);
118+
Point textSize = searchText.computeSize(SWT.DEFAULT, SWT.DEFAULT);
119+
Point textSize100 = searchText.computeSize(100, SWT.DEFAULT);
120+
121+
System.out.println("--- " + label + " ---");
122+
System.out.println(" topRight.computeSize(DEFAULT,DEFAULT) = " + topRightSize);
123+
System.out.println(" toolbar.computeSize(DEFAULT,DEFAULT) = " + toolbarSize);
124+
System.out.println(" text.computeSize(DEFAULT,DEFAULT) = " + textSize);
125+
System.out.println(" text.computeSize(100,DEFAULT) = " + textSize100);
126+
System.out.println(" folder.getSize() = " + folder.getSize());
127+
128+
// Compute what CTabFolder sees as available width
129+
int folderWidth = folder.getSize().x;
130+
int itemWidth = 0;
131+
for (CTabItem item : folder.getItems()) {
132+
itemWidth += item.getBounds().width;
133+
}
134+
System.out.println(" total tab item width = " + itemWidth);
135+
System.out.println(" remaining width (folder - items) = " + (folderWidth - itemWidth));
136+
System.out.println(" topRight preferred vs remaining = " + topRightSize.x
137+
+ " vs " + (folderWidth - itemWidth));
138+
System.out.println(" WOULD OVERFLOW? = " + (topRightSize.x >= (folderWidth - itemWidth)));
139+
System.out.println();
140+
});
141+
}
142+
}

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,87 @@ public void test_childControlOverlap() {
435435
setTopRightAndCheckOverlap.accept(null, 0);
436436
}
437437

438+
/**
439+
* Test that a topRight control with SWT.RIGHT | SWT.WRAP renders inline
440+
* when there is sufficient horizontal space, and wraps to a second row
441+
* when space is tight. Verifies that the control is not clipped vertically
442+
* when inline (tab row must grow to accommodate taller controls like Text).
443+
*
444+
* See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3138
445+
*/
446+
@Test
447+
public void test_topRightWrapOverflow() {
448+
makeCleanEnvironment(SWT.BORDER);
449+
shell.setSize(800, 400);
450+
shell.setLayout(new FillLayout());
451+
452+
for (int i = 1; i <= 3; i++) {
453+
CTabItem item = new CTabItem(ctabFolder, SWT.CLOSE);
454+
item.setText("Tab " + i);
455+
Label content = new Label(ctabFolder, SWT.NONE);
456+
content.setText("Content " + i);
457+
item.setControl(content);
458+
}
459+
ctabFolder.setSelection(0);
460+
461+
// Create a topRight composite with a ToolBar and a Text (search field),
462+
// similar to Eclipse view toolbars (e.g., EGit Staging View)
463+
Composite topRight = new Composite(ctabFolder, SWT.NONE);
464+
GridLayout gl = new GridLayout(2, false);
465+
gl.marginHeight = 0;
466+
gl.marginWidth = 0;
467+
topRight.setLayout(gl);
468+
469+
ToolBar toolbar = new ToolBar(topRight, SWT.FLAT);
470+
for (int i = 0; i < 3; i++) {
471+
ToolItem ti = new ToolItem(toolbar, SWT.PUSH);
472+
ti.setText("B" + i);
473+
}
474+
Text searchText = new Text(topRight, SWT.BORDER | SWT.SEARCH);
475+
searchText.setMessage("Filter...");
476+
searchText.setLayoutData(new GridData(80, SWT.DEFAULT));
477+
478+
ctabFolder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP);
479+
480+
SwtTestUtil.openShell(shell);
481+
processEvents();
482+
483+
// With 800px shell, there is plenty of space — topRight must be inline
484+
Rectangle topRightBounds = topRight.getBounds();
485+
CTabItem firstTab = ctabFolder.getItem(0);
486+
Rectangle tabBounds = firstTab.getBounds();
487+
488+
// topRight should be on the same row as the tabs (y positions overlap)
489+
assertTrue(topRightBounds.y < tabBounds.y + tabBounds.height,
490+
"topRight should be on the same row as tabs when there is enough space. "
491+
+ "topRight.y=" + topRightBounds.y + " tab.bottom=" + (tabBounds.y + tabBounds.height));
492+
493+
// topRight must not be clipped: its full height should be visible within the folder
494+
Rectangle folderBounds = ctabFolder.getBounds();
495+
assertTrue(topRightBounds.y >= 0,
496+
"topRight must not be clipped at the top. topRight.y=" + topRightBounds.y);
497+
assertTrue(topRightBounds.y + topRightBounds.height <= folderBounds.height,
498+
"topRight must not be clipped at the bottom");
499+
500+
// Now shrink the shell so there is not enough space — topRight should wrap
501+
int totalTabWidth = 0;
502+
for (CTabItem item : ctabFolder.getItems()) {
503+
totalTabWidth += item.getBounds().width;
504+
}
505+
int topRightWidth = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
506+
// Make shell narrow enough that tabs + topRight don't fit
507+
shell.setSize(totalTabWidth + topRightWidth / 2, 400);
508+
processEvents();
509+
510+
topRightBounds = topRight.getBounds();
511+
tabBounds = ctabFolder.getItem(0).getBounds();
512+
513+
// topRight should now be below the tab row (wrapped)
514+
assertTrue(topRightBounds.y >= tabBounds.y + tabBounds.height,
515+
"topRight should wrap below tabs when space is tight. "
516+
+ "topRight.y=" + topRightBounds.y + " tab.bottom=" + (tabBounds.y + tabBounds.height));
517+
}
518+
438519
/**
439520
* Min/max and chevron icon can appear below tab row.
440521
* Test for bug 499215, 533582.

0 commit comments

Comments
 (0)