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

com.alee.managers.tooltip.WebCustomTooltip 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.managers.tooltip;

import com.alee.extended.label.WebStyledLabel;
import com.alee.laf.WebFonts;
import com.alee.laf.label.WebLabel;
import com.alee.managers.hotkey.HotkeyManager;
import com.alee.managers.language.data.TooltipWay;
import com.alee.managers.style.ShapeProvider;
import com.alee.managers.style.StyleId;
import com.alee.utils.CollectionUtils;
import com.alee.utils.GraphicsUtils;
import com.alee.utils.SwingUtils;
import com.alee.utils.TextUtils;
import com.alee.utils.laf.ShadeType;
import com.alee.utils.swing.AncestorAdapter;
import com.alee.utils.swing.FadeStateType;
import com.alee.utils.swing.WebTimer;

import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.RoundRectangle2D;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Mikle Garin
 */

public class WebCustomTooltip extends JComponent implements ShapeProvider
{
    /**
     * todo 1. Make this a custom styleable component
     * todo 2. Move all painting into painter
     */

    /**
     * Tooltip constants.
     */
    private static final String ID_PREFIX = "WCT";
    private static final int fadeFps = 24;
    private static final long fadeTime = 200;
    private static final int cornerLength = 8;
    private static final int cornerSideX = 7;

    /**
     * Default tooltip display way.
     * If set to null - best display way will be calculated each time tooltip shown.
     */
    private TooltipWay displayWay = null;

    /**
     * Whether to show component hotkeys information at the right side of the tooltip or not.
     * Only non-hidden hotkeys information will be displayed.
     */
    private boolean showHotkey = true;

    /**
     * Hotkey location inside the tooltip.
     * It might have either SwingConstants.LEFT, RIGHT, LEADING or TRAILING value.
     */
    private int hotkeyLocation = SwingConstants.TRAILING;

    /**
     * Whether tooltip should use default close behavior () or allow user to define his own close behavior.
     * Default behavior is when tooltip closes when cursor leave component area, or when any mouse/key press occurs.
     */
    private boolean defaultCloseBehavior = true;

    /**
     * Spacing between tooltip border and tooltip content.
     */
    private int contentSpacing = 4;

    /**
     * Additional left and right sides content spacing.
     * This is basically used to improve text readability in tooltips.
     */
    private int leftRightSpacing = 0;

    /**
     * Minimal spacing between window edge and tooltip.
     * Used to avoid tooltips falling behind the window edge when shown close to it.
     */
    private int windowSideSpacing = 5;

    /**
     * Tooltip corners rounding.
     */
    private int round = 4;

    /**
     * Decoration shade type.
     */
    private ShadeType shadeType = ShadeType.gradient;

    /**
     * Decoration shade width.
     */
    private int shadeWidth = 0;

    /**
     * Decoration shade color.
     */
    private Color shadeColor = Color.GRAY;

    /**
     * Tooltip border color.
     * When set to null border won't be drawn at all.
     */
    private Color borderColor = null;

    /**
     * Tooltip top background color.
     * When set to null background won't be drawn at all.
     */
    private Color topBgColor = Color.BLACK;

    /**
     * Tooltip bottom background color.
     * When set to null background will be filled with topBgColor.
     */
    private Color bottomBgColor = Color.BLACK;

    /**
     * Tooltip text color.
     */
    private Color textColor = Color.WHITE;

    /**
     * Tooltip background opacity.
     */
    private float opacity = 0.85f;

    /**
     * Tooltip listeners.
     */
    private final List listeners = new ArrayList ( 2 );

    /**
     * Component listeners.
     */
    private AncestorListener ancestorListener;

    /**
     * Tooltip variables.
     */
    private final WebLabel hotkey;
    private int cornerPeak = 0;

    /**
     * Animation variables.
     */
    private final WebTimer fadeTimer;
    private FadeStateType fadeStateType;
    private float fade = 0;

    /**
     * Runtime variables.
     */
    private final String id;
    private final WeakReference component;
    private JComponent tooltip;
    private Point displayLocation;
    private Rectangle relativeToBounds;
    private WeakReference relativeToComponent;

    public WebCustomTooltip ( final Component component, final String tooltip )
    {
        this ( component, null, tooltip );
    }

    public WebCustomTooltip ( final Component component, final Icon icon, final String tooltip )
    {
        this ( component, createDefaultComponent ( icon, tooltip ) );
    }

    public WebCustomTooltip ( final Component component, final String tooltip, final TooltipWay tooltipWay )
    {
        this ( component, null, tooltip, tooltipWay );
    }

