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); } } }