From 1f6a23f909cfd19e5bf815a8762d4835d7556c3c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 5 Nov 2021 15:27:31 +0100 Subject: [PATCH 1/2] Styling: support defining component type specific styles, which are applied to all components of that type (e.g. `[style]ScrollPane = focusedBorderColor: #f00`) --- .../flatlaf/ui/FlatStylingSupport.java | 25 +- .../formdev/flatlaf/ui/TestFlatStyleType.java | 293 ++++++++++++++++++ .../com/formdev/flatlaf/ui/TestUtils.java | 8 +- 3 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java index cbbbeec1c..dfb51780c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java @@ -97,7 +97,8 @@ public static Object getResolvedStyle( JComponent c, String type ) { Object style = getStyle( c ); Object styleClass = getStyleClass( c ); Object styleForClasses = getStyleForClasses( styleClass, type ); - return joinStyles( styleForClasses, style ); + Object styleForType = getStyleForType( type ); + return joinStyles( joinStyles( styleForType, styleForClasses ), style ); } /** @@ -162,6 +163,28 @@ private static Object getStyleForClass( String styleClass, String type ) { UIManager.get( "[style]" + type + '.' + styleClass ) ); } + /** + * Returns the styles for the given type. + *

+ * The style rules must be defined in UI defaults either as strings (in CSS syntax) + * or as {@link java.util.Map}<String, Object> (with binary values). + * The key must be in syntax: {@code [style]type}. + * E.g. in FlatLaf properties file: + *

{@code
+	 * [style]Button = borderColor: #08f; background: #08f; foreground: #fff
+	 * }
+ * or in Java code: + *
{@code
+	 * UIManager.put( "[style]Button", "borderColor: #08f; background: #08f; foreground: #fff" );
+	 * }
+ * + * @param type the type of the component + * @return the styles + */ + public static Object getStyleForType( String type ) { + return UIManager.get( "[style]" + type ); + } + /** * Joins two styles. They can be either strings (in CSS syntax) * or {@link java.util.Map}<String, Object> (with binary values). diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java new file mode 100644 index 000000000..6954efd5f --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java @@ -0,0 +1,293 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.ui; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.awt.Color; +import java.awt.Dimension; +import javax.swing.*; +import javax.swing.table.JTableHeader; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import com.formdev.flatlaf.FlatSystemProperties; + +/** + * @author Karl Tauber + */ +public class TestFlatStyleType +{ + @BeforeAll + static void setup() { + System.setProperty( FlatSystemProperties.UI_SCALE_ENABLED, "false" ); + TestUtils.setup( false ); + + UIManager.put( "[style]Button", "foreground: #000001" ); + UIManager.put( "[style]CheckBox", "foreground: #000002" ); + UIManager.put( "[style]ComboBox", "foreground: #000003" ); + UIManager.put( "[style]EditorPane", "foreground: #000004" ); + UIManager.put( "[style]FormattedTextField", "foreground: #000005" ); + UIManager.put( "[style]InternalFrame", "foreground: #000006" ); + UIManager.put( "[style]Label", "foreground: #000007" ); + UIManager.put( "[style]List", "foreground: #000008" ); + UIManager.put( "[style]MenuBar", "foreground: #000009" ); + UIManager.put( "[style]Menu", "foreground: #000010" ); + UIManager.put( "[style]MenuItem", "foreground: #000011" ); + UIManager.put( "[style]CheckBoxMenuItem", "foreground: #000012" ); + UIManager.put( "[style]RadioButtonMenuItem", "foreground: #000013" ); + UIManager.put( "[style]PasswordField", "foreground: #000014" ); + UIManager.put( "[style]PopupMenu", "foreground: #000015" ); + UIManager.put( "[style]PopupMenuSeparator", "foreground: #000016" ); + UIManager.put( "[style]ProgressBar", "foreground: #000017" ); + UIManager.put( "[style]RadioButton", "foreground: #000018" ); + UIManager.put( "[style]ScrollBar", "foreground: #000019" ); + UIManager.put( "[style]ScrollPane", "foreground: #000020" ); + UIManager.put( "[style]Separator", "foreground: #000021" ); + UIManager.put( "[style]Slider", "foreground: #000022" ); + UIManager.put( "[style]Spinner", "foreground: #000023" ); + UIManager.put( "[style]SplitPane", "foreground: #000024" ); + UIManager.put( "[style]TabbedPane", "foreground: #000025" ); + UIManager.put( "[style]Table", "foreground: #000026" ); + UIManager.put( "[style]TableHeader", "foreground: #000027" ); + UIManager.put( "[style]TextArea", "foreground: #000028" ); + UIManager.put( "[style]TextField", "foreground: #000029" ); + UIManager.put( "[style]TextPane", "foreground: #000030" ); + UIManager.put( "[style]ToggleButton", "foreground: #000031" ); + UIManager.put( "[style]ToolBar", "foreground: #000032" ); + UIManager.put( "[style]Tree", "foreground: #000033" ); + + // JToolBar.Separator + UIManager.put( "[style]ToolBarSeparator", "separatorWidth: 21" ); + } + + @AfterAll + static void cleanup() { + TestUtils.cleanup(); + System.clearProperty( FlatSystemProperties.UI_SCALE_ENABLED ); + } + + @Test + void styleForType() { + assertEquals( "foreground: #000001", FlatStylingSupport.getStyleForType( "Button" ) ); + } + + //---- components --------------------------------------------------------- + + @Test + void button() { + JButton c = new JButton(); + assertEquals( new Color( 0x000001 ), c.getForeground() ); + } + + @Test + void checkBox() { + JCheckBox c = new JCheckBox(); + assertEquals( new Color( 0x000002 ), c.getForeground() ); + } + + @Test + void comboBox() { + JComboBox c = new JComboBox<>(); + assertEquals( new Color( 0x000003 ), c.getForeground() ); + } + + @Test + void editorPane() { + JEditorPane c = new JEditorPane(); + assertEquals( new Color( 0x000004 ), c.getForeground() ); + } + + @Test + void formattedTextField() { + JFormattedTextField c = new JFormattedTextField(); + assertEquals( new Color( 0x000005 ), c.getForeground() ); + } + + @Test + void internalFrame() { + JInternalFrame c = new JInternalFrame(); + assertEquals( new Color( 0x000006 ), c.getForeground() ); + } + + @Test + void label() { + JLabel c = new JLabel(); + assertEquals( new Color( 0x000007 ), c.getForeground() ); + } + + @Test + void list() { + JList c = new JList<>(); + assertEquals( new Color( 0x000008 ), c.getForeground() ); + } + + @Test + void menuBar() { + JMenuBar c = new JMenuBar(); + assertEquals( new Color( 0x000009 ), c.getForeground() ); + } + + @Test + void menu() { + JMenu c = new JMenu(); + assertEquals( new Color( 0x000010 ), c.getForeground() ); + } + + @Test + void menuItem() { + JMenuItem c = new JMenuItem(); + assertEquals( new Color( 0x000011 ), c.getForeground() ); + } + + @Test + void checkBoxMenuItem() { + JCheckBoxMenuItem c = new JCheckBoxMenuItem(); + assertEquals( new Color( 0x000012 ), c.getForeground() ); + } + + @Test + void radioButtonMenuItem() { + JRadioButtonMenuItem c = new JRadioButtonMenuItem(); + assertEquals( new Color( 0x000013 ), c.getForeground() ); + } + + @Test + void passwordField() { + JPasswordField c = new JPasswordField(); + assertEquals( new Color( 0x000014 ), c.getForeground() ); + } + + @Test + void popupMenu() { + JPopupMenu c = new JPopupMenu(); + assertEquals( new Color( 0x000015 ), c.getForeground() ); + } + + @Test + void popupMenuSeparator() { + JPopupMenu.Separator c = new JPopupMenu.Separator(); + assertEquals( new Color( 0x000016 ), c.getForeground() ); + } + + @Test + void progressBar() { + JProgressBar c = new JProgressBar(); + assertEquals( new Color( 0x000017 ), c.getForeground() ); + } + + @Test + void radioButton() { + JRadioButton c = new JRadioButton(); + assertEquals( new Color( 0x000018 ), c.getForeground() ); + } + + @Test + void scrollBar() { + JScrollBar c = new JScrollBar(); + assertEquals( new Color( 0x000019 ), c.getForeground() ); + } + + @Test + void scrollPane() { + JScrollPane c = new JScrollPane(); + assertEquals( new Color( 0x000020 ), c.getForeground() ); + } + + @Test + void separator() { + JSeparator c = new JSeparator(); + assertEquals( new Color( 0x000021 ), c.getForeground() ); + } + + @Test + void slider() { + JSlider c = new JSlider(); + assertEquals( new Color( 0x000022 ), c.getForeground() ); + } + + @Test + void spinner() { + JSpinner c = new JSpinner(); + assertEquals( new Color( 0x000023 ), c.getForeground() ); + } + + @Test + void splitPane() { + JSplitPane c = new JSplitPane(); + assertEquals( new Color( 0x000024 ), c.getForeground() ); + } + + @Test + void tabbedPane() { + JTabbedPane c = new JTabbedPane(); + assertEquals( new Color( 0x000025 ), c.getForeground() ); + } + + @Test + void table() { + JTable c = new JTable(); + assertEquals( new Color( 0x000026 ), c.getForeground() ); + } + + @Test + void tableHeader() { + JTableHeader c = new JTableHeader(); + assertEquals( new Color( 0x000027 ), c.getForeground() ); + } + + @Test + void textArea() { + JTextArea c = new JTextArea(); + assertEquals( new Color( 0x000028 ), c.getForeground() ); + } + + @Test + void textField() { + JTextField c = new JTextField(); + assertEquals( new Color( 0x000029 ), c.getForeground() ); + } + + @Test + void textPane() { + JTextPane c = new JTextPane(); + assertEquals( new Color( 0x000030 ), c.getForeground() ); + } + + @Test + void toggleButton() { + JToggleButton c = new JToggleButton(); + assertEquals( new Color( 0x000031 ), c.getForeground() ); + } + + @Test + void toolBar() { + JToolBar c = new JToolBar(); + assertEquals( new Color( 0x000032 ), c.getForeground() ); + } + + @Test + void toolBarSeparator() { + JToolBar.Separator c = new JToolBar.Separator(); + assertEquals( new Dimension( 0, 21 ), c.getPreferredSize() ); + } + + @Test + void tree() { + JTree c = new JTree(); + assertEquals( new Color( 0x000033 ), c.getForeground() ); + } +} diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java index d93993837..ccbe0df40 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java @@ -17,6 +17,7 @@ package com.formdev.flatlaf.ui; import java.awt.Font; +import java.util.Iterator; import java.util.Map; import java.util.Objects; import javax.swing.UIManager; @@ -42,7 +43,12 @@ public static void setup( boolean withFocus ) { } public static void cleanup() { - UIManager.put( "defaultFont", null ); + // remove all properties added by UIManager.put() + Iterator it = UIManager.getDefaults().keySet().iterator(); + while( it.hasNext() ) { + it.next(); + it.remove(); + } } public static void scaleFont( float factor ) { From 19483b64647ab23dce40816c928b15ada47ee950 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 5 Nov 2021 17:02:08 +0100 Subject: [PATCH 2/2] Styling: fixed IllegalAccessException when using component specific style `[style]Spinner = foreground: #f00` and spinner labels are painted (caused by private subclass of JLabel where getForeground() is overridden) --- .../flatlaf/ui/FlatStylingSupport.java | 38 ++++++++++++------- .../formdev/flatlaf/ui/TestFlatStyleType.java | 13 +++++++ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java index dfb51780c..13fae8a65 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java @@ -485,22 +485,34 @@ private static Object applyToProperty( Object obj, String name, Object value ) String getterName = buildMethodName( "get", name ); String setterName = buildMethodName( "set", name ); - try { - Method getter; + for(;;) { try { - getter = cls.getMethod( getterName ); + Method getter; + try { + getter = cls.getMethod( getterName ); + } catch( NoSuchMethodException ex ) { + getter = cls.getMethod( buildMethodName( "is", name ) ); + } + Method setter = cls.getMethod( setterName, getter.getReturnType() ); + Object oldValue = getter.invoke( obj ); + setter.invoke( obj, convertToEnum( value, getter.getReturnType() ) ); + return oldValue; } catch( NoSuchMethodException ex ) { - getter = cls.getMethod( buildMethodName( "is", name ) ); + throw new UnknownStyleException( name ); + } catch( Exception ex ) { + if( ex instanceof IllegalAccessException ) { + // this may happen for private subclasses of public Swing classes + // that override public property getter/setter + // e.g. class JSlider.SmartHashtable.LabelUIResource.getForeground() + // --> try again with superclass + cls = cls.getSuperclass(); + if( cls != null && cls != Object.class ) + continue; + } + + throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "." + + getterName + "()' or '" + setterName + "(...)'", ex ); } - Method setter = cls.getMethod( setterName, getter.getReturnType() ); - Object oldValue = getter.invoke( obj ); - setter.invoke( obj, convertToEnum( value, getter.getReturnType() ) ); - return oldValue; - } catch( NoSuchMethodException ex ) { - throw new UnknownStyleException( name ); - } catch( Exception ex ) { - throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "." - + getterName + "()' or '" + setterName + "(...)'", ex ); } } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java index 6954efd5f..aeef84a39 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleType.java @@ -219,6 +219,19 @@ void slider() { assertEquals( new Color( 0x000022 ), c.getForeground() ); } + @Test + void slider2() { + JSlider c = new JSlider(); + + // when slider labels are painted, then a Java private subclass of JLabel + // is used that overrides getForeground(), which is not accessible via reflection + // see class JSlider.SmartHashtable.LabelUIResource + c.setPaintLabels( true ); + c.setMajorTickSpacing( 50 ); + + assertEquals( new Color( 0x000022 ), c.getForeground() ); + } + @Test void spinner() { JSpinner c = new JSpinner();