001    /*
002     * $Id: JXButton.java 3164 2009-01-01 20:27:17Z rah003 $
003     *
004     * Copyright 2006 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.Graphics;
025    import java.awt.Graphics2D;
026    import java.awt.Insets;
027    
028    import javax.swing.*;
029    
030    import org.jdesktop.swingx.painter.AbstractPainter;
031    import org.jdesktop.swingx.painter.Painter;
032    
033    /**
034     * <p>A {@link org.jdesktop.swingx.painter.Painter} enabled subclass of {@link javax.swing.JButton}.
035     * This class supports setting the foreground and background painters of the button separately. By default,
036     * <code>JXButton</code> creates and installs two <code>Painter</code>s; one for the foreground, and one
037     * for the background. These default <code>Painter</code>s delegate to the installed UI delegate.</p>
038     *
039     * <p>For example, if you wanted to blur <em>just the text</em> on the button, and let everything else be
040     * handled by the UI delegate for your look and feel, then you could:
041     * <pre><code>
042     *  JXButton b = new JXButton("Execute");
043     *  AbstractPainter fgPainter = (AbstractPainter)b.getForegroundPainter();
044     *  StackBlurFilter filter = new StackBlurFilter();
045     *  fgPainter.setFilters(filter);
046     * </code></pre>
047     *
048     * <p>If <em>either</em> the foreground painter or the background painter is set,
049     * then super.paintComponent() is not called. By setting both the foreground and background
050     * painters to null, you get <em>exactly</em> the same painting behavior as JButton.
051     * By contrast, the <code>Painters</code> installed by default will delegate to the UI delegate,
052     * thus achieving the same look as a typical JButton, but at the cost of some additional painting
053     * overhead.</p>
054     *
055     * <div class="examples">
056     * <h3>Examples</h3>
057     * {@demo org.jdesktop.swingx.JXButtonDemo ../../../../../demo}
058     * </div>
059     *
060     * @author rbair
061     * @author rah003
062     * @author Jan Stola
063     */
064    public class JXButton extends JButton {
065        //properties used to split foreground and background painting.
066        //overwritten to suppress event notification while painting
067        private String text = "";
068        private boolean borderPainted;
069        private boolean contentAreaFilled;
070    
071        private Painter<JXButton> fgPainter = new DefaultForegroundPainter();
072        private Painter<JXButton> bgPainter = new DefaultBackgroundPainter();
073    
074        /** Creates a new instance of JXButton */
075        public JXButton() {}
076        public JXButton(String text) {
077            super(text);
078            this.text = text;
079        }
080        public JXButton(Action a) {
081            super();
082            // swingx-849 Has to set action explicitly after UI resources are already initialized by
083            //implicit constructor to ensure properties defined in action are initialized properly.
084            setAction(a);
085        }
086        public JXButton(Icon icon) { super(icon); }
087        public JXButton(String text, Icon icon) {
088            super(text, icon);
089            this.text = text;
090        }
091    
092        @Override
093        protected void init(String text, Icon icon) {
094            borderPainted = true;
095            contentAreaFilled = true;
096            super.init(text, icon);
097        }
098    
099        @Override
100        public void setText(String text) {
101            this.text = text;
102            super.setText(text);
103        }
104        @Override
105        public void repaint() {
106            if (painting) {
107                // skip repaint requests while painting
108                return;
109            }
110            super.repaint();
111        }
112    
113        @Override
114        public String getText() {
115            return this.text;
116        }
117    
118        @Override
119        public void setBorderPainted(boolean b) {
120            this.borderPainted = b;
121            super.setBorderPainted(b);
122        }
123    
124        @Override
125        public boolean isBorderPainted() {
126            return this.borderPainted;
127        }
128    
129        @Override
130        public void setContentAreaFilled(boolean b) {
131            this.contentAreaFilled = b;
132            super.setContentAreaFilled(b);
133        }
134    
135        @Override
136        public boolean isContentAreaFilled() {
137            return this.contentAreaFilled;
138        }
139    
140        public Painter<JXButton> getBackgroundPainter() {
141            return bgPainter;
142        }
143    
144        public void setBackgroundPainter(Painter<JXButton> p) {
145            Painter old = getBackgroundPainter();
146            this.bgPainter = p;
147            firePropertyChange("backgroundPainter", old, getBackgroundPainter());
148            repaint();
149        }
150        public Painter<JXButton> getForegroundPainter() {
151            return fgPainter;
152        }
153    
154        public void setForegroundPainter(Painter<JXButton> p) {
155            Painter old = getForegroundPainter();
156            this.fgPainter = p;
157            firePropertyChange("foregroundPainter", old, getForegroundPainter());
158            repaint();
159        }
160    
161        private boolean paintBorderInsets = true;
162        private boolean painting;
163        private boolean opaque = true;
164    
165        /**
166         * Returns true if the background painter should paint where the border is
167         * or false if it should only paint inside the border. This property is
168         * true by default. This property affects the width, height,
169         * and intial transform passed to the background painter.
170         */
171        public boolean isPaintBorderInsets() {
172            return paintBorderInsets;
173        }
174    
175        /**
176         * Sets the paintBorderInsets property.
177         * Set to true if the background painter should paint where the border is
178         * or false if it should only paint inside the border. This property is true by default.
179         * This property affects the width, height,
180         * and intial transform passed to the background painter.
181         *
182         * This is a bound property.
183         */
184        public void setPaintBorderInsets(boolean paintBorderInsets) {
185            boolean old = this.isPaintBorderInsets();
186            this.paintBorderInsets = paintBorderInsets;
187            firePropertyChange("paintBorderInsets", old, isPaintBorderInsets());
188        }
189    
190        @Override
191        public boolean isOpaque() {
192            return painting ? opaque : super.isOpaque();
193        }
194    
195        protected void paintComponent(Graphics g) {
196            Painter<JXButton> bgPainter = getBackgroundPainter();
197            Painter<JXButton> fgPainter = getForegroundPainter();
198            if (painting || (bgPainter == null && fgPainter == null)) {
199                super.paintComponent(g);
200            } else {
201                invokePainter(g, bgPainter);
202                invokePainter(g, fgPainter);
203            }
204        }
205    
206        private void invokePainter(Graphics g, Painter<JXButton> ptr) {
207            if(ptr == null) return;
208    
209            Graphics2D g2d = (Graphics2D) g.create();
210    
211            try {
212                if(isPaintBorderInsets()) {
213                    ptr.paint(g2d, this, getWidth(), getHeight());
214                } else {
215                    Insets ins = this.getInsets();
216                    g2d.translate(ins.left, ins.top);
217                    ptr.paint(g2d, this,
218                            this.getWidth() - ins.left - ins.right,
219                            this.getHeight() - ins.top - ins.bottom);
220                }
221            } finally {
222                g2d.dispose();
223            }
224        }
225        // paint anything but text and icon
226        private static final class DefaultBackgroundPainter extends AbstractPainter<JXButton> {
227            protected void doPaint(Graphics2D g, JXButton b, int width, int height) {
228                boolean op = b.opaque;
229                // have to read this before setting painting == true !!!
230                b.opaque = b.isOpaque();
231                b.setPainting(true);
232                String tmp = b.text;
233                // #swingx-874
234                Icon tmpIcon = b.getIcon();
235                b.setIcon(null);
236                b.text = "";
237                try {
238                    b.paint(g);
239                } finally {
240                    // restore original values no matter what
241                    b.opaque = op;
242                    b.text = tmp;
243                    b.setIcon(tmpIcon);
244                    b.setPainting(false);
245                }
246            }
247    
248            //if any of the state of the JButton that affects the background has changed,
249            //then I must clear the cache. This is really hard to get right, there are
250            //bound to be bugs. An alternative is to NEVER cache.
251            protected boolean shouldUseCache() {
252                return false;
253            }
254        }
255        // paint only a text and icon (if any)
256        private static final class DefaultForegroundPainter extends AbstractPainter<JXButton> {
257            protected void doPaint(Graphics2D g, JXButton b, int width, int height) {
258                b.setPainting(true);
259                boolean t1 = b.isBorderPainted();
260                boolean t2 = b.isContentAreaFilled();
261                boolean op = b.opaque;
262                b.borderPainted = false;
263                b.contentAreaFilled = false;
264                b.opaque = false;
265                try {
266                    b.paint(g);
267                } finally {
268                    // restore original values no matter what
269                    b.opaque = op;
270                    b.borderPainted = t1;
271                    b.contentAreaFilled = t2;
272                    b.setPainting(false);
273                }
274            }
275    
276            //if any of the state of the JButton that affects the foreground has changed,
277            //then I must clear the cache. This is really hard to get right, there are
278            //bound to be bugs. An alternative is to NEVER cache.
279            protected boolean shouldUseCache() {
280                return false;
281            }
282        }
283    
284        protected void setPainting(boolean b) {
285            painting = b;
286        }
287    
288    }