/* * Copyright (c) 2005, romain guy (romain.guy@jext.org) and craig wickesser (craig@codecraig.com) and henry story * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.java.swingfx.waitwithstyle; import java.awt.AWTException; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Robot; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyAdapter; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionAdapter; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; /** * A InfiniteProgressPanel-like component, but more efficient. This is the * preferred class to use unless you need the total control over the appearance * that InfiniteProgressPanel gives you.<br /> * <br /> * An infinite progress panel displays a rotating figure and a message to notice * the user of a long, duration unknown task. The shape and the text are drawn * upon a white veil which alpha level (or shield value) lets the underlying * component shine through. This panel is meant to be used as a <i>glass * pane</i> in the window performing the long operation. <br /> * <br /> * Calling setVisible(true) makes the component visible and starts the * animation. Calling setVisible(false) halts the animation and makes the * component invisible. Once you've started the animation all the mouse events * are intercepted by this panel, preventing them from being forwared to the * underlying components. <br /> * <br /> * The panel can be controlled by the <code>setVisible()</code>, method. <br /> * <br /> * This version of the infinite progress panel does not display any fade in/out * when the animation is started/stopped.<br /> * <br /> * Example: <br /> * <br /> * * <pre> * PerformanceInfiniteProgressPanel pane = new PerformanceInfiniteProgressPanel(); * frame.setGlassPane(pane); * pane.setVisible(true); * // Do something here, presumably launch a new thread * // ... * // When the thread terminates: * pane.setVisible(false); * </pre> * * @see InfiniteProgressPanel <br /> * <br /> * $Revision: 1.5 $ * @author Romain Guy * @author Henry Story * @version 1.0 */ public class PerformanceInfiniteProgressPanel extends JComponent implements ActionListener, CancelableAdaptee { /** * */ private static final long serialVersionUID = 7564909929056647161L; private static final int DEFAULT_NUMBER_OF_BARS = 12; private final int numBars; protected InfiniteProgressAdapter infiniteProgressAdapter; private final double dScale = 1.2d; private final MouseAdapter mouseAdapter = new MouseAdapter() { }; private final MouseMotionAdapter mouseMotionAdapter = new MouseMotionAdapter() { }; private final KeyAdapter keyAdapter = new KeyAdapter() { }; private final ComponentAdapter componentAdapter = new ComponentAdapter() { @Override public void componentResized(final ComponentEvent e) { if (PerformanceInfiniteProgressPanel.this.useBackBuffer == true) { PerformanceInfiniteProgressPanel.this.setOpaque(false); PerformanceInfiniteProgressPanel.this.imageBuf = null; PerformanceInfiniteProgressPanel.this.iterate = 3; } } }; private BufferedImage imageBuf = null; private final Area[] bars; private Rectangle barsBounds = null; private Rectangle barsScreenBounds = null; private AffineTransform centerAndScaleTransform = null; private final Timer timer = new Timer(1000 / 4, this); private Color[] colors = null; private int colorOffset = 0; private final boolean useBackBuffer; private final boolean tempHide = false; private String text; /** * @param i_bUseBackBuffer * When true a screen capture of the underlying window is taken. * Therefore no update in the background can be visible through * this glass pane. Increases performances. */ public PerformanceInfiniteProgressPanel() { this(true); } public PerformanceInfiniteProgressPanel(final boolean i_bUseBackBuffer) { this(i_bUseBackBuffer, PerformanceInfiniteProgressPanel.DEFAULT_NUMBER_OF_BARS); } public PerformanceInfiniteProgressPanel(final int numBars) { this(true, numBars, null); } public PerformanceInfiniteProgressPanel( final InfiniteProgressAdapter infiniteProgressAdapter) { this(true, PerformanceInfiniteProgressPanel.DEFAULT_NUMBER_OF_BARS, infiniteProgressAdapter); } public PerformanceInfiniteProgressPanel(final boolean i_bUseBackBuffer, final int numBars) { this(i_bUseBackBuffer, numBars, null); } public PerformanceInfiniteProgressPanel(final boolean i_bUseBackBuffer, final InfiniteProgressAdapter infiniteProgressAdapter) { this(i_bUseBackBuffer, PerformanceInfiniteProgressPanel.DEFAULT_NUMBER_OF_BARS, infiniteProgressAdapter); } public PerformanceInfiniteProgressPanel(final int numBars, final InfiniteProgressAdapter infiniteProgressAdapter) { this(true, numBars, infiniteProgressAdapter); } public PerformanceInfiniteProgressPanel(final boolean i_bUseBackBuffer, final int numBars, final InfiniteProgressAdapter infiniteProgressAdapter) { this.useBackBuffer = i_bUseBackBuffer; this.numBars = numBars; this.setInfiniteProgressAdapter(infiniteProgressAdapter); this.colors = new Color[numBars * 2]; // build bars this.bars = PerformanceInfiniteProgressPanel.buildTicker(numBars); // calculate bars bounding rectangle this.barsBounds = new Rectangle(); for (final Area bar : this.bars) { this.barsBounds = this.barsBounds.union(bar.getBounds()); } // create colors for (int i = 0; i < this.bars.length; i++) { final int channel = 224 - (128 / (i + 1)); this.colors[i] = new Color(channel, channel, channel); this.colors[numBars + i] = this.colors[i]; } // set cursor this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // set opaque this.setOpaque(this.useBackBuffer); } protected void setInfiniteProgressAdapter( final InfiniteProgressAdapter infiniteProgressAdapter) { this.infiniteProgressAdapter = infiniteProgressAdapter; } int iterate; // we draw use transparency to draw a number of iterations // before making a snapshot /** * Called to animate the rotation of the bar's colors */ @Override public void actionPerformed(final ActionEvent e) { // rotate colors if (this.colorOffset == (this.numBars - 1)) { this.colorOffset = 0; } else { this.colorOffset++; } // repaint if (this.barsScreenBounds != null) { this.repaint(this.barsScreenBounds); } else { this.repaint(); } if (this.useBackBuffer && (this.imageBuf == null)) { if (this.iterate < 0) { try { this.makeSnapshot(); this.setOpaque(true); } catch (final AWTException e1) { e1.printStackTrace(); // todo: decide what exception to // throw } } else { this.iterate--; } } } /** * Show/Hide the pane, starting and stopping the animation as you go */ @Override public void setVisible(final boolean i_bIsVisible) { this.setOpaque(false); // capture if (i_bIsVisible) { if (this.useBackBuffer) { // add window resize listener final Window w = SwingUtilities.getWindowAncestor(this); if (w != null) { w.addComponentListener(this.componentAdapter); } else { this.addAncestorListener(new AncestorListener() { @Override public void ancestorAdded(final AncestorEvent event) { final Window w = SwingUtilities .getWindowAncestor(PerformanceInfiniteProgressPanel.this); if (w != null) { w.addComponentListener(PerformanceInfiniteProgressPanel.this.componentAdapter); } } @Override public void ancestorRemoved(final AncestorEvent event) { } @Override public void ancestorMoved(final AncestorEvent event) { } }); } this.iterate = 3; } // capture events this.addMouseListener(this.mouseAdapter); this.addMouseMotionListener(this.mouseMotionAdapter); this.addKeyListener(this.keyAdapter); // start anim if (this.infiniteProgressAdapter != null) { this.infiniteProgressAdapter.animationStarting(); this.infiniteProgressAdapter.rampUpEnded(); } this.timer.start(); } else { // stop anim this.timer.stop(); if (this.infiniteProgressAdapter != null) { this.infiniteProgressAdapter.animationStopping(); } // / free back buffer this.imageBuf = null; // stop capturing events this.removeMouseListener(this.mouseAdapter); this.removeMouseMotionListener(this.mouseMotionAdapter); this.removeKeyListener(this.keyAdapter); // remove window resize listener final Window oWindow = SwingUtilities.getWindowAncestor(this); if (oWindow != null) { oWindow.removeComponentListener(this.componentAdapter); } } super.setVisible(i_bIsVisible); } private void makeSnapshot() throws AWTException { final Window oWindow = SwingUtilities.getWindowAncestor(this); final Insets oInsets = oWindow.getInsets(); final Rectangle oRectangle = new Rectangle(oWindow.getBounds()); oRectangle.x += oInsets.left; oRectangle.y += oInsets.top; oRectangle.width -= oInsets.left + oInsets.right; oRectangle.height -= oInsets.top + oInsets.bottom; // capture window contents this.imageBuf = new Robot().createScreenCapture(oRectangle); // no need to fade because we are allready using an image that is // showing through } /** * Recalc bars based on changes in size */ @Override public void setBounds(final int x, final int y, final int width, final int height) { super.setBounds(x, y, width, height); // update centering transform this.centerAndScaleTransform = new AffineTransform(); this.centerAndScaleTransform.translate(this.getWidth() / 2d, this.getHeight() / 2d); this.centerAndScaleTransform.scale(this.dScale, this.dScale); // calc new bars bounds if (this.barsBounds != null) { final Area oBounds = new Area(this.barsBounds); oBounds.transform(this.centerAndScaleTransform); this.barsScreenBounds = oBounds.getBounds(); } } /** * paint background dimed and bars over top */ @Override protected void paintComponent(final Graphics g) { if (!this.tempHide) { final Rectangle oClip = g.getClipBounds(); if (this.imageBuf != null) { // draw background image // g.drawImage(imageBuf, 0, 0, // null); } else { g.setColor(new Color(255, 255, 255, 180)); g.fillRect(oClip.x, oClip.y, oClip.width, oClip.height); } // move to center final Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.transform(this.centerAndScaleTransform); // draw ticker for (int i = 0; i < this.bars.length; i++) { g2.setColor(this.colors[i + this.colorOffset]); g2.fill(this.bars[i]); } final double maxY = InfiniteProgressPanel.drawTextAt(this.text, this.getFont(), g2, this.getWidth(), this.barsScreenBounds.getMaxY(), this.getForeground()); if (this.infiniteProgressAdapter != null) { this.infiniteProgressAdapter.paintSubComponents(maxY); } } } /** * Builds the circular shape and returns the result as an array of * <code>Area</code>. Each <code>Area</code> is one of the bars composing * the shape. */ private static Area[] buildTicker(final int i_iBarCount) { final Area[] ticker = new Area[i_iBarCount]; final Point2D.Double center = new Point2D.Double(0, 0); final double fixedAngle = (2.0 * Math.PI) / i_iBarCount; for (double i = 0.0; i < i_iBarCount; i++) { final Area primitive = PerformanceInfiniteProgressPanel .buildPrimitive(); final AffineTransform toCenter = AffineTransform .getTranslateInstance(center.getX(), center.getY()); final AffineTransform toBorder = AffineTransform .getTranslateInstance(45.0, -6.0); final AffineTransform toCircle = AffineTransform.getRotateInstance( -i * fixedAngle, center.getX(), center.getY()); final AffineTransform toWheel = new AffineTransform(); toWheel.concatenate(toCenter); toWheel.concatenate(toBorder); primitive.transform(toWheel); primitive.transform(toCircle); ticker[(int) i] = primitive; } return ticker; } /** * Builds a bar. */ private static Area buildPrimitive() { final Rectangle2D.Double body = new Rectangle2D.Double(6, 0, 30, 12); final Ellipse2D.Double head = new Ellipse2D.Double(0, 0, 12, 12); final Ellipse2D.Double tail = new Ellipse2D.Double(30, 0, 12, 12); final Area tick = new Area(body); tick.add(new Area(head)); tick.add(new Area(tail)); return tick; } @Override public void start() { this.setVisible(true); } @Override public void stop() { this.setVisible(false); } @Override public void setText(final String text) { this.text = text; this.repaint(); } public String getText() { return this.text; } @Override public JComponent getComponent() { return this; } /** * Adds a listener to the cancel button in this progress panel. * * @throws RuntimeException * if the infiniteProgressAdapter is null or is not a * CancelableProgessAdapter * @param listener */ @Override public void addCancelListener(final ActionListener listener) { if (this.infiniteProgressAdapter instanceof CancelableProgessAdapter) { ((CancelableProgessAdapter) this.infiniteProgressAdapter) .addCancelListener(listener); } else { throw new RuntimeException( "Expected CancelableProgessAdapter for cancel listener. Adapter is " + this.infiniteProgressAdapter); } } /** * Removes a listener to the cancel button in this progress panel. * * @throws RuntimeException * if the infiniteProgressAdapter is null or is not a * CancelableProgessAdapter * @param listener */ @Override public void removeCancelListener(final ActionListener listener) { if (this.infiniteProgressAdapter instanceof CancelableProgessAdapter) { ((CancelableProgessAdapter) this.infiniteProgressAdapter) .removeCancelListener(listener); } else { throw new RuntimeException( "Expected CancelableProgessAdapter for cancel listener. Adapter is " + this.infiniteProgressAdapter); } } }