Styling: support styling any component property that has public getter and setter methods

This commit is contained in:
Karl Tauber
2021-09-01 13:32:31 +02:00
parent 397c369114
commit cdbdccf1ad
24 changed files with 370 additions and 76 deletions

View File

@@ -55,6 +55,7 @@ import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.ColorUIResource;
@@ -794,7 +795,16 @@ public abstract class FlatLaf
public static Object parseDefaultsValue( String key, String value, Class<?> valueType )
throws IllegalArgumentException
{
return UIDefaultsLoader.parseValue( key, value, valueType );
// parse value
Object val = UIDefaultsLoader.parseValue( key, value, valueType );
// create actual value if lazy or active
if( val instanceof LazyValue )
val = ((LazyValue)val).createValue( null );
else if( val instanceof ActiveValue )
val = ((ActiveValue)val).createValue( null );
return val;
}
private static void reSetLookAndFeel() {

View File

@@ -391,9 +391,9 @@ class UIDefaultsLoader
(key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) )
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) || key.equals( "border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) )
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) || key.equals( "icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.equals( "margin" ) ||
key.endsWith( ".padding" ) || key.equals( "padding" ) ||

View File

@@ -117,7 +117,7 @@ public class FlatCheckBoxMenuItemUI
// ignore
}
return FlatMenuItemUI.applyStyleProperty( this, key, value );
return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value );
}
/**

View File

@@ -188,7 +188,7 @@ public class FlatEditorPaneUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/**

View File

@@ -83,7 +83,7 @@ public class FlatLabelUI
public void installUI( JComponent c ) {
super.installUI( c );
applyStyle( FlatStylingSupport.getStyle( c ) );
applyStyle( (JLabel) c, FlatStylingSupport.getStyle( c ) );
}
@Override
@@ -132,7 +132,7 @@ public class FlatLabelUI
ui = (FlatLabelUI) c.getUI();
}
ui.applyStyle( style );
ui.applyStyle( c, style );
c.revalidate();
c.repaint();
}
@@ -140,15 +140,16 @@ public class FlatLabelUI
/**
* @since TODO
*/
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
protected void applyStyle( JLabel c, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( c, key, value ) );
}
/**
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
protected Object applyStyleProperty( JLabel c, String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, c, key, value );
}
/**

View File

@@ -178,7 +178,7 @@ public class FlatListUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, list, key, value );
}
/**

View File

@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -119,10 +120,10 @@ public class FlatMenuItemUI
// ignore
}
return applyStyleProperty( this, key, value );
return applyStyleProperty( this, menuItem, key, value );
}
static Object applyStyleProperty( BasicMenuItemUI ui, String key, Object value ) {
static Object applyStyleProperty( BasicMenuItemUI ui, JMenuItem menuItem, String key, Object value ) {
switch( key ) {
// BasicMenuItemUI
case "selectionBackground":
@@ -132,7 +133,8 @@ public class FlatMenuItemUI
case "acceleratorSelectionForeground":
return FlatStylingSupport.applyToField( ui, key, key, value );
default: throw new UnknownStyleException( key );
default:
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( ui, menuItem, key, value );
}
}

View File

@@ -165,7 +165,7 @@ public class FlatMenuUI
// ignore
}
return FlatMenuItemUI.applyStyleProperty( this, key, value );
return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value );
}
/**

View File

@@ -140,7 +140,7 @@ public class FlatProgressBarUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, progressBar, key, value );
}
/**

View File

@@ -117,7 +117,7 @@ public class FlatRadioButtonMenuItemUI
// ignore
}
return FlatMenuItemUI.applyStyleProperty( this, key, value );
return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value );
}
/**

View File

@@ -92,7 +92,7 @@ public class FlatRadioButtonUI
public void installUI( JComponent c ) {
super.installUI( c );
applyStyle( FlatStylingSupport.getStyle( c ) );
applyStyle( (AbstractButton) c, FlatStylingSupport.getStyle( c ) );
}
@Override
@@ -150,7 +150,7 @@ public class FlatRadioButtonUI
ui = (FlatRadioButtonUI) b.getUI();
}
ui.applyStyle( style );
ui.applyStyle( b, style );
b.revalidate();
b.repaint();
}
@@ -158,14 +158,15 @@ public class FlatRadioButtonUI
/**
* @since TODO
*/
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
protected void applyStyle( AbstractButton b, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( b, key, value ) );
}
/**
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
if( !(icon instanceof FlatCheckBoxIcon) )
@@ -180,7 +181,7 @@ public class FlatRadioButtonUI
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, b, key, value );
}
/**

View File

@@ -246,7 +246,7 @@ public class FlatScrollBarUI
case "maximumThumbSize": oldValue = maximumThumbSize; maximumThumbSize = (Dimension) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, scrollbar, key, value );
}
/**

View File

@@ -82,7 +82,7 @@ public class FlatSeparatorUI
public void installUI( JComponent c ) {
super.installUI( c );
applyStyle( FlatStylingSupport.getStyle( c ) );
applyStyle( (JSeparator) c, FlatStylingSupport.getStyle( c ) );
}
@Override
@@ -131,7 +131,7 @@ public class FlatSeparatorUI
ui = (FlatSeparatorUI) s.getUI();
}
ui.applyStyle( style );
ui.applyStyle( s, style );
s.revalidate();
s.repaint();
}
@@ -139,15 +139,16 @@ public class FlatSeparatorUI
/**
* @since TODO
*/
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
protected void applyStyle( JSeparator s, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( s, key, value ) );
}
/**
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
protected Object applyStyleProperty( JSeparator s, String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, s, key, value );
}
/**

View File

@@ -201,7 +201,7 @@ public class FlatSliderUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, slider, key, value );
}
/**

View File

@@ -158,7 +158,7 @@ public class FlatSplitPaneUI
} catch( UnknownStyleException ex ) {
// ignore
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, splitPane, key, value );
}
/**

View File

@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -81,7 +82,7 @@ public class FlatStylingSupport
* @return map of old values modified by the given style, or {@code null}
* @throws UnknownStyleException on unknown style keys
* @throws IllegalArgumentException on syntax errors
* @throws ClassCastException if value type does not fit to expected type 
* @throws ClassCastException if value type does not fit to expected type
*/
public static Map<String, Object> parseAndApply( Map<String, Object> oldStyleValues,
Object style, BiFunction<String, Object, Object> applyProperty )
@@ -210,7 +211,7 @@ public class FlatStylingSupport
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type 
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObject( Object obj, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
@@ -239,7 +240,7 @@ public class FlatStylingSupport
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a field with given name
* @throws IllegalArgumentException if value type does not fit to expected type 
* @throws IllegalArgumentException if value type does not fit to expected type
*/
static Object applyToField( Object obj, String fieldName, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
@@ -292,12 +293,90 @@ public class FlatStylingSupport
return (modifiers & (Modifier.FINAL|Modifier.STATIC)) == 0 && !f.isSynthetic();
}
/**
* Applies the given value to a property of the given object.
* Works only for properties that have public getter and setter methods.
* First the property getter is invoked to get the old value,
* then the property setter is invoked to set the new value.
*
* @param obj the object
* @param name the name of the property
* @param value the new value
* @return the old value of the property
* @throws UnknownStyleException if object does not have a property with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
private static Object applyToProperty( Object obj, String name, Object value )
throws UnknownStyleException, IllegalArgumentException
{
Class<?> cls = obj.getClass();
String getterName = buildMethodName( "get", name );
String setterName = buildMethodName( "set", name );
try {
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, value );
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 );
}
}
private static String buildMethodName( String prefix, String name ) {
int prefixLength = prefix.length();
int nameLength = name.length();
char[] chars = new char[prefixLength + nameLength];
prefix.getChars( 0, prefixLength, chars, 0 );
name.getChars( 0, nameLength, chars, prefixLength );
chars[prefixLength] = Character.toUpperCase( chars[prefixLength] );
return new String( chars );
}
/**
* Applies the given value to an annotated field of the given object
* or to a property of the given component.
* The field must be annotated with {@link Styleable}.
* The component property must have public getter and setter methods.
*
* @param obj the object
* @param comp the component, or {@code null}
* @param key the name of the field
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObjectOrComponent( Object obj, Object comp, String key, Object value ) {
try {
return applyToAnnotatedObject( obj, key, value );
} catch( UnknownStyleException ex ) {
try {
if( comp != null )
return applyToProperty( comp, key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
throw ex;
}
}
static Object applyToAnnotatedObjectOrBorder( Object obj, String key, Object value,
JComponent c, AtomicBoolean borderShared )
{
try {
return applyToAnnotatedObject( obj, key, value );
} catch( UnknownStyleException ex ) {
// apply to border
Border border = c.getBorder();
if( border instanceof StyleableBorder ) {
if( borderShared.get() ) {
@@ -312,6 +391,13 @@ public class FlatStylingSupport
// ignore
}
}
// apply to component property
try {
return applyToProperty( c, key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
throw ex;
}
}

View File

@@ -626,7 +626,7 @@ public class FlatTabbedPaneUI
}
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tabPane, key, value );
}
/**

View File

@@ -149,7 +149,7 @@ public class FlatTableHeaderUI
if( key.equals( "sortIconPosition" ) && value instanceof String )
value = parseSortIconPosition( (String) value );
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, header, key, value );
}
/**

View File

@@ -254,7 +254,7 @@ public class FlatTableUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
}
/**

View File

@@ -162,7 +162,7 @@ public class FlatTextAreaUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/**

View File

@@ -170,7 +170,7 @@ public class FlatTextPaneUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/**

View File

@@ -152,7 +152,7 @@ public class FlatToolBarUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, toolBar, key, value );
}
/**

View File

@@ -320,7 +320,7 @@ public class FlatTreeUI
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tree, key, value );
}
/**