package net.java.swingfx.waitwithstyle;
import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
/**
* An InfiniteProgressAdapter that adds a cancel button to a CancelableAdaptee
* (the InfiniteProgressPanel or the PerformanceProgressPanel).
*
* @author Michael Bushe michael@bushe.com
*/
public class CancelableProgessAdapter implements InfiniteProgressAdapter {
protected CancelableAdaptee progressPanel;
protected JLabel textLabel;
protected JButton cancelButton;
protected JButton applicationDefaultButton;
private JRootPane rootPane;
/**
* Construct with an adaptee
*
* @param progressPanel
* the adaptee, if null, setAdaptee() can be called later (before
* animation starting) with a non-null value.
*/
public CancelableProgessAdapter(final CancelableAdaptee progressPanel) {
this.setAdaptee(progressPanel);
}
/**
* Must be called with a non-null before any of the adaptee's calls to
* animationStarting, etc. are called.
*
* @param progressPanel
*/
public void setAdaptee(final CancelableAdaptee progressPanel) {
this.progressPanel = progressPanel;
}
/**
* Adds a cancel listener that will be called back when the the cancel
* button is clicked.
*
* @param listener
* a cancel callback
*/
public void addCancelListener(final ActionListener listener) {
if (this.cancelButton == null) {
this.cancelButton = this.createCancelButton();
}
if (this.cancelButton != null) {
this.cancelButton.addActionListener(listener);
}
}
/**
* Eemoves a cancel listener that would have been called back when the the
* cancel button was clicked.
*
* @param listener
* a cancel callback
*/
public void removeCancelListener(final ActionListener listener) {
if (this.cancelButton != null) {
this.cancelButton.removeActionListener(listener);
}
}
/**
* Overridable to supply your own button. Overriders should:
* <ul>
* <li>Call progresspanel.stop() on action performed.
* <li>Set the cursor on their button, else the busy cursor will not
* indicate to the user that the button is clickable.
* </ul>
*/
protected JButton createCancelButton() {
if (this.progressPanel instanceof JComponent) {
this.rootPane = ((JComponent) this.progressPanel).getRootPane();
if (this.rootPane != null) {
this.rootPane
.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(
final PropertyChangeEvent evt) {
if ("defaultButton".equals(evt
.getPropertyName())) {
/*
* Track the default button every time it
* changes so that when the cancel button
* becomes the default button, as it must
* when the user clicks it, its possible to
* reset the default button back to being
* the button that the application expects
* it to be... Ideally, the cancel button
* should never even get focus as a focus
* change is not transparent to application
* code. A better scheme might be to set
* cancel button to be not focusable and
* then click the button programmatically as
* appropriate.
*/
final JButton oldDefaultButton = (JButton) evt
.getOldValue();
if (oldDefaultButton != CancelableProgessAdapter.this.cancelButton) {
CancelableProgessAdapter.this.applicationDefaultButton = oldDefaultButton;
}
}
}
});
}
}
final JButton button = new JButton("Cancel");
button.setCursor(Cursor.getDefaultCursor());
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
CancelableProgessAdapter.this.progressPanel.stop();
if (CancelableProgessAdapter.this.rootPane != null) {
if (CancelableProgessAdapter.this.applicationDefaultButton != null) {
CancelableProgessAdapter.this.rootPane
.setDefaultButton(CancelableProgessAdapter.this.applicationDefaultButton);
}
CancelableProgessAdapter.this.applicationDefaultButton = null;
}
}
});
return button;
}
/**
* Called by the CancelableAdaptee (progress panel) when the animation is
* starting. Does nothing by default.
*/
@Override
public void animationStarting() {
}
/**
* Called by the CancelableAdaptee (progress panel) when the animation is
* stopping. Removes the button from the progressPanel by default.
*/
@Override
public void animationStopping() {
if (this.cancelButton != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CancelableProgessAdapter.this.progressPanel.getComponent()
.remove(CancelableProgessAdapter.this.cancelButton);
}
});
}
}
/**
* Called by the CancelableAdaptee (progress panel) when it is finished
* painting itself.
* <p>
* By default, paints the cancelButton if it is not null.
*
* @param maxY
* the lowest (on the screen) Y that the adapteee used - you
* should paint your components before this.
*/
@Override
public void paintSubComponents(final double maxY) {
if (this.cancelButton != null) {
final int buttonWidth = 80;
final Rectangle cancelButtonBoundedRectangle = new Rectangle(
((this.progressPanel.getComponent().getWidth() / 2) - (buttonWidth / 2)),
(int) maxY + 10, 80, 21);
this.cancelButton.setBounds(cancelButtonBoundedRectangle);
}
}
/**
* Called by the CancelableAdaptee (progress panel) when the animation's
* ramp up (fade in) is over. Adds the cancel button to the progressPanel by
* default (if not null), via an invokeLater().
*/
@Override
public void rampUpEnded() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (CancelableProgessAdapter.this.cancelButton != null) {
CancelableProgessAdapter.this.progressPanel.getComponent()
.add(CancelableProgessAdapter.this.cancelButton);
}
}
});
}
/**
* Called to programmatically click the cancel button. Can be called from
* any thread.
*/
public void doCancel() {
final Runnable runner = new Runnable() {
@Override
public void run() {
if (CancelableProgessAdapter.this.cancelButton == null) {
// could be called programmatically, easy way to simulate,
// don't care about expense
CancelableProgessAdapter.this.cancelButton = CancelableProgessAdapter.this
.createCancelButton();
}
if (CancelableProgessAdapter.this.cancelButton != null) {
CancelableProgessAdapter.this.cancelButton.doClick();
}
}
};
if (SwingUtilities.isEventDispatchThread()) {
runner.run();
} else {
SwingUtilities.invokeLater(runner);
}
}
}