    public WebCustomTooltip ( final Component component, final Icon icon, final String tooltip, final TooltipWay tooltipWay )
    {
        this ( component, createDefaultComponent ( icon, tooltip ), tooltipWay );
    }

    public WebCustomTooltip ( final Component component, final String tooltip, final boolean showHotkey )
    {
        this ( component, null, tooltip, showHotkey );
    }

    public WebCustomTooltip ( final Component component, final Icon icon, final String tooltip, final boolean showHotkey )
    {
        this ( component, createDefaultComponent ( icon, tooltip ), showHotkey );
    }

    public WebCustomTooltip ( final Component component, final String tooltip, final TooltipWay tooltipWay, final boolean showHotkey )
    {
        this ( component, null, tooltip, tooltipWay, showHotkey );
    }

    public WebCustomTooltip ( final Component component, final Icon icon, final String tooltip, final TooltipWay tooltipWay,
                              final boolean showHotkey )
    {
        this ( component, createDefaultComponent ( icon, tooltip ), tooltipWay, showHotkey );
    }

    public WebCustomTooltip ( final Component component, final JComponent tooltip )
    {
        this ( component, tooltip, null );
    }

    public WebCustomTooltip ( final Component component, final JComponent tooltip, final TooltipWay tooltipWay )
    {
        this ( component, tooltip, tooltipWay, true );
    }

    public WebCustomTooltip ( final Component component, final JComponent tooltip, final boolean showHotkey )
    {
        this ( component, tooltip, null, showHotkey );
    }

    public WebCustomTooltip ( final Component component, final JComponent tooltip, final TooltipWay tooltipWay, final boolean showHotkey )
    {
        super ();

        SwingUtils.setOrientation ( this );
        // SwingUtils.setForegroundRecursively ( tooltip, textColor );
        setOpaque ( false );

        // Tooltip unique id
        this.id = TextUtils.generateId ( ID_PREFIX );

        // Component to which this tooltip attached
        this.component = new WeakReference ( component );

        // Tooltip component
        this.tooltip = tooltip;

        // Show component hotkey on tooltip
        this.showHotkey = showHotkey;

        // Tooltip display direction
        this.displayWay = tooltipWay;

        // Tooltip hotkey preview component
        hotkey = new WebLabel ( StyleId.customtooltipHotkeyLabel );
        hotkey.setFont ( WebFonts.getSystemAcceleratorFont () );

        // Components placement on tooltip
        setLayout ( new BorderLayout ( 6, 6 ) );
        add ( tooltip, BorderLayout.CENTER );

        // Fade in-out timer
        fadeTimer = new WebTimer ( "WebCustomTooltip.fade", 1000 / fadeFps );
        fadeTimer.addActionListener ( new ActionListener ()
        {
            @Override
            public void actionPerformed ( final ActionEvent e )
            {
                final float roundsCount = fadeTime / ( 1000f / fadeFps );
                final float fadeSpeed = 1f / roundsCount;
                if ( fadeStateType.equals ( FadeStateType.fadeIn ) )
                {
                    if ( fade < 1f )
                    {
                        fade = Math.min ( fade + fadeSpeed, 1f );
                        WebCustomTooltip.this.repaint ();
                    }
                    else
                    {
                        fireTooltipFullyShown ();
                        fadeTimer.stop ();
                    }
                }
                else if ( fadeStateType.equals ( FadeStateType.fadeOut ) )
                {
                    if ( fade > 0 )
                    {
                        fade = Math.max ( fade - fadeSpeed, 0f );
                        WebCustomTooltip.this.repaint ();
                    }
                    else
                    {
                        final JComponent parent = ( JComponent ) WebCustomTooltip.this.getParent ();
                        if ( parent != null )
                        {
                            final Rectangle b = WebCustomTooltip.this.getBounds ();
                            parent.remove ( WebCustomTooltip.this );
                            parent.repaint ( b );
                        }
                        fadeTimer.stop ();
                    }
                }
            }
        } );
        addAncestorListener ( new AncestorListener ()
        {
            @Override
            public void ancestorAdded ( final AncestorEvent event )
            {
                // Updating tooltip hotkey
                updateHotkey ();

                // Updating tooltip layout and location
                updateBorder ();
                updateLocation ();

                // Starting fade-in animation
                fade = 0;
                fadeStateType = FadeStateType.fadeIn;
                fadeTimer.start ();

                // Informing listeners that tooltip was shown
                fireTooltipShown ();
            }

            @Override
            public void ancestorRemoved ( final AncestorEvent event )
            {
                // Check that tooltip actually doesn't have a parent anymore
                // This check is added for the case when parent glasspane is moved forcing this listener to be called
                if ( WebCustomTooltip.this.getParent () == null )
                {
                    // Informing listeners that tooltip was hidden
                    fireTooltipHidden ();
                }
            }

            @Override
            public void ancestorMoved ( final AncestorEvent event )
            {
                // Updating location of the tooltip
                updateBorder ();
                updateLocation ();
            }
        } );

        // Parent component ancestor listener
        if ( component instanceof JComponent )
        {
            ancestorListener = new AncestorAdapter ()
            {
                @Override
                public void ancestorRemoved ( final AncestorEvent event )
                {
                    // Closing tooltip
                    closeTooltip ();
                }

                @Override
                public void ancestorMoved ( final AncestorEvent event )
                {
                    // Closing tooltip
                    closeTooltip ();
                }
            };
            ( ( JComponent ) component ).addAncestorListener ( ancestorListener );
        }
    }

    /**
     * Tooltip hotkey label update
     */

    private void updateHotkey ()
    {
        // Check various conditions of displaying hotkey
        if ( showHotkey )
        {
            final Component c = getComponent ();
            if ( c instanceof JComponent )
            {
                final String hotkeyText = HotkeyManager.getComponentHotkeysString ( ( JComponent ) c );
                if ( !TextUtils.isEmpty ( hotkeyText ) )
                {
                    // Updating hotkey
                    hotkey.setText ( hotkeyText );

                    // Adding or re-adding hotkey label to tooltip
                    if ( WebCustomTooltip.this.getComponentZOrder ( hotkey ) != -1 )
                    {
                        WebCustomTooltip.this.remove ( hotkey );
                    }
                    WebCustomTooltip.this.add ( hotkey, getActualHotkeyLocation () );

                    // Return to avoid hotkey removal
                    return;
                }
            }
        }

        // Removing hotkey label from tooltip if we reach this part
        WebCustomTooltip.this.remove ( hotkey );
    }

    private String getActualHotkeyLocation ()
    {
        switch ( hotkeyLocation )
        {
            case SwingConstants.LEADING:
                return BorderLayout.LINE_START;
            case SwingConstants.TRAILING:
                return BorderLayout.LINE_END;
            case SwingConstants.LEFT:
                return BorderLayout.WEST;
            case SwingConstants.RIGHT:
                return BorderLayout.EAST;
            default:
                return BorderLayout.LINE_END;
        }
    }

    /**
     * Tooltip hide
     */

    public void closeTooltip ()
    {
        if ( getParent () == null )
        {
            return;
        }

        fadeStateType = FadeStateType.fadeOut;
        if ( !fadeTimer.isRunning () )
        {
            fadeTimer.start ();
        }
    }

    /**
     * Tooltip destroy
     */

    public void destroyTooltip ()
    {
        final Component component = getComponent ();
        if ( component instanceof JComponent )
        {
            ( ( JComponent ) component ).removeAncestorListener ( ancestorListener );
        }
        fireTooltipDestroyed ();
    }

    /**
     * Calculated display way
     */

    public TooltipWay getActualDisplayWay ()
    {
        final Component component = getComponent ();
        if ( displayWay != null )
        {
            if ( displayWay.equals ( TooltipWay.leading ) || displayWay.equals ( TooltipWay.trailing ) )
            {
                final boolean ltr = component.getComponentOrientation ().isLeftToRight ();
                if ( displayWay.equals ( TooltipWay.leading ) && ltr || displayWay.equals ( TooltipWay.trailing ) && !ltr )
                {
                    return TooltipWay.left;
                }
                else
                {
                    return TooltipWay.right;
                }
            }
            return displayWay;
        }
        else
        {
            if ( !component.isShowing () )
            {
                return TooltipWay.down;
            }

            final Component glassPane = SwingUtilities.getRootPane ( component ).getGlassPane ();
            if ( !glassPane.isShowing () )
            {
                return TooltipWay.down;
            }

            final Dimension rootSize = glassPane.getSize ();
            final Rectangle componentBounds = SwingUtils.getRelativeBounds ( component, glassPane );
            final Dimension ps = WebCustomTooltip.this.getPreferredSize ();

            if ( componentBounds.y + getTooltipPoint ( component, TooltipWay.down ).y + ps.height < rootSize.height - windowSideSpacing )
            {
                return TooltipWay.down;
            }
            else if ( componentBounds.y + getTooltipPoint ( component, TooltipWay.up ).y - ps.height > windowSideSpacing )
            {
                return TooltipWay.up;
            }
            else if ( componentBounds.x + getTooltipPoint ( component, TooltipWay.right ).x +
                    ps.width < rootSize.width - windowSideSpacing )
            {
                return TooltipWay.right;
            }
            else if ( componentBounds.x + getTooltipPoint ( component, TooltipWay.left ).x - ps.width > windowSideSpacing )
            {
                return TooltipWay.left;
            }
            else
            {
                return TooltipWay.down;
            }
        }
    }

