com.alee.managers.style.data.ComponentStyle Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weblaf-ui Show documentation
Show all versions of weblaf-ui Show documentation
WebLaf is a Java Swing Look and Feel and extended components library for cross-platform applications
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.managers.style.data;
import com.alee.managers.log.Log;
import com.alee.managers.style.*;
import com.alee.painter.Painter;
import com.alee.utils.*;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Component style information class.
*
* @author Mikle Garin
* @see How to use StyleManager
* @see com.alee.managers.style.StyleManager
*/
@XStreamAlias ("style")
@XStreamConverter (ComponentStyleConverter.class)
public final class ComponentStyle implements Serializable, Cloneable
{
/**
* Base painter ID.
*/
public static final String BASE_PAINTER_ID = "painter";
/**
* Style component type.
* Refers to component type this style belongs to.
*/
private StyleableComponent type;
/**
* Unique component style ID.
* Default component style depends on component type.
* Use {@link com.alee.managers.style.StyleableComponent#getDefaultStyleId()} to retrieve default style ID.
*/
private String id;
/**
* Another component style ID which is extended by this style.
* You can specify any existing style ID here to extend it.
*/
private String extendsId;
/**
* Component settings.
* Contains field-value pairs which will be applied to component fields.
*/
private Map componentProperties;
/**
* Component UI settings.
* Contains field-value pairs which will be applied to component UI fields.
*/
private Map uiProperties;
/**
* Component painters settings.
* Contains list of painter style information objects.
*/
private List painters;
/**
* Nested styles.
* Contains list of styles usually provided for child elements.
*/
private List styles;
/**
* Parent style.
* This variable is only set in runtime for style usage convenience.
*/
private transient ComponentStyle parent;
/**
* Constructs new component style information.
*/
public ComponentStyle ()
{
super ();
}
/**
* Returns supported component type.
*
* @return supported component type
*/
public StyleableComponent getType ()
{
return type;
}
/**
* Sets supported component type.
*
* @param type new supported component type
* @return this style
*/
public ComponentStyle setType ( final StyleableComponent type )
{
this.type = type;
return this;
}
/**
* Returns component style ID.
*
* @return component style ID
*/
public String getId ()
{
return id;
}
/**
* Returns complete style ID.
*
* @return complete style ID
*/
public String getCompleteId ()
{
return getParent () != null ? getParent ().getCompleteId () + StyleId.styleSeparator + getId () : getId ();
}
/**
* Sets component style ID.
*
* @param id new component style ID
* @return this style
*/
public ComponentStyle setId ( final String id )
{
this.id = id;
return this;
}
/**
* Returns extended component style ID or null if none extended.
*
* @return extended component style ID or null if none extended
*/
public String getExtendsId ()
{
return extendsId;
}
/**
* Sets extended component style ID.
* Set this to null in case you don't want to extend any style.
*
* @param id new extended component style ID
* @return this style
*/
public ComponentStyle setExtendsId ( final String id )
{
this.extendsId = id;
return this;
}
/**
* Returns component properties.
*
* @return component properties
*/
public Map getComponentProperties ()
{
return componentProperties;
}
/**
* Sets component properties.
*
* @param componentProperties new component properties
* @return this style
*/
public ComponentStyle setComponentProperties ( final Map componentProperties )
{
this.componentProperties = componentProperties;
return this;
}
/**
* Returns component UI properties.
*
* @return component UI properties
*/
public Map getUIProperties ()
{
return uiProperties;
}
/**
* Sets component UI properties
*
* @param uiProperties new component UI properties
* @return this style
*/
public ComponentStyle setUIProperties ( final Map uiProperties )
{
this.uiProperties = uiProperties;
return this;
}
/**
* Returns component painters.
*
* @return component painters
*/
public List getPainters ()
{
return painters;
}
/**
* Sets component painters.
*
* @param painters new component painters
* @return this style
*/
public ComponentStyle setPainters ( final List painters )
{
this.painters = painters;
return this;
}
/**
* Returns component base painter.
*
* @return component base painter
*/
public PainterStyle getBasePainter ()
{
if ( painters.size () == 1 )
{
return painters.get ( 0 );
}
else
{
for ( final PainterStyle painter : painters )
{
if ( painter.isBase () )
{
return painter;
}
}
return null;
}
}
/**
* Returns nested styles list.
*
* @return nested styles list
*/
public List getStyles ()
{
return styles;
}
/**
* Returns nested styles count.
*
* @return nested styles count
*/
public int getStylesCount ()
{
return getStyles () != null ? getStyles ().size () : 0;
}
/**
* Sets nested styles list.
*
* @param styles nested styles list
* @return this style
*/
public ComponentStyle setStyles ( final List styles )
{
this.styles = styles;
return this;
}
/**
* Returns parent component style.
*
* @return parent component style
*/
public ComponentStyle getParent ()
{
return parent;
}
/**
* Sets parent component style.
*
* @param parent parent component style
* @return this style
*/
public ComponentStyle setParent ( final ComponentStyle parent )
{
this.parent = parent;
return this;
}
/**
* Applies this style to the specified component.
* Returns whether style was successfully applied or not.
*
* @param component component to apply style to
* @return true if style was applied, false otherwise
*/
public boolean apply ( final JComponent component )
{
try
{
final ComponentUI ui = getComponentUIImpl ( component );
// Installing painters
for ( final PainterStyle painterStyle : getPainters () )
{
installPainter ( ui, component, true, painterStyle );
}
// Applying UI properties
applyProperties ( ui, appendEmptyUIProperties ( ui, getUIProperties () ) );
// Applying component properties
applyProperties ( component, getComponentProperties () );
return true;
}
catch ( final Throwable e )
{
Log.error ( this, e );
return false;
}
}
/**
* Installs painter into specified object based on provided painter style.
*
* @param object object to install painter into
* @param component component painter is installed for
* @param customizable whether or not this painter customizable through {@link com.alee.managers.style.StyleManager}
* @param painterStyle painter style
* @throws NoSuchFieldException if painter could not be set into object
* @throws NoSuchMethodException if painter setter method could not be found
* @throws InvocationTargetException if painter setter method invocation failed
* @throws IllegalAccessException if painter setter method is not accessible
*/
protected void installPainter ( final Object object, final JComponent component, final boolean customizable,
final PainterStyle painterStyle )
throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
// Retrieving painter to install into component
// Custom painter can be null - that will just mean that component should not have painter installed
final Painter painter;
final Map customPainters = customizable ? StyleManager.getCustomPainters ( component ) : null;
if ( customPainters != null && customPainters.containsKey ( painterStyle.getId () ) )
{
// Using custom painter provided in the application code
// This painter is set through API provided by {@link com.alee.painter.Paintable} interface
painter = customPainters.get ( painterStyle.getId () );
}
else
{
// Creating painter instance
// Be aware that all painters must have default constructor
// todo Only reapply settings if painter already exists?
final String painterClass = painterStyle.getPainterClass ();
painter = ReflectUtils.createInstanceSafely ( painterClass );
if ( painter == null )
{
final String msg = "Unable to create painter \"%s\" for component \"%s\" in style \"%s\"";
final String componentType = component != null ? component.toString () : "none";
throw new StyleException ( String.format ( msg, painterClass, componentType, getId () ) );
}
// Applying painter properties
// These properties are applied only for style-provided painters
applyProperties ( painter, painterStyle.getProperties () );
}
// Installing painter into the UI
// todo Update component instead of reinstalling painter if it is the same?
setFieldValue ( object, painterStyle.getId (), painter );
}
/**
* Applies properties to specified object fields.
*
* @param object object instance
* @param skinProperties skin properties to apply, these properties come from the skin
* @throws NoSuchFieldException if painter could not be set into object
* @throws NoSuchMethodException if painter setter method could not be found
* @throws InvocationTargetException if painter setter method invocation failed
* @throws IllegalAccessException if painter setter method is not accessible
*/
private void applyProperties ( final Object object, final Map skinProperties )
throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
// Applying merged properties
if ( skinProperties != null && skinProperties.size () > 0 )
{
for ( final Map.Entry entry : skinProperties.entrySet () )
{
final Object value = entry.getValue ();
if ( value instanceof PainterStyle )
{
// PainterStyle is handled differently
final PainterStyle style = ( PainterStyle ) value;
style.setId ( entry.getKey () );
installPainter ( object, null, false, style );
}
else
{
// Other fields are simply set through common means
setFieldValue ( object, entry.getKey (), value );
}
}
}
}
/**
* Appends empty property values if required.
*
* @param ui component UI
* @param uiProperties properties
* @return modified properties map
*/
protected Map appendEmptyUIProperties ( final ComponentUI ui, final Map uiProperties )
{
if ( ui instanceof MarginSupport && !uiProperties.containsKey ( ComponentStyleConverter.MARGIN_ATTRIBUTE ) )
{
uiProperties.put ( ComponentStyleConverter.MARGIN_ATTRIBUTE, new Insets ( 0, 0, 0, 0 ) );
}
if ( ui instanceof PaddingSupport && !uiProperties.containsKey ( ComponentStyleConverter.PADDING_ATTRIBUTE ) )
{
uiProperties.put ( ComponentStyleConverter.PADDING_ATTRIBUTE, new Insets ( 0, 0, 0, 0 ) );
}
return uiProperties;
}
/**
* Removes this style from the specified component.
*
* @param component component to remove style from
* @return true if style was successfully removed, false otherwise
*/
public boolean remove ( final JComponent component )
{
try
{
final ComponentUI ui = getComponentUIImpl ( component );
// Uninstalling skin painters from the UI
for ( final PainterStyle painterStyle : getPainters () )
{
final String setterMethod = ReflectUtils.getSetterMethodName ( painterStyle.getId () );
ReflectUtils.callMethod ( ui, setterMethod, ( Painter ) null );
}
return true;
}
catch ( final Throwable e )
{
Log.error ( this, e );
return false;
}
}
/**
* Applies specified value to object field.
* This method allows to access and modify even private object fields.
* Note that this method might also work even if there is no real field with the specified name but there is fitting setter method.
*
* @param object object instance
* @param field object field
* @param value field value
* @return true if value was applied successfully, false otherwise
* @throws java.lang.reflect.InvocationTargetException if method throws an exception
* @throws java.lang.IllegalAccessException if method is inaccessible
*/
private boolean setFieldValue ( final Object object, final String field, final Object value )
throws InvocationTargetException, IllegalAccessException
{
// Skip ignored values
if ( value == IgnoredValue.VALUE )
{
return false;
}
// todo Still need to check method here? Throw exceptions?
// Trying to use setter method to apply the specified value
try
{
final String setterMethod = ReflectUtils.getSetterMethodName ( field );
ReflectUtils.callMethod ( object, setterMethod, value );
return true;
}
catch ( final NoSuchMethodException e )
{
// Ignoring this error and proceeding to direct field access
}
// Applying field value directly
try
{
final Field actualField = ReflectUtils.getField ( object.getClass (), field );
actualField.set ( object, value );
return true;
}
catch ( final NoSuchFieldException e )
{
Log.error ( ComponentStyle.class, e );
return false;
}
}
/**
* Returns component UI object.
*
* @param component component instance
* @return component UI object
*/
private ComponentUI getComponentUIImpl ( final JComponent component )
{
final ComponentUI ui = LafUtils.getUI ( component );
if ( ui == null )
{
throw new StyleException ( "Unable to retrieve UI from component: " + component );
}
return ui;
}
/**
* Returns actual painter used within specified component.
*
* @param component component to retrieve painter from
* @param painter type
* @return actual painter used within specified component
*/
public T getPainter ( final JComponent component )
{
return getPainter ( component, BASE_PAINTER_ID );
}
/**
* Returns actual painter used within specified component.
*
* @param component component to retrieve painter from
* @param painterId painter ID
* @param painter type
* @return actual painter used within specified component
*/
public T getPainter ( final JComponent component, final String painterId )
{
final String pid = painterId != null ? painterId : getBasePainter ().getId ();
final ComponentUI ui = getComponentUIImpl ( component );
return getFieldValue ( ui, pid );
}
/**
* Returns object field value.
* This method allows to access even private object fields.
* Note that this method might also work even if there is no real field with the specified name but there is fitting getter method.
*
* @param object object instance
* @param field object field
* @param value type
* @return field value for the specified object or null
*/
public static T getFieldValue ( final Object object, final String field )
{
final Class> objectClass = object.getClass ();
// Trying to use getter method to retrieve value
// Note that this method might work even if there is no real field with the specified name but there is fitting getter method
// This was made to improve call speed (no real field check) and avoid accessing field directly (in most of cases)
try
{
final Method getter = ReflectUtils.getFieldGetter ( object, field );
return ( T ) getter.invoke ( object );
}
catch ( final InvocationTargetException e )
{
Log.error ( ComponentStyle.class, e );
}
catch ( final IllegalAccessException e )
{
Log.error ( ComponentStyle.class, e );
}
// Retrieving field value directly
// This one is rarely used and in most of times will be called when inappropriate property is set
try
{
final Field actualField = ReflectUtils.getField ( objectClass, field );
return ( T ) actualField.get ( object );
}
catch ( final NoSuchFieldException e )
{
Log.error ( ComponentStyle.class, e );
return null;
}
catch ( final IllegalAccessException e )
{
Log.error ( ComponentStyle.class, e );
return null;
}
}
/**
* Merges specified style on top of this style.
*
* @param style style to merge on top of this one
* @return merge result
*/
public ComponentStyle merge ( final ComponentStyle style )
{
// Applying new parent
setParent ( style.getParent () );
// Applying style ID from the merged style
setId ( style.getId () );
// Applying extended ID from the merged style
setExtendsId ( style.getExtendsId () );
// Apply style settings as in case it was extended
// The same mechanism is used because this is basically an extension of existing style
extend ( style );
// Returns merge result
return this;
}
/**
* Applies specified style settings on top of this style settings.
*
* @param style style to merge on top of this one
* @return current style
*/
private ComponentStyle extend ( final ComponentStyle style )
{
// Copying settings from extended style
mergeProperties ( getComponentProperties (), style.getComponentProperties () );
mergeProperties ( getUIProperties (), style.getUIProperties () );
mergePainters ( this, style );
// Merging nested styles
// In case there are no merged styles we don't need to do anything
final int nestedCount = getStylesCount ();
final int mergedCount = style.getStylesCount ();
if ( nestedCount > 0 && mergedCount > 0 )
{
// Inherits items that have a parent in a new element, but not in the current
for ( final ComponentStyle child : getStyles () )
{
extendChild ( style, child );
}
// Merge styles
final List nestedStyles = getStyles ();
for ( final ComponentStyle mergedNestedStyle : style.getStyles () )
{
// Looking for existing style with the same ID and type
ComponentStyle existing = null;
for ( final ComponentStyle nestedStyle : getStyles () )
{
if ( mergedNestedStyle.getType () == nestedStyle.getType () &&
CompareUtils.equals ( mergedNestedStyle.getId (), nestedStyle.getId () ) )
{
existing = nestedStyle;
break;
}
}
// Either merging style with existing one or simply saving clone
if ( existing != null )
{
// Merging existing nested style and moving it to the end
nestedStyles.remove ( existing );
existing.extend ( mergedNestedStyle );
nestedStyles.add ( existing );
}
else
{
// Simply using merged style clone
final ComponentStyle mergedNestedStyleClone = mergedNestedStyle.clone ();
mergedNestedStyleClone.setParent ( ComponentStyle.this );
nestedStyles.add ( mergedNestedStyleClone );
}
}
setStyles ( nestedStyles );
}
else if ( mergedCount > 0 )
{
// Simply set merged styles
final List mergedStylesClone = MergeUtils.clone ( style.getStyles () );
for ( final ComponentStyle mergedStyleClone : mergedStylesClone )
{
mergedStyleClone.setParent ( this );
}
setStyles ( mergedStylesClone );
}
else if ( nestedCount > 0 )
{
// Simply set base styles
final List baseStylesClone = MergeUtils.clone ( getStyles () );
for ( final ComponentStyle baseStyleClone : baseStylesClone )
{
baseStyleClone.setParent ( this );
}
setStyles ( baseStylesClone );
}
return this;
}
/**
* Inherits items that have a parent in a new element, but not in the current
*
* @param style component style that is merged on top of this one
* @param child this component style child
*/
private void extendChild ( final ComponentStyle style, final ComponentStyle child )
{
// Skipping styles that are already provided in style merged on top of this
for ( final ComponentStyle newParentChild : style.getStyles () )
{
if ( child.getId ().equals ( newParentChild.getId () ) )
{
return;
}
}
// Overriding provided style with this style child
for ( final ComponentStyle mergedChild : style.getStyles () )
{
if ( CompareUtils.equals ( child.getExtendsId (), mergedChild.getId () ) )
{
style.getStyles ().add ( child.clone ().extend ( mergedChild ) );
return;
}
}
}
/**
* Performs painter settings copy from extended style.
*
* @param merged style
* @param style extended style
*/
private void mergePainters ( final ComponentStyle style, final ComponentStyle merged )
{
// Converting painters into maps
final Map stylePainters = collectPainters ( style, style.getPainters () );
final Map mergedPainters = collectPainters ( merged, merged.getPainters () );
// Copying proper painters data
for ( final Map.Entry entry : stylePainters.entrySet () )
{
final String painterId = entry.getKey ();
final PainterStyle stylePainter = entry.getValue ();
if ( mergedPainters.containsKey ( painterId ) )
{
// Copying painter properties if extended painter class type is assignable from current painter class type
final PainterStyle mergedPainter = mergedPainters.get ( painterId );
final Class painterClass = ReflectUtils.getClassSafely ( mergedPainter.getPainterClass () );
final Class extendedPainterClass = ReflectUtils.getClassSafely ( stylePainter.getPainterClass () );
if ( painterClass == null || extendedPainterClass == null )
{
final String pc = painterClass == null ? mergedPainter.getPainterClass () : stylePainter.getPainterClass ();
final String sid = merged.getType () + ":" + merged.getId ();
throw new StyleException ( "Component style \"" + sid + "\" points to missing painter class: \"" + pc + "\"" );
}
if ( ReflectUtils.isAssignable ( extendedPainterClass, painterClass ) )
{
// Adding painter based on extended one and merged
stylePainter.setBase ( mergedPainter.isBase () );
stylePainter.setPainterClass ( mergedPainter.getPainterClass () );
mergeProperties ( stylePainter.getProperties (), mergedPainter.getProperties () );
mergedPainters.put ( painterId, stylePainter );
}
else
{
// Adding painter fully based on merged painter
mergedPainters.put ( painterId, mergedPainter.clone () );
}
}
else
{
// todo Base painters might clash as the result
// Adding a full copy of base painter style
mergedPainters.put ( painterId, stylePainter.clone () );
}
}
// Fixing possible base mark issues
if ( mergedPainters.size () > 0 )
{
boolean hasBase = false;
for ( final Map.Entry entry : mergedPainters.entrySet () )
{
final PainterStyle painterStyle = entry.getValue ();
if ( painterStyle.isBase () )
{
hasBase = true;
break;
}
}
if ( !hasBase )
{
PainterStyle painterStyle = mergedPainters.get ( BASE_PAINTER_ID );
if ( painterStyle == null )
{
painterStyle = mergedPainters.entrySet ().iterator ().next ().getValue ();
}
painterStyle.setBase ( true );
}
}
// Updating painters list
style.setPainters ( new ArrayList ( mergedPainters.values () ) );
}
/**
* Performs properties merge.
*
* @param properties base properties
* @param merged merged properties
*/
private void mergeProperties ( final Map properties, final Map merged )
{
for ( final Map.Entry property : merged.entrySet () )
{
final String key = property.getKey ();
final Object e = properties.get ( key );
final Object m = property.getValue ();
try
{
// Cloning existing property value properly
final Object existing = MergeUtils.clone ( e );
// Merging another one on top of it
final Object result = MergeUtils.merge ( existing, m );
// Saving merge result
properties.put ( key, result );
}
catch ( final Throwable ex )
{
Log.get ().error ( "Unable to merge property \"" + key + "\" values: " + e + " and " + m, ex );
}
}
}
/**
* Collects all specified painters into map by their IDs.
*
* @param style component style
* @param painters painters list
* @return map of painters by their IDs
*/
private Map collectPainters ( final ComponentStyle style, final List painters )
{
final Map paintersMap = new LinkedHashMap ( painters.size () );
for ( final PainterStyle painter : painters )
{
final String painterId = painter.getId ();
if ( paintersMap.containsKey ( painterId ) )
{
final String sid = style.getType () + ":" + style.getId ();
throw new StyleException ( "Component style \"" + sid + "\" has duplicate painters for id \"" + painterId + "\"" );
}
paintersMap.put ( painterId, painter );
}
return paintersMap;
}
/**
* Returns cloned instance of this component style.
*
* @return cloned instance of this component style
*/
@Override
public ComponentStyle clone ()
{
// Creating style clone
final ComponentStyle clone = MergeUtils.cloneByFieldsSafely ( this );
// Updating transient parent field
clone.setParent ( getParent () );
// Updating transient parent field for children to cloned one
if ( !CollectionUtils.isEmpty ( clone.getStyles () ) )
{
for ( final ComponentStyle style : clone.getStyles () )
{
style.setParent ( clone );
}
}
return clone;
}
@Override
public String toString ()
{
return getType () + ":" + getCompleteId ();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy