001    /*
002     * $Id: JXGlassBox.java 3235 2009-02-01 15:01:07Z rah003 $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.applet.Applet;
025    import java.awt.Color;
026    import java.awt.Component;
027    import java.awt.Container;
028    import java.awt.Dimension;
029    import java.awt.Graphics;
030    import java.awt.Rectangle;
031    import java.awt.Window;
032    import java.awt.event.ActionEvent;
033    import java.awt.event.ActionListener;
034    import java.awt.event.MouseAdapter;
035    import java.awt.event.MouseEvent;
036    
037    import javax.swing.JComponent;
038    import javax.swing.SwingConstants;
039    import javax.swing.SwingUtilities;
040    import javax.swing.Timer;
041    
042    /**
043     * Component used to display transluscent user-interface content.
044     * This component and all of its content will be displayed with the specified
045     * "alpha" transluscency property value.  When this component is made visible,
046     * it's content will fade in until the alpha transluscency level is reached.
047     * <p>
048     * If the glassbox's &quot;dismissOnClick&quot; property is <code>true</code>
049     * (the default) then the glassbox will be made invisible when the user
050     * clicks on it.</p>
051     * <p>
052     * This component is particularly useful for displaying transient messages
053     * on the glasspane.</p>
054     *
055     * @author Amy Fowler
056     * @author Karl George Schaefer
057     * @version 1.0
058     */
059    public class JXGlassBox extends JXPanel {
060        private static final int SHOW_DELAY = 30; // ms
061        private static final int TIMER_INCREMENT = 10; // ms
062    
063        private float alphaStart = 0.01f;
064        private float alphaEnd = 0.8f;
065    
066        private Timer animateTimer;
067        private float alphaIncrement = 0.02f;
068    
069        private boolean dismissOnClick = false;
070        private MouseAdapter dismissListener = null;
071    
072        public JXGlassBox() {
073            setOpaque(false);
074            setAlpha(alphaStart);
075            setBackground(Color.white);
076            setDismissOnClick(true);
077    
078            animateTimer = new Timer(TIMER_INCREMENT, new ActionListener() {
079                public void actionPerformed(ActionEvent e) {
080                    setAlpha(Math.min(alphaEnd, getAlpha() + alphaIncrement));
081                }
082            });
083        }
084    
085        public JXGlassBox(float alpha) {
086            this();
087            setAlpha(alpha);
088        }
089    
090        public void setAlpha(float alpha) {
091            super.setAlpha(alpha);
092            this.alphaIncrement = (alphaEnd - alphaStart)/(SHOW_DELAY/TIMER_INCREMENT);
093        }
094    
095        /**
096         * Dismisses this glass box. This causes the glass box to be removed from
097         * it's parent and ensure that the display is correctly updated.
098         */
099        public void dismiss() {
100            JComponent parent = (JComponent) getParent();
101            
102            if (parent != null) {
103                Container toplevel = parent.getTopLevelAncestor();
104                parent.remove(this);
105                toplevel.validate();
106                toplevel.repaint();
107            }
108        }
109    
110        /**
111         * Determines if the glass box if dismissed when a user clicks on it.
112         * 
113         * @return {@code true} if the glass box can be dismissed with a click;
114         *         {@code false} otherwise
115         * @see #setDismissOnClick(boolean)
116         * @see #dismiss()
117         */
118        public boolean isDismissOnClick() {
119            return dismissOnClick;
120        }
121    
122        /**
123         * Configures the glass box to dismiss (or not) when clicked.
124         * 
125         * @param dismissOnClick
126         *            {@code true} if the glass box should dismiss when clicked;
127         *            {@code false} otherwise
128         * @see #isDismissOnClick()
129         * @see #dismiss()
130         */
131        public void setDismissOnClick(boolean dismissOnClick) {
132            boolean oldDismissOnClick = this.dismissOnClick;
133            this.dismissOnClick = dismissOnClick;
134            
135            firePropertyChange("dismissOnClick", oldDismissOnClick, isDismissOnClick());
136            
137            //TODO do this as a reaction to the property change?
138            if (dismissOnClick && !oldDismissOnClick) {
139                if (dismissListener == null) {
140                    dismissListener = new MouseAdapter() {
141                        public void mouseClicked(MouseEvent e) {
142                            dismiss();
143                        }
144                    };
145                }
146                addMouseListener(dismissListener);
147            }
148            else if (!dismissOnClick && oldDismissOnClick) {
149                removeMouseListener(dismissListener);
150            }
151        }
152    
153        public void paint(Graphics g) {
154            super.paint(g);
155            if (!animateTimer.isRunning() && getAlpha() < alphaEnd ) {
156                animateTimer.start();
157            }
158            if (animateTimer.isRunning() && getAlpha() >= alphaEnd) {
159                animateTimer.stop();
160            }
161        }
162    
163        public void setVisible(boolean visible) {
164            boolean old = isVisible();
165            setAlpha(alphaStart);
166            super.setVisible(visible);
167            firePropertyChange("visible", old, isVisible());
168        }
169    
170        private Container getTopLevel() {
171            Container p = getParent();
172            while (p != null && !(p instanceof Window || p instanceof Applet)) {
173                p = p.getParent();
174            }
175            return p;
176        }
177    
178        /**
179         * Shows this glass box on the glass pane. The position of the box is
180         * relative to the supplied component and offsets.
181         * 
182         * @param glassPane
183         *            the glass pane
184         * @param origin
185         *            the component representing the origin location
186         * @param offsetX
187         *            the offset on the X-axis from the origin
188         * @param offsetY
189         *            the offset on the Y-axis from the origin
190         * @param positionHint
191         *            a {@code SwingConstants} box position hint ({@code CENTER},
192         *            {@code TOP}, {@code BOTTOM}, {@code LEFT}, or {@code RIGHT})
193         * @throws NullPointerException
194         *             if {@code glassPane} or {@code origin} is {@code null}
195         * @throws IllegalArgumentException
196         *             if {@code positionHint} is not a valid hint
197         */
198        //TODO replace SwingConstant with enum other non-int
199        //TODO this method places the box outside of the origin component
200        // that continues the implementation approach that Amy used.  I
201        // think it would be a useful poll to determine whether the box
202        // should be place inside or outside of the origin (by default).
203        public void showOnGlassPane(Container glassPane, Component origin,
204                                    int offsetX, int offsetY, int positionHint) {
205            Rectangle r = SwingUtilities.convertRectangle(origin, 
206                    origin.getBounds(), glassPane);
207            Dimension d = getPreferredSize();
208            
209            int originX = offsetX + r.x;
210            int originY = offsetY + r.y;
211            
212            switch (positionHint) {
213            case SwingConstants.TOP:
214                originX += (r.width - d.width) / 2; 
215                originY -= d.height;
216                break;
217            case SwingConstants.BOTTOM:
218                originX += (r.width - d.width) / 2; 
219                originY += r.height;
220                break;
221            case SwingConstants.LEFT:
222                originX -= d.width; 
223                originY += (r.height - d.height) / 2;
224                break;
225            case SwingConstants.RIGHT:
226                originX += r.width; 
227                originY += (r.height - d.height) / 2;
228                break;
229            case SwingConstants.CENTER:
230                originX += (r.width - d.width) / 2; 
231                originY += (r.height - d.height) / 2;
232                break;
233            default:
234                throw new IllegalArgumentException("inavlid position hint");
235            }
236            
237            showOnGlassPane(glassPane, originX, originY);
238        }
239    
240        /**
241         * Shows this glass box on the glass pane.
242         * 
243         * @param glassPane
244         *            the glass pane
245         * @param originX
246         *            the location on the X-axis to position the glass box
247         * @param originY
248         *            the location on the Y-axis to position the glass box
249         */
250        public void showOnGlassPane(Container glassPane, int originX, int originY) {
251            Dimension gd = glassPane.getSize();
252            Dimension bd = getPreferredSize();
253            
254            int x = Math.min(originX, gd.width - bd.width);
255            int y = Math.min(originY, gd.height - bd.height);
256            
257            if (x < 0) {
258                x = 0;
259            }
260            
261            if (y < 0) {
262                y = 0;
263            }
264            
265            int width = x + bd.width < gd.width ? bd.width : gd.width;
266            int height = y + bd.height < gd.height ? bd.height : gd.height;
267            
268            glassPane.setLayout(null);
269            setBounds(x, y, width, height);
270            glassPane.add(this);
271            glassPane.setVisible(true);
272    
273            Container topLevel = getTopLevel();
274            topLevel.validate();
275            topLevel.repaint();
276        }
277    
278    }