    private Point getTooltipPoint ( final Component component, final TooltipWay tooltipWay )
    {
        if ( displayLocation == null )
        {
            if ( tooltipWay.equals ( TooltipWay.down ) )
            {
                return new Point ( component.getWidth () / 2, component.getHeight () - cornerLength / 2 + shadeWidth );
            }
            else if ( tooltipWay.equals ( TooltipWay.up ) )
            {
                return new Point ( component.getWidth () / 2, cornerLength / 2 - shadeWidth );
            }
            else if ( tooltipWay.equals ( TooltipWay.left ) )
            {
                return new Point ( cornerLength / 2 - shadeWidth, getHeight () / 2 );
            }
            else
            {
                return new Point ( component.getWidth () - cornerLength / 2 + shadeWidth, getHeight () / 2 );
            }
        }
        else
        {
            return displayLocation;
        }
    }

    /**
     * Tooltip layout update
     */

    public void updateBorder ()
    {
        final TooltipWay displayWay = getActualDisplayWay ();

        // Default margins
        final int leftSpacing = shadeWidth + contentSpacing + leftRightSpacing +
                ( displayWay.equals ( TooltipWay.right ) ? cornerLength : 0 );
        final int rightSpacing = shadeWidth + contentSpacing + leftRightSpacing +
                ( displayWay.equals ( TooltipWay.left ) ? cornerLength : 0 );
        final int topSpacing = shadeWidth +
                contentSpacing + ( displayWay.equals ( TooltipWay.down ) ? cornerLength : 0 );
        final int bottomSpacing = shadeWidth +
                contentSpacing + ( displayWay.equals ( TooltipWay.up ) ? cornerLength : 0 );

        // Additional hotkey margins
        final Insets hm = getHotkeyMargins ();

        // Updating border
        setBorder ( BorderFactory
                .createEmptyBorder ( topSpacing + hm.top, leftSpacing + hm.left, bottomSpacing + hm.bottom, rightSpacing + hm.right ) );
        revalidate ();
    }

    private Insets getHotkeyMargins ()
    {
        final boolean ltr = getComponentOrientation ().isLeftToRight ();
        final boolean leftHotkey = hotkeyLocation == SwingConstants.LEFT ||
                hotkeyLocation == SwingConstants.LEADING && ltr ||
                hotkeyLocation == SwingConstants.TRAILING && !ltr;
        final int left = showHotkey && leftHotkey ? 0 : 2;
        final int right = showHotkey && !leftHotkey ? 0 : 2;
        return new Insets ( 0, left, 0, right );
    }

    /**
     * Close tooltip on orientation change to avoid location problems
     */

    @Override
    public void applyComponentOrientation ( final ComponentOrientation o )
    {
        super.applyComponentOrientation ( o );
        closeTooltip ();
    }

    /**
     * Tooltip spacing between sides and content
     */

    public int getContentSpacing ()
    {
        return contentSpacing;
    }

    public void setContentSpacing ( final int contentSpacing )
    {
        this.contentSpacing = contentSpacing;
        updateBorder ();
    }

    /**
     * Additional tooltip content left/right spacing
     */

    public int getLeftRightSpacing ()
    {
        return leftRightSpacing;
    }

    public void setLeftRightSpacing ( final int leftRightSpacing )
    {
        this.leftRightSpacing = leftRightSpacing;
        updateBorder ();
        updateLocation ();
    }

