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

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

There is a newer version: 1.2.14
Show newest version
/*
 * 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.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.laf.WebLookAndFeel;
import com.alee.managers.style.*;
import com.alee.painter.decoration.AbstractDecorationPainter;
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 javax.swing.plaf.UIResource;
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}
     */
    @NotNull
    private static final WeakComponentData installedPainters =
            new WeakComponentData ( "PainterSupport.painter", 200 );

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

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

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


    /**
     * Returns {@link Painter} currently installed on the specified {@link Component}.
     * Note that {@link Painter}s can only be installed on {@link JComponent}s, but this method accepts {@link Component} for convenience.
     *
     * @param component {@link Component} to retreive {@link Painter} for
     * @return {@link Painter} currently installed on the specified {@link Component}
     */
    @Nullable
    public static Painter getPainter ( @Nullable final Component component )
    {
        return component instanceof JComponent ? installedPainters.get ( ( JComponent ) component ) : null;
    }

    /**
     * Sets {@link Painter} for the specified {@link JComponent}.
     * Provided {@link Painter} can be {@code null} in which case current {@link Painter} will simply be uninstalled.
     *
     * @param component   {@link JComponent}
     * @param componentUI {@link ComponentUI}
     * @param painter     {@link Painter} to install
     */
    public static void setPainter ( @NotNull final JComponent component, @NotNull final ComponentUI componentUI,
                                    @Nullable final Painter painter )
    {
        final ComponentDescriptor descriptor = StyleManager.getDescriptor ( component );

        // Creating adaptive painter if required
        final SpecificPainter newPainter = getApplicablePainter (
                painter,
                descriptor.getPainterInterface (),
                descriptor.getPainterAdapterClass ()
        );

        // Uninstalling old painter
        final Painter oldPainter = installedPainters.get ( component );
        if ( oldPainter != null )
        {
            oldPainter.uninstall ( component, componentUI );
            installedPainters.clear ( component );
        }

        // Installing new painter
        if ( newPainter != null )
        {
            // Installing painter
            newPainter.install ( component, componentUI );
            installedPainters.set ( component, newPainter );

            // Applying initial component settings
            final Boolean opaque = newPainter.isOpaque ();
            if ( opaque != null )
            {
                LookAndFeel.installProperty ( component, WebLookAndFeel.OPAQUE_PROPERTY, opaque ? Boolean.TRUE : Boolean.FALSE );
            }
        }

        // Firing painter change event
        SwingUtils.firePropertyChanged ( component, WebLookAndFeel.PAINTER_PROPERTY, oldPainter, newPainter );
    }

    /**
     * Returns specified {@link Painter} if it can be assigned to requested {@link Painter} type, otherwise returns newly created
     * {@link AdaptivePainter} that acts as a decorator for the specified {@link Painter}.
     * Used by {@link ComponentUI}s to adapt non-specific {@link Painter}s for their specific needs.
     *
     * @param painter       {@link Painter}
     * @param requested     requested {@link SpecificPainter} class
     * @param adaptiveClass {@link AdaptivePainter} class
     * @param 

requested {@link SpecificPainter} type * @return specified {@link Painter} if it can be assigned to requested {@link Painter} type, otherwise returns newly created * {@link AdaptivePainter} that acts as a decorator for the specified {@link Painter} */ private static

