001    /*
002     * $Id: JXPanel.java,v 1.20 2006/05/14 08:12:17 dmouse Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.awt.AlphaComposite;
025    import java.awt.Color;
026    import java.awt.Component;
027    import java.awt.Composite;
028    import java.awt.Dimension;
029    import java.awt.GradientPaint;
030    import java.awt.Graphics;
031    import java.awt.Graphics2D;
032    import java.awt.Insets;
033    import java.awt.LayoutManager;
034    import java.awt.Rectangle;
035    import java.awt.image.BufferedImage;
036    
037    import javax.swing.JPanel;
038    import javax.swing.RepaintManager;
039    import javax.swing.Scrollable;
040    import org.jdesktop.swingx.painter.Painter;
041    
042    /**
043     * A simple JPanel extension that adds translucency support.
044     * This component and all of its content will be displayed with the specified
045     * "alpha" transluscency property value. It also supports the
046     * Painter API.
047     *
048     * @author rbair
049     */
050    public class JXPanel extends JPanel implements Scrollable {
051        private boolean scrollableTracksViewportHeight;
052        private boolean scrollableTracksViewportWidth;
053        
054        /**
055         * The alpha level for this component.
056         */
057        private float alpha = 1.0f;
058        /**
059         * If the old alpha value was 1.0, I keep track of the opaque setting because
060         * a translucent component is not opaque, but I want to be able to restore
061         * opacity to its default setting if the alpha is 1.0. Honestly, I don't know
062         * if this is necessary or not, but it sounded good on paper :)
063         * <p>TODO: Check whether this variable is necessary or not</p>
064         */
065        private boolean oldOpaque;
066        /**
067         * Indicates whether this component should inherit its parent alpha value
068         */
069        private boolean inheritAlpha = true;
070        /**
071         * Indicates whether the JXPanel should draw a gradient or not
072         * @deprecated Use setBackgroundPainter instead
073         */
074        private boolean drawGradient = false;
075        /**
076         * @deprecated Specify the Resize property on a GradientPainter instead
077         */
078        private boolean gradientTrackWidth = true;
079        /**
080         * @deprecated Specify the Resize property on a GradientPainter instead
081         */
082        private boolean gradientTrackHeight = true;
083        /**
084         * If the JXPanel is to draw a gradient, this paint indicates how it should
085         * be painted
086         * 
087         * @deprecated 
088         */
089        private GradientPaint gradientPaint;
090        /**
091         * Specifies the Painter to use for painting the background of this panel.
092         * If no painter is specified, the normal painting routine for JPanel
093         * is called. Old behavior is also honored for the time being if no
094         * backgroundPainter is specified
095         */
096        private Painter backgroundPainter;
097        /**
098         * Keeps track of the old dimensions so that if the dimensions change, the
099         * saved gradient image can be thrown out and re-rendered. This size is
100         * AFTER applying the insets!
101         */
102        private Dimension oldSize;
103        /**
104         * The cached gradient image
105         */
106        private BufferedImage cachedGradient;
107        
108        /** 
109         * Creates a new instance of JXPanel
110         */
111        public JXPanel() {
112        }
113        
114        /**
115         * @param isDoubleBuffered
116         */
117        public JXPanel(boolean isDoubleBuffered) {
118            super(isDoubleBuffered);
119        }
120    
121        /**
122         * @param layout
123         */
124        public JXPanel(LayoutManager layout) {
125            super(layout);
126        }
127    
128        /**
129         * @param layout
130         * @param isDoubleBuffered
131         */
132        public JXPanel(LayoutManager layout, boolean isDoubleBuffered) {
133            super(layout, isDoubleBuffered);
134        }
135        
136        /**
137         * Set the alpha transparency level for this component. This automatically
138         * causes a repaint of the component.
139         * 
140         * <p>TODO add support for animated changes in translucency</p>
141         *
142         * @param alpha must be a value between 0 and 1 inclusive.
143         */
144        public void setAlpha(float alpha) {
145            if (this.alpha != alpha) {
146                assert alpha >= 0 && alpha <= 1.0;
147                float oldAlpha = this.alpha;
148                this.alpha = alpha;
149                if (alpha > 0f && alpha < 1f) {
150                    if (oldAlpha == 1) {
151                        //it used to be 1, but now is not. Save the oldOpaque
152                        oldOpaque = isOpaque();
153                        setOpaque(false);
154                    }
155                    RepaintManager manager = RepaintManager.currentManager(this);
156                    if (!manager.getClass().isAnnotationPresent(TranslucentRepaintManager.class)) {
157                        RepaintManager.setCurrentManager(new RepaintManagerX());
158                    }
159                } else if (alpha == 1) {
160                    //restore the oldOpaque if it was true (since opaque is false now)
161                    if (oldOpaque) {
162                       setOpaque(true);
163                    }
164                }
165                firePropertyChange("alpha", oldAlpha, alpha);
166                repaint();
167            }
168        }
169        
170        /**
171         * @return the alpha translucency level for this component. This will be
172         * a value between 0 and 1, inclusive.
173         */
174        public float getAlpha() {
175            return alpha;
176        }
177    
178        /**
179         * Unlike other properties, alpha can be set on a component, or on one of
180         * its parents. If the alpha of a parent component is .4, and the alpha on
181         * this component is .5, effectively the alpha for this component is .4
182         * because the lowest alpha in the heirarchy &quot;wins&quot;
183         */ 
184        public float getEffectiveAlpha() {
185            if (inheritAlpha) {
186                float a = alpha;
187                Component c = this;
188                while ((c = c.getParent()) != null) {
189                    if (c instanceof JXPanel) {
190                        a = Math.min(((JXPanel)c).getAlpha(), a);
191                    }
192                }
193                return a;
194            } else {
195                return alpha;
196            }
197        }
198        
199        public boolean isInheritAlpha() {
200            return inheritAlpha;
201        }
202        
203        public void setInheritAlpha(boolean val) {
204            if (inheritAlpha != val) {
205                inheritAlpha = val;
206                firePropertyChange("inheritAlpha", !inheritAlpha, inheritAlpha);
207            }
208        }
209        
210        /* (non-Javadoc)
211         * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
212         */
213        public boolean getScrollableTracksViewportHeight() {
214            return scrollableTracksViewportHeight;
215        }
216        
217        /* (non-Javadoc)
218         * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
219         */
220        public boolean getScrollableTracksViewportWidth() {
221            return scrollableTracksViewportWidth;
222        }
223        
224        /* (non-Javadoc)
225         * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
226         */
227        public Dimension getPreferredScrollableViewportSize() {
228            return getPreferredSize();
229        }
230        
231        /* (non-Javadoc)
232         * @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int)
233         */
234        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
235            return 10;
236        }
237        
238        /* (non-Javadoc)
239         * @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
240         */
241        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
242            return 10;
243        }
244        /**
245         * @param scrollableTracksViewportHeight The scrollableTracksViewportHeight to set.
246         */
247        public void setScrollableTracksViewportHeight(boolean scrollableTracksViewportHeight) {
248            this.scrollableTracksViewportHeight = scrollableTracksViewportHeight;
249        }
250        /**
251         * @param scrollableTracksViewportWidth The scrollableTracksViewportWidth to set.
252         */
253        public void setScrollableTracksViewportWidth(boolean scrollableTracksViewportWidth) {
254            this.scrollableTracksViewportWidth = scrollableTracksViewportWidth;
255        }
256    
257        /**
258         * @deprecated To specify a gradient for the panel, use the
259         *             #setBackgroundPainter method, along with a Painter, like
260         *             this:
261         *  <pre><code>
262         *      BasicGradientPainter gradient = 
263         *          new BasicGradientPainter(new GradientPaint(
264         *              new Point2D.Double(0,0),
265         *              Color.WHITE, 
266         *              new Point2D.Double(1,0), 
267         *              UIManager.getColor("control")));
268         *      panel.setBackgroundPainter(gradient);
269         *  </code></pre>
270         *
271         *  There are several predefined gradients that may also be used. For example:
272         *  <pre><code>
273         *      BasicGradientPainter gradient = 
274         *          new BasicGradientPainter(BasicGradientPainter.WHITE_TO_CONTROL_HORIZONTAL);
275         *      panel.setBackgroundPainter(gradient);
276         *  </code></pre>
277         */
278        public void setGradientPaint(GradientPaint paint) {
279            GradientPaint oldPaint = this.gradientPaint;
280            this.gradientPaint = paint;
281            firePropertyChange("gradientPaint", oldPaint, paint);
282            repaint();
283        }
284    
285        /**
286         * @deprecated See setGradientPaint
287         */
288        public GradientPaint getGradientPaint() {
289            return gradientPaint;
290        }
291    
292        /**
293         * @deprecated See setGradientPaint
294         */
295        public void setDrawGradient(boolean b) {
296            if (drawGradient != b) {
297                boolean old = drawGradient;
298                drawGradient = b;
299                oldSize = getSize();
300                firePropertyChange("drawGradient", old, b);
301                repaint();
302            }
303        }
304        
305        /**
306         * @deprecated See setGradientPaint
307         */
308        public boolean isDrawGradient() {
309            return drawGradient;
310        }
311        
312        /**
313         * @deprecated See setGradientPaint
314         */
315        public void setGradientTrackWidth(boolean b) {
316            if (gradientTrackWidth != b) {
317                boolean old = gradientTrackWidth;
318                gradientTrackWidth = b;
319                firePropertyChange("gradientTrackWidth", old, b);
320                repaint();
321            }
322        }
323        
324        /**
325         * @deprecated See setGradientPaint
326         */
327        public boolean isGradientTrackWidth() {
328            return gradientTrackWidth;
329        }
330        
331        /**
332         * @deprecated See setGradientPaint
333         */
334        public void setGradientTrackHeight(boolean b) {
335            if (gradientTrackHeight != b) {
336                boolean old = gradientTrackHeight;
337                gradientTrackHeight = b;
338                firePropertyChange("gradientTrackHeight", old, b);
339                repaint();
340            }
341        }
342        
343        /**
344         * @deprecated See setGradientPaint
345         */
346        public boolean isGradientTrackHeight() {
347            return gradientTrackHeight;
348        }
349        
350        /**
351         * Specifies a Painter to use to paint the background of this JXPanel.
352         * If <code>p</code> is not null, then setOpaque(false) will be called
353         * as a side effect. A component should not be opaque if painters are
354         * being used, because Painters may paint transparent pixels or not
355         * paint certain pixels, such as around the border insets.
356         */
357        public void setBackgroundPainter(Painter p) {
358            Painter old = getBackgroundPainter();
359            this.backgroundPainter = p;
360            
361            if (p != null) {
362                setOpaque(false);
363            }
364            
365            firePropertyChange("backgroundPainter", old, getBackgroundPainter());
366            repaint();
367        }
368        
369        public Painter getBackgroundPainter() {
370            return backgroundPainter;
371        }
372        
373        /**
374         * Overriden paint method to take into account the alpha setting
375         */
376        @Override
377        public void paint(Graphics g) {
378            Graphics2D g2d = (Graphics2D)g;
379            Composite oldComp = g2d.getComposite();
380            float alpha = getEffectiveAlpha();
381            Composite alphaComp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
382            g2d.setComposite(alphaComp);
383            super.paint(g2d);
384            g2d.setComposite(oldComp);
385        }
386    
387        /**
388         * overridden to provide gradient painting
389         * 
390         * TODO: Chris says that in OGL we actually suffer here by caching the
391         * gradient paint since the OGL pipeline will render gradients on
392         * hardware for us. The decision to use cacheing is based on my experience
393         * with gradient title borders slowing down repaints -- this could use more
394         * extensive analysis.
395         */
396        @Override
397        protected void paintComponent(Graphics g) {
398            if (backgroundPainter != null) {
399                backgroundPainter.paint((Graphics2D)g, this);
400            } else {
401                super.paintComponent(g);
402                if (drawGradient) {
403                    Insets insets = getInsets();
404                    int width = getWidth() - insets.right - insets.left;
405                    int height = getHeight() - insets.top - insets.bottom;
406    
407                    //TODO need to detect a change in gradient paint as well
408                    if (gradientPaint == null || oldSize == null || oldSize.width != width || oldSize.height != height) {
409                        Color c1 = null;//UIManager.getColor("control");
410                        Color c2 = null;//c.darker();
411                        if (gradientPaint == null) {
412                            c1 = getBackground();
413                            c2 = new Color(c1.getRed() - 40, c1.getGreen() - 40, c1.getBlue() - 40);
414                            float x1 = 0f;
415                            float y1 = 0f;
416                            float x2 = width;
417                            float y2 = 0;
418                            boolean cyclic = false;
419                            gradientPaint = new GradientPaint(x1, y1, c1, x2, y2, c2, cyclic);
420                        } else {
421                            //same GP as before, but where the values differed for x1, x2, replace
422                            //x2 with the current width, and where values differed for y1, y2
423                            //replace with current height
424                            GradientPaint gp = gradientPaint;
425                            float x2 = (float)gp.getPoint2().getX();
426                            if (gradientTrackWidth) {
427                                float ratio = (float)width / (float)oldSize.width;
428                                x2 = ((float)gp.getPoint2().getX()) * ratio;
429                            }
430                            float y2 = (float)gp.getPoint2().getY();
431                            if (gradientTrackHeight) {
432                                float ratio = (float)height / (float)oldSize.height;
433                                y2 = ((float)gp.getPoint2().getY()) * ratio;
434                            }
435                            gradientPaint = new GradientPaint((float)gp.getPoint1().getX(),
436                                    (float)gp.getPoint1().getY(), gp.getColor1(),
437                                    x2, y2, gp.getColor2(),
438                                    gp.isCyclic());
439                        }
440    
441                        oldSize = new Dimension(width, height);
442                        cachedGradient = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
443                        Graphics2D imgg = (Graphics2D)cachedGradient.getGraphics();
444                        imgg.setPaint(gradientPaint);
445                        imgg.fillRect(0, 0, width, height);
446                    }
447    
448                    // draw the image
449                    Graphics2D g2 = (Graphics2D)g;
450                    g2.drawImage(cachedGradient, null, insets.left, insets.top);
451                }
452            }
453        }
454    }