    /**
     * Updates tooltip location on GlassPane.
     * This method takes into account various tooltip settings and chooses the way tooltip should be displayed.
     */
    public void updateLocation ()
    {
        final Component component = getComponent ();
        if ( getParent () != null && getParent ().isShowing () && component.isShowing () )
        {
            final TooltipWay displayWay = getActualDisplayWay ();
            final Point p = getParent ().getLocationOnScreen ();
            final Point c = component.getLocationOnScreen ();
            final Dimension ps = getPreferredSize ();

            final int x0 = c.x - p.x;
            final int y0 = c.y - p.y;

            if ( displayWay.equals ( TooltipWay.up ) || displayWay.equals ( TooltipWay.down ) )
            {
                final int compMiddle;
                final int compTipY;
                if ( displayLocation == null )
                {
                    if ( relativeToBounds == null )
                    {
                        final Component rtc = getRelativeToComponent ();
                        if ( rtc == null )
                        {
                            compMiddle = x0 + component.getWidth () / 2;
                            compTipY = y0 + ( displayWay.equals ( TooltipWay.up ) ? ( cornerLength / 2 - shadeWidth - ps.height ) :
                                    ( component.getHeight () - cornerLength / 2 + shadeWidth ) );
                        }
                        else
                        {
                            final Rectangle b = SwingUtils.getRelativeBounds ( rtc, component );
                            compMiddle = x0 + b.x + b.width / 2;
                            compTipY = y0 + b.y + ( displayWay.equals ( TooltipWay.up ) ? ( cornerLength / 2 - shadeWidth - ps.height ) :
                                    ( b.height - cornerLength / 2 + shadeWidth ) );
                        }
                    }
                    else
                    {
                        final Rectangle b = relativeToBounds;
                        compMiddle = x0 + b.x + b.width / 2;
                        compTipY = y0 + b.y + ( displayWay.equals ( TooltipWay.up ) ? ( cornerLength / 2 - shadeWidth - ps.height ) :
                                ( b.height - cornerLength / 2 + shadeWidth ) );
                    }
                }
                else
                {
                    compMiddle = x0 + displayLocation.x;
                    compTipY = y0 + displayLocation.y - ( displayWay.equals ( TooltipWay.up ) ? ps.height : 0 );
                }

                if ( compMiddle - ps.width / 2 < windowSideSpacing )
                {
                    final int cw = windowSideSpacing - ( compMiddle - ps.width / 2 );
                    cornerPeak = Math.max ( shadeWidth + round + cornerSideX + 1, getWidth () / 2 - cw );
                    setLocation ( windowSideSpacing, compTipY );
                }
                else if ( compMiddle + ps.width / 2 > getParent ().getWidth () - windowSideSpacing )
                {
                    final int cw = ( compMiddle + ps.width / 2 ) - ( getParent ().getWidth () - windowSideSpacing );
                    cornerPeak = Math.min ( ps.width - shadeWidth - round - cornerSideX - 1, getWidth () / 2 + cw );
                    setLocation ( getParent ().getWidth () - windowSideSpacing - ps.width, compTipY );
                }
                else
                {
                    cornerPeak = getWidth () / 2;
                    setLocation ( compMiddle - ps.width / 2, compTipY );
                }
            }
            else if ( displayWay.equals ( TooltipWay.left ) || displayWay.equals ( TooltipWay.right ) )
            {
                final int compMiddle;
                final int compTipX;
                if ( displayLocation == null )
                {
                    if ( relativeToBounds == null )
                    {
                        final Component rtc = getRelativeToComponent ();
                        if ( rtc == null )
                        {
                            compMiddle = y0 + component.getHeight () / 2;
                            compTipX = x0 + ( displayWay.equals ( TooltipWay.left ) ? ( cornerLength / 2 - shadeWidth - ps.width ) :
                                    ( component.getWidth () - cornerLength / 2 + shadeWidth ) );
                        }
                        else
                        {
                            final Rectangle b = SwingUtils.getRelativeBounds ( rtc, component );
                            compMiddle = y0 + b.y + b.height / 2;
                            compTipX = x0 + b.x + ( displayWay.equals ( TooltipWay.left ) ? ( cornerLength / 2 - shadeWidth - ps.width ) :
                                    ( b.width - cornerLength / 2 + shadeWidth ) );
                        }
                    }
                    else
                    {
                        final Rectangle b = relativeToBounds;
                        compMiddle = y0 + b.y + b.height / 2;
                        compTipX = x0 + b.x + ( displayWay.equals ( TooltipWay.left ) ? ( cornerLength / 2 - shadeWidth - ps.width ) :
                                ( b.width - cornerLength / 2 + shadeWidth ) );
                    }
                }
                else
                {
                    compMiddle = y0 + displayLocation.y;
                    compTipX = x0 + displayLocation.x - ( displayWay.equals ( TooltipWay.left ) ? ps.width : 0 );
                }

                if ( compMiddle - ps.height / 2 < windowSideSpacing )
                {
                    final int cw = windowSideSpacing - ( compMiddle - ps.height / 2 );
                    cornerPeak = Math.max ( shadeWidth + round + cornerSideX + 1, getHeight () / 2 - cw );
                    setLocation ( compTipX, windowSideSpacing );
                }
                else if ( compMiddle + ps.height / 2 > getParent ().getHeight () - windowSideSpacing )
                {
                    final int cw = ( compMiddle + ps.height / 2 ) - ( getParent ().getHeight () - windowSideSpacing );
                    cornerPeak = Math.min ( ps.height - shadeWidth - round - cornerSideX - 1, getHeight () / 2 + cw );
                    setLocation ( compTipX, getParent ().getHeight () - windowSideSpacing - ps.height );
                }
                else
                {
                    cornerPeak = getHeight () / 2;
                    setLocation ( compTipX, compMiddle - ps.height / 2 );
                }
            }
            setSize ( getPreferredSize () );
        }
    }

