diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java index db75c0a1..bac450a2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; @@ -27,6 +28,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; import javax.swing.plaf.basic.BasicMenuItemUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; @@ -66,7 +68,7 @@ import com.formdev.flatlaf.util.LoggingFacade; public class FlatCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { private FlatMenuItemRenderer renderer; private Map oldStyleValues; @@ -140,6 +142,14 @@ public class FlatCheckBoxMenuItemUI return FlatMenuItemUI.getStyleableValue( this, renderer, key ); } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java index e2303bcf..88fd7ff5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java @@ -42,6 +42,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; @@ -71,6 +72,7 @@ import javax.swing.plaf.basic.ComboPopup; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.SystemInfo; @@ -122,7 +124,7 @@ import com.formdev.flatlaf.util.SystemInfo; public class FlatComboBoxUI extends BasicComboBoxUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { @Styleable protected int minimumWidth; @Styleable protected int editorColumns; @@ -511,6 +513,14 @@ public class FlatComboBoxUI return FlatStylingSupport.getAnnotatedStyleableValue( this, comboBox.getBorder(), key ); } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override public void update( Graphics g, JComponent c ) { float focusWidth = FlatUIUtils.getBorderFocusWidth( c ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java index 07d2d50c..1320013e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; @@ -27,6 +28,7 @@ import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.LoggingFacade; @@ -67,7 +69,7 @@ import com.formdev.flatlaf.util.LoggingFacade; public class FlatMenuItemUI extends BasicMenuItemUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { private FlatMenuItemRenderer renderer; private Map oldStyleValues; @@ -166,6 +168,14 @@ public class FlatMenuItemUI return value; } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java index 97de06d1..5587121b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java @@ -22,6 +22,7 @@ import java.awt.Font; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.function.Function; import javax.swing.ButtonModel; @@ -38,6 +39,7 @@ import javax.swing.plaf.MenuBarUI; import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicMenuUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; @@ -87,7 +89,7 @@ import com.formdev.flatlaf.util.LoggingFacade; public class FlatMenuUI extends BasicMenuUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { private FlatMenuItemRenderer renderer; private Map oldStyleValues; @@ -188,6 +190,14 @@ public class FlatMenuUI return FlatMenuItemUI.getStyleableValue( this, renderer, key ); } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override public Dimension getMinimumSize( JComponent c ) { // avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java index 714087ba..2543fe5f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; @@ -27,6 +28,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; @@ -66,7 +68,7 @@ import com.formdev.flatlaf.util.LoggingFacade; public class FlatRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { private FlatMenuItemRenderer renderer; private Map oldStyleValues; @@ -140,6 +142,14 @@ public class FlatRadioButtonMenuItemUI return FlatMenuItemUI.getStyleableValue( this, renderer, key ); } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java index fa5d6253..f6256ff7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java @@ -24,6 +24,7 @@ import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; +import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.Objects; import javax.swing.InputMap; @@ -38,6 +39,7 @@ import javax.swing.plaf.basic.BasicScrollBarUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -86,7 +88,7 @@ import com.formdev.flatlaf.util.UIScale; public class FlatScrollBarUI extends BasicScrollBarUI - implements StyleableUI + implements StyleableUI, StyleableLookupProvider { // overrides BasicScrollBarUI.supportsAbsolutePositioning (which is private) @Styleable protected boolean allowsAbsolutePositioning; @@ -268,6 +270,14 @@ public class FlatScrollBarUI return FlatStylingSupport.getAnnotatedStyleableValue( this, key ); } + /** @since 2.5 */ + @Override + public MethodHandles.Lookup getLookupForStyling() { + // MethodHandles.lookup() is caller sensitive and must be invoked in this class, + // otherwise it is not possible to access protected fields in JRE superclass + return MethodHandles.lookup(); + } + @Override public Dimension getPreferredSize( JComponent c ) { return UIScale.scale( super.getPreferredSize( c ) ); 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 82351f02..bb1d44cf 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 @@ -22,6 +22,7 @@ import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -70,6 +71,9 @@ public class FlatStylingSupport *

* Use this annotation, instead of {@link Styleable}, to style fields * in superclasses, where it is not possible to use {@link Styleable}. + *

+ * Classes using this annotation may implement {@link StyleableLookupProvider} + * to give access to protected fields (in JRE) in modular applications. * * @since 2.5 */ @@ -107,6 +111,11 @@ public class FlatStylingSupport /** @since 2.5 */ Object getStyleableValue( String key ); } + /** @since 2.5 */ + public interface StyleableLookupProvider { + MethodHandles.Lookup getLookupForStyling(); + } + /** * Returns the style specified in client property {@link FlatClientProperties#STYLE}. @@ -443,14 +452,14 @@ public class FlatStylingSupport try { Field f = cls.getDeclaredField( fieldName ); if( predicate == null || predicate.test( f ) ) - return applyToField( f, obj, value ); + return applyToField( f, obj, value, false ); } catch( NoSuchFieldException ex ) { // field not found in class --> try superclass } for( StyleableField styleableField : cls.getAnnotationsByType( StyleableField.class ) ) { if( key.equals( styleableField.key() ) ) - return applyToField( getStyleableField( styleableField ), obj, value ); + return applyToField( getStyleableField( styleableField ), obj, value, true ); } cls = cls.getSuperclass(); @@ -465,9 +474,23 @@ public class FlatStylingSupport } } - private static Object applyToField( Field f, Object obj, Object value ) { + private static Object applyToField( Field f, Object obj, Object value, boolean useMethodHandles ) { checkValidField( f ); + if( useMethodHandles && obj instanceof StyleableLookupProvider ) { + try { + // use method handles to access protected fields in JRE in modular applications + MethodHandles.Lookup lookup = ((StyleableLookupProvider)obj).getLookupForStyling(); + + // get old value and set new value + Object oldValue = lookup.unreflectGetter( f ).invoke( obj ); + lookup.unreflectSetter( f ).invoke( obj, convertToEnum( value, f.getType() ) ); + return oldValue; + } catch( Throwable ex ) { + throw newFieldAccessFailed( f, ex ); + } + } + try { // necessary to access protected fields in other packages f.setAccessible( true ); @@ -481,9 +504,19 @@ public class FlatStylingSupport } } - private static Object getFieldValue( Field f, Object obj ) { + private static Object getFieldValue( Field f, Object obj, boolean useMethodHandles ) { checkValidField( f ); + if( useMethodHandles && obj instanceof StyleableLookupProvider ) { + // use method handles to access protected fields in JRE in modular applications + try { + MethodHandles.Lookup lookup = ((StyleableLookupProvider)obj).getLookupForStyling(); + return lookup.unreflectGetter( f ).invoke( obj ); + } catch( Throwable ex ) { + throw newFieldAccessFailed( f, ex ); + } + } + try { f.setAccessible( true ); return f.get( obj ); @@ -492,7 +525,7 @@ public class FlatStylingSupport } } - private static IllegalArgumentException newFieldAccessFailed( Field f, IllegalAccessException ex ) { + private static IllegalArgumentException newFieldAccessFailed( Field f, Throwable ex ) { return new IllegalArgumentException( "failed to access field '" + f.getDeclaringClass().getName() + "." + f.getName() + "'", ex ); } @@ -792,7 +825,7 @@ public class FlatStylingSupport if( styleable.type() != Void.class ) throw new IllegalArgumentException( "'Styleable.type' on field '" + fieldName + "' not supported" ); - return getFieldValue( f, obj ); + return getFieldValue( f, obj, false ); } } catch( NoSuchFieldException ex ) { // field not found in class --> try superclass @@ -800,10 +833,8 @@ public class FlatStylingSupport // find field specified in 'StyleableField' annotation for( StyleableField styleableField : cls.getAnnotationsByType( StyleableField.class ) ) { - if( key.equals( styleableField.key() ) ) { - Field f = getStyleableField( styleableField ); - return getFieldValue( f, obj ); - } + if( key.equals( styleableField.key() ) ) + return getFieldValue( getStyleableField( styleableField ), obj, true ); } cls = cls.getSuperclass(); diff --git a/flatlaf-testing/flatlaf-testing-modular-app/FlatModularAppTest JAR.launch b/flatlaf-testing/flatlaf-testing-modular-app/FlatModularAppTest JAR.launch index 1437b0c9..30882653 100644 --- a/flatlaf-testing/flatlaf-testing-modular-app/FlatModularAppTest JAR.launch +++ b/flatlaf-testing/flatlaf-testing-modular-app/FlatModularAppTest JAR.launch @@ -12,13 +12,13 @@ - + - - - + + + diff --git a/flatlaf-testing/flatlaf-testing-modular-app/src/main/java/com/formdev/flatlaf/testing/modular/app/FlatModularAppTest.java b/flatlaf-testing/flatlaf-testing-modular-app/src/main/java/com/formdev/flatlaf/testing/modular/app/FlatModularAppTest.java index fb0306e0..d97429b2 100644 --- a/flatlaf-testing/flatlaf-testing-modular-app/src/main/java/com/formdev/flatlaf/testing/modular/app/FlatModularAppTest.java +++ b/flatlaf-testing/flatlaf-testing-modular-app/src/main/java/com/formdev/flatlaf/testing/modular/app/FlatModularAppTest.java @@ -20,7 +20,9 @@ import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollBar; import javax.swing.SwingUtilities; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.extras.FlatSVGIcon; @@ -39,6 +41,8 @@ public class FlatModularAppTest JButton button1 = new JButton( "Hello" ); JButton button2 = new JButton( "World" ); + JScrollBar scrollBar = new JScrollBar(); + scrollBar.putClientProperty( FlatClientProperties.STYLE, "track: #0f0" ); button1.setIcon( new FlatSVGIcon( FlatModularAppTest.class.getResource( "/com/formdev/flatlaf/testing/modular/app/icons/copy.svg" ) ) ); @@ -47,6 +51,7 @@ public class FlatModularAppTest panel.add( new JLabel( "Hello World" ) ); panel.add( button1 ); panel.add( button2 ); + panel.add( scrollBar ); JFrame frame = new JFrame( "FlatModularAppTest" ); frame.setIconImages( FlatSVGUtils.createWindowIconImages(