P getApplicablePainter ( @Nullable final Painter painter, @NotNull final Class

requested, @NotNull final Class adaptiveClass ) { final P result; if ( painter != null ) { if ( ReflectUtils.isAssignable ( requested, painter.getClass () ) ) { result = ( P ) painter; } else { result = ReflectUtils.createInstanceSafely ( adaptiveClass, painter ); } } else { result = null; } return result; } /** * Returns {@link Component} border insets or {@code null} if {@link 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 */ @Nullable public static Insets getInsets ( @Nullable final Component component ) { final Insets insets; if ( component instanceof JComponent ) { insets = ( ( JComponent ) component ).getInsets (); } else { insets = null; } return insets; } /** * Returns current {@link JComponent} margin. * Might return {@code null} which is basically the same as an empty [0,0,0,0] margin. * * @param component {@link JComponent} to retrieve margin from * @return current {@link JComponent} margin */ @Nullable public static Insets getMargin ( @NotNull final JComponent component ) { return getMargin ( component, false ); } /** * Returns current {@link JComponent} margin. * Might return {@code null} which is basically the same as an empty [0,0,0,0] margin. * * @param component {@link JComponent} to retrieve margin from * @param applyOrientation whether or not {@link ComponentOrientation} of the specified {@link JComponent} should be applied to margin * @return current {@link JComponent} margin */ @Nullable public static Insets getMargin ( @NotNull final JComponent component, final boolean applyOrientation ) { final Insets result; final Insets margin = margins.get ( component ); if ( margin != null ) { if ( applyOrientation ) { 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 = new Insets ( margin.top, margin.left, margin.bottom, margin.right ); } } else { result = null; } return result; } /** * Sets {@link JComponent} margin. * * @param component {@link JComponent} to set margin for * @param margin new margin */ public static void setMargin ( @NotNull final JComponent component, final int margin ) { setMargin ( component, margin, margin, margin, margin ); } /** * Sets {@link JComponent} margin. * * @param component {@link JComponent} to set margin for * @param top new top margin * @param left new left margin * @param bottom new bottom margin * @param right new right margin */ public static void setMargin ( @NotNull final JComponent component, final int top, final int left, final int bottom, final int right ) { setMargin ( component, new Insets ( top, left, bottom, right ) ); } /** * Sets {@link JComponent} margin. * {@code null} can be provided to set an empty [0,0,0,0] margin. * * @param component {@link JComponent} to set margin for * @param margin new margin */ public static void setMargin ( @NotNull final JComponent component, @Nullable final Insets margin ) { final Insets oldMargin = margins.get ( component ); if ( oldMargin == null || oldMargin instanceof UIResource || !( margin instanceof UIResource ) ) { // Updating margin cache margins.set ( component, margin ); // Notifying everyone about component margin changes SwingUtils.firePropertyChanged ( component, WebLookAndFeel.LAF_MARGIN_PROPERTY, oldMargin, margin ); } } /** * Returns current {@link JComponent} padding. * Might return {@code null} which is basically the same as an empty [0,0,0,0] padding. * * @param component {@link JComponent} to retrieve padding from * @return current {@link JComponent} padding */ @Nullable public static Insets getPadding ( @NotNull final JComponent component ) { return getPadding ( component, false ); } /** * Returns current {@link JComponent} padding. * Might return {@code null} which is basically the same as an empty [0,0,0,0] padding. * * @param component {@link JComponent} to retrieve padding from * @param applyOrientation whether or not {@link ComponentOrientation} of the specified {@link JComponent} should be applied to padding * @return current {@link JComponent} padding */ @Nullable public static Insets getPadding ( @NotNull final JComponent component, final boolean applyOrientation ) { final Insets result; final Insets padding = paddings.get ( component ); if ( padding != null ) { if ( applyOrientation ) { 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 = new Insets ( padding.top, padding.left, padding.bottom, padding.right ); } } else { result = null; } return result; } /** * Sets {@link JComponent} padding. * * @param component {@link JComponent} to set padding for * @param padding new padding */ public static void setPadding ( @NotNull final JComponent component, final int padding ) { setPadding ( component, padding, padding, padding, padding ); } /** * Sets {@link JComponent} padding. * * @param component {@link JComponent} to set padding for * @param top new top padding * @param left new left padding * @param bottom new bottom padding * @param right new right padding */ public static void setPadding ( @NotNull final JComponent component, final int top, final int left, final int bottom, final int right ) { setPadding ( component, new Insets ( top, left, bottom, right ) ); } /** * Sets {@link JComponent} padding. * {@code null} can be provided to set an empty [0,0,0,0] padding. * * @param component {@link JComponent} to set padding for * @param padding new padding */ public static void setPadding ( @NotNull final JComponent component, @Nullable final Insets padding ) { final Insets oldPadding = paddings.get ( component ); if ( oldPadding == null || oldPadding instanceof UIResource || !( padding instanceof UIResource ) ) { // Updating padding cache paddings.set ( component, padding ); // Notifying everyone about component padding changes SwingUtils.firePropertyChanged ( component, WebLookAndFeel.LAF_PADDING_PROPERTY, oldPadding, padding ); } } /** * Returns {@link Shape} of the specified {@link JComponent}. * * @param component {@link JComponent} to return {@link Shape} for * @return {@link Shape} of the specified {@link JComponent} */ @NotNull public static Shape getShape ( @NotNull final JComponent component ) { final Shape shape; final Painter painter = getPainter ( component ); if ( painter instanceof PainterShapeProvider ) { shape = ( ( PainterShapeProvider ) painter ).provideShape ( component, BoundsType.margin.bounds ( component ) ); } else { shape = BoundsType.margin.bounds ( component ); } return shape; } /** * Returns whether or not {@link JComponent}'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 * @return {@code true} if {@link JComponent}'s custom {@link Shape} is used for better mouse events detection, {@code false} otherwise */ public static boolean isShapeDetectionEnabled ( @NotNull final JComponent component ) { final Boolean enabled = shapeDetectionEnabled.get ( component ); return enabled != null ? enabled : WebLookAndFeel.isShapeDetectionEnabled (); } /** * Sets whether or not {@link JComponent}'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 enabled whether or not {@link JComponent}'s custom {@link Shape} should be used for better mouse events detection */ public static void setShapeDetectionEnabled ( @NotNull final JComponent component, final boolean enabled ) { shapeDetectionEnabled.set ( component, enabled ); } /** * Returns whether or not specified (x,y) location is contained within the shape of the {@link JComponent}. * * @param component {@link JComponent} * @param componentUI {@link ComponentUI} * @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 ( @NotNull final JComponent component, @NotNull final ComponentUI componentUI, final int x, final int y ) { final boolean contains; final Painter painter = getPainter ( component ); if ( painter != null && isShapeDetectionEnabled ( component ) ) { contains = painter.contains ( component, componentUI, new Bounds ( component ), x, y ); } else { contains = 0 <= x && x < component.getWidth () && 0 <= y && y < component.getHeight (); } return contains; } /** * Returns {@link JComponent} baseline measured from the top of the component bounds for the specified {@link JComponent} size. * A return value less than {@code 0} indicates this component does not have a reasonable baseline. * This method is primarily meant for {@link LayoutManager}s to align components along their baseline. * * @param component {@link JComponent} * @param componentUI {@link ComponentUI} * @param width offered component width * @param height offered component height * @return {@link JComponent} baseline measured from the top of the component bounds for the specified {@link JComponent} size */ public static int getBaseline ( @NotNull final JComponent component, @NotNull final ComponentUI componentUI, final int width, final int height ) { // Default baseline int baseline = -1; // Painter baseline support final Painter painter = getPainter ( component ); 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, componentUI, 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 {@link java.awt.Component.BaselineResizeBehavior} indicating how baseline of a {@link Component} changes when resized. * * @param component {@link JComponent} * @param componentUI {@link ComponentUI} * @return {@link java.awt.Component.BaselineResizeBehavior} indicating how baseline of a {@link Component} changes when resized */ @NotNull public static Component.BaselineResizeBehavior getBaselineResizeBehavior ( @NotNull final JComponent component, @NotNull final ComponentUI componentUI ) { final Component.BaselineResizeBehavior behavior; final Painter painter = getPainter ( component ); if ( painter != null ) { behavior = painter.getBaselineResizeBehavior ( component, componentUI ); } else { final Border border = component.getBorder (); if ( border instanceof AbstractBorder ) { final AbstractBorder abstractBorder = ( AbstractBorder ) border; behavior = abstractBorder.getBaselineResizeBehavior ( component ); } else { behavior = Component.BaselineResizeBehavior.OTHER; } } return behavior; } /** * Paints {@link JComponent} on the specified {@link Graphics}. * * @param g {@link Graphics} to paint on * @param component {@link JComponent} to paint * @param ui {@link JComponent}'s {@link ComponentUI} */ public static void paint ( @NotNull final Graphics g, @NotNull final JComponent component, @NotNull final ComponentUI ui ) { final Painter painter = PainterSupport.getPainter ( component ); if ( painter != null ) { painter.paint ( ( Graphics2D ) g, component, ui, new Bounds ( component ) ); } } /** * Paints {@link JComponent} on the specified {@link Graphics}. * * @param g {@link Graphics} to paint on * @param component {@link JComponent} to paint * @param ui {@link JComponent}'s {@link ComponentUI} * @param parameters {@link PaintParameters} */ public static void paint ( @NotNull final Graphics g, @NotNull final JComponent component, @NotNull final ComponentUI ui, @NotNull final PaintParameters parameters ) { final Painter painter = PainterSupport.getPainter ( component ); if ( painter != null ) { if ( painter instanceof ParameterizedPaint ) { final ParameterizedPaint parameterizedPaint = ( ParameterizedPaint ) painter; parameterizedPaint.prepareToPaint ( parameters ); painter.paint ( ( Graphics2D ) g, component, ui, new Bounds ( component ) ); } else { throw new PainterException ( "Painter doesn't support parameters: " + painter ); } } } /** * Returns {@link JComponent} preferred size or {@code null} if there is no preferred size. * * @param component {@link JComponent} * @return {@link JComponent} preferred size or {@code null} if there is no preferred size */ @Nullable public static Dimension getPreferredSize ( @NotNull final JComponent component ) { return getPreferredSize ( component, null, false ); } /** * 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 * @return component preferred size or {@code null} if there is no preferred size */ @Nullable public static Dimension getPreferredSize ( @NotNull final JComponent component, @Nullable final Dimension preferred ) { return getPreferredSize ( component, preferred, 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 ignoreLayoutSize whether or not layout preferred size should be ignored * @return component preferred size or {@code null} if there is no preferred size */ @Nullable public static Dimension getPreferredSize ( @NotNull final JComponent component, @Nullable final Dimension preferred, final boolean ignoreLayoutSize ) { // Event Dispatch Thread check WebLookAndFeel.checkEventDispatchThread (); // Painter's preferred size final Painter painter = getPainter ( component ); 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 ( @Nullable final Component component ) { // todo Add additional decoration conditions for: && ((AbstractDecorationPainter)painter)... ? return getPainter ( component ) instanceof AbstractDecorationPainter; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy