/* * 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.extended.painter.Painter; import com.alee.laf.panel.WebPanel; import com.alee.managers.focus.DefaultFocusTracker; import com.alee.managers.focus.FocusManager; 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.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. * * @author Mikle Garin * @see PopupManager * @see 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 focusableChilds = 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.getDefaultPopupPainter () ); } public WebPopup ( final PopupStyle popupStyle ) { this ( PopupManager.getPopupPainter ( popupStyle ) ); } public WebPopup ( final Painter stylePainter ) { super ( stylePainter ); initializePopup (); } /** * Initializes various popup settings. */ protected void initializePopup () { setOpaque ( false ); // 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 recieve 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 styling */ public void setPopupStyle ( final PopupStyle popupStyle ) { setPainter ( PopupManager.getPopupPainter ( popupStyle ) ); } /** * 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; } /** * Focusable components which will not force popup to close */ public List getFocusableChilds () { return focusableChilds; } public void addFocusableChild ( final Component child ) { focusableChilds.add ( child ); } public void removeFocusableChild ( final Component child ) { focusableChilds.remove ( child ); } public boolean isChildFocused () { for ( final Component child : focusableChilds ) { if ( SwingUtils.hasFocusOwner ( child ) ) { return true; } } return false; } /** * Popup display methods */ public void showAsPopupMenu ( final Component component ) { showPopup ( component, new DataProvider () { @Override public Rectangle provide () { // Detrmining 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 */ /** * {@inheritDoc} */ @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 (); } } /** * {@inheritDoc} */ @Override protected void paintComponent ( final Graphics g ) { // Fade animation and transparency if ( fade < 1f ) { GraphicsUtils.setupAlphaComposite ( ( Graphics2D ) g, fade ); } super.paintComponent ( g ); } }