    /**
     * Custom display location relative to component
     */

    public Point getDisplayLocation ()
    {
        return displayLocation;
    }

    public void setDisplayLocation ( final int x, final int y )
    {
        setDisplayLocation ( new Point ( x, y ) );
    }

    public void setDisplayLocation ( final Point displayLocation )
    {
        this.displayLocation = displayLocation;
        updateLocation ();
    }

    /**
     * Returns bounds relative to which tooltip should be displayed.
     *
     * @return bounds relative to which tooltip should be displayed
     */
    public Rectangle getRelativeToBounds ()
    {
        return relativeToBounds;
    }

    /**
     * Sets bounds relative to which tooltip should be displayed.
     *
     * @param relativeToBounds bounds relative to which tooltip should be displayed
     */
    public void setRelativeToBounds ( final Rectangle relativeToBounds )
    {
        this.relativeToBounds = relativeToBounds;
        updateLocation ();
    }

    /**
     * Returns inner component relative to which tooltip should be displayed.
     *
     * @return inner component relative to which tooltip should be displayed
     */
    public Component getRelativeToComponent ()
    {
        return relativeToComponent != null ? relativeToComponent.get () : null;
    }

    /**
     * Sets inner component relative to which tooltip should be displayed.
     *
     * @param component inner component relative to which tooltip should be displayed
     */
    public void setRelativeToComponent ( final Component component )
    {
        this.relativeToComponent = new WeakReference ( component );
        updateLocation ();
    }

    /**
     * Minimal spacing between tooltip and window edge
     */

    public int getWindowSideSpacing ()
    {
        return windowSideSpacing;
    }

    public void setWindowSideSpacing ( final int windowSideSpacing )
    {
        this.windowSideSpacing = windowSideSpacing;
        updateLocation ();
    }

    /**
     * Tooltip corners rounding
     */

    public int getRound ()
    {
        return round;
    }

    public void setRound ( final int round )
    {
        this.round = round;
        updateLocation ();
    }

    /**
     * Tooltip shade type
     */

    public ShadeType getShadeType ()
    {
        return shadeType;
    }

    public void setShadeType ( ShadeType shadeType )
    {
        this.shadeType = shadeType;
    }

    /**
     * Tooltip shade width
     */

    public int getShadeWidth ()
    {
        return shadeWidth;
    }

    public void setShadeWidth ( final int shadeWidth )
    {
        this.shadeWidth = shadeWidth;
        updateBorder ();
        updateLocation ();
    }

    /**
     * Tooltip shade color
     */

    public Color getShadeColor ()
    {
        return shadeColor;
    }

    public void setShadeColor ( final Color shadeColor )
    {
        this.shadeColor = shadeColor;
        repaint ();
    }

    /**
     * Component to which this tooltip attached
     */

    public Component getComponent ()
    {
        return component.get ();
    }

    /**
     * Tooltip unique id
     */

    public String getId ()
    {
        return id;
    }

    /**
     * Tooltip component
     */

    public JComponent getTooltip ()
    {
        return tooltip;
    }

    public void setTooltip ( final String tooltip )
    {
        // Updating label tooltip text
        if ( this.tooltip != null && this.tooltip instanceof JLabel )
        {
            ( ( JLabel ) this.tooltip ).setText ( tooltip );
            updateBorder ();
            updateLocation ();
        }
    }

    public void setTooltip ( final JComponent tooltip )
    {
        // Removing old tooltip
        if ( this.tooltip != null )
        {
            remove ( this.tooltip );
        }

        // Adding new tooltip component
        this.tooltip = tooltip;
        add ( tooltip, BorderLayout.CENTER );
        updateBorder ();
        updateLocation ();
    }

    /**
     * Tooltip display way
     */

