Package com.alee.managers.style.skin

Source Code of com.alee.managers.style.skin.WebLafSkin

/*
* 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 <http://www.gnu.org/licenses/>.
*/

package com.alee.managers.style.skin;

import com.alee.extended.painter.Painter;
import com.alee.managers.log.Log;
import com.alee.managers.style.StyleException;
import com.alee.managers.style.StyleManager;
import com.alee.managers.style.SupportedComponent;
import com.alee.managers.style.data.ComponentStyle;
import com.alee.managers.style.data.IgnoredValue;
import com.alee.managers.style.data.PainterStyle;
import com.alee.utils.LafUtils;
import com.alee.utils.ReflectUtils;
import com.alee.utils.SystemUtils;

import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This abstract class represents single WebLaF skin.
* Each skin combines a group of component painters and settings to provide an unique visual style.
*
* @author Mikle Garin
* @see <a href="https://github.com/mgarin/weblaf/wiki/How-to-use-StyleManager">How to use StyleManager</a>
* @see com.alee.managers.style.StyleManager
* @see com.alee.managers.style.skin.CustomSkin
*/

public abstract class WebLafSkin
{
    /**
     * Constant provided in the skin that supports any kind of systems.
     */
    public static final String ALL_SYSTEMS_SUPPORTED = "all";

    /**
     * Returns unique skin ID.
     * Used to collect and manage skins within StyleManager.
     *
     * @return unique skin ID
     */
    public abstract String getId ();

    /**
     * Returns skin name.
     *
     * @return skin name
     */
    public abstract String getName ();

    /**
     * Returns skin description.
     *
     * @return skin description
     */
    public abstract String getDescription ();

    /**
     * Returns skin author.
     *
     * @return skin author
     */
    public abstract String getAuthor ();

    /**
     * Returns list of supported OS.
     *
     * @return list of supported OS
     */
    public abstract List<String> getSupportedSystems ();

    /**
     * Returns whether this skin is supported or not.
     * This method reflects the default mechanism of checking skin support.
     * You can override it in your own skin to provide any custom checks.
     *
     * @return true if this skin is supported, false otherwise
     */
    public boolean isSupported ()
    {
        final List<String> systems = getSupportedSystems ();
        final boolean supportsAny = systems != null && systems.size () > 0;
        return supportsAny && ( systems.contains ( ALL_SYSTEMS_SUPPORTED ) || systems.contains ( SystemUtils.getShortOsName () ) );
    }

    /**
     * Returns skin base class name.
     *
     * @return skin base class name
     */
    public abstract String getSkinClass ();

    /**
     * Returns style for the specified supported component type.
     * Custom style ID can be specified in any Web-component or Web-UI to override default component style.
     * If style for such custom ID is not found in skin descriptor then default style for that component is used.
     *
     * @param component component instance
     * @param type      component type
     * @return component style
     */
    public abstract ComponentStyle getComponentStyle ( JComponent component, SupportedComponent type );

    /**
     * Returns component type.
     *
     * @param component component instance
     * @return component type
     */
    protected SupportedComponent getSupportedComponentTypeImpl ( final JComponent component )
    {
        final SupportedComponent type = SupportedComponent.getComponentTypeByUIClassID ( component.getUIClassID () );
        if ( type == null )
        {
            throw new StyleException ( "Unknown component UI class ID: " + component.getUIClassID () );
        }
        return type;
    }

    /**
     * Returns component style.
     * This method does a check that style exists in addition to abstract one.
     *
     * @param component component instance
     * @return component style
     */
    protected ComponentStyle getComponentStyleImpl ( final JComponent component )
    {
        return getComponentStyleImpl ( component, getSupportedComponentTypeImpl ( component ) );
    }

    /**
     * Returns component style.
     * This method does a check that style exists in addition to abstract one.
     *
     * @param component component instance
     * @param type      component type
     * @return component style
     */
    protected ComponentStyle getComponentStyleImpl ( final JComponent component, final SupportedComponent type )
    {
        final ComponentStyle style = getComponentStyle ( component, type );
        if ( style == null )
        {
            throw new StyleException ( "Skin doesn't contain style for UI class ID: " + component.getUIClassID () );
        }
        return style;
    }

