/*
* $Id: JXGlassBox.java,v 1.7 2009/02/01 15:01:00 rah003 Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/**
* Component used to display transluscent user-interface content.
* This component and all of its content will be displayed with the specified
* "alpha" transluscency property value. When this component is made visible,
* it's content will fade in until the alpha transluscency level is reached.
* <p>
* If the glassbox's "dismissOnClick" property is <code>true</code>
* (the default) then the glassbox will be made invisible when the user
* clicks on it.</p>
* <p>
* This component is particularly useful for displaying transient messages
* on the glasspane.</p>
*
* @author Amy Fowler
* @author Karl George Schaefer
* @version 1.0
*/
public class JXGlassBox extends JXPanel {
private static final int SHOW_DELAY = 150; // ms
private static final int TIMER_INCREMENT = 10; // ms
private float alphaStart = 0.01f;
private float alphaEnd = 0.8f;
private Timer animateTimer;
private float alphaIncrement = 0.02f;
private boolean dismissOnClick = false;
private MouseAdapter dismissListener = null;
public JXGlassBox() {
setOpaque(false);
setAlpha(alphaStart);
setBackground(Color.white);
setDismissOnClick(true);
animateTimer = new Timer(TIMER_INCREMENT, new ActionListener() {
public void actionPerformed(ActionEvent e) {
setAlpha(Math.min(alphaEnd, getAlpha() + alphaIncrement));
}
});
}
public JXGlassBox(float alpha) {
this();
this.alphaEnd = alpha;
setAlpha(alpha);
}
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
this.alphaIncrement = (alphaEnd - alphaStart)/(SHOW_DELAY/TIMER_INCREMENT);
}
/**
* Dismisses this glass box. This causes the glass box to be removed from
* it's parent and ensure that the display is correctly updated.
*/
public void dismiss() {
JComponent parent = (JComponent) getParent();
if (parent != null) {
Container toplevel = parent.getTopLevelAncestor();
parent.remove(this);
toplevel.validate();
toplevel.repaint();
}
}
/**
* Determines if the glass box if dismissed when a user clicks on it.
*
* @return {@code true} if the glass box can be dismissed with a click;
* {@code false} otherwise
* @see #setDismissOnClick(boolean)
* @see #dismiss()
*/
public boolean isDismissOnClick() {
return dismissOnClick;
}
/**
* Configures the glass box to dismiss (or not) when clicked.
*
* @param dismissOnClick
* {@code true} if the glass box should dismiss when clicked;
* {@code false} otherwise
* @see #isDismissOnClick()
* @see #dismiss()
*/
public void setDismissOnClick(boolean dismissOnClick) {
boolean oldDismissOnClick = this.dismissOnClick;
this.dismissOnClick = dismissOnClick;
firePropertyChange("dismissOnClick", oldDismissOnClick, isDismissOnClick());
//TODO do this as a reaction to the property change?
if (dismissOnClick && !oldDismissOnClick) {
if (dismissListener == null) {
dismissListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dismiss();
}
};
}
addMouseListener(dismissListener);
}
else if (!dismissOnClick && oldDismissOnClick) {
removeMouseListener(dismissListener);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (!animateTimer.isRunning() && getAlpha() < alphaEnd ) {
animateTimer.start();
}
if (animateTimer.isRunning() && getAlpha() >= alphaEnd) {
animateTimer.stop();
}
}
@Override
public void setVisible(boolean visible) {
boolean old = isVisible();
setAlpha(alphaStart);
super.setVisible(visible);
firePropertyChange("visible", old, isVisible());
}
private Container getTopLevel() {
Container p = getParent();
while (p != null && !(p instanceof Window || p instanceof Applet)) {
p = p.getParent();
}
return p;
}
/**
* Shows this glass box on the glass pane. The position of the box is
* relative to the supplied component and offsets.
*
* @param glassPane
* the glass pane
* @param origin
* the component representing the origin location
* @param offsetX
* the offset on the X-axis from the origin
* @param offsetY
* the offset on the Y-axis from the origin
* @param positionHint
* a {@code SwingConstants} box position hint ({@code CENTER},
* {@code TOP}, {@code BOTTOM}, {@code LEFT}, or {@code RIGHT})
* @throws NullPointerException
* if {@code glassPane} or {@code origin} is {@code null}
* @throws IllegalArgumentException
* if {@code positionHint} is not a valid hint
*/
//TODO replace SwingConstant with enum other non-int
//TODO this method places the box outside of the origin component
// that continues the implementation approach that Amy used. I
// think it would be a useful poll to determine whether the box
// should be place inside or outside of the origin (by default).
public void showOnGlassPane(Container glassPane, Component origin,
int offsetX, int offsetY, int positionHint) {
Rectangle r = SwingUtilities.convertRectangle(origin,
origin.getBounds(), glassPane);
Dimension d = getPreferredSize();
int originX = offsetX + r.x;
int originY = offsetY + r.y;
switch (positionHint) {
case SwingConstants.TOP:
originX += (r.width - d.width) / 2;
originY -= d.height;
break;
case SwingConstants.BOTTOM:
originX += (r.width - d.width) / 2;
originY += r.height;
break;
case SwingConstants.LEFT:
originX -= d.width;
originY += (r.height - d.height) / 2;
break;
case SwingConstants.RIGHT:
originX += r.width;
originY += (r.height - d.height) / 2;
break;
case SwingConstants.CENTER:
originX += (r.width - d.width) / 2;
originY += (r.height - d.height) / 2;
break;
default:
throw new IllegalArgumentException("inavlid position hint");
}
showOnGlassPane(glassPane, originX, originY);
}
/**
* Shows this glass box on the glass pane.
*
* @param glassPane
* the glass pane
* @param originX
* the location on the X-axis to position the glass box
* @param originY
* the location on the Y-axis to position the glass box
*/
public void showOnGlassPane(Container glassPane, int originX, int originY) {
Dimension gd = glassPane.getSize();
Dimension bd = getPreferredSize();
int x = Math.min(originX, gd.width - bd.width);
int y = Math.min(originY, gd.height - bd.height);
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
int width = x + bd.width < gd.width ? bd.width : gd.width;
int height = y + bd.height < gd.height ? bd.height : gd.height;
glassPane.setLayout(null);
setBounds(x, y, width, height);
glassPane.add(this);
glassPane.setVisible(true);
Container topLevel = getTopLevel();
topLevel.validate();
topLevel.repaint();
}
}