    public TooltipWay getDisplayWay ()
    {
        return displayWay;
    }

    public void setDisplayWay ( final TooltipWay displayWay )
    {
        this.displayWay = displayWay;
        updateBorder ();
        updateLocation ();
    }

    /**
     * Should display component hotkey on tooltip or not
     */

    public boolean isShowHotkey ()
    {
        return showHotkey;
    }

    public void setShowHotkey ( final boolean showHotkey )
    {
        this.showHotkey = showHotkey;
        updateHotkey ();
        updateBorder ();
        updateLocation ();
    }

    /**
     * Hotkey display location
     */

    public int getHotkeyLocation ()
    {
        return hotkeyLocation;
    }

    public void setHotkeyLocation ( final int hotkeyLocation )
    {
        this.hotkeyLocation = hotkeyLocation;
        updateHotkey ();
        updateBorder ();
        updateLocation ();
    }

    /**
     * Default tooltip close behavior
     */

    public boolean isDefaultCloseBehavior ()
    {
        return defaultCloseBehavior;
    }

    public void setDefaultCloseBehavior ( final boolean defaultCloseBehavior )
    {
        this.defaultCloseBehavior = defaultCloseBehavior;
    }

    /**
     * Tooltip text color
     */

    public Color getTextColor ()
    {
        return textColor;
    }

    public void setTextColor ( final Color textColor )
    {
        //todo
        this.textColor = textColor;
    }

    /**
     * Hotkey text color
     */

    public Color getHotkeyColor ()
    {
        return hotkey.getForeground ();
    }

    public void setHotkeyColor ( final Color hotkeyColor )
    {
        hotkey.setForeground ( hotkeyColor );
    }

    /**
     * Tooltip top background color
     */

    public Color getTopBgColor ()
    {
        return topBgColor;
    }

    public void setTopBgColor ( final Color topBgColor )
    {
        this.topBgColor = topBgColor;
    }

    /**
     * Tooltip bottom background color
     */

    public Color getBottomBgColor ()
    {
        return bottomBgColor;
    }

    public void setBottomBgColor ( final Color bottomBgColor )
    {
        this.bottomBgColor = bottomBgColor;
    }

    /**
     * Tooltip border color
     */

    public Color getBorderColor ()
    {
        return borderColor;
    }

    public void setBorderColor ( final Color borderColor )
    {
        this.borderColor = borderColor;
    }

    /**
     * Tooltip background opacity
     */

    public float getOpacity ()
    {
        return opacity;
    }

    public void setOpacity ( final float opacity )
    {
        this.opacity = opacity;
    }

    /**
     * Shape provider
     */

    @Override
    public Shape provideShape ()
    {
        return getTooltipShape ( getActualDisplayWay (), true );
    }

    /**
     * Tooltip background painting
     */

    @Override
    protected void paintComponent ( final Graphics g )
    {
        super.paintComponent ( g );

        // Draw settings
        final Graphics2D g2d = ( Graphics2D ) g;
        final Object aa = GraphicsUtils.setupAntialias ( g2d );

        // Fade animation and opacity
        if ( fade < 1f )
        {
            GraphicsUtils.setupAlphaComposite ( g2d, fade );
        }
        Composite oc = null;
        if ( opacity < 1f )
        {
            oc = GraphicsUtils.setupAlphaComposite ( g2d, opacity );
        }

        // Tooltip settings
        final TooltipWay displayWay = getActualDisplayWay ();

        // Initialize border and shade shape
        Shape bs = null;
        if ( shadeWidth > 0 && shadeColor != null )
        {
            bs = getTooltipShape ( displayWay, false );
        }

        // Shade
        GraphicsUtils.drawShade ( g2d, bs, shadeType, shadeColor, shadeWidth );

        // Background
        if ( topBgColor != null )
        {
            if ( bottomBgColor != null )
            {
                // Gradient background
                if ( displayWay.equals ( TooltipWay.down ) )
                {
                    g2d.setPaint ( new GradientPaint ( 0, getHeight () * 2 / 3, topBgColor, 0, getHeight (), bottomBgColor ) );
                }
                else
                {
                    g2d.setPaint ( new GradientPaint ( 0, getHeight () / 3, topBgColor, 0, getHeight (), bottomBgColor ) );
                }
            }
            else
            {
                // Single color background
                g2d.setPaint ( topBgColor );
            }
            //            g2d.fill ( bs );
            g2d.fill ( getTooltipShape ( displayWay, true ) );
        }

        // Border
        if ( borderColor != null )
        {
            g2d.setPaint ( borderColor );
            g2d.draw ( bs );
        }

        // Restore composite
        if ( oc != null )
        {
            GraphicsUtils.restoreComposite ( g2d, oc );
        }

        // Restore antialias
        GraphicsUtils.restoreAntialias ( g2d, aa );
    }

