001    /*
002     * $Id: JXPanel.java 3341 2009-05-22 19:43:05Z kschaefe $
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.Graphics;
030    import java.awt.Graphics2D;
031    import java.awt.Insets;
032    import java.awt.LayoutManager;
033    import java.awt.Rectangle;
034    
035    import javax.swing.JPanel;
036    import javax.swing.RepaintManager;
037    import javax.swing.Scrollable;
038    import javax.swing.plaf.UIResource;
039    
040    import org.jdesktop.swingx.painter.Painter;
041    
042    /**
043     * <p>
044     * An extended {@code JPanel} that provides additional features. First, the
045     * component is {@code Scrollable}, using reasonable defaults. Second, the
046     * component is alpha-channel enabled. This means that the {@code JXPanel} can
047     * be made fully or partially transparent. Finally, {@code JXPanel} has support
048     * for {@linkplain Painter painters}.
049     * </p>
050     * <p>
051     * A transparency example, this following code will show the black background of
052     * the parent:
053     * 
054     * <pre>
055     * JXPanel panel = new JXPanel();
056     * panel.add(new JButton(&quot;Push Me&quot;));
057     * panel.setAlpha(.5f);
058     * 
059     * container.setBackground(Color.BLACK);
060     * container.add(panel);
061     * </pre>
062     * 
063     * </p>
064     * <p>
065     * A painter example, this following code will show how to add a simple painter:
066     * 
067     * <pre>
068     * JXPanel panel = new JXPanel();
069     * panel.setBackgroundPainter(new PinstripePainter());
070     * </pre>
071     * 
072     * </p>
073     * 
074     * @author rbair
075     * @see Scrollable
076     * @see Painter
077     */
078    public class JXPanel extends JPanel implements Scrollable {
079        private boolean scrollableTracksViewportHeight = true;
080        private boolean scrollableTracksViewportWidth = true;
081        
082        /**
083         * The alpha level for this component.
084         */
085        private float alpha = 1.0f;
086        /**
087         * If the old alpha value was 1.0, I keep track of the opaque setting because
088         * a translucent component is not opaque, but I want to be able to restore
089         * opacity to its default setting if the alpha is 1.0. Honestly, I don't know
090         * if this is necessary or not, but it sounded good on paper :)
091         * <p>TODO: Check whether this variable is necessary or not</p>
092         */
093        private boolean oldOpaque;
094        /**
095         * Indicates whether this component should inherit its parent alpha value
096         */
097        private boolean inheritAlpha = true;
098        /**
099         * Specifies the Painter to use for painting the background of this panel.
100         * If no painter is specified, the normal painting routine for JPanel
101         * is called. Old behavior is also honored for the time being if no
102         * backgroundPainter is specified
103         */
104        private Painter backgroundPainter;
105        
106        /**
107         * Creates a new <code>JXPanel</code> with a double buffer
108         * and a flow layout.
109         */
110        public JXPanel() {
111        }
112        
113        /**
114         * Creates a new <code>JXPanel</code> with <code>FlowLayout</code>
115         * and the specified buffering strategy.
116         * If <code>isDoubleBuffered</code> is true, the <code>JXPanel</code>
117         * will use a double buffer.
118         *
119         * @param isDoubleBuffered  a boolean, true for double-buffering, which
120         *        uses additional memory space to achieve fast, flicker-free 
121         *        updates
122         */
123        public JXPanel(boolean isDoubleBuffered) {
124            super(isDoubleBuffered);
125        }
126        
127        /**
128         * Create a new buffered JXPanel with the specified layout manager
129         *
130         * @param layout  the LayoutManager to use
131         */
132        public JXPanel(LayoutManager layout) {
133            super(layout);
134        }
135        
136        /**
137         * Creates a new JXPanel with the specified layout manager and buffering
138         * strategy.
139         *
140         * @param layout  the LayoutManager to use
141         * @param isDoubleBuffered  a boolean, true for double-buffering, which
142         *        uses additional memory space to achieve fast, flicker-free 
143         *        updates
144         */
145        public JXPanel(LayoutManager layout, boolean isDoubleBuffered) {
146            super(layout, isDoubleBuffered);
147        }
148        
149        /**
150         * Set the alpha transparency level for this component. This automatically
151         * causes a repaint of the component.
152         *
153         * <p>TODO add support for animated changes in translucency</p>
154         *
155         * @param alpha must be a value between 0 and 1 inclusive.
156         */
157        public void setAlpha(float alpha) {
158            if (this.alpha != alpha) {
159                assert alpha >= 0 && alpha <= 1.0;
160                float oldAlpha = this.alpha;
161                this.alpha = alpha;
162                if (alpha > 0f && alpha < 1f) {
163                    if (oldAlpha == 1) {
164                        //it used to be 1, but now is not. Save the oldOpaque
165                        oldOpaque = isOpaque();
166                        setOpaque(false);
167                    }
168                    
169                    RepaintManager manager = RepaintManager.currentManager(this);
170                    RepaintManager trm = SwingXUtilities.getTranslucentRepaintManager(manager);
171                    RepaintManager.setCurrentManager(trm);
172                } else if (alpha == 1) {
173                    //restore the oldOpaque if it was true (since opaque is false now)
174                    if (oldOpaque) {
175                        setOpaque(true);
176                    }
177                }
178                firePropertyChange("alpha", oldAlpha, alpha);
179                repaint();
180            }
181        }
182        
183        /**
184         * @return the alpha translucency level for this component. This will be
185         * a value between 0 and 1, inclusive.
186         */
187        public float getAlpha() {
188            return alpha;
189        }
190        
191        /**
192         * Unlike other properties, alpha can be set on a component, or on one of
193         * its parents. If the alpha of a parent component is .4, and the alpha on
194         * this component is .5, effectively the alpha for this component is .4
195         * because the lowest alpha in the heirarchy &quot;wins&quot;
196         */
197        public float getEffectiveAlpha() {
198            if (inheritAlpha) {
199                float a = alpha;
200                Component c = this;
201                while ((c = c.getParent()) != null) {
202                    if (c instanceof JXPanel) {
203                        a = Math.min(((JXPanel)c).getAlpha(), a);
204                    }
205                }
206                return a;
207            } else {
208                return alpha;
209            }
210        }
211    
212        /**
213         * Returns the state of the panel with respect to inheriting alpha values.
214         * 
215         * @return {@code true} if this panel inherits alpha values; {@code false}
216         *         otherwise
217         * @see JXPanel#setInheritAlpha(boolean)
218         */
219        public boolean isInheritAlpha() {
220            return inheritAlpha;
221        }
222    
223        /**
224         * Determines if the effective alpha of this component should include the
225         * alpha of ancestors.
226         * 
227         * @param val
228         *            {@code true} to include ancestral alpha data; {@code false}
229         *            otherwise
230         * @see #isInheritAlpha()
231         * @see #getEffectiveAlpha()
232         */
233        public void setInheritAlpha(boolean val) {
234            if (inheritAlpha != val) {
235                inheritAlpha = val;
236                firePropertyChange("inheritAlpha", !inheritAlpha, inheritAlpha);
237            }
238        }
239        
240        /**
241         * {@inheritDoc}
242         */
243        public boolean getScrollableTracksViewportHeight() {
244            return scrollableTracksViewportHeight;
245        }
246        
247        /**
248         * {@inheritDoc}
249         */
250        public boolean getScrollableTracksViewportWidth() {
251            return scrollableTracksViewportWidth;
252        }
253        
254        /**
255         * {@inheritDoc}
256         */
257        public Dimension getPreferredScrollableViewportSize() {
258            return getPreferredSize();
259        }
260        
261        /**
262         * {@inheritDoc}
263         */
264        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
265            return 10;
266        }
267        
268        /**
269         * {@inheritDoc}
270         */
271        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
272            return 10;
273        }
274        /**
275         * @param scrollableTracksViewportHeight The scrollableTracksViewportHeight to set.
276         */
277        public void setScrollableTracksViewportHeight(boolean scrollableTracksViewportHeight) {
278            this.scrollableTracksViewportHeight = scrollableTracksViewportHeight;
279        }
280        /**
281         * @param scrollableTracksViewportWidth The scrollableTracksViewportWidth to set.
282         */
283        public void setScrollableTracksViewportWidth(boolean scrollableTracksViewportWidth) {
284            this.scrollableTracksViewportWidth = scrollableTracksViewportWidth;
285        }
286    
287        /**
288         * Sets the background color for this component by
289         * 
290         * @param bg
291         *            the desired background <code>Color</code>
292         * @see java.swing.JComponent#getBackground
293         * @see #setOpaque
294         * 
295        * @beaninfo
296        *    preferred: true
297        *        bound: true
298        *    attribute: visualUpdate true
299        *  description: The background color of the component.
300         */
301        @Override
302        public void setBackground(Color bg) {
303            super.setBackground(bg);
304            
305            //TODO problem with SwingX #964.  Had to undo changes.
306            //Change causes background to be painter when using setOpaque=false hack
307    //        if (canInstallBackgroundUIResourceAsPainter(bg)) {
308    //            setBackgroundPainter(new PainterUIResource(new MattePainter<JXPanel>(bg)));
309    //        } else {
310    //            setBackgroundPainter(new MattePainter<JXPanel>(bg));
311    //        }
312        }
313    
314        private boolean canInstallBackgroundUIResourceAsPainter(Color bg) {
315            Painter<?> p = getBackgroundPainter();
316            
317            return bg instanceof UIResource && (p == null || p instanceof UIResource);
318        }
319        
320        /**
321         * Sets a Painter to use to paint the background of this JXPanel.
322         * 
323         * @param p the new painter
324         * @see #getBackgroundPainter()
325         */
326        public void setBackgroundPainter(Painter p) {
327            Painter old = getBackgroundPainter();
328            backgroundPainter = p;
329            firePropertyChange("backgroundPainter", old, getBackgroundPainter());
330            repaint();
331        }
332        
333        /**
334         * Returns the current background painter. The default value of this property 
335         * is a painter which draws the normal JPanel background according to the current look and feel.
336         * @return the current painter
337         * @see #setBackgroundPainter(Painter)
338         * @see #isPaintBorderInsets()
339         */
340        public Painter getBackgroundPainter() {
341            return backgroundPainter;
342        }
343        
344        
345        private boolean paintBorderInsets = true;
346        
347        /**
348         * Returns true if the background painter should paint where the border is
349         * or false if it should only paint inside the border. This property is 
350         * true by default. This property affects the width, height,
351         * and intial transform passed to the background painter.
352         */
353        public boolean isPaintBorderInsets() {
354            return paintBorderInsets;
355        }
356        
357        /**
358         * Sets the paintBorderInsets property.
359         * Set to true if the background painter should paint where the border is
360         * or false if it should only paint inside the border. This property is true by default.
361         * This property affects the width, height,
362         * and intial transform passed to the background painter.
363         * 
364         * This is a bound property.
365         */
366        public void setPaintBorderInsets(boolean paintBorderInsets) {
367            boolean old = this.isPaintBorderInsets();
368            this.paintBorderInsets = paintBorderInsets;
369            firePropertyChange("paintBorderInsets", old, isPaintBorderInsets());
370        }
371        
372        /**
373         * Overriden paint method to take into account the alpha setting
374         * @param g 
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 Painter support. It will call backgroundPainter.paint()
389         * if it is not null, else it will call super.paintComponent().
390         */
391        @Override
392        protected void paintComponent(Graphics g) {
393            if(backgroundPainter != null) {
394                if (isOpaque()) super.paintComponent(g);
395                
396                Graphics2D g2 = (Graphics2D)g.create();
397                
398                try {
399                    // account for the insets
400                    if(isPaintBorderInsets()) {
401                        backgroundPainter.paint(g2, this, this.getWidth(), this.getHeight());
402                    } else {
403                        Insets ins = this.getInsets();
404                        g2.translate(ins.left, ins.top);            
405                        backgroundPainter.paint(g2, this,
406                                                this.getWidth() - ins.left - ins.right,
407                                                this.getHeight() - ins.top - ins.bottom);
408                    }
409                } finally {
410                    g2.dispose();
411                }
412            } else {
413                super.paintComponent(g);
414            }
415        }
416        
417    }