001    /*
002     * Copyright (c) 2005, romain guy (romain.guy@jext.org) and craig wickesser (craig@codecraig.com)
003     * All rights reserved.
004     * 
005     * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
006     * 
007     *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
008     *     * 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.
009     *     * 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.
010     * 
011     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
012     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
013     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
014     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
015     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
016     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
017     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
018     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
019     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
020     * POSSIBILITY OF SUCH DAMAGE.
021     */
022    
023    package net.java.swingfx.waitwithstyle;
024    import java.awt.AlphaComposite;
025    import java.awt.Color;
026    import java.awt.Graphics;
027    import java.awt.Graphics2D;
028    import java.awt.Image;
029    import java.awt.RenderingHints;
030    import java.awt.font.FontRenderContext;
031    import java.awt.font.TextLayout;
032    import java.awt.geom.Rectangle2D;
033    import java.awt.image.BufferedImage;
034    import java.awt.image.BufferedImageOp;
035    import java.awt.image.ConvolveOp;
036    import java.awt.image.Kernel;
037    
038    import javax.swing.ImageIcon;
039    import javax.swing.JPanel;
040    
041    import net.java.swingfx.common.Utils;
042    
043    /**
044     * Display an animated panel. The panel contains a picture and a text message.
045     * As soon as <code>start()</code> is called, the pictures and the text glow
046     * in cycles. The animation can be stopped at anytime by calling
047     * <code>stop()</code>. You can set the font and its color by calling
048     * <code>setFont()</code> and <code>setForeground()</code>.
049     * 
050     * @author      Romain Guy, 17/02/2005
051     * @since       1.0
052     * <br>
053     * $Revision: 1.2 $
054     */
055    public class AnimatedPanel extends JPanel {
056            private static final long serialVersionUID = 3257288036894324529L;
057            
058            protected float gradient;
059        protected String message;
060        protected Thread animator;
061        protected BufferedImage convolvedImage;
062        protected BufferedImage originalImage;
063        protected static AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
064        protected RenderingHints brightnessHints;
065    
066        /**
067         * Creates an animated panel with a message and a picture.
068         * 
069         * @param message The message to display, can not be null nor empty.
070         * @param icon The picture to display, can not be null
071         */
072        public AnimatedPanel(String message, ImageIcon icon) {
073            // since the message can not be null or empty, validate it
074            validateMessage(message);
075            // the icon can't be null either
076            if (icon == null) {
077                    throw new IllegalArgumentException("Icon can not be null.");
078            }
079            
080            this.message = message;
081    
082            Image image = icon.getImage();
083            originalImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
084            convolvedImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
085            Graphics g = originalImage.createGraphics();
086            g.drawImage(image, 0, 0, this);
087            g.dispose();
088    
089            brightnessHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
090            
091            setBrightness(1.0f);
092            setOpaque(false);
093        }
094    
095        /**
096         * Performs basic validation on the given <code>msg</code>
097         * 
098         * @param msg       the message to validate
099         * 
100         * @throws IllegalArgumentException if <code>msg</code> is <code>null</code> or empty
101         */
102        private void validateMessage(String msg) throws IllegalArgumentException {
103            if (Utils.isNullOrEmpty(msg)) {
104                    throw new IllegalArgumentException("Invalid message.  Message can not be null or empty.");
105            }
106        }
107        
108        /**
109         * Changes the displayed message at runtime.
110         *
111         * @param text The message to be displayed. Can not be null or empty.
112         */
113        public void setText(String text) {
114            // since the message can not be null or empty, validate it
115            validateMessage(text);
116            
117            this.message = text;
118            repaint();
119        }
120    
121        /**
122         * Returns the current message.
123         */
124        public String getText() {
125            return message;
126        }
127    
128        public void paintComponent(Graphics g) {
129            super.paintComponent(g);
130    
131            if (convolvedImage != null) {
132                int width = getWidth();
133                int height = getHeight();
134    
135                synchronized (convolvedImage) {
136                    Graphics2D g2 = (Graphics2D) g;
137                    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
138                    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
139                    g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
140    
141                    FontRenderContext context = g2.getFontRenderContext();
142                    TextLayout layout = new TextLayout(message, getFont(), context);
143                    Rectangle2D bounds = layout.getBounds();
144    
145                    int x = (width - convolvedImage.getWidth(null)) / 2;
146                    int y = (int) (height - (convolvedImage.getHeight(null) + bounds.getHeight() + layout.getAscent())) / 2;
147    
148                    g2.drawImage(convolvedImage, x, y, this);
149                    Color foreground = getForeground();
150                    g2.setColor(new Color(foreground.getRed(), foreground.getGreen(), foreground.getBlue(),
151                                          (int) (gradient * 255)));
152                    layout.draw(g2, (float) (width - bounds.getWidth()) / 2,
153                        (float) (y + convolvedImage.getHeight(null) + bounds.getHeight() + layout.getAscent()));
154                }
155            }
156        }
157        
158        /**
159         * Changes the image luminosity.
160         */
161        private void setBrightness(float multiple) {
162            float[] brightKernel = { multiple };
163            BufferedImageOp bright = new ConvolveOp(new Kernel(1, 1, brightKernel), ConvolveOp.EDGE_NO_OP, brightnessHints);
164            bright.filter(originalImage, convolvedImage);
165            repaint();
166        }
167    
168        /**
169         * Changes the text gradient control value.
170         */
171        private void setGradientFactor(float gradient) {
172            this.gradient = gradient;
173        }
174    
175        /**
176         * Starts the animation. A thread called "Highlighter" is spawned and can be
177         * interrupted at anytime by invoking <code>stop()</code>.
178         */
179        public void start() {
180            this.animator = new Thread(new HighlightCycler(), "Highlighter");
181            this.animator.start();
182        }
183    
184        /**
185         * Safely stops the animation.
186         */
187        public void stop() {
188            if (this.animator != null)
189                this.animator.interrupt();
190            this.animator = null;
191        }
192    
193        /**
194         * Makes the image luminosity and the text gradient to cycle.
195         */
196        class HighlightCycler implements Runnable {
197    
198            private int way = 1;
199            private final int LOWER_BOUND = 10;
200            private final int UPPER_BOUND = 35;
201            private int value = LOWER_BOUND;
202    
203            public void run() {
204                while (true) {
205                    try {
206                        Thread.sleep(1000 / (UPPER_BOUND - LOWER_BOUND));
207                    } catch (InterruptedException e) {
208                        return;
209                    }
210    
211                    value += this.way;
212                    if (value > UPPER_BOUND) {
213                        value = UPPER_BOUND;
214                        this.way = -1;
215                    } else if (value < LOWER_BOUND) {
216                        value = LOWER_BOUND;
217                        this.way = 1;
218                    }
219    
220                    synchronized (convolvedImage) {
221                        setBrightness((float) value / 10);
222                        setGradientFactor((float) value / UPPER_BOUND);
223                    }
224                }
225            }
226        }
227    }