    private Shape getTooltipShape ( final TooltipWay displayWay, final boolean fill )
    {
        final Area borderShape;

        // Fill shape extend
        float fillExtend = fill ? 1f : 0;

        // Content area shape
        final float widthMinus =
                ( displayWay.equals ( TooltipWay.left ) || displayWay.equals ( TooltipWay.right ) ? cornerLength + 1 : 1 ) - fillExtend +
                        shadeWidth * 2;
        final float heightMinus =
                ( displayWay.equals ( TooltipWay.up ) || displayWay.equals ( TooltipWay.down ) ? cornerLength + 1 : 1 ) - fillExtend +
                        shadeWidth * 2;
        borderShape = new Area ( new RoundRectangle2D.Double ( shadeWidth + ( displayWay.equals ( TooltipWay.right ) ? cornerLength : 0 ),
                shadeWidth + ( displayWay.equals ( TooltipWay.down ) ? cornerLength : 0 ), getWidth () - widthMinus,
                getHeight () - heightMinus, round * 2, round * 2 ) );

        // Corner shape
        fillExtend = fill ? 0.5f : 0;
        final GeneralPath gp = new GeneralPath ( GeneralPath.WIND_EVEN_ODD );
        if ( displayWay.equals ( TooltipWay.up ) )
        {
            gp.moveTo ( cornerPeak + fillExtend, getHeight () - shadeWidth - 1 );
            gp.lineTo ( cornerPeak - cornerSideX + fillExtend, getHeight () - shadeWidth - 1 - cornerLength );
            gp.lineTo ( cornerPeak + cornerSideX + fillExtend, getHeight () - shadeWidth - 1 - cornerLength );
        }
        else if ( displayWay.equals ( TooltipWay.down ) )
        {
            gp.moveTo ( cornerPeak, shadeWidth );
            gp.lineTo ( cornerPeak - cornerSideX, shadeWidth + cornerLength );
            gp.lineTo ( cornerPeak + cornerSideX, shadeWidth + cornerLength );
        }
        else if ( displayWay.equals ( TooltipWay.left ) )
        {
            gp.moveTo ( getWidth () - shadeWidth - 1, cornerPeak + fillExtend );
            gp.lineTo ( getWidth () - shadeWidth - 1 - cornerLength, cornerPeak + fillExtend - cornerSideX );
            gp.lineTo ( getWidth () - shadeWidth - 1 - cornerLength, cornerPeak + fillExtend + cornerSideX );
        }
        else if ( displayWay.equals ( TooltipWay.right ) )
        {
            gp.moveTo ( shadeWidth, cornerPeak );
            gp.lineTo ( shadeWidth + cornerLength, cornerPeak - cornerSideX );
            gp.lineTo ( shadeWidth + cornerLength, cornerPeak + cornerSideX );
        }
        gp.closePath ();
        borderShape.add ( new Area ( gp ) );

        return borderShape;
    }

    /**
     * Tooltip listeners
     */

    public void addTooltipListener ( final TooltipListener listener )
    {
        listeners.add ( listener );
    }

    public void removeTooltipListener ( final TooltipListener listener )
    {
        listeners.remove ( listener );
    }

    public void removeAllTooltipListeners ()
    {
        listeners.clear ();
    }

    private void fireTooltipShown ()
    {
        for ( final TooltipListener listener : CollectionUtils.copy ( listeners ) )
        {
            listener.tooltipShowing ();
        }
    }

    private void fireTooltipFullyShown ()
    {
        for ( final TooltipListener listener : CollectionUtils.copy ( listeners ) )
        {
            listener.tooltipShown ();
        }
    }

    private void fireTooltipHidden ()
    {
        for ( final TooltipListener listener : CollectionUtils.copy ( listeners ) )
        {
            listener.tooltipHidden ();
        }
    }

    private void fireTooltipDestroyed ()
    {
        for ( final TooltipListener listener : CollectionUtils.copy ( listeners ) )
        {
            listener.tooltipDestroyed ();
        }
    }

    public static WebStyledLabel createDefaultComponent ( final Icon icon, final String tooltip )
    {
        final WebStyledLabel label = new WebStyledLabel ( StyleId.customtooltipLabel, tooltip, icon );
        label.setFont ( WebFonts.getSystemTooltipFont () );
        return label;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy