All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.painter.PainterSupport Maven / Gradle / Ivy

/*
 * 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.painter;

import com.alee.api.jdk.Consumer;
import com.alee.laf.WebLookAndFeel;
import com.alee.managers.style.Bounds;
import com.alee.managers.style.BoundsType;
import com.alee.managers.style.PainterShapeProvider;
import com.alee.managers.style.StyleManager;
import com.alee.managers.style.data.ComponentStyle;
import com.alee.painter.decoration.AbstractDecorationPainter;
import com.alee.utils.LafUtils;
import com.alee.utils.ReflectUtils;
import com.alee.utils.SwingUtils;
import com.alee.utils.swing.WeakComponentData;

import javax.swing.*;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import java.awt.*;

/**
 * This class provides utilities for linking {@link Painter}s with component UIs.
 * It was added to simplify {@link Painter}s usage within UI classes tied to specific {@link ComponentUI} implementations.
 * Without this utility class a lot of code copy-paste would be required between all different UI implementations.
 *
 * {@link Painter}s do not suffer from that issue since they are implemented differently - each specific Painters has its own interface
 * unlike {@link ComponentUI}s which are not based on interfaces but always abstract classes, like {@link javax.swing.plaf.ButtonUI} one.
 *
 * @author Mikle Garin
 */
public final class PainterSupport
{
    /**
     * Installed painters.
     * todo These should be moved into {@link StyleManager} and preserver in {@link com.alee.managers.style.StyleData}
     *
     * @see #installPainter(JComponent, Painter)
     * @see #uninstallPainter(JComponent, Painter)
     */
    private static final WeakComponentData installedPainters =
            new WeakComponentData ( "PainterSupport.painter", 200 );

    /**
     * Margins saved per-component instance.
     * todo These settings should be completely moved into {@link AbstractPainter} upon multiple painters elimination
     *
     * @see #getMargin(JComponent)
     * @see #setMargin(JComponent, Insets)
     */
    private static final WeakComponentData margins =
            new WeakComponentData ( "PainterSupport.margin", 200 );

    /**
     * Paddings saved per-component instance.
     * todo These settings should be completely moved into {@link AbstractPainter} upon multiple painters elimination
     *
     * @see #getPadding(JComponent)
     * @see #setPadding(JComponent, Insets)
     */
    private static final WeakComponentData paddings =
            new WeakComponentData ( "PainterSupport.padding", 200 );

    /**
     * Shape detection settings saved per-component instance.
     * todo These settings should be completely moved into {@link AbstractPainter} upon multiple painters elimination
     *
     * @see #isShapeDetectionEnabled(JComponent, Painter)
     * @see #setShapeDetectionEnabled(JComponent, Painter, boolean)
     */
    private static final WeakComponentData shapeDetectionEnabled =
            new WeakComponentData ( "PainterSupport.shapeDetectionEnabled", 200 );

