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

com.alee.managers.popup.WebPopup 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.popup;

import com.alee.laf.panel.WebPanel;
import com.alee.managers.focus.DefaultFocusTracker;
import com.alee.managers.focus.FocusManager;
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.swing.*;

import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.*;
import java.awt.event.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * This is base popup class which offers basic popups functionality and contains all features needed to create great-looking popups within
 * the window root pane bounds. To create one outside of the window root pane bounds consider using customized dialogs, frames or existing
 * {@link com.alee.extended.window.WebPopOver} component.
 *
 * @author Mikle Garin
 * @see com.alee.managers.popup.PopupManager
 * @see com.alee.managers.popup.PopupLayer
 */

public class WebPopup extends WebPanel
{
    protected List popupListeners = new ArrayList ( 2 );

    // Popup constants
    protected static final int fadeFps = 24;
    protected static final long fadeTime = 400;

    protected boolean animated = false;
    protected boolean closeOnFocusLoss = false;
    protected boolean requestFocusOnShow = true;
    protected Component defaultFocusComponent = null;
    protected List> focusableChildren = new ArrayList> ();

    protected Component lastComponent = null;
    protected ComponentListener lastComponentListener = null;
    protected AncestorListener lastAncestorListener = null;

    protected boolean focused = false;

    // Animation variables
    protected FadeStateType fadeStateType;
    protected float fade = 0;
    protected WebTimer fadeTimer;

    // Focus tracker strong reference
    protected DefaultFocusTracker focusTracker;

    public WebPopup ()
    {
        this ( PopupManager.getDefaultPopupStyleId () );
    }

    public WebPopup ( final StyleId styleId )
    {
        super ( styleId );
        initializePopup ();
    }

    /**
     * Initializes various popup settings.
     */
    protected void initializePopup ()
    {
        // Popup doesn't allow focus to move outside of it
        setFocusCycleRoot ( true );

        // Listeners to block events passing to underlying components
        EmptyMouseAdapter.install ( this );

        // Fade in-out timer
        fadeTimer = new WebTimer ( "WebPopup.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 );
                        WebPopup.this.repaint ();
                    }
                    else
                    {
                        fadeTimer.stop ();
                    }
                }
                else if ( fadeStateType.equals ( FadeStateType.fadeOut ) )
                {
                    if ( fade > 0 )
                    {
                        fade = Math.max ( fade - fadeSpeed, 0f );
                        WebPopup.this.repaint ();
                    }
                    else
                    {
                        hidePopupImpl ();
                        fadeTimer.stop ();
                    }
                }
            }
        } );

        // Listener to determine popup appearance and disappearance
        addAncestorListener ( new AncestorAdapter ()
        {
            @Override
            public void ancestorAdded ( final AncestorEvent event )
            {
                if ( requestFocusOnShow )
                {
                    if ( defaultFocusComponent != null )
                    {
                        defaultFocusComponent.requestFocusInWindow ();
                    }
                    else
                    {
                        WebPopup.this.transferFocus ();
                    }
                }

                // Starting fade-in animation
                if ( animated )
                {
                    fade = 0;
                    fadeStateType = FadeStateType.fadeIn;
                    fadeTimer.start ();
                }
                else
                {
                    fade = 1;
                }

                // Informing about popup state change
                firePopupOpened ();
            }

            @Override
            public void ancestorRemoved ( final AncestorEvent event )
            {
                // Informing about popup state change
                firePopupClosed ();
            }
        } );

        // Focus tracking
        focusTracker = new DefaultFocusTracker ( true )
        {
            @Override
            public boolean isTrackingEnabled ()
            {
                return WebPopup.this.isShowing ();
            }

            @Override
            public void focusChanged ( final boolean focused )
            {
                WebPopup.this.focusChanged ( focused );
            }
        };
        FocusManager.addFocusTracker ( this, focusTracker );
    }

    /**
     * Called when this popup receive or lose focus.
     * You can your own behavior for focus change by overriding this method.
     *
     * @param focused whether popup has focus or not
     */
    protected void focusChanged ( final boolean focused )
    {
        // todo Replace with MultiFocusTracker (for multiply components)
        if ( WebPopup.this.isShowing () && !focused && !isChildFocused () && closeOnFocusLoss )
        {
            hidePopup ();
        }
    }

    /**
     * Returns popup layer this WebPopup is added into.
     *
     * @return popup layer this WebPopup is added into
     */
    public PopupLayer getPopupLayer ()
    {
        return ( PopupLayer ) getParent ();
    }

    /**
     * Popup settings
     */

    public boolean isAnimated ()
    {
        return animated;
    }

    public void setAnimated ( final boolean animated )
    {
        this.animated = animated;
    }

    public boolean isCloseOnFocusLoss ()
    {
        return closeOnFocusLoss;
    }

    public void setCloseOnFocusLoss ( final boolean closeOnFocusLoss )
    {
        this.closeOnFocusLoss = closeOnFocusLoss;
    }

    public boolean isRequestFocusOnShow ()
    {
        return requestFocusOnShow;
    }

    public void setRequestFocusOnShow ( final boolean requestFocusOnShow )
    {
        this.requestFocusOnShow = requestFocusOnShow;
    }

    public Component getDefaultFocusComponent ()
    {
        return defaultFocusComponent;
    }

    public void setDefaultFocusComponent ( final Component defaultFocusComponent )
    {
        this.defaultFocusComponent = defaultFocusComponent;
    }

    /**
     * Returns focusable children that don't force dialog to close even if it set to close on focus loss.
     *
     * @return focusable children that don't force dialog to close even if it set to close on focus loss
     */
    public List getFocusableChildren ()
    {
        final List actualFocusableChildren = new ArrayList ( focusableChildren.size () );
        for ( final WeakReference focusableChild : focusableChildren )
        {
            final Component component = focusableChild.get ();
            if ( component != null )
            {
                actualFocusableChildren.add ( component );
            }
        }
        return actualFocusableChildren;
    }

    /**
     * Adds focusable child that won't force dialog to close even if it set to close on focus loss.
     *
     * @param child focusable child that won't force dialog to close even if it set to close on focus loss
     */
    public void addFocusableChild ( final Component child )
    {
        focusableChildren.add ( new WeakReference ( child ) );
    }

    /**
     * Removes focusable child that doesn't force dialog to close even if it set to close on focus loss.
     *
     * @param child focusable child that doesn't force dialog to close even if it set to close on focus loss
     */
    public void removeFocusableChild ( final Component child )
    {
        focusableChildren.remove ( child );
    }

    /**
     * Returns whether one of focusable children is focused or not.
     *
     * @return true if one of focusable children is focused, false otherwise
     */
    public boolean isChildFocused ()
    {
        for ( final WeakReference focusableChild : focusableChildren )
        {
            final Component component = focusableChild.get ();
            if ( component != null )
            {
                if ( SwingUtils.hasFocusOwner ( component ) )
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Popup display methods
     */

    public void showAsPopupMenu ( final Component component )
    {
        showPopup ( component, new DataProvider ()
        {
            @Override
            public Rectangle provide ()
            {
                // Determining component position inside window
                final Rectangle cb = SwingUtils.getBoundsInWindow ( component );
                final Dimension rps = SwingUtils.getRootPane ( component ).getSize ();
                final Dimension ps = WebPopup.this.getPreferredSize ();
                //        Painter bp = getPainter ();
                //        Insets bm = bp != null ? bp.getMargin ( this ) : new Insets ( 0, 0, 0, 0 );

                // Choosing display way
                final Point p = new Point ();
                if ( cb.x + ps.width < rps.width || rps.width - cb.x - ps.width > cb.x )
                {
                    // Expanding popup to the right side
                    p.x = 0;
                }
                else
                {
                    // Expanding popup to the left side
                    p.x = cb.width - ps.width;
                }
                if ( cb.y + cb.height + ps.height < rps.height || rps.height - cb.y - cb.height - ps.height > cb.y )
                {
                    // Displaying popup below the component
                    p.y = cb.height;
                }
                else
                {
                    // Displaying popup above the component
                    p.y = -ps.height;
                }

                // Returning proper location
                return new Rectangle ( p, WebPopup.this.getPreferredSize () );
            }
        } );
    }

    public void showPopup ( final Component component )
    {
        PopupManager.showPopup ( component, this, requestFocusOnShow );
        clearLocationListeners ();
    }

    public void showPopup ( final Component component, final Rectangle bounds )
    {
        showPopup ( component, bounds.x, bounds.y, bounds.width, bounds.height );
    }

    public void showPopup ( final Component component, final int x, final int y, final int width, final int height )
    {
        updatePopupBounds ( component, x, y, width, height );
        PopupManager.showPopup ( component, this, requestFocusOnShow );
        updateComponentAncestorListener ( component, x, y, width, height );
    }

    public void showPopup ( final Component component, final Point location )
    {
        showPopup ( component, location.x, location.y );
    }

    public void showPopup ( final Component component, final int x, final int y )
    {
        showPopup ( component, new DataProvider ()
        {
            @Override
            public Rectangle provide ()
            {
                final Dimension ps = WebPopup.this.getPreferredSize ();
                return new Rectangle ( x, y, ps.width, ps.height );
            }
        } );
    }

    public void showPopup ( final Component component, final DataProvider boundsProvider )
    {
        updatePopupBounds ( component, boundsProvider.provide () );
        PopupManager.showPopup ( component, this, requestFocusOnShow );
        updateLocationListeners ( component, boundsProvider );
    }

    protected void updatePopupBounds ( final Component component, final Rectangle bounds )
    {
        updatePopupBounds ( component, bounds.x, bounds.y, bounds.width, bounds.height );
    }

    protected void updatePopupBounds ( final Component component, final int x, final int y, final int width, final int height )
    {
        // Updating popup bounds with component-relative values
        if ( component.isShowing () )
        {
            final Rectangle cb = SwingUtils.getBoundsInWindow ( component );
            setBounds ( cb.x + x, cb.y + y, width, height );
            revalidate ();
            repaint ();
        }
    }

    protected void updateComponentAncestorListener ( final Component component, final int x, final int y, final int width,
                                                     final int height )
    {
        clearLocationListeners ();

        lastComponent = component;
        if ( component instanceof JComponent )
        {
            lastAncestorListener = new AncestorAdapter ()
            {
                @Override
                public void ancestorMoved ( final AncestorEvent event )
                {
                    updatePopupBounds ( component, x, y, width, height );
                }
            };
            ( ( JComponent ) lastComponent ).addAncestorListener ( lastAncestorListener );
        }
    }

    protected void updateLocationListeners ( final Component component, final DataProvider boundsProvider )
    {
        clearLocationListeners ();

        lastComponent = component;

        lastComponentListener = new ComponentAdapter ()
        {
            @Override
            public void componentResized ( final ComponentEvent e )
            {
                updatePopupBounds ( component, boundsProvider.provide () );
            }

            @Override
            public void componentMoved ( final ComponentEvent e )
            {
                updatePopupBounds ( component, boundsProvider.provide () );
            }
        };
        lastComponent.addComponentListener ( lastComponentListener );

        if ( component instanceof JComponent )
        {
            lastAncestorListener = new AncestorAdapter ()
            {
                @Override
                public void ancestorMoved ( final AncestorEvent event )
                {
                    updatePopupBounds ( component, boundsProvider.provide () );
                }
            };
            ( ( JComponent ) lastComponent ).addAncestorListener ( lastAncestorListener );
        }
    }

    protected void clearLocationListeners ()
    {
        if ( lastComponent != null )
        {
            if ( lastComponentListener != null )
            {
                lastComponent.removeComponentListener ( lastComponentListener );
            }
            if ( lastAncestorListener != null && lastComponent instanceof JComponent )
            {
                ( ( JComponent ) lastComponent ).removeAncestorListener ( lastAncestorListener );
            }
        }
    }

    /**
     * Modal popup display methods
     */

    public void showPopupAsModal ( final Component component )
    {
        showPopupAsModal ( component, false, false );
    }

    public void showPopupAsModal ( final Component component, final boolean hfill, final boolean vfill )
    {
        PopupManager.showModalPopup ( component, this, hfill, vfill );
    }

    /**
     * Popup hide methods
     */

    public void hidePopup ()
    {
        if ( animated )
        {
            fadeStateType = FadeStateType.fadeOut;
            if ( !fadeTimer.isRunning () )
            {
                fadeTimer.start ();
            }
        }
        else
        {
            hidePopupImpl ();
        }
    }

    protected void hidePopupImpl ()
    {
        clearLocationListeners ();

        final PopupLayer layer = ( PopupLayer ) this.getParent ();
        if ( layer != null )
        {
            layer.hidePopup ( this );
        }
    }

    /**
     * Popup pack method
     */

    public void packPopup ()
    {
        setSize ( getPreferredSize () );
    }

    public void updateBounds ()
    {
        if ( lastAncestorListener != null )
        {
            // todo Replace with a better solution
            lastAncestorListener.ancestorMoved ( null );
        }
    }

    /**
     * Shape-based point check
     */

    @Override
    public boolean contains ( final int x, final int y )
    {
        return fadeStateType != FadeStateType.fadeOut && provideShape ().contains ( x, y );
    }

    /**
     * Popup listeners
     */

    public void addPopupListener ( final PopupListener listener )
    {
        popupListeners.add ( listener );
    }

    public void removePopupListener ( final PopupListener listener )
    {
        popupListeners.remove ( listener );
    }

    public void firePopupWillBeOpened ()
    {
        for ( final PopupListener listener : CollectionUtils.copy ( popupListeners ) )
        {
            listener.popupWillBeOpened ();
        }
    }

    public void firePopupOpened ()
    {
        for ( final PopupListener listener : CollectionUtils.copy ( popupListeners ) )
        {
            listener.popupOpened ();
        }
    }

    public void firePopupWillBeClosed ()
    {
        for ( final PopupListener listener : CollectionUtils.copy ( popupListeners ) )
        {
            listener.popupWillBeClosed ();
        }
    }

    public void firePopupClosed ()
    {
        for ( final PopupListener listener : CollectionUtils.copy ( popupListeners ) )
        {
            listener.popupClosed ();
        }
    }

    @Override
    protected void paintComponent ( final Graphics g )
    {
        // Fade animation and opacity
        if ( fade < 1f )
        {
            GraphicsUtils.setupAlphaComposite ( ( Graphics2D ) g, fade );
        }
        super.paintComponent ( g );
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy