diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTColorHelper.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTColorHelper.java index bafb10abdd9..859589df519 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTColorHelper.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTColorHelper.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2015 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -96,6 +96,15 @@ public static boolean hasColorDefinitionAsValue(CSSValue value) { return false; } + /** + * Returns whether the value looks like a color definition reference but + * resolves neither to a color, such as an SWT system color, nor to a definition. + */ + public static boolean isUnresolvedColorDefinition(CSSValue value) { + return hasColorDefinitionAsValue(value) && getRGBA(value) == null + && findColorByDefinition(((CssText) value).value()) == null; + } + public static boolean hasColorDefinitionAsValue(String name) { if (name.startsWith(COLOR_DEFINITION_MARKER)) { return !HEX_COLOR_VALUE_PATTERN.matcher(name).matches(); diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTFontHelper.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTFontHelper.java index 854357d7ecb..4d3ba5bccf4 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTFontHelper.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTFontHelper.java @@ -304,6 +304,44 @@ public static CSS2FontProperties resolveRelativeSize(CSS2FontProperties fontProp return resolved; } + /** + * Returns the properties unchanged unless the family references an unresolved + * font definition; then returns the font the element had before it was styled, + * with the size, weight and style set in CSS applied on top. + */ + public static CSS2FontProperties resolveUnresolvedFontDefinition(CSS2FontProperties fontProperties, + CSSElementContext context, Widget widget) { + if (!hasFontDefinitionAsFamily(fontProperties) + || findFontDataByDefinition((CssText) fontProperties.getFamily()).length > 0) { + return fontProperties; + } + // not the current font, which may carry styling of another state such as :selected + FontData baseFontData = getBaseFontData(context); + if (baseFontData == null) { + baseFontData = getFirstFontData(getFont(widget)); + } + if (baseFontData == null) { + return fontProperties; + } + int baseStyle = baseFontData.getStyle(); + CSS2FontProperties resolved = new CSS2FontPropertiesImpl(); + resolved.setFamily(new CssText(CssText.Kind.STRING, baseFontData.getName())); + resolved.setSize(new CssDimension(baseFontData.getHeight(), CssUnit.PT)); + resolved.setSizeFromCSS(true); + resolved.setWeight(new CssText(CssText.Kind.IDENT, (baseStyle & SWT.BOLD) != 0 ? "bold" : "normal")); + resolved.setStyle(new CssText(CssText.Kind.IDENT, (baseStyle & SWT.ITALIC) != 0 ? "italic" : "normal")); + if (fontProperties.isSizeFromCSS()) { + resolved.setSize(fontProperties.getSize()); + } + if (fontProperties.getWeight() != null) { + resolved.setWeight(fontProperties.getWeight()); + } + if (fontProperties.getStyle() != null) { + resolved.setStyle(fontProperties.getStyle()); + } + return resolved; + } + private static boolean isRelativeSize(CssPrimitive size) { if (isKeyword(size, "larger") || isKeyword(size, "smaller")) { return true; diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/converters/CSSValueSWTColorConverterImpl.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/converters/CSSValueSWTColorConverterImpl.java index 0c7d50c8e4d..043534be7f7 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/converters/CSSValueSWTColorConverterImpl.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/converters/CSSValueSWTColorConverterImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2014 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -44,6 +44,10 @@ public CSSValueSWTColorConverterImpl() { @Override public Color convert(CSSValue value, CSSEngine engine, Object context) throws DOMException { + if (CSSSWTColorHelper.isUnresolvedColorDefinition(value)) { + // treated like unset, so the widget keeps its default color instead of black + return null; + } Display display = (Display) context; Color color = CSSSWTColorHelper.getSWTColor(value, display); if (color == null) { diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBackgroundSWTHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBackgroundSWTHandler.java index 35267a7575f..4f2b2d69d49 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBackgroundSWTHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBackgroundSWTHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2015 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -86,6 +86,11 @@ public void applyCSSPropertyBackgroundColor(Object element, CSSValue value, if (grad == null) { return; // warn? } + if (grad.getRGBs().isEmpty()) { + // no stop resolved, treated like unset + resetGradient(widget, pseudo); + return; + } if (widget instanceof CTabItem) { CTabFolder folder = ((CTabItem) widget).getParent(); Color[] colors = CSSSWTColorHelper.getSWTColors(grad, @@ -105,6 +110,20 @@ public void applyCSSPropertyBackgroundColor(Object element, CSSValue value, } } + private static void resetGradient(Widget widget, String pseudo) { + if (widget instanceof CTabItem item) { + if ("selected".equals(pseudo)) { + item.getParent().setSelectionBackground(null, null, true); + } else { + item.getParent().setBackground(null, null, true); + } + } else if (widget instanceof Control control) { + GradientBackgroundListener.remove(control); + CSSSWTColorHelper.setBackground(control, null); + CompositeElement.setBackgroundOverriddenByCSSMarker(widget); + } + } + @Override public void applyCSSPropertyBackgroundImage(Object element, CSSValue value, String pseudo, CSSEngine engine) throws Exception { diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java index 157ed7dc9dc..95e8ce04dea 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java @@ -74,11 +74,13 @@ private static void setFont(Widget widget, Font font) { /** * Resolves a relative font size against the font the element had before it was - * styled, so that reapplying a style does not scale an already scaled font. + * styled, and an unresolved font definition against the widget's own font. */ - private static CSS2FontProperties resolveRelativeSize(CSS2FontProperties fontProperties, - CSSElementContext context) { - return CSSSWTFontHelper.resolveRelativeSize(fontProperties, CSSSWTFontHelper.getBaseFontData(context)); + private static CSS2FontProperties resolve(CSS2FontProperties fontProperties, CSSElementContext context, + Widget widget) { + CSS2FontProperties resolved = CSSSWTFontHelper.resolveRelativeSize(fontProperties, + CSSSWTFontHelper.getBaseFontData(context)); + return CSSSWTFontHelper.resolveUnresolvedFontDefinition(resolved, context, widget); } private static void updateChildrenFonts(CTabFolder folder, Font font) { @@ -234,7 +236,7 @@ public void onAllCSSPropertiesApplied(Object element, CSSEngine engine) if (fontProperties == null) { return; } - Font font = (Font) engine.convert(resolveRelativeSize(fontProperties, context), Font.class, widget); + Font font = (Font) engine.convert(resolve(fontProperties, context, widget), Font.class, widget); setFont(widget, font); } @@ -323,7 +325,7 @@ private void applyStyles(CSSStyleDeclaration styleDeclaration, try { // set the font Font font = (Font) engine.convert( - resolveRelativeSize(fontProperties, engine.getCSSElementContext(item)), + resolve(fontProperties, engine.getCSSElementContext(item), item), Font.class, item); setFont(item, font); } catch (Exception e) { diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertye4SelectedTabFillHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertye4SelectedTabFillHandler.java index f01c10cec6b..4e80c492816 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertye4SelectedTabFillHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertye4SelectedTabFillHandler.java @@ -46,12 +46,9 @@ protected void applyCSSProperty(Control control, String property, if (value instanceof CssPrimitive) { Color newColor = (Color) engine.convert(value, Color.class, control.getDisplay()); - if (newColor == null) { - return; - } - if (SWT_SELECTED_TAB_HIGHLIGHT.equals(property)) { - if ("none".equalsIgnoreCase(value.getCssText()) || "transparent".equalsIgnoreCase(value.getCssText())) { + if (newColor == null || "none".equalsIgnoreCase(value.getCssText()) + || "transparent".equalsIgnoreCase(value.getCssText())) { ((ICTabRendering) renderer).setSelectedTabHighlight(null); } else { ((ICTabRendering) renderer).setSelectedTabHighlight(newColor); @@ -59,13 +56,20 @@ protected void applyCSSProperty(Control control, String property, } else if (SWT_SELECTED_HIGHLIGHT_TOP.equals(property)) { Boolean drawHiglightOnTop = (Boolean) engine.convert(value, Boolean.class, control.getDisplay()); ((ICTabRendering) renderer).setSelectedTabHighlightTop(drawHiglightOnTop); + } else if (newColor == null) { + // unresolved definition, treated like unset + ((ICTabRendering) renderer).setSelectedTabFill(null, null); } else { ((ICTabRendering) renderer).setSelectedTabFill(newColor); } } else if (value instanceof CssList) { Gradient grad = (Gradient) engine.convert(value, Gradient.class, control.getDisplay()); - if (grad == null || grad.getRGBs().isEmpty()) { + if (grad == null) { + return; + } + if (grad.getRGBs().isEmpty()) { + ((ICTabRendering) renderer).setSelectedTabFill(null, null); return; } Color[] colors = CSSSWTColorHelper.getSWTColors(grad, diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/ColorAndFontUtil.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/ColorAndFontUtil.java index 67c6b85b4e8..90e0e54d095 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/ColorAndFontUtil.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/ColorAndFontUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2019 IBM Corporation and others. + * Copyright (c) 2010, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,38 +14,34 @@ package org.eclipse.e4.ui.internal.css.swt; import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; +import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; public class ColorAndFontUtil { - static IColorAndFontProvider colorAndFontProvider = null; + private static final IColorAndFontProvider DEFAULT_PROVIDER = new JFaceColorAndFontProvider(); - static { - if (FrameworkUtil.getBundle(ColorAndFontUtil.class).getBundleContext() != null) { - ServiceTracker colorAndFontProviderTracker = new ServiceTracker<>( - FrameworkUtil.getBundle(ColorAndFontUtil.class).getBundleContext(), - IColorAndFontProvider.class.getName(), null) { - @Override - public IColorAndFontProvider addingService(ServiceReference reference) { - // this is needed so that the unit test can exchange the color and font provider - // with a mocked version - colorAndFontProvider = super.addingService(reference); - return colorAndFontProvider; - } - }; - colorAndFontProviderTracker.open(); + private static final ServiceTracker TRACKER = openTracker(); + + private static ServiceTracker openTracker() { + BundleContext context = FrameworkUtil.getBundle(ColorAndFontUtil.class).getBundleContext(); + if (context == null) { + return null; } + ServiceTracker tracker = new ServiceTracker<>(context, + IColorAndFontProvider.class, null); + tracker.open(); + return tracker; } /** - * Util method to access the OSGI immediate component IColorAndFontProvider - * defined in the same bundle as we do life in the same bundle we do not track - * its life-cycle (as it is the same as our life-cycle) + * Returns the registered {@link IColorAndFontProvider} service, or a provider + * backed by the JFace registries if none is registered. */ public static IColorAndFontProvider getColorAndFontProvider() { - return colorAndFontProvider; + IColorAndFontProvider provider = TRACKER != null ? TRACKER.getService() : null; + return provider != null ? provider : DEFAULT_PROVIDER; } } diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/ColorAndFontProviderImpl.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/JFaceColorAndFontProvider.java similarity index 53% rename from bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/ColorAndFontProviderImpl.java rename to bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/JFaceColorAndFontProvider.java index 783b05dab3b..b8ab6ed10a8 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/ColorAndFontProviderImpl.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/JFaceColorAndFontProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2016 IBM Corporation and others. + * Copyright (c) 2013, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,34 +10,30 @@ * * Contributors: * IBM Corporation - initial API and implementation - ******************************************************************************/ - -package org.eclipse.ui.internal.themes; + *******************************************************************************/ +package org.eclipse.e4.ui.internal.css.swt; import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; +import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; -import org.eclipse.ui.internal.Workbench; -import org.eclipse.ui.themes.ITheme; -import org.osgi.service.component.annotations.Component; /** - * @since 3.5 + * Resolves color and font definitions from the JFace registries, which the + * workbench keeps in sync with its current theme. */ -@Component -public class ColorAndFontProviderImpl implements IColorAndFontProvider { +class JFaceColorAndFontProvider implements IColorAndFontProvider { @Override public FontData[] getFont(String symbolicName) { - return getCurrentTheme().getFontRegistry().getFontData(symbolicName); + FontRegistry registry = JFaceResources.getFontRegistry(); + // getFontData returns the default font for unknown keys + return registry.hasValueFor(symbolicName) ? registry.getFontData(symbolicName) : null; } @Override public RGB getColor(String symbolicName) { - return getCurrentTheme().getColorRegistry().getRGB(symbolicName); - } - - private ITheme getCurrentTheme() { - return Workbench.getInstance().getThemeManager().getCurrentTheme(); + return JFaceResources.getColorRegistry().getRGB(symbolicName); } } diff --git a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF index d445c136917..d78d2f6ab88 100644 --- a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF @@ -139,5 +139,4 @@ Automatic-Module-Name: org.eclipse.ui.workbench Service-Component: OSGI-INF/org.eclipse.ui.internal.BindingToModelProcessor.xml, OSGI-INF/org.eclipse.ui.internal.CommandToModelProcessor.xml, OSGI-INF/org.eclipse.ui.internal.ContextToModelProcessor.xml, - OSGI-INF/org.eclipse.ui.internal.WindowsDefenderConfigurator.xml, - OSGI-INF/org.eclipse.ui.internal.themes.ColorAndFontProviderImpl.xml + OSGI-INF/org.eclipse.ui.internal.WindowsDefenderConfigurator.xml diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/WorkbenchThemeManager.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/WorkbenchThemeManager.java index 691725a4665..fe445d865ad 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/WorkbenchThemeManager.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/themes/WorkbenchThemeManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2017 IBM Corporation and others. + * Copyright (c) 2004, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -96,12 +96,13 @@ public static synchronized void disposeManager() { private ITheme currentTheme; private final IPropertyChangeListener currentThemeListener = event -> { - firePropertyChange(event); + // JFace first, as the CSS engine resolves definitions from the JFace registries if (event.getSource() instanceof FontRegistry) { JFaceResources.getFontRegistry().put(event.getProperty(), (FontData[]) event.getNewValue()); } else if (event.getSource() instanceof ColorRegistry) { JFaceResources.getColorRegistry().put(event.getProperty(), (RGB) event.getNewValue()); } + firePropertyChange(event); }; private ColorRegistry defaultThemeColorRegistry; @@ -337,7 +338,6 @@ public void setCurrentTheme(String id) { init(); ITheme oldTheme = currentTheme; if (WorkbenchThemeManager.getInstance().doSetCurrentTheme(id)) { - firePropertyChange(CHANGE_CURRENT_THEME, oldTheme, getCurrentTheme()); if (oldTheme != null) { oldTheme.removePropertyChangeListener(currentThemeListener); } @@ -361,6 +361,7 @@ public void setCurrentTheme(String id) { for (String key : themeFonts.getKeySet()) { jfaceFonts.put(key, themeFonts.getFontData(key)); } + firePropertyChange(CHANGE_CURRENT_THEME, oldTheme, getCurrentTheme()); if (oldTheme != null && eventBroker != null) { eventBroker.send(UIEvents.UILifeCycle.THEME_CHANGED, null); eventBroker.send(UIEvents.UILifeCycle.THEME_DEFINITION_CHANGED, context.get(MApplication.class)); diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTHelperTestCase.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTHelperTestCase.java index da849dd1916..779e1eacb96 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTHelperTestCase.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/CSSSWTHelperTestCase.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import java.util.ArrayList; import java.util.List; import org.eclipse.e4.ui.css.core.css2.CSS2FontHelper; @@ -32,13 +33,22 @@ import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; +import org.junit.jupiter.api.AfterEach; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; public abstract class CSSSWTHelperTestCase { protected static final String CSS_ITALIC = CSS2FontHelper.getFontStyle(true); protected static final String CSS_BOLD = CSS2FontHelper.getFontWeight(true); + private final List> registrations = new ArrayList<>(); + + @AfterEach + void unregisterProviders() { + registrations.forEach(ServiceRegistration::unregister); + } + protected void registerFontProviderWith(String expectedSymbolicName, String family, int size, int style) { IColorAndFontProvider provider = mock(IColorAndFontProvider.class); doReturn(new FontData[] { new FontData(family, size, style) }).when(provider).getFont(expectedSymbolicName); @@ -49,12 +59,11 @@ protected void registerColorProviderWith(String expectedSymbolicName, RGB rgb) { IColorAndFontProvider provider = mock(IColorAndFontProvider.class); doReturn(rgb).when(provider).getColor(expectedSymbolicName); registerProvider(provider); - } private void registerProvider(final IColorAndFontProvider provider) { - FrameworkUtil.getBundle(getClass()).getBundleContext().registerService(IColorAndFontProvider.class, provider, - null); + registrations.add(FrameworkUtil.getBundle(getClass()).getBundleContext() + .registerService(IColorAndFontProvider.class, provider, null)); } protected CSS2FontProperties fontProperties(String family) { diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemTest.java index 15d02d3b6a1..b7145f76bc5 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemTest.java @@ -27,6 +27,7 @@ import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; @@ -113,6 +114,38 @@ protected CTabFolder createTestTabFolder(String styleSheet, boolean open) { return folder; } + @Test + void testUnresolvedFontDefinitionKeepsHeightOfEachItem() { + CTabFolder folder = createTestTabFolder(false); + CTabItem small = folder.getItem(0); + CTabItem large = folder.getItem(1); + Font smallFont = new Font(css.getDisplay(), "Arial", 9, SWT.NORMAL); + Font largeFont = new Font(css.getDisplay(), "Arial", 14, SWT.NORMAL); + small.setFont(smallFont); + large.setFont(largeFont); + CSSEngine engine = css.createEngine("CTabItem { font-family: '#UNKNOWN-FONT-DEFINITION'; }"); + + engine.applyStyles(folder.getShell(), true); + shell.open(); + spinEventLoop(); + + assertEquals(9, small.getFont().getFontData()[0].getHeight()); + assertEquals(14, large.getFont().getFontData()[0].getHeight()); + shell.dispose(); + smallFont.dispose(); + largeFont.dispose(); + } + + @Test + void testUnresolvedFontDefinitionDropsSelectedStyleOnDeselect() { + CTabFolder folder = createTestTabFolder("CTabItem { font-family: '#UNKNOWN-FONT-DEFINITION'; }\n" + + "CTabItem:selected { font-weight: bold; }"); + spinEventLoop(); + for (int i = 0; i < folder.getItemCount(); i++) { + testSelectedFontBold(folder, i); + } + } + @Test void testFontRegular() { CTabFolder folder = createTestTabFolder("Button { font-family: Verdana; font-size: 12 }\n" diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ColorDefinitionTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ColorDefinitionTest.java index 8e607614877..d91af881182 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ColorDefinitionTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/ColorDefinitionTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2019 IBM Corporation and others. + * Copyright (c) 2013, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,14 +17,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import java.util.Hashtable; - import org.eclipse.e4.ui.css.core.engine.CSSEngine; +import org.eclipse.e4.ui.internal.css.swt.ColorAndFontUtil; import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; @@ -33,15 +34,26 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.internal.themes.ColorDefinition; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; public class ColorDefinitionTest { @RegisterExtension CssSwtEngine css = new CssSwtEngine(); + private ServiceRegistration registration; + + @AfterEach + void unregisterProvider() { + if (registration != null) { + registration.unregister(); + } + } + @Test void testColorDefinition() { //given @@ -150,6 +162,61 @@ void testUnset() { shell.dispose(); } + @Test + void testUnresolvedColorDefinitionKeepsDefaultColor() { + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Button {background-color: '#UNKNOWN-COLOR-DEFINITION';}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Button button = new Button(shell, SWT.NONE); + RGB defaultBackground = button.getBackground().getRGB(); + + engine.applyStyles(button, true); + + assertEquals(defaultBackground, button.getBackground().getRGB()); + engine.dispose(); + shell.dispose(); + } + + @Test + void testColorDefinitionFromJFaceRegistryWithoutProvider() { + JFaceResources.getColorRegistry().put("CSS_TEST_JFACE_COLOR", new RGB(1, 2, 3)); + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Label {background-color: '#CSS_TEST_JFACE_COLOR'}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label label = new Label(shell, SWT.NONE); + + engine.applyStyles(label, true); + + assertEquals(new RGB(1, 2, 3), label.getBackground().getRGB()); + engine.dispose(); + shell.dispose(); + } + + @Test + void testQuotedSystemColorIsNotTreatedAsUnresolvedDefinition() { + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Label {background-color: '#COLOR-LIST-SELECTION'}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label label = new Label(shell, SWT.NONE); + + engine.applyStyles(label, true); + + assertEquals(display.getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB(), label.getBackground().getRGB()); + engine.dispose(); + shell.dispose(); + } + + @Test + void testUnregisteredColorProviderIsNoLongerUsed() { + registerColorProviderWith("ACTIVE_HYPERLINK_COLOR", new RGB(255, 0, 0)); + IColorAndFontProvider provider = ColorAndFontUtil.getColorAndFontProvider(); + + registration.unregister(); + registration = null; + + assertNotSame(provider, ColorAndFontUtil.getColorAndFontProvider()); + } + @Test void testSetColorDefinitionWithSystemColor() { // given @@ -178,10 +245,7 @@ private ColorDefinition colorDefinition(String uniqueId, String name, private void registerColorProviderWith(final String symbolicName, final RGB rgb) { IColorAndFontProvider provider = mock(IColorAndFontProvider.class); doReturn(rgb).when(provider).getColor(symbolicName); - Hashtable properties = new Hashtable<>(); - properties.put("service.ranking", "1000"); - - FrameworkUtil.getBundle(getClass()).getBundleContext().registerService(IColorAndFontProvider.class, provider, - properties); + registration = FrameworkUtil.getBundle(getClass()).getBundleContext() + .registerService(IColorAndFontProvider.class, provider, null); } } diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/FontDefinitionTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/FontDefinitionTest.java index 2d143e8c6da..40ae84cb207 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/FontDefinitionTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/FontDefinitionTest.java @@ -19,13 +19,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import java.util.Hashtable; - import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; @@ -33,15 +33,26 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.internal.themes.FontDefinition; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; public class FontDefinitionTest { @RegisterExtension CssSwtEngine css = new CssSwtEngine(); + private ServiceRegistration registration; + + @AfterEach + void unregisterProvider() { + if (registration != null) { + registration.unregister(); + } + } + @Test void testFontDefinition() { //given @@ -154,6 +165,85 @@ void testWidgetWithFontDefinitionAsFontFamily() { font.dispose(); } + @Test + void testFontDefinitionFromJFaceRegistryWithoutProvider() { + JFaceResources.getFontRegistry().put("CSS_TEST_JFACE_FONT", + new FontData[] { new FontData("Times", 13, SWT.NORMAL) }); + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Label {font-family: '#CSS_TEST_JFACE_FONT'}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label label = new Label(shell, SWT.NONE); + + engine.applyStyles(label, true); + + assertEquals("Times", label.getFont().getFontData()[0].getName()); + assertEquals(13, label.getFont().getFontData()[0].getHeight()); + shell.dispose(); + } + + @Test + void testUnresolvedFontDefinitionKeepsWidgetFont() { + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Label {font-family: '#UNKNOWN-FONT-DEFINITION'}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label label = new Label(shell, SWT.NONE); + Font font = new Font(display, "DejaVu Sans", 9, SWT.BOLD); + label.setFont(font); + String fontName = font.getFontData()[0].getName(); + + engine.applyStyles(label, true); + + assertEquals(fontName, label.getFont().getFontData()[0].getName()); + assertEquals(9, label.getFont().getFontData()[0].getHeight()); + assertEquals(SWT.BOLD, label.getFont().getFontData()[0].getStyle()); + shell.dispose(); + font.dispose(); + } + + @Test + void testUnresolvedFontDefinitionAppliesOtherFontAttributes() { + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine( + "Label {font-family: '#UNKNOWN-FONT-DEFINITION'; font-size: 14pt; font-style: italic}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label label = new Label(shell, SWT.NONE); + Font font = new Font(display, "Arial", 9, SWT.BOLD); + label.setFont(font); + + engine.applyStyles(label, true); + + assertEquals("Arial", label.getFont().getFontData()[0].getName()); + assertEquals(14, label.getFont().getFontData()[0].getHeight()); + assertEquals(SWT.BOLD | SWT.ITALIC, label.getFont().getFontData()[0].getStyle()); + shell.dispose(); + font.dispose(); + } + + @Test + void testUnresolvedFontDefinitionKeepsFamilyOfEachWidget() { + Display display = css.getDisplay(); + CSSEngine engine = css.createEngine("Label {font-family: '#UNKNOWN-FONT-DEFINITION'}"); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + Label arialLabel = new Label(shell, SWT.NONE); + Font arial = new Font(display, "Arial", 9, SWT.BOLD); + arialLabel.setFont(arial); + Label timesLabel = new Label(shell, SWT.NONE); + Font times = new Font(display, "Times", 9, SWT.BOLD); + timesLabel.setFont(times); + String arialName = arialLabel.getFont().getFontData()[0].getName(); + String timesName = timesLabel.getFont().getFontData()[0].getName(); + assumeFalse(arialName.equals(timesName)); + + engine.applyStyles(arialLabel, true); + engine.applyStyles(timesLabel, true); + + assertEquals(arialName, arialLabel.getFont().getFontData()[0].getName()); + assertEquals(timesName, timesLabel.getFont().getFontData()[0].getName()); + shell.dispose(); + arial.dispose(); + times.dispose(); + } + private FontDefinition fontDefinition(String uniqueId, String name, String categoryId, String description) { return new FontDefinition(new FontDefinition(name, uniqueId, @@ -164,11 +254,7 @@ private FontDefinition fontDefinition(String uniqueId, String name, private void registerFontProviderWith(final String symbolicName, final FontData fontData) { IColorAndFontProvider provider = mock(IColorAndFontProvider.class); doReturn(new FontData[] { fontData }).when(provider).getFont(symbolicName); - - Hashtable properties = new Hashtable<>(); - properties.put("service.ranking", "1000"); - - FrameworkUtil.getBundle(getClass()).getBundleContext().registerService(IColorAndFontProvider.class, provider, - null); + registration = FrameworkUtil.getBundle(getClass()).getBundleContext() + .registerService(IColorAndFontProvider.class, provider, null); } } diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/GradientTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/GradientTest.java index 484bef03783..05d04f0d75b 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/GradientTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/GradientTest.java @@ -16,12 +16,21 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; +import static org.mockito.Mockito.withSettings; import java.lang.reflect.Field; import org.eclipse.e4.ui.css.core.engine.CSSEngine; +import org.eclipse.e4.ui.css.swt.properties.custom.CSSPropertye4SelectedTabFillHandler; +import org.eclipse.e4.ui.css.swt.properties.css2.CSSPropertyBackgroundSWTHandler; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabFolderRenderer; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; @@ -125,6 +134,59 @@ void testBadPercents() { // percent } + @Test + void testBackgroundGradientWithOnlyUnresolvedDefinitionsIsUnset() throws Exception { + CSSEngine engine = css.createEngine(""); + Shell shell = new Shell(css.getDisplay(), SWT.SHELL_TRIM); + Composite composite = new Composite(shell, SWT.NONE); + composite.setSize(100, 100); + CSSPropertyBackgroundSWTHandler handler = new CSSPropertyBackgroundSWTHandler(); + handler.applyCSSPropertyBackgroundColor(engine.getElement(composite), + engine.parsePropertyValue("#FF0000 #0000FF 100%"), null, engine); + assertNotNull(composite.getBackgroundImage()); + + handler.applyCSSPropertyBackgroundColor(engine.getElement(composite), + engine.parsePropertyValue("'#MISSING-A' '#MISSING-B' 100%"), null, engine); + + assertNull(composite.getBackgroundImage()); + shell.dispose(); + } + + @Test + void testTabGradientWithOnlyUnresolvedDefinitionsIsUnset() throws Exception { + CTabFolder folderToTest = createTestCTabFolder("CTabItem:selected { background-color: #FF0000 #0000FF}"); + CSSEngine engine = css.createEngine(""); + + new CSSPropertyBackgroundSWTHandler().applyCSSPropertyBackgroundColor( + engine.getElement(folderToTest.getItem(0)), + engine.parsePropertyValue("'#MISSING-A' '#MISSING-B' 100%"), "selected", engine); + + assertNull(getSelectionGradientPercents(folderToTest)); + } + + @Test + void testSelectedTabFillWithUnresolvedDefinitionResetsFill() throws Exception { + CTabFolder folder = createTestCTabFolder(""); + CSSEngine engine = css.createEngine(""); + Class renderingType = Class.forName("org.eclipse.e4.ui.internal.css.swt.ICTabRendering"); + CTabFolderRenderer renderer = mock(CTabFolderRenderer.class, withSettings().extraInterfaces(renderingType)); + folder.setRenderer(renderer); + CSSPropertye4SelectedTabFillHandler handler = new CSSPropertye4SelectedTabFillHandler(); + + for (String value : new String[] { "'#MISSING-A'", "'#MISSING-A' '#MISSING-B' 100%" }) { + handler.applyCSSProperty(engine.getElement(folder), "swt-selected-tab-fill", engine.parsePropertyValue(value), + null, engine); + } + + long resets = mockingDetails(renderer).getInvocations().stream() + .filter(i -> i.getMethod().getName().equals("setSelectedTabFill") + && i.getArguments().length == 2 && i.getArguments()[0] == null && i.getArguments()[1] == null) + .count(); + assertEquals(2, resets); + assertTrue(mockingDetails(renderer).getInvocations().stream() + .noneMatch(i -> i.getMethod().getName().equals("setSelectedTabFill") && i.getArguments().length == 1)); + } + /* * Test to ensure that one bad apple doesn't spoil it for everyone * (non-existent color name is ignored) diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/themes/JFaceThemeTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/themes/JFaceThemeTest.java index a73518e2065..28137a2c6f5 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/themes/JFaceThemeTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/themes/JFaceThemeTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2007 IBM Corporation and others. + * Copyright (c) 2004, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -16,15 +16,22 @@ import static org.eclipse.ui.PlatformUI.getWorkbench; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.e4.ui.internal.css.swt.ColorAndFontUtil; +import org.eclipse.e4.ui.internal.css.swt.definition.IColorAndFontProvider; import org.eclipse.jface.resource.ColorDescriptor; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.ui.themes.ITheme; @@ -83,6 +90,74 @@ public void testPushdown() { assertEquals(10, listener.getEvents().size()); } + @Test + public void testColorAndFontProviderResolvesCurrentTheme() { + IColorAndFontProvider provider = ColorAndFontUtil.getColorAndFontProvider(); + assertEquals(new RGB(1, 1, 2), provider.getColor("rgbcolor")); + + fManager.setCurrentTheme(THEME1); + assertEquals(new RGB(2, 1, 1), provider.getColor("rgbcolor")); + + ITheme theme = fManager.getCurrentTheme(); + ColorRegistry themeColors = theme.getColorRegistry(); + for (String key : themeColors.getKeySet()) { + assertEquals(themeColors.getRGB(key), provider.getColor(key)); + } + FontRegistry themeFonts = theme.getFontRegistry(); + for (String key : themeFonts.getKeySet()) { + assertArrayEquals(themeFonts.getFontData(key), provider.getFont(key)); + } + } + + @Test + public void testColorAndFontProviderIsUpdatedBeforeThemeListeners() { + IColorAndFontProvider provider = ColorAndFontUtil.getColorAndFontProvider(); + List seen = new ArrayList<>(); + IPropertyChangeListener listener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + seen.add(provider.getColor("rgbcolor")); + } + }; + fManager.addPropertyChangeListener(listener); + try { + fManager.setCurrentTheme(THEME1); + } finally { + fManager.removePropertyChangeListener(listener); + } + + assertFalse(seen.isEmpty()); + for (RGB rgb : seen) { + assertEquals(new RGB(2, 1, 1), rgb); + } + } + + @Test + public void testColorAndFontProviderIsUpdatedBeforeThemeValueListeners() { + IColorAndFontProvider provider = ColorAndFontUtil.getColorAndFontProvider(); + ColorRegistry themeColors = fManager.getCurrentTheme().getColorRegistry(); + RGB original = themeColors.getRGB("rgbcolor"); + List seen = new ArrayList<>(); + IPropertyChangeListener listener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + seen.add(provider.getColor("rgbcolor")); + } + }; + fManager.addPropertyChangeListener(listener); + try { + themeColors.put("rgbcolor", new RGB(9, 9, 9)); + } finally { + fManager.removePropertyChangeListener(listener); + themeColors.put("rgbcolor", original); + } + + assertFalse(seen.isEmpty()); + for (RGB rgb : seen) { + assertEquals(new RGB(9, 9, 9), rgb); + } + } + /** * Tests to ensure correct behavior of getColorDescriptor methods. */