/* * 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.skin; import com.alee.extended.painter.Painter; 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 How to use StyleManager * @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 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 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> customPainterProperties, final Map 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 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 ) { e.printStackTrace (); 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 getCustomPainterProperties ( final Map> 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 ) { e.printStackTrace (); 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 style property value type * @return style property value */ public 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 style property value type * @return style property value */ public 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 skinProperties, final Map customProperties ) { // Merging skin and custom properties final Map 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 ( 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 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 ) { e.printStackTrace (); } catch ( final InvocationTargetException e ) { e.printStackTrace (); } catch ( final IllegalAccessException e ) { e.printStackTrace (); } // 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 ) { e.printStackTrace (); return false; } catch ( final IllegalAccessException e ) { e.printStackTrace (); 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 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 ) { e.printStackTrace (); } catch ( final IllegalAccessException e ) { e.printStackTrace (); } // 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 ) { e.printStackTrace (); return null; } catch ( final IllegalAccessException e ) { e.printStackTrace (); 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 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 (); } }