From 9f90faeff890bf5a34b47b369539f27f36a42ec7 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Thu, 3 Sep 2026 13:15:02 +0200 Subject: [PATCH] Style CTabFolder pages that are hidden when they are skinned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Search dialog opened unstyled until a tab was switched. A CTabFolder exposes only the page of its selected tab to the CSS engine, and applyStyles skips an element that is not visible. SearchDialog attaches its page before selecting the tab, so the page is hidden during the skin pass, and the programmatic setSelection that follows fires no selection event. A page whose creation runs the event loop is skinned before it is attached at all, and attaching it to an already selected tab sends no show event either. Watch a skipped CTabFolder child for the resize or show that makes it the selected tab's page and style it then. Also drop the swt-simple test: curved tabs are gone, so getSimple() always returns true and the assertion tested nothing. Assisted-by: multiple AI agents and layers of automated tooling 🤖 --- .../swt/engine/CSSSWTApplyStylesListener.java | 44 ++++++++++- .../e4/ui/tests/css/swt/CTabFolderTest.java | 76 ++++++++++++++++--- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTApplyStylesListener.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTApplyStylesListener.java index 29a5a7ed714..7d13391ed59 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTApplyStylesListener.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTApplyStylesListener.java @@ -15,22 +15,58 @@ import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; /** - * Add SWT filter to the {@link Display} to apply styles when SWT widget is - * resized or showed. + * Applies styles to the widgets of a {@link Display} when they are skinned, and + * to a {@link CTabFolder} page skipped while hidden once it is the selected + * tab's page. */ public class CSSSWTApplyStylesListener { CSSEngine engine; public CSSSWTApplyStylesListener(Display display, final CSSEngine engine) { this.engine = engine; display.addListener(SWT.Skin, event -> { - if (engine != null) { - engine.applyStyles(event.widget, false); + if (engine == null) { + return; + } + engine.applyStyles(event.widget, false); + if (event.widget instanceof Control control && control.getParent() instanceof CTabFolder folder + && !isPageOfSelectedTab(folder, control)) { + // the folder exposes only the selected page, so the engine skipped this one + styleWhenItBecomesThePage(folder, control); } }); } + /** + * Styles the control once it is the selected tab's page, which attaching it + * to its item (a resize) or selecting its tab (a show) makes it. + */ + private void styleWhenItBecomesThePage(CTabFolder folder, Control control) { + Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + if (!isPageOfSelectedTab(folder, control)) { + return; + } + control.removeListener(SWT.Show, this); + control.removeListener(SWT.Resize, this); + engine.applyStyles(control, true); + } + }; + control.addListener(SWT.Show, listener); + control.addListener(SWT.Resize, listener); + } + + private static boolean isPageOfSelectedTab(CTabFolder folder, Control control) { + int selected = folder.getSelectionIndex(); + return selected >= 0 && folder.getItem(selected).getControl() == control; + } + } diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderTest.java index 8c5b08ad131..9ba9cd05bfe 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderTest.java @@ -21,9 +21,14 @@ import static org.eclipse.e4.ui.tests.css.swt.CssSwtEngine.WHITE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringReader; import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.swt.dom.WidgetElement; +import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; @@ -190,17 +195,6 @@ void testBorderVisible() { assertEquals(false, folderToTest.getBorderVisible()); assertEquals("false", css.getEngine().retrieveCSSProperty(folderToTest, "border-visible", null)); } - @Test - void testSimple() { - CTabFolder folderToTest = createTestCTabFolder("CTabFolder { swt-simple: true}"); - assertEquals(true, folderToTest.getSimple()); - assertEquals("true", css.getEngine().retrieveCSSProperty(folderToTest, "swt-simple", null)); - folderToTest.getShell().close(); - folderToTest = createTestCTabFolder("CTabFolder { swt-simple: false}"); - // Curved tabs are no longer supported, so getSimple() always returns true - assertEquals(true, folderToTest.getSimple()); - assertEquals("true", css.getEngine().retrieveCSSProperty(folderToTest, "swt-simple", null)); - } @Test void testMaximizeVisible() { @@ -356,4 +350,64 @@ void testMinimumCharacters() { assertEquals(1, folderToTest.getMinimumCharacters()); assertEquals("1", css.getEngine().retrieveCSSProperty(folderToTest, "swt-tab-text-minimum-characters", null)); } + + @Test + void testPageSelectedProgrammaticallyAfterSkinningIsStyled() throws IOException { + Display display = css.getDisplay(); + CSSEngine engine = new CSSSWTEngineImpl(display, true); + engine.setErrorHandler(e -> fail(e.getMessage())); + // the class selector keeps this engine's skin listener off other tests' widgets + engine.parseStyleSheet(new StringReader(".tabPage { background-color: #FF0000 }")); + + Shell shell = new Shell(display, SWT.SHELL_TRIM); + shell.setLayout(new FillLayout()); + CTabFolder folderToTest = new CTabFolder(shell, SWT.NONE); + CTabItem tab1 = new CTabItem(folderToTest, SWT.NONE); + tab1.setText("A TAB ITEM"); + // no selection yet, so the folder hides the page from the engine + Composite page = new Composite(folderToTest, SWT.NONE); + WidgetElement.setCSSClass(page, "tabPage"); + tab1.setControl(page); + spinEventLoop(display); // the skin pass skips the hidden page + + folderToTest.setSelection(0); // programmatic, so no selection event + + spinEventLoop(display); + + assertEquals(RED, page.getBackground().getRGB()); + } + + @Test + void testPageAttachedToItsItemAfterSkinningIsStyled() throws IOException { + Display display = css.getDisplay(); + CSSEngine engine = new CSSSWTEngineImpl(display, true); + engine.setErrorHandler(e -> fail(e.getMessage())); + engine.parseStyleSheet(new StringReader(".tabPage { background-color: #FF0000 }")); + + Shell shell = new Shell(display, SWT.SHELL_TRIM); + shell.setLayout(new FillLayout()); + CTabFolder folderToTest = new CTabFolder(shell, SWT.NONE); + CTabItem tab1 = new CTabItem(folderToTest, SWT.NONE); + tab1.setText("A TAB ITEM"); + folderToTest.setSelection(0); + shell.setSize(400, 300); + shell.layout(true, true); + + Composite page = new Composite(folderToTest, SWT.NONE); + WidgetElement.setCSSClass(page, "tabPage"); + // skinned before it is attached, as a Search dialog page is + spinEventLoop(display); + + tab1.setControl(page); // already visible, so no show event either + + spinEventLoop(display); + + assertEquals(RED, page.getBackground().getRGB()); + } + + private static void spinEventLoop(Display display) { + while (display.readAndDispatch()) { + // deliver pending skin and show events + } + } }