    /**
     * Returns component UI object.
     *
     * @param component component instance
     * @return component UI object
     */
    protected 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;
    }

    /**
     * Applies this skin to the specified component.
     * Returns whether sking was successfully applied or not.
     *
     * @param component component to apply skin to
     * @return true if skin was applied, false otherwise
     */
    public boolean applySkin ( final JComponent component )
    {
        return applySkin ( component, StyleManager.getCustomPainterProperties ( component ), StyleManager.getCustomPainters ( component ) );
    }

    /**
     * Applies this skin to the specified component.
     * Returns whether sking was successfully applied or not.
     *
     * @param component               component to apply skin to
     * @param customPainterProperties custom painter properties to apply
     * @param customPainters          custom painters to apply
     * @return true if skin was applied, false otherwise
     */
    public boolean applySkin ( final JComponent component, final Map<String, Map<String, Object>> customPainterProperties,
                               final Map<String, Painter> customPainters )
    {
        try
        {
            final SupportedComponent type = getSupportedComponentTypeImpl ( component );
            final ComponentStyle style = getComponentStyleImpl ( component, type );
            final ComponentUI ui = getComponentUIImpl ( component );

            // Installing painters
            for ( final PainterStyle painterStyle : style.getPainters () )
            {
                // Painter ID
                final String painterId = painterStyle.getId ();

                // 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;
                if ( customPainters != null && customPainters.containsKey ( painterId ) )
                {
                    // Using provided custom painter
                    // This might be set using Web-component "set...Painter"-like methods
                    painter = customPainters.get ( painterId );
                }
                else
                {
                    // Creating painter instance
                    // Be aware that all painters must have default constructor
                    painter = ReflectUtils.createInstanceSafely ( painterStyle.getPainterClass () );
                    if ( painter == null )
                    {
                        throw new StyleException (
                                "Unable to create painter \"" + painterStyle.getPainterClass () + "\" for component: " + component );
                    }

                    // Applying painter properties
                    // These properties are applied only for style-provided painters
                    // Customly provided painters are not affected by these properties to avoid unexpected changes in them
                    final Map<String, Object> cpp = getCustomPainterProperties ( customPainterProperties, painterStyle, painterId );
                    applyProperties ( painter, painterStyle.getProperties (), cpp );
                }

                // Installing painter into the UI
                final String setterMethod = ReflectUtils.getSetterMethodName ( painterId );
                ReflectUtils.callMethod ( ui, setterMethod, painter );
            }

            // Applying UI properties
            // todo Check whether properties should be applied or not somehow? Additional settings?
            applyProperties ( ui, style.getUIProperties (), null );

            // Applying component properties
            // todo Check whether properties should be applied or not somehow? Additional settings?
            applyProperties ( component, style.getComponentProperties (), null );

            return true;
        }
        catch ( final Throwable e )
        {
            Log.error ( this, e );
            return false;
        }
    }

    /**
     * Returns custom painter properties based on painter ID.
     *
     * @param customPainterProperties all custom painter properties
     * @param painterStyle            painter style
     * @param painterId               painter ID
     * @return specific custom painter properties
     */
    protected Map<String, Object> getCustomPainterProperties ( final Map<String, Map<String, Object>> customPainterProperties,
                                                               final PainterStyle painterStyle, final String painterId )
    {
        return customPainterProperties != null ? customPainterProperties.get ( painterStyle.isBase () ? null : painterId ) : null;
    }

    /**
     * Removes this skin from the specified component.
     *
     * @param component component to remove skin from
     * @return true if skin was successfully removed, false otherwise
     */
    public boolean removeSkin ( final JComponent component )
    {
        try
        {
            // Uninstalling skin painters from the UI
            final ComponentStyle style = getComponentStyleImpl ( component );
            final ComponentUI ui = getComponentUIImpl ( component );
            for ( final PainterStyle painterStyle : style.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;
        }
    }

    /**
     * Returns painter property value from the specified component.
     * Specified property is searched only inside the base painter so far.
     *
     * @param component component to retrieve style property from
     * @param key       style property key
     * @param <T>       style property value type
     * @return style property value
     */
    public <T> T getPainterPropertyValue ( final JComponent component, final String key )
    {
        return getPainterPropertyValue ( component, null, key );
    }

    /**
     * Returns painter property value from the specified component.
     * Specified property is searched only inside the base painter so far.
     *
     * @param component component to retrieve style property from
     * @param painterId painter ID
     * @param key       style property key
     * @param <T>       style property value type
     * @return style property value
     */
    public <T> T getPainterPropertyValue ( final JComponent component, final String painterId, final String key )
    {
        final Painter painter = getPainter ( component, painterId );
        if ( painter != null )
        {
            // Retrieving painter field value
            return getFieldValue ( painter, key );
        }
        else if ( !StyleManager.isStrictStyleChecks () )
        {
            // Simply return null
            return null;
        }
        else
        {
            // Throw exception if painter was not found
            throw new StyleException ( "Painter with ID \"" + painterId + "\" was not found for component: " + component );
        }
    }

    /**
     * Sets custom value for painter property for the specified component.
     * This tricky method retrieves component painter throught its UI and skin settings and applies the specified style property.
     *
     * @param component component to apply custom style property to
     * @param key       custom style property key
     * @param value     custom style property value
     * @return true if custom style property was applied, false otherwise
     */
    public boolean setCustomPainterProperty ( final JComponent component, final String key, final Object value )
    {
        return setCustomPainterProperty ( component, null, key, value );
    }

    /**
     * Sets custom value for painter property for the specified component.
     * This tricky method retrieves component painter throught its UI and skin settings and applies the specified style property.
     *
     * @param component component to apply custom style property to
     * @param painterId painter ID
     * @param key       custom style property key
     * @param value     custom style property value
     * @return true if custom style property was applied, false otherwise
     */
    public boolean setCustomPainterProperty ( final JComponent component, final String painterId, final String key, final Object value )
    {
        final Painter painter = getPainter ( component, painterId );
        if ( painter != null )
        {
            // Updating painter field with custom style property value
            return setFieldValue ( painter, key, value );
        }
        else if ( !StyleManager.isStrictStyleChecks () )
        {
            // Simply return false
            return false;
        }
        else
        {
            // Throw exception if painter was not found
            throw new StyleException ( "Painter with ID \"" + painterId + "\" was not found for component: " + component );
        }
    }

    /**
     * Applies properties to specified object fields.
     *
     * @param object           object instance
     * @param skinProperties   skin properties to apply, these properties come from the skin
     * @param customProperties custom properties to apply, these properties are provided directly into the specific component
     */
    protected void applyProperties ( final Object object, final Map<String, Object> skinProperties,
                                     final Map<String, Object> customProperties )
    {
        // Merging skin and custom properties
        final Map<String, Object> mergedProperties;
        if ( customProperties != null && customProperties.size () > 0 )
        {
            if ( skinProperties != null && skinProperties.size () > 0 )
            {
                // Custom properties are added after skin properties to replace existing values
                mergedProperties = new HashMap<String, Object> ( Math.max ( skinProperties.size (), customProperties.size () ) );
                mergedProperties.putAll ( skinProperties );
                mergedProperties.putAll ( customProperties );
            }
            else
            {
                mergedProperties = customProperties;
            }
        }
        else
        {
            mergedProperties = skinProperties;
        }

        // Applying merged properties
        if ( mergedProperties != null && mergedProperties.size () > 0 )
        {
            for ( final Map.Entry<String, Object> entry : mergedProperties.entrySet () )
            {
                setFieldValue ( object, entry.getKey (), entry.getValue () );
            }
        }
    }

    /**
     * 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
     */
    public static boolean setFieldValue ( final Object object, final String field, final Object value )
    {
        // Skip ignored values
        if ( value == IgnoredValue.VALUE )
        {
            return false;
        }

        // 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 )
        {
            Log.error ( WebLafSkin.class, e );
        }
        catch ( final InvocationTargetException e )
        {
            Log.error ( WebLafSkin.class, e );
        }
        catch ( final IllegalAccessException e )
        {
            Log.error ( WebLafSkin.class, e );
        }

        // Applying field value directly
        try
        {
            final Field actualField = ReflectUtils.getField ( object.getClass (), field );
            actualField.setAccessible ( true );
            actualField.set ( object, value );
            return true;
        }
        catch ( final NoSuchFieldException e )
        {
            Log.error ( WebLafSkin.class, e );
            return false;
        }
        catch ( final IllegalAccessException e )
        {
            Log.error ( WebLafSkin.class, e );
            return false;
        }
    }

    /**
     * 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 <T>    value type
     * @return field value for the specified object or null
     */
    public static <T> 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 ( WebLafSkin.class, e );
        }
        catch ( final IllegalAccessException e )
        {
            Log.error ( WebLafSkin.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 );
            actualField.setAccessible ( true );
            return ( T ) actualField.get ( object );
        }
        catch ( final NoSuchFieldException e )
        {
            Log.error ( WebLafSkin.class, e );
            return null;
        }
        catch ( final IllegalAccessException e )
        {
            Log.error ( WebLafSkin.class, e );
            return null;
        }
    }

    /**
     * Returns component painter for the specified painter ID.
     *
     * @param component component to retrieve painter from
     * @param painterId painter ID
     * @return component painter
     */
    public <T extends Painter> T getPainter ( final JComponent component, final String painterId )
    {
        final String pid = painterId != null ? painterId : getComponentStyleImpl ( component ).getBasePainter ().getId ();
        final ComponentUI ui = getComponentUIImpl ( component );
        return getFieldValue ( ui, pid );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString ()
    {
        return getName ();
    }
}
TOP

Related Classes of com.alee.managers.style.skin.WebLafSkin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.