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 "dismissOnClick" 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 }