001/*
002 * $Id: JXButton.java 4158 2012-02-03 18:29:40Z kschaefe $
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
022package org.jdesktop.swingx;
023
024import java.awt.Color;
025import java.awt.Dimension;
026import java.awt.Graphics;
027import java.awt.Graphics2D;
028import java.awt.Insets;
029import java.awt.Rectangle;
030import java.awt.image.BufferedImage;
031import java.awt.image.BufferedImageOp;
032
033import javax.swing.Action;
034import javax.swing.ButtonModel;
035import javax.swing.CellRendererPane;
036import javax.swing.Icon;
037import javax.swing.JButton;
038import javax.swing.SwingUtilities;
039import javax.swing.plaf.basic.BasicGraphicsUtils;
040
041import org.jdesktop.beans.JavaBean;
042import org.jdesktop.swingx.painter.AbstractPainter;
043import org.jdesktop.swingx.painter.Painter;
044import org.jdesktop.swingx.painter.PainterPaint;
045import org.jdesktop.swingx.util.GraphicsUtilities;
046import org.jdesktop.swingx.util.PaintUtils;
047
048/**
049 * <p>A {@link org.jdesktop.swingx.painter.Painter} enabled subclass of {@link javax.swing.JButton}.
050 * This class supports setting the foreground and background painters of the button separately.</p>
051 *
052 * <p>For example, if you wanted to blur <em>just the text</em> on the button, and let everything else be
053 * handled by the UI delegate for your look and feel, then you could:
054 * <pre><code>
055 *  JXButton b = new JXButton("Execute");
056 *  AbstractPainter fgPainter = (AbstractPainter)b.getForegroundPainter();
057 *  StackBlurFilter filter = new StackBlurFilter();
058 *  fgPainter.setFilters(filter);
059 * </code></pre>
060 *
061 * <p>If <em>either</em> the foreground painter or the background painter is set,
062 * then super.paintComponent() is not called. By setting both the foreground and background
063 * painters to null, you get <em>exactly</em> the same painting behavior as JButton.</p>
064 *
065 * @author rbair
066 * @author rah003
067 * @author Jan Stola
068 * @author Karl George Schaefer
069 */
070@JavaBean
071@SuppressWarnings({ "nls", "serial" })
072public class JXButton extends JButton implements BackgroundPaintable {
073    private class BackgroundButton extends JButton {
074
075        /**
076         * {@inheritDoc}
077         */
078        @Override
079        public boolean isDefaultButton() {
080            return JXButton.this.isDefaultButton();
081        }
082
083        /**
084         * {@inheritDoc}
085         */
086        @Override
087        public Icon getDisabledIcon() {
088            return null;
089        }
090
091        /**
092         * {@inheritDoc}
093         */
094        @Override
095        public Icon getDisabledSelectedIcon() {
096            return null;
097        }
098
099        /**
100         * {@inheritDoc}
101         */
102        @Override
103        public int getDisplayedMnemonicIndex() {
104            return -1;
105        }
106
107        /**
108         * {@inheritDoc}
109         */
110        @Override
111        public int getHorizontalAlignment() {
112            return JXButton.this.getHorizontalAlignment();
113        }
114
115        /**
116         * {@inheritDoc}
117         */
118        @Override
119        public int getHorizontalTextPosition() {
120            return JXButton.this.getHorizontalTextPosition();
121        }
122
123        /**
124         * {@inheritDoc}
125         */
126        @Override
127        public Icon getIcon() {
128            return null;
129        }
130
131        /**
132         * {@inheritDoc}
133         */
134        @Override
135        public int getIconTextGap() {
136            return JXButton.this.getIconTextGap();
137        }
138
139        /**
140         * {@inheritDoc}
141         */
142        @Override
143        public Insets getMargin() {
144            return JXButton.this.getMargin();
145        }
146
147        /**
148         * {@inheritDoc}
149         */
150        @Override
151        public int getMnemonic() {
152            return -1;
153        }
154
155        /**
156         * {@inheritDoc}
157         */
158        @Override
159        public ButtonModel getModel() {
160            return JXButton.this.getModel();
161        }
162
163        /**
164         * {@inheritDoc}
165         */
166        @Override
167        public Icon getPressedIcon() {
168            return null;
169        }
170
171        /**
172         * {@inheritDoc}
173         */
174        @Override
175        public Icon getRolloverIcon() {
176            return null;
177        }
178
179        /**
180         * {@inheritDoc}
181         */
182        @Override
183        public Icon getRolloverSelectedIcon() {
184            return null;
185        }
186
187        /**
188         * {@inheritDoc}
189         */
190        @Override
191        public Icon getSelectedIcon() {
192            return null;
193        }
194
195        /**
196         * {@inheritDoc}
197         */
198        @Override
199        public String getText() {
200            return "";
201        }
202
203        /**
204         * {@inheritDoc}
205         */
206        @Override
207        public int getVerticalAlignment() {
208            return JXButton.this.getVerticalAlignment();
209        }
210
211        /**
212         * {@inheritDoc}
213         */
214        @Override
215        public int getVerticalTextPosition() {
216            return JXButton.this.getVerticalTextPosition();
217        }
218
219        /**
220         * {@inheritDoc}
221         */
222        @Override
223        public boolean isBorderPainted() {
224            return JXButton.this.isBorderPainted();
225        }
226
227        /**
228         * {@inheritDoc}
229         */
230        @Override
231        public boolean isContentAreaFilled() {
232            return JXButton.this.isContentAreaFilled();
233        }
234
235        /**
236         * {@inheritDoc}
237         */
238        @Override
239        public boolean isFocusPainted() {
240            return false;
241        }
242
243        /**
244         * {@inheritDoc}
245         */
246        @Override
247        public boolean isRolloverEnabled() {
248            return JXButton.this.isRolloverEnabled();
249        }
250
251        /**
252         * {@inheritDoc}
253         */
254        @Override
255        public boolean isSelected() {
256            return JXButton.this.isSelected();
257        }
258        
259    }
260    
261    private class ForegroundButton extends JButton {
262        /**
263         * {@inheritDoc}
264         */
265        @Override
266        public Color getForeground() {
267            if (fgPainter == null) {
268                return JXButton.this.getForeground();
269            }
270            
271            return PaintUtils.setAlpha(JXButton.this.getForeground(), 0);
272        }
273        /**
274         * {@inheritDoc}
275         */
276        @Override
277        public boolean isDefaultButton() {
278            return JXButton.this.isDefaultButton();
279        }
280        
281        /**
282         * {@inheritDoc}
283         */
284        @Override
285        public Icon getDisabledIcon() {
286            return JXButton.this.getDisabledIcon();
287        }
288        
289        /**
290         * {@inheritDoc}
291         */
292        @Override
293        public Icon getDisabledSelectedIcon() {
294            return JXButton.this.getDisabledSelectedIcon();
295        }
296        
297        /**
298         * {@inheritDoc}
299         */
300        @Override
301        public int getDisplayedMnemonicIndex() {
302            return JXButton.this.getDisplayedMnemonicIndex();
303        }
304        
305        /**
306         * {@inheritDoc}
307         */
308        @Override
309        public int getHorizontalAlignment() {
310            return JXButton.this.getHorizontalAlignment();
311        }
312        
313        /**
314         * {@inheritDoc}
315         */
316        @Override
317        public int getHorizontalTextPosition() {
318            return JXButton.this.getHorizontalTextPosition();
319        }
320        
321        /**
322         * {@inheritDoc}
323         */
324        @Override
325        public Icon getIcon() {
326            return JXButton.this.getIcon();
327        }
328        
329        /**
330         * {@inheritDoc}
331         */
332        @Override
333        public int getIconTextGap() {
334            return JXButton.this.getIconTextGap();
335        }
336        
337        /**
338         * {@inheritDoc}
339         */
340        @Override
341        public Insets getMargin() {
342            return JXButton.this.getMargin();
343        }
344        
345        /**
346         * {@inheritDoc}
347         */
348        @Override
349        public int getMnemonic() {
350            return JXButton.this.getMnemonic();
351        }
352        
353        /**
354         * {@inheritDoc}
355         */
356        @Override
357        public ButtonModel getModel() {
358            return JXButton.this.getModel();
359        }
360        
361        /**
362         * {@inheritDoc}
363         */
364        @Override
365        public Icon getPressedIcon() {
366            return JXButton.this.getPressedIcon();
367        }
368        
369        /**
370         * {@inheritDoc}
371         */
372        @Override
373        public Icon getRolloverIcon() {
374            return JXButton.this.getRolloverIcon();
375        }
376        
377        /**
378         * {@inheritDoc}
379         */
380        @Override
381        public Icon getRolloverSelectedIcon() {
382            return JXButton.this.getRolloverSelectedIcon();
383        }
384        
385        /**
386         * {@inheritDoc}
387         */
388        @Override
389        public Icon getSelectedIcon() {
390            return JXButton.this.getSelectedIcon();
391        }
392        
393        /**
394         * {@inheritDoc}
395         */
396        @Override
397        public String getText() {
398            return JXButton.this.getText();
399        }
400        
401        /**
402         * {@inheritDoc}
403         */
404        @Override
405        public int getVerticalAlignment() {
406            return JXButton.this.getVerticalAlignment();
407        }
408        
409        /**
410         * {@inheritDoc}
411         */
412        @Override
413        public int getVerticalTextPosition() {
414            return JXButton.this.getVerticalTextPosition();
415        }
416        
417        /**
418         * {@inheritDoc}
419         */
420        @Override
421        public boolean isBorderPainted() {
422            return JXButton.this.isBorderPainted();
423        }
424        
425        /**
426         * {@inheritDoc}
427         */
428        @Override
429        public boolean isContentAreaFilled() {
430            return false;
431        }
432        
433        /**
434         * {@inheritDoc}
435         */
436        @Override
437        public boolean hasFocus() {
438            return JXButton.this.hasFocus();
439        }
440        
441        /**
442         * {@inheritDoc}
443         */
444        @Override
445        public boolean isFocusPainted() {
446            return JXButton.this.isFocusPainted();
447        }
448        
449        /**
450         * {@inheritDoc}
451         */
452        @Override
453        public boolean isRolloverEnabled() {
454            return JXButton.this.isRolloverEnabled();
455        }
456        
457        /**
458         * {@inheritDoc}
459         */
460        @Override
461        public boolean isSelected() {
462            return JXButton.this.isSelected();
463        }
464    }
465    
466    private ForegroundButton fgStamp;
467    private Painter fgPainter;
468    private PainterPaint fgPaint;
469    private BackgroundButton bgStamp;
470    private Painter bgPainter;
471    
472    private boolean paintBorderInsets = true;
473
474    private Rectangle viewRect = new Rectangle();
475    private Rectangle textRect = new Rectangle();
476    private Rectangle iconRect = new Rectangle();
477    
478    /**
479     * Creates a button with no set text or icon.
480     */
481    public JXButton() {
482        init();
483    }
484
485    /**
486     * Creates a button with text.
487     * 
488     * @param text
489     *            the text of the button
490     */
491    public JXButton(String text) {
492        super(text);
493        init();
494    }
495
496    /**
497     * Creates a button where properties are taken from the {@code Action} supplied.
498     * 
499     * @param a
500     *            the {@code Action} used to specify the new button
501     */
502    public JXButton(Action a) {
503        super(a);
504        init();
505    }
506
507    /**
508     * Creates a button with an icon.
509     * 
510     * @param icon
511     *            the Icon image to display on the button
512     */
513    public JXButton(Icon icon) {
514        super(icon);
515        init();
516    }
517
518    /**
519     * Creates a button with initial text and an icon.
520     * 
521     * @param text
522     *            the text of the button
523     * @param icon
524     *            the Icon image to display on the button
525     */
526    public JXButton(String text, Icon icon) {
527        super(text, icon);
528        init();
529    }
530    
531    private void init() {
532        fgStamp = new ForegroundButton();
533    }
534
535    /**
536     * Sets the background color for this component by
537     * 
538     * @param bg
539     *            the desired background <code>Color</code>
540     * @see javax.swing.JComponent#getBackground()
541     * @see #setOpaque
542     * 
543    * @beaninfo
544    *    preferred: true
545    *        bound: true
546    *    attribute: visualUpdate true
547    *  description: The background color of the component.
548     */
549    @Override
550    public void setBackground(Color bg) {
551        super.setBackground(bg);
552        
553        SwingXUtilities.installBackground(this, bg);
554    }
555    
556    /**
557     * {@inheritDoc}
558     */
559    @Override
560    @SuppressWarnings("rawtypes")
561    public Painter getBackgroundPainter() {
562        return bgPainter;
563    }
564
565    /**
566     * {@inheritDoc}
567     */
568    @Override
569    @SuppressWarnings("rawtypes")
570    public void setBackgroundPainter(Painter p) {
571        Painter old = getBackgroundPainter();
572        this.bgPainter = p;
573        firePropertyChange("backgroundPainter", old, getBackgroundPainter());
574        repaint();
575    }
576    
577    /**
578     * @return the foreground painter for this button
579     */
580    @SuppressWarnings("rawtypes")
581    public Painter getForegroundPainter() {
582        return fgPainter;
583    }
584
585    @SuppressWarnings({ "unchecked", "rawtypes" })
586    public void setForegroundPainter(Painter p) {
587        Painter old = getForegroundPainter();
588        this.fgPainter = p;
589        
590        if (fgPainter == null) {
591            fgPaint = null;
592        } else {
593            fgPaint = new PainterPaint(fgPainter, this);
594            
595            if (bgStamp == null) {
596                bgStamp = new BackgroundButton();
597            }
598        }
599        
600        firePropertyChange("foregroundPainter", old, getForegroundPainter());
601        repaint();
602    }
603
604    /**
605     * Returns true if the background painter should paint where the border is
606     * or false if it should only paint inside the border. This property is
607     * true by default. This property affects the width, height,
608     * and initial transform passed to the background painter.
609     */
610    @Override
611    public boolean isPaintBorderInsets() {
612        return paintBorderInsets;
613    }
614
615    /**
616     * Sets the paintBorderInsets property.
617     * Set to true if the background painter should paint where the border is
618     * or false if it should only paint inside the border. This property is true by default.
619     * This property affects the width, height,
620     * and initial transform passed to the background painter.
621     *
622     * This is a bound property.
623     */
624    @Override
625    public void setPaintBorderInsets(boolean paintBorderInsets) {
626        boolean old = this.isPaintBorderInsets();
627        this.paintBorderInsets = paintBorderInsets;
628        firePropertyChange("paintBorderInsets", old, isPaintBorderInsets());
629    }
630    
631    /**
632     * {@inheritDoc}
633     */
634    @Override
635    public Dimension getPreferredSize() {
636        if (getComponentCount() == 1 && getComponent(0) instanceof CellRendererPane) {
637            return BasicGraphicsUtils.getPreferredButtonSize(fgStamp, getIconTextGap());
638        }
639        
640        return super.getPreferredSize();
641    }
642
643    /**
644     * {@inheritDoc}
645     */
646    @Override
647    protected void paintComponent(Graphics g) {
648        if (fgPainter == null && bgPainter == null) {
649            super.paintComponent(g);
650        } else {
651            if (fgPainter == null) {
652                Graphics2D g2d = (Graphics2D) g.create();
653                
654                try{
655                    paintWithoutForegroundPainter(g2d);
656                } finally {
657                    g2d.dispose();
658                }
659            } else if (fgPainter instanceof AbstractPainter && ((AbstractPainter<?>) fgPainter).getFilters().length > 0) {
660                paintWithForegroundPainterWithFilters(g);
661            } else {
662                Graphics2D g2d = (Graphics2D) g.create();
663                
664                try {
665                    paintWithForegroundPainterWithoutFilters(g2d);
666                } finally {
667                    g2d.dispose();
668                }
669            }
670        }
671    }
672    
673    private void paintWithoutForegroundPainter(Graphics2D g2d) {
674        if (bgPainter == null) {
675            SwingUtilities.paintComponent(g2d, bgStamp, this, 0, 0, getWidth(), getHeight());
676        } else {
677            SwingXUtilities.paintBackground(this, g2d);
678        }
679        
680        SwingUtilities.paintComponent(g2d, fgStamp, this, 0, 0, getWidth(), getHeight());
681    }
682    
683    private void paintWithForegroundPainterWithoutFilters(Graphics2D g2d) {
684        paintWithoutForegroundPainter(g2d);
685        
686        if (getText() != null && !getText().isEmpty()) {
687            Insets i = getInsets();
688            viewRect.x = i.left;
689            viewRect.y = i.top;
690            viewRect.width = getWidth() - (i.right + viewRect.x);
691            viewRect.height = getHeight() - (i.bottom + viewRect.y);
692
693            textRect.x = textRect.y = textRect.width = textRect.height = 0;
694            iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
695
696            // layout the text and icon
697            String text = SwingUtilities.layoutCompoundLabel(
698                this, g2d.getFontMetrics(), getText(), getIcon(), 
699                getVerticalAlignment(), getHorizontalAlignment(),
700                getVerticalTextPosition(), getHorizontalTextPosition(),
701                viewRect, iconRect, textRect, 
702                getText() == null ? 0 : getIconTextGap());
703            
704            if (!isPaintBorderInsets()) {
705                g2d.translate(i.left, i.top);
706            }
707            
708            g2d.setPaint(fgPaint);
709            BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, text, getDisplayedMnemonicIndex(),
710                    textRect.x, textRect.y + g2d.getFontMetrics().getAscent());
711        }
712    }
713    
714    private void paintWithForegroundPainterWithFilters(Graphics g) {
715        BufferedImage im = GraphicsUtilities.createCompatibleImage(getWidth(), getHeight());
716        Graphics2D g2d = im.createGraphics();
717        paintWithForegroundPainterWithoutFilters(g2d);
718        
719        for (BufferedImageOp filter : ((AbstractPainter<?>) fgPainter).getFilters()) {
720            im = filter.filter(im, null);
721        }
722        
723        g.drawImage(im, 0, 0, this);
724    }
725    
726    /**
727     * Notification from the <code>UIManager</code> that the L&F has changed.
728     * Replaces the current UI object with the latest version from the <code>UIManager</code>.
729     * 
730     * @see javax.swing.JComponent#updateUI
731     */
732    @Override
733    public void updateUI() {
734        super.updateUI();
735        
736        if (bgStamp != null) {
737            bgStamp.updateUI();
738        }
739        
740        if (fgStamp != null) {
741            fgStamp.updateUI();
742        }
743    }
744}