    /**
     * Returns either the specified painter if it is not an adapted painter or the adapted painter.
     * Used by component UIs to retrieve painters adapted for their specific needs.
     *
     * @param painter painter to process
     * @param 

desired painter type * @return either the specified painter if it is not an adapted painter or the adapted painter */ public static

P getPainter ( final Painter painter ) { return ( P ) ( painter != null && painter instanceof AdaptivePainter ? ( ( AdaptivePainter ) painter ).getPainter () : painter ); } /** * Sets component painter. * {@code null} can be provided to uninstall painter. * * @param component component painter should be installed into * @param setter runnable that updates actual painter field * @param oldPainter previously installed painter * @param painter painter to install * @param specificClass specific painter class * @param specificAdapterClass specific painter adapter class * @param

specific painter class type */ public static

void setPainter ( final JComponent component, final Consumer

setter, final P oldPainter, final Painter painter, final Class

specificClass, final Class specificAdapterClass ) { // Creating adaptive painter if required final P newPainter = getApplicablePainter ( painter, specificClass, specificAdapterClass ); // Properly updating painter uninstallPainter ( component, oldPainter ); setter.accept ( newPainter ); installPainter ( component, newPainter ); // Firing painter change event SwingUtils.firePropertyChanged ( component, WebLookAndFeel.PAINTER_PROPERTY, oldPainter, newPainter ); } /** * Returns the specified painter if it can be assigned to proper painter type. * Otherwise returns newly created adapter painter that wraps the specified painter. * Used by component UIs to adapt general-type painters for their specific-type needs. * * @param painter processed painter * @param properClass proper painter class * @param adapterClass adapter painter class * @param

proper painter type * @return specified painter if it can be assigned to proper painter type, new painter adapter if it cannot be assigned */ private static

P getApplicablePainter ( final Painter painter, final Class

properClass, final Class adapterClass ) { if ( painter == null ) { return null; } else { if ( ReflectUtils.isAssignable ( properClass, painter.getClass () ) ) { return ( P ) painter; } else { return ( P ) ReflectUtils.createInstanceSafely ( adapterClass, painter ); } } } /** * Installs painter into the specified component. * It is highly recommended to call this method only from EDT. * todo Move this code into {@link AbstractPainter#install(JComponent, ComponentUI)} * * @param component component painter is applied to * @param painter painter to install */ private static void installPainter ( final JComponent component, final Painter painter ) { // Simply ignore this call if empty painter is set or component doesn't exist if ( component != null && painter != null ) { // Installing painter if ( !installedPainters.contains ( component ) ) { // Installing painter painter.install ( component, LafUtils.getUI ( component ) ); // Applying initial component settings final Boolean opaque = painter.isOpaque (); if ( opaque != null ) { LookAndFeel.installProperty ( component, WebLookAndFeel.OPAQUE_PROPERTY, opaque ? Boolean.TRUE : Boolean.FALSE ); } // Saving painter-listener pair installedPainters.set ( component, painter ); } else { // Inform about painters usage issue final String msg = "Another painter is already installed on component: %s"; throw new PainterException ( String.format ( msg, component ) ); } } } /** * Uninstalls painter from the specified component. * It is highly recommended to call this method only from EDT. * todo Move this code into {@link AbstractPainter#uninstall(JComponent, ComponentUI)} * * @param component component painter is uninstalled from * @param painter painter to uninstall */ private static void uninstallPainter ( final JComponent component, final Painter painter ) { // Simply ignore this call if painter or component doesn't exist if ( component != null && painter != null ) { if ( installedPainters.contains ( component ) ) { final Painter installedPainter = installedPainters.get ( component ); if ( installedPainter == painter ) { // Uninstalling painter installedPainter.uninstall ( component, LafUtils.getUI ( component ) ); // Removing painter reference installedPainters.clear ( component ); } else { // Inform about painters usage issue final String msg = "Wrong painter uninstall was requested for component: %s"; throw new PainterException ( String.format ( msg, component ) ); } } else { // Inform about painters usage issue final String msg = "There are no painters installed on component: %s"; throw new PainterException ( String.format ( msg, component ) ); } } } /** * Returns component border insets or {@code null} if component doesn't have borders. * {@code null} is basically the same as an empty [0,0,0,0] border insets. * * @param component component to retrieve border insets from * @return component border insets or {@code null} if component doesn't have borders */ public static Insets getInsets ( final Component component ) { if ( component instanceof JComponent ) { return ( ( JComponent ) component ).getInsets (); } else { return null; } } /** * Returns current component margin. * Might return {@code null} which is basically the same as an empty [0,0,0,0] margin. * * @param component component to retrieve margin from * @return current component margin */ public static Insets getMargin ( final JComponent component ) { final Insets margin = margins.get ( component ); return margin != null ? new Insets ( margin.top, margin.left, margin.bottom, margin.right ) : null; } /** * Returns current component margin. * Might return {@code null} which is basically the same as an empty [0,0,0,0] margin. * * @param component component to retrieve margin from * @param applyOrientation whether or not {@link ComponentOrientation} of the specified {@link JComponent} should be applied to margin * @return current component margin */ public static Insets getMargin ( final JComponent component, final boolean applyOrientation ) { final Insets result; if ( applyOrientation ) { final Insets margin = margins.get ( component ); if ( margin != null ) { final boolean ltr = component.getComponentOrientation ().isLeftToRight (); result = new Insets ( margin.top, ltr ? margin.left : margin.right, margin.bottom, ltr ? margin.right : margin.left ); } else { result = null; } } else { result = getMargin ( component ); } return result; } /** * Sets new component margin. * {@code null} can be provided to set an empty [0,0,0,0] margin. * * @param component component to set margin for * @param margin new margin */ public static void setMargin ( final JComponent component, final Insets margin ) { // Updating margin cache final Insets oldMargin = margins.set ( component, margin ); // Notifying everyone about component margin changes SwingUtils.firePropertyChanged ( component, WebLookAndFeel.LAF_MARGIN_PROPERTY, oldMargin, margin ); } /** * Returns current component padding. * Might return {@code null} which is basically the same as an empty [0,0,0,0] padding. * * @param component component to retrieve padding from * @return current component padding */ public static Insets getPadding ( final JComponent component ) { final Insets padding = paddings.get ( component ); return padding != null ? new Insets ( padding.top, padding.left, padding.bottom, padding.right ) : null; } /** * Returns current component padding. * Might return {@code null} which is basically the same as an empty [0,0,0,0] padding. * * @param component component to retrieve padding from * @param applyOrientation whether or not {@link ComponentOrientation} of the specified {@link JComponent} should be applied to padding * @return current component padding */ public static Insets getPadding ( final JComponent component, final boolean applyOrientation ) { final Insets result; if ( applyOrientation ) { final Insets padding = paddings.get ( component ); if ( padding != null ) { final boolean ltr = component.getComponentOrientation ().isLeftToRight (); result = new Insets ( padding.top, ltr ? padding.left : padding.right, padding.bottom, ltr ? padding.right : padding.left ); } else { result = null; } } else { result = getPadding ( component ); } return result; } /** * Sets new padding. * {@code null} can be provided to set an empty [0,0,0,0] padding. * * @param component component to set padding for * @param padding new padding */ public static void setPadding ( final JComponent component, final Insets padding ) { // Updating padding cache final Insets oldPadding = paddings.set ( component, padding ); // Notifying everyone about component padding changes SwingUtils.firePropertyChanged ( component, WebLookAndFeel.LAF_PADDING_PROPERTY, oldPadding, padding ); } /** * Returns component {@link Shape}. * * @param component {@link JComponent} to return {@link Shape} for * @param painter {@link Painter} currently used by the {@link JComponent} * @return component {@link Shape} */ public static Shape getShape ( final JComponent component, final Painter painter ) { if ( painter != null && painter instanceof PainterShapeProvider ) { return ( ( PainterShapeProvider ) painter ).provideShape ( component, BoundsType.margin.bounds ( component ) ); } else { return BoundsType.margin.bounds ( component ); } } /** * Returns whether or not component's custom {@link Shape} is used for better mouse events detection. * If it wasn't explicitly specified - {@link WebLookAndFeel#isShapeDetectionEnabled()} is used as result. * * @param component {@link JComponent} to return {@link Shape} for * @param painter {@link Painter} currently used by the {@link JComponent} * @return {@code true} if component's custom {@link Shape} is used for better mouse events detection, {@code false} otherwise */ public static boolean isShapeDetectionEnabled ( final JComponent component, final Painter painter ) { final Boolean enabled = shapeDetectionEnabled.get ( component ); return enabled != null ? enabled : WebLookAndFeel.isShapeDetectionEnabled (); } /** * Sets whether or not component's custom {@link Shape} should be used for better mouse events detection. * It can be enabled globally through {@link com.alee.laf.WebLookAndFeel#setShapeDetectionEnabled(boolean)}. * * @param component {@link JComponent} to return {@link Shape} for * @param painter {@link Painter} currently used by the {@link JComponent} * @param enabled whether or not component's custom {@link Shape} should be used for better mouse events detection */ public static void setShapeDetectionEnabled ( final JComponent component, final Painter painter, final boolean enabled ) { shapeDetectionEnabled.set ( component, enabled ); } /** * Returns whether or not specified (x,y) location is contained within the shape of the component. * * @param component {@link JComponent} * @param ui {@link ComponentUI} * @param painter {@link Painter} * @param x X coordinate * @param y Y coordinate * @return {@code true} if specified (x,y) location is contained within the shape of the component, {@code false} otherwise */ public static boolean contains ( final JComponent component, final ComponentUI ui, final Painter painter, final int x, final int y ) { final boolean contains; if ( painter != null && isShapeDetectionEnabled ( component, painter ) ) { contains = painter.contains ( component, ui, new Bounds ( component ), x, y ); } else { contains = 0 <= x && x < component.getWidth () && 0 <= y && y < component.getHeight (); } return contains; } /** * Returns component baseline for the specified component size, measured from the top of the component bounds. * A return value less than {@code 0} indicates this component does not have a reasonable baseline. * This method is primarily meant for {@code java.awt.LayoutManager}s to align components along their baseline. * * @param component aligned component * @param ui aligned component UI * @param painter aligned component painter * @param width approximate component width * @param height approximate component height * @return component baseline within the specified bounds, measured from the top of the bounds */ public static int getBaseline ( final JComponent component, final ComponentUI ui, final Painter painter, final int width, final int height ) { // Default baseline int baseline = -1; // Painter baseline support if ( painter != null ) { // Creating appropriate bounds for painter final Bounds componentBounds = new Bounds ( new Dimension ( width, height ) ); // Retrieving baseline provided by painter baseline = painter.getBaseline ( component, ui, componentBounds ); } // Border baseline support // Taken from JPanel baseline implementation if ( baseline == -1 ) { final Border border = component.getBorder (); if ( border instanceof AbstractBorder ) { baseline = ( ( AbstractBorder ) border ).getBaseline ( component, width, height ); } } return baseline; } /** * Returns enum indicating how the baseline of the component changes as the size changes. * * @param component aligned component * @param ui aligned component UI * @param painter aligned component painter * @return enum indicating how the baseline of the component changes as the size changes */ public static Component.BaselineResizeBehavior getBaselineResizeBehavior ( final JComponent component, final ComponentUI ui, final Painter painter ) { // Default behavior Component.BaselineResizeBehavior behavior = Component.BaselineResizeBehavior.OTHER; // Painter baseline behavior support if ( painter != null ) { // Retrieving baseline behavior provided by painter return painter.getBaselineResizeBehavior ( component, ui ); } // Border baseline behavior support // Taken from JPanel baseline implementation if ( behavior == Component.BaselineResizeBehavior.OTHER ) { final Border border = component.getBorder (); if ( border instanceof AbstractBorder ) { behavior = ( ( AbstractBorder ) border ).getBaselineResizeBehavior ( component ); } } return behavior; } /** * Returns component preferred size or {@code null} if there is no preferred size. * * @param component component painter is applied to * @param painter component painter * @return component preferred size or {@code null} if there is no preferred size */ public static Dimension getPreferredSize ( final JComponent component, final Painter painter ) { return getPreferredSize ( component, null, painter ); } /** * Returns component preferred size or {@code null} if there is no preferred size. * todo Probably get rid of this method and force painters to determine full preferred size? * * @param component component painter is applied to * @param preferred component preferred size * @param painter component painter * @return component preferred size or {@code null} if there is no preferred size */ public static Dimension getPreferredSize ( final JComponent component, final Dimension preferred, final Painter painter ) { return getPreferredSize ( component, preferred, painter, false ); } /** * Returns component preferred size or {@code null} if there is no preferred size. * * @param component component painter is applied to * @param preferred component preferred size * @param painter component painter * @param ignoreLayoutSize whether or not layout preferred size should be ignored * @return component preferred size or {@code null} if there is no preferred size */ public static Dimension getPreferredSize ( final JComponent component, final Dimension preferred, final Painter painter, final boolean ignoreLayoutSize ) { // Event Dispatch Thread check WebLookAndFeel.checkEventDispatchThread (); // Painter's preferred size Dimension ps = SwingUtils.max ( preferred, painter != null ? painter.getPreferredSize () : null ); // Layout preferred size if ( !ignoreLayoutSize ) { final LayoutManager layout = component.getLayout (); if ( layout != null ) { ps = SwingUtils.max ( ps, layout.preferredLayoutSize ( component ) ); } } return ps; } /** * Returns whether or not component uses decoratable painter. * * @param component component to process * @return true if component uses decoratable painter, false otherwise */ public static boolean isDecoratable ( final Component component ) { if ( component instanceof JComponent ) { final JComponent jComponent = ( JComponent ) component; final ComponentStyle style = StyleManager.getSkin ( jComponent ).getStyle ( jComponent ); final Painter painter = style != null ? style.getPainter ( jComponent ) : null; return painter != null && painter instanceof AbstractDecorationPainter; // todo Add additional decoration conditions? For: && ((AbstractDecorationPainter)painter)... } else { return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy