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

com.alee.extended.window.PopOverPainter Maven / Gradle / Ivy

The 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.extended.window;

import com.alee.api.jdk.Supplier;
import com.alee.extended.behavior.ComponentMoveBehavior;
import com.alee.extended.behavior.VisibilityBehavior;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.menu.AbstractPopupPainter;
import com.alee.laf.menu.PopupStyle;
import com.alee.laf.rootpane.WRootPaneUI;
import com.alee.utils.CoreSwingUtils;
import com.alee.utils.ProprietaryUtils;
import com.alee.utils.SystemUtils;
import com.alee.utils.swing.AncestorAdapter;
import com.thoughtworks.xstream.annotations.XStreamAlias;

import javax.swing.*;
import javax.swing.event.AncestorEvent;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

/**
 * Basic painter for {@link WebPopOver} component.
 *
 * @param  component type
 * @param  component UI type
 * @author Mikle Garin
 */
@XStreamAlias ( "PopOverPainter" )
public class PopOverPainter extends AbstractPopupPainter
        implements IPopOverPainter
{
    /**
     * todo 1. This painter must extend {@link com.alee.laf.rootpane.RootPanePainter}
     * todo 2. All {@link AbstractPopupPainter} stuff should be done by proper decoration elements instead
     */

    /**
     * Listeners.
     */
    protected transient ComponentMoveBehavior moveAdapter;
    protected transient WindowFocusListener focusListener;
    protected transient ComponentAdapter resizeAdapter;
    protected transient VisibilityBehavior windowVisibilityBehavior;

    /**
     * Whether or not popover is focused.
     */
    protected boolean popOverFocused = false;

    /**
     * Whether or not popover is attached to invoker component.
     */
    protected boolean attached = false;

    /**
     * WebPopOver display source point.
     */
    protected PopOverSourcePoint popOverSourcePoint = PopOverSourcePoint.componentSide;

    /**
     * Preferred direction in which WebPopOver should be displayed.
     */
    protected PopOverDirection preferredDirection = null;

    /**
     * Current display direction.
     */
    protected PopOverDirection currentDirection = null;

    /**
     * Preferred WebPopOver alignment relative to display source point.
     */
    protected PopOverAlignment preferredAlignment = null;

    @Override
    protected void installPropertiesAndListeners ()
    {
        super.installPropertiesAndListeners ();

        // Retrieving popover instance
        final WebPopOver popOver = getPopOver ( component );

        // Window focus listener
        focusListener = new WindowFocusListener ()
        {
            @Override
            public void windowGainedFocus ( final WindowEvent e )
            {
                setPopOverFocused ( true );
            }

            @Override
            public void windowLostFocus ( final WindowEvent e )
            {
                setPopOverFocused ( false );
            }
        };
        popOver.addWindowFocusListener ( focusListener );

        // Popover drag listener
        moveAdapter = new ComponentMoveBehavior ( component )
        {
            @Override
            protected Rectangle getDragStartBounds ( final MouseEvent e )
            {
                if ( popOver.isMovable () )
                {
                    final int sw = getShadeWidth ();
                    return new Rectangle ( sw, sw, component.getWidth () - sw * 2, component.getHeight () - sw * 2 );
                }
                else
                {
                    return null;
                }
            }

            @Override
            public void mouseDragged ( final MouseEvent e )
            {
                // De-attach dialog
                if ( dragging && attached )
                {
                    attached = false;
                    preferredDirection = null;
                    setPopupStyle ( PopupStyle.simple );
                    repaint ();
                    popOver.fireDetached ();
                }

                super.mouseDragged ( e );
            }
        };
        moveAdapter.install ();

        // Custom window shaping
        if ( !ProprietaryUtils.isWindowTransparencyAllowed () && ProprietaryUtils.isWindowShapeAllowed () )
        {
            resizeAdapter = new ComponentAdapter ()
            {
                @Override
                public void componentResized ( final ComponentEvent e )
                {
                    final Rectangle bounds = popOver.getBounds ();
                    bounds.width++;
                    bounds.height++;
                    ProprietaryUtils.setWindowShape ( popOver, provideShape ( component, bounds ) );
                }
            };
            popOver.addComponentListener ( resizeAdapter );
        }

        // Installs behavior that informs L&F about window visibility changes
        // todo Remove this behavior as soon as this painter extends RootPanePainter
        windowVisibilityBehavior = new VisibilityBehavior ( component )
        {
            @Override
            public void displayed ()
            {
                if ( popOver != null )
                {
                    /**
                     * Notifying popover listeners.
                     * Note that if this pop-over is modal this event will be fired only after it is closed.
                     * Unfortunately there is no good way to provide this event after dialog is opened but before it is closed in that case.
                     */
                    popOver.fireOpened ();

                    /**
                     * Notifying L&F about opened window.
                     */
                    WebLookAndFeel.fireWindowDisplayed ( popOver );
                }
            }

            @Override
            public void hidden ()
            {
                if ( popOver != null )
                {
                    /**
                     * Notifying popover listeners.
                     */
                    popOver.fireClosed ();

                    /**
                     * Notifying L&F about closed window.
                     */
                    WebLookAndFeel.fireWindowHidden ( popOver );
                }
            }
        };
        windowVisibilityBehavior.install ();
    }

    @Override
    protected void uninstallPropertiesAndListeners ()
    {
        // Uninstalls behavior that informs L&F about window visibility changes
        // todo Remove this behavior as soon as this painter extends RootPanePainter
        windowVisibilityBehavior.uninstall ();
        windowVisibilityBehavior = null;

        // Retrieving popover instance
        final WebPopOver popOver = getPopOver ( component );

        // Custom window shaping
        if ( resizeAdapter != null )
        {
            popOver.removeComponentListener ( resizeAdapter );
            resizeAdapter = null;
        }

        // Popover drag listener
        moveAdapter.uninstall ();
        moveAdapter = null;

        // Window focus listener
        popOver.removeWindowFocusListener ( focusListener );
        focusListener = null;

        super.uninstallPropertiesAndListeners ();
    }

    @Override
    public boolean isDecorated ()
    {
        return true;
    }

    /**
     * Returns whether this WebPopOver is focus owner or not.
     *
     * @return true if this WebPopOver is focus owner, false otherwise
     */
    public boolean isPopOverFocused ()
    {
        return popOverFocused;
    }

    /**
     * Sets whether this WebPopOver is focus owner or not.
     *
     * @param focused whether this WebPopOver is focus owner or not
     */
    public void setPopOverFocused ( final boolean focused )
    {
        if ( this.popOverFocused != focused )
        {
            this.popOverFocused = focused;
            repaint ();
        }
    }

    @Override
    protected float getCurrentShadeOpacity ()
    {
        // Reducing the shade when WebPopOver is not focused
        return popOverFocused ? shadeOpacity : shadeOpacity * 0.7f;
    }

    /**
     * Returns popup display source point.
     *
     * @return popup display source point
     */
    public PopOverSourcePoint getPopOverSourcePoint ()
    {
        return popOverSourcePoint;
    }

    /**
     * Sets popup display source point.
     *
     * @param popOverSourcePoint popup display source point
     */
    public void setPopOverSourcePoint ( final PopOverSourcePoint popOverSourcePoint )
    {
        this.popOverSourcePoint = popOverSourcePoint;
    }

    /**
     * Returns preferred direction in which WebPopOver should be displayed.
     *
     * @return preferred direction in which WebPopOver should be displayed
     */
    public PopOverDirection getPreferredDirection ()
    {
        return preferredDirection;
    }

    /**
     * Sets preferred direction in which WebPopOver should be displayed.
     *
     * @param direction preferred direction in which WebPopOver should be displayed
     */
    public void setPreferredDirection ( final PopOverDirection direction )
    {
        this.preferredDirection = direction;
    }

    /**
     * Returns current display direction.
     *
     * @return current display direction
     */
    public PopOverDirection getCurrentDirection ()
    {
        return currentDirection;
    }

    /**
     * Sets current display direction.
     *
     * @param direction current display direction
     */
    public void setCurrentDirection ( final PopOverDirection direction )
    {
        this.currentDirection = direction;
    }

    @Override
    public void configure ( final WebPopOver popOver, final PopOverLocation location )
    {
        // Updating WebPopOver variables
        attached = false;
        preferredDirection = null;
        setPopupStyle ( PopupStyle.simple );

        // Updating dialog location on screen and size
        final Dimension ss = Toolkit.getDefaultToolkit ().getScreenSize ();
        popOver.pack ();
        switch ( location )
        {
            case center:
            {
                popOver.setLocation ( ss.width / 2 - popOver.getWidth () / 2, ss.height / 2 - popOver.getHeight () / 2 );
                break;
            }
            case topLeft:
            {
                popOver.setLocation ( 0, 0 );
                break;
            }
            case topRight:
            {
                popOver.setLocation ( ss.width - popOver.getWidth (), 0 );
                break;
            }
            case bottomLeft:
            {
                popOver.setLocation ( 0, ss.height - popOver.getHeight () );
                break;
            }
            case bottomRight:
            {
                popOver.setLocation ( ss.width - popOver.getWidth (), ss.height - popOver.getHeight () );
                break;
            }
            case topCenter:
            {
                popOver.setLocation ( ss.width / 2 - popOver.getWidth () / 2, 0 );
                break;
            }
            case bottomCenter:
            {
                popOver.setLocation ( ss.width / 2 - popOver.getWidth () / 2, ss.height - popOver.getHeight () );
                break;
            }
            case leftCenter:
            {
                popOver.setLocation ( 0, ss.height / 2 - popOver.getHeight () / 2 );
                break;
            }
            case rightCenter:
            {
                popOver.setLocation ( ss.width - popOver.getWidth (), ss.height / 2 - popOver.getHeight () / 2 );
                break;
            }
        }
    }

    @Override
    public void configure ( final WebPopOver popOver, final int x, final int y )
    {
        // Updating WebPopOver variables
        attached = false;
        preferredDirection = null;
        setPopupStyle ( PopupStyle.simple );

        // Updating dialog location on screen and size
        popOver.pack ();
        popOver.setLocation ( x - getShadeWidth (), y - getShadeWidth () );
    }

    @Override
    public void configure ( final WebPopOver popOver, final Component invoker, final Supplier boundsSupplier,
                            final PopOverDirection direction, final PopOverAlignment alignment )
    {
        // Translating coordinates into screen coordinates system
        final Supplier actualBoundsProvider = boundsSupplier == null ? null : new Supplier ()
        {
            private Rectangle lastBounds = new Rectangle ();

            @Override
            public Rectangle get ()
            {
                // Invoker might be hidden while WebPopOver is still visible
                // This is why we should simply stop updating its position when that happens
                // It is not the best workaround but at least it will keep us safe from exceptions
                if ( invoker.isShowing () )
                {
                    final Rectangle bounds = boundsSupplier.get ();
                    final Point los = CoreSwingUtils.locationOnScreen ( invoker );
                    lastBounds = new Rectangle ( los.x + bounds.x, los.y + bounds.y, bounds.width, bounds.height );
                }
                return lastBounds;
            }
        };

        // Updating WebPopOver variables
        attached = true;
        preferredDirection = direction != null ? direction : PopOverDirection.down;
        preferredAlignment = alignment;

        // Updating dialog location on screen and size
        popOver.pack ();
        updatePopOverLocation ( popOver, invoker, actualBoundsProvider );
        installPopOverLocationUpdater ( popOver, invoker, actualBoundsProvider );
    }

    /**
     * Updates WebPopOver location on screen.
     *
     * @param popOver        WebPopOver to configure
     * @param invoker        invoker component
     * @param boundsProvider source area bounds provider
     */
    protected void updatePopOverLocation ( final WebPopOver popOver, final Component invoker, final Supplier boundsProvider )
    {
        if ( boundsProvider != null )
        {
            updatePopOverLocation ( popOver, invoker, boundsProvider.get () );
        }
        else
        {
            updatePopOverLocation ( popOver, invoker );
        }
    }

    /**
     * Updates WebPopOver location on screen.
     *
     * @param popOver WebPopOver to configure
     * @param invoker invoker component
     */
    protected void updatePopOverLocation ( final WebPopOver popOver, final Component invoker )
    {
        if ( invoker.isShowing () )
        {
            updatePopOverLocation ( popOver, invoker, CoreSwingUtils.getBoundsOnScreen ( invoker ) );
        }
    }

    /**
     * Updates WebPopOver location on screen.
     *
     * @param popOver       WebPopOver to configure
     * @param invoker       invoker component
     * @param invokerBounds invoker component bounds on screen
     */
    protected void updatePopOverLocation ( final WebPopOver popOver, final Component invoker, final Rectangle invokerBounds )
    {
        // Applying proper painter style
        setPopupStyle ( PopupStyle.dropdown );

        // WebPopOver preferred size without shadow
        final Dimension size = popOver.getSize ();
        final int sw = getShadeWidth ();
        final int round = getRound ();
        final int cw = getCornerWidth ();
        final Dimension ps = new Dimension ( size.width - sw * 2, size.height - sw * 2 );
        final Rectangle screenBounds = SystemUtils.getDeviceBounds ( invoker, false );

        // Determining actual direction
        final PopOverDirection actualDirection = getActualDirection ( invokerBounds, ltr, cw, ps, screenBounds );
        setCornerSide ( actualDirection.getCornerSide ( ltr ) );
        currentDirection = actualDirection;

        // Determining position according to alignment
        final Point actualLocation = getActualLocation ( invokerBounds, ltr, round, cw, ps, screenBounds, actualDirection );
        actualLocation.x -= sw;
        actualLocation.y -= sw;

        // Updating corner position
        setCornerAlignment ( -1 );
        setRelativeCorner ( getRelativeCorner ( invokerBounds, actualDirection, actualLocation ) );

        // Updating WebPopOver location
        popOver.setLocation ( actualLocation );
    }

    /**
     * Installs listeners to update WebPopOver location.
     *
     * @param popOver        WebPopOver to configure
     * @param invoker        invoker component
     * @param boundsProvider source area bounds provider
     */
    protected void installPopOverLocationUpdater ( final WebPopOver popOver, final Component invoker,
                                                   final Supplier boundsProvider )
    {
        // Invoker component window
        final Window invokerWindow = CoreSwingUtils.getWindowAncestor ( invoker );

        // Invoker window follow adapter
        final WindowFollowBehavior windowFollowBehavior = new WindowFollowBehavior ( popOver, invokerWindow )
        {
            @Override
            public boolean isEnabled ()
            {
                return !PopOverPainter.this.attached;
            }
        };
        windowFollowBehavior.install ();

        // Invoker window state listener
        final WindowStateListener windowStateListener = new WindowStateListener ()
        {
            @Override
            public void windowStateChanged ( final WindowEvent e )
            {
                if ( attached )
                {
                    if ( e.getNewState () == WindowEvent.WINDOW_ICONIFIED )
                    {
                        popOver.setVisible ( false );
                    }
                    else if ( e.getOldState () == WindowEvent.WINDOW_ICONIFIED )
                    {
                        popOver.setVisible ( true );
                    }
                }
            }
        };
        invokerWindow.addWindowStateListener ( windowStateListener );

        // Invoker window adapter
        final ComponentAdapter invokerWindowAdapter = new ComponentAdapter ()
        {
            @Override
            public void componentMoved ( final ComponentEvent e )
            {
                if ( attached )
                {
                    updatePopOverLocation ( popOver, invoker, boundsProvider );
                    windowFollowBehavior.updateLastLocation ();
                }
            }
        };
        invokerWindow.addComponentListener ( invokerWindowAdapter );

        // Invoker component adapter
        final ComponentAdapter invokerAdapter = new ComponentAdapter ()
        {
            @Override
            public void componentMoved ( final ComponentEvent e )
            {
                if ( attached )
                {
                    updatePopOverLocation ( popOver, invoker, boundsProvider );
                    windowFollowBehavior.updateLastLocation ();
                }
            }

            @Override
            public void componentResized ( final ComponentEvent e )
            {
                if ( attached )
                {
                    updatePopOverLocation ( popOver, invoker, boundsProvider );
                    windowFollowBehavior.updateLastLocation ();
                }
            }
        };
        invoker.addComponentListener ( invokerAdapter );

        final AncestorAdapter ancestorAdapter;
        if ( invoker instanceof JComponent )
        {
            ancestorAdapter = new AncestorAdapter ()
            {
                @Override
                public void ancestorMoved ( final AncestorEvent event )
                {
                    if ( attached )
                    {
                        updatePopOverLocation ( popOver, invoker, boundsProvider );
                        windowFollowBehavior.updateLastLocation ();
                    }
                }

                // todo Probably hide WebPopOver in some cases here?
                //                @Override
                //                public void ancestorRemoved ( final AncestorEvent event )
                //                {
                //                    super.ancestorRemoved ( event );
                //                }
            };
            ( ( JComponent ) invoker ).addAncestorListener ( ancestorAdapter );
        }
        else
        {
            ancestorAdapter = null;
        }

        // WebPopOver orientation change listener
        final PropertyChangeListener orientationListener = new PropertyChangeListener ()
        {
            @Override
            public void propertyChange ( final PropertyChangeEvent evt )
            {
                updatePopOverLocation ( popOver, invoker, boundsProvider );
                windowFollowBehavior.updateLastLocation ();
            }
        };
        popOver.addPropertyChangeListener ( WebLookAndFeel.COMPONENT_ORIENTATION_PROPERTY, orientationListener );

        // Removing all listeners on window close event
        popOver.addPopOverListener ( new PopOverAdapter ()
        {
            @Override
            public void reopened ( final WebPopOver popOver )
            {
                destroy ();
            }

            @Override
            public void closed ( final WebPopOver popOver )
            {
                destroy ();
            }

            /**
             * Destroys popover.
             */
            protected void destroy ()
            {
                popOver.removePopOverListener ( this );
                invokerWindow.removeComponentListener ( invokerWindowAdapter );
                windowFollowBehavior.uninstall ();
                invoker.removeComponentListener ( invokerAdapter );
                if ( invoker instanceof JComponent )
                {
                    ( ( JComponent ) invoker ).removeAncestorListener ( ancestorAdapter );
                }
                popOver.removePropertyChangeListener ( WebLookAndFeel.COMPONENT_ORIENTATION_PROPERTY, orientationListener );
            }
        } );
    }

    /**
     * Returns relative corner position.
     *
     * @param ib              invoker component bounds on screen
     * @param actualDirection actual WebPopOver direction
     * @param actualLocation  actual WebPopOver location
     * @return relative corner position
     */
    protected int getRelativeCorner ( final Rectangle ib, final PopOverDirection actualDirection, final Point actualLocation )
    {
        switch ( actualDirection )
        {
            case up:
            case down:
                return ib.x + ib.width / 2 - actualLocation.x;
            case left:
            case right:
                return ib.y + ib.height / 2 - actualLocation.y;
        }
        return -1;
    }

    /**
     * Returns actual WebPopOver location.
     * Shade width is not yet taken into account within this location.
     *
     * @param ib              invoker component bounds on screen
     * @param ltr             whether LTR orientation is active or not
     * @param round           corners round
     * @param cw              corner width
     * @param ps              WebPopOver size without shadow widths
     * @param screenBounds    screen bounds
     * @param actualDirection actual WebPopOver direction
     * @return actual WebPopOver location
     */
    protected Point getActualLocation ( final Rectangle ib, final boolean ltr, final int round, final int cw, final Dimension ps,
                                        final Rectangle screenBounds, final PopOverDirection actualDirection )
    {
        final Point sp = getActualSourcePoint ( ib, ltr, actualDirection );
        if ( actualDirection == PopOverDirection.up )
        {
            if ( preferredAlignment == PopOverAlignment.centered )
            {
                final Point location = new Point ( sp.x - ps.width / 2, sp.y - cw - ps.height );
                return checkRightCollision ( checkLeftCollision ( location, screenBounds ), ps, screenBounds );
            }
            else if ( preferredAlignment == ( ltr ? PopOverAlignment.leading : PopOverAlignment.trailing ) )
            {
                return checkLeftCollision ( new Point ( sp.x + cw * 2 + round - ps.width, sp.y - cw - ps.height ), screenBounds );
            }
            else if ( preferredAlignment == ( ltr ? PopOverAlignment.trailing : PopOverAlignment.leading ) )
            {
                return checkRightCollision ( new Point ( sp.x - cw * 2 - round, sp.y - cw - ps.height ), ps, screenBounds );
            }
        }
        else if ( actualDirection == PopOverDirection.down )
        {
            if ( preferredAlignment == PopOverAlignment.centered )
            {
                final Point location = new Point ( sp.x - ps.width / 2, sp.y + cw );
                return checkRightCollision ( checkLeftCollision ( location, screenBounds ), ps, screenBounds );
            }
            else if ( preferredAlignment == ( ltr ? PopOverAlignment.leading : PopOverAlignment.trailing ) )
            {
                return checkLeftCollision ( new Point ( sp.x + cw * 2 + round - ps.width, sp.y + cw ), screenBounds );
            }
            else if ( preferredAlignment == ( ltr ? PopOverAlignment.trailing : PopOverAlignment.leading ) )
            {
                return checkRightCollision ( new Point ( sp.x - cw * 2 - round, sp.y + cw ), ps, screenBounds );
            }
        }
        else if ( actualDirection == ( ltr ? PopOverDirection.left : PopOverDirection.right ) )
        {
            if ( preferredAlignment == PopOverAlignment.centered )
            {
                final Point location = new Point ( sp.x - cw - ps.width, sp.y - ps.height / 2 );
                return checkBottomCollision ( checkTopCollision ( location, screenBounds ), ps, screenBounds );
            }
            else if ( preferredAlignment == PopOverAlignment.leading )
            {
                return checkTopCollision ( new Point ( sp.x - cw - ps.width, sp.y + cw * 2 + round - ps.height ), screenBounds );
            }
            else if ( preferredAlignment == PopOverAlignment.trailing )
            {
                return checkBottomCollision ( new Point ( sp.x - cw - ps.width, sp.y - cw * 2 - round ), ps, screenBounds );
            }
        }
        else if ( actualDirection == ( ltr ? PopOverDirection.right : PopOverDirection.left ) )
        {
            if ( preferredAlignment == PopOverAlignment.centered )
            {
                final Point location = new Point ( sp.x + cw, sp.y - ps.height / 2 );
                return checkBottomCollision ( checkTopCollision ( location, screenBounds ), ps, screenBounds );
            }
            else if ( preferredAlignment == PopOverAlignment.leading )
            {
                return checkTopCollision ( new Point ( sp.x + cw, sp.y + cw * 2 + round - ps.height ), screenBounds );
            }
            else if ( preferredAlignment == PopOverAlignment.trailing )
            {
                return checkBottomCollision ( new Point ( sp.x + cw, sp.y - cw * 2 - round ), ps, screenBounds );
            }
        }
        return null;
    }

    /**
     * Checks whether WebPopOver will collide with top screen border and modifies location accordingly.
     *
     * @param location     approximate WebPopOver location
     * @param screenBounds screen bounds
     * @return either modified or unmodified WebPopOver location
     */
    protected Point checkTopCollision ( final Point location, final Rectangle screenBounds )
    {
        if ( location.y < screenBounds.y )
        {
            location.y = screenBounds.y;
        }
        return location;
    }

    /**
     * Checks whether WebPopOver will collide with bottom screen border and modifies location accordingly.
     *
     * @param location     approximate WebPopOver location
     * @param ps           WebPopOver size without shadow widths
     * @param screenBounds screen bounds
     * @return either modified or unmodified WebPopOver location
     */
    protected Point checkBottomCollision ( final Point location, final Dimension ps, final Rectangle screenBounds )
    {
        if ( location.y + ps.height > screenBounds.y + screenBounds.height )
        {
            location.y = screenBounds.y + screenBounds.height - ps.height;
        }
        return location;
    }

    /**
     * Checks whether WebPopOver will collide with left screen border and modifies location accordingly.
     *
     * @param location     approximate WebPopOver location
     * @param screenBounds screen bounds
     * @return either modified or unmodified WebPopOver location
     */
    protected Point checkLeftCollision ( final Point location, final Rectangle screenBounds )
    {
        if ( location.x < screenBounds.x )
        {
            location.x = screenBounds.x;
        }
        return location;
    }

    /**
     * Checks whether WebPopOver will collide with right screen border and modifies location accordingly.
     *
     * @param location     approximate WebPopOver location
     * @param ps           WebPopOver size without shadow widths
     * @param screenBounds screen bounds
     * @return either modified or unmodified WebPopOver location
     */
    protected Point checkRightCollision ( final Point location, final Dimension ps, final Rectangle screenBounds )
    {
        if ( location.x + ps.width > screenBounds.x + screenBounds.width )
        {
            location.x = screenBounds.x + screenBounds.width - ps.width;
        }
        return location;
    }

    /**
     * Returns actual direction depending on preferred WebPopOver direction, its sizes and source point.
     *
     * @param ib           invoker component bounds on screen
     * @param ltr          whether LTR orientation is active or not
     * @param cw           corner with
     * @param ps           WebPopOver size without shadow widths
     * @param screenBounds screen bounds
     * @return actual WebPopOver direction
     */
    protected PopOverDirection getActualDirection ( final Rectangle ib, final boolean ltr, final int cw, final Dimension ps,
                                                    final Rectangle screenBounds )
    {
        for ( final PopOverDirection checkedDirection : preferredDirection.getPriority () )
        {
            final Point sp = getActualSourcePoint ( ib, ltr, checkedDirection );
            if ( checkedDirection == PopOverDirection.up )
            {
                if ( sp.y - cw - ps.height > screenBounds.y )
                {
                    return checkedDirection;
                }
            }
            else if ( checkedDirection == PopOverDirection.down )
            {
                if ( sp.y + cw + ps.height < screenBounds.y + screenBounds.height )
                {
                    return checkedDirection;
                }
            }
            else if ( checkedDirection == ( ltr ? PopOverDirection.left : PopOverDirection.right ) )
            {
                if ( sp.x - cw - ps.width > screenBounds.x )
                {
                    return checkedDirection;
                }
            }
            else if ( checkedDirection == ( ltr ? PopOverDirection.right : PopOverDirection.left ) )
            {
                if ( sp.x + cw + ps.width < screenBounds.x + screenBounds.width )
                {
                    return checkedDirection;
                }
            }
        }
        return preferredDirection;
    }

    /**
     * Returns actual source point depending on WebPopOver direction and invoker component location on screen.
     *
     * @param ib        invoker component bounds on screen
     * @param ltr       whether LTR orientation is active or not
     * @param direction WebPopOver direction  @return actual source point
     * @return actual source point depending on WebPopOver direction and invoker component location on screen
     */
    protected Point getActualSourcePoint ( final Rectangle ib, final boolean ltr, final PopOverDirection direction )
    {
        if ( popOverSourcePoint == PopOverSourcePoint.componentCenter )
        {
            return new Point ( ib.x + ib.width / 2, ib.y + ib.height / 2 );
        }
        else
        {
            if ( direction == PopOverDirection.up )
            {
                return new Point ( ib.x + ib.width / 2, ib.y );
            }
            else if ( direction == PopOverDirection.down )
            {
                return new Point ( ib.x + ib.width / 2, ib.y + ib.height );
            }
            else if ( direction == ( ltr ? PopOverDirection.left : PopOverDirection.right ) )
            {
                return new Point ( ib.x, ib.y + ib.height / 2 );
            }
            else if ( direction == ( ltr ? PopOverDirection.right : PopOverDirection.left ) )
            {
                return new Point ( ib.x + ib.width, ib.y + ib.height / 2 );
            }
        }
        return null;
    }

    /**
     * Returns popover instance for the specified root pane.
     *
     * @param c root pane
     * @return popover instance for the specified root pane
     */
    protected final WebPopOver getPopOver ( final C c )
    {
        return ( WebPopOver ) c.getClientProperty ( WebPopOver.POPOVER_INSTANCE );
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy