File: ImageButton.java

package info (click to toggle)
mac-widgets 0.10.0%2Bsvn416-dfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,968 kB
  • sloc: java: 9,909; makefile: 13; sh: 12
file content (151 lines) | stat: -rw-r--r-- 5,362 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.explodingpixels.widgets;

import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.plaf.basic.BasicButtonUI;

/**
 * A button backed by an image. Additionally, a click mask can be provided. Any fully
 * non-transparent pixels in the mask will not be clickable.
 */
public class ImageButton extends JButton {

    // create a static index for the alpha channel of a raster image. i'm not exactly sure where
    // it's specified that red = channel 0, green = channel 1, blue = channel 2, and
    // alpha = channel 3, but this have been the values i've observed.
    private static final int ALPHA_BAND = 3;

    // a buffered image representing the mask for this button.
    private final BufferedImage fMask;

    private Icon fInactiveIcon;

    /**
     * Creates an image based button.
     *
     * @param icon the icon to use for the button.
     */
    public ImageButton(Icon icon) {
        this(icon, icon);
    }

    /**
     * Creates an image based button with the given click mask.
     *
     * @param icon the icon to use for the button.
     * @param mask the click mask to use for the button.
     * @throws IllegalArgumentException if the given icon is null, the given mask is null or
     *                                  the given mask's bounds do not match the given icons bounds.
     */
    public ImageButton(Icon icon, Icon mask) {
        super(icon);

        if (icon == null) {
            throw new IllegalArgumentException("The icon cannot be null.");
        }

        if (mask == null) {
            throw new IllegalArgumentException("The mask cannot be null.");
        }

        checkIconMatchesMaskBounds(icon, mask);

        // remove the margins from this button, request that the content area not be filled, and
        // indicate that the border not be painted.
        setMargin(new Insets(0, 0, 0, 0));
        setBorder(BorderFactory.createEmptyBorder());
        setContentAreaFilled(false);

        // create the mask from the supplied icon.
        fMask = createMask(mask);

        // repaint this button when the parent window's focus state changes so
        // that we can correctly show the active or inactive icon.
        WindowUtils.installJComponentRepainterOnWindowFocusChanged(this);
    }

    private BufferedImage createMask(Icon mask) {
        // create a BufferedImage to paint the mask into so that we can later retrieve pixel data
        // out of the image.
        BufferedImage image = new BufferedImage(
                mask.getIconWidth(), mask.getIconHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics graphics = image.getGraphics();
        mask.paintIcon(null, graphics, 0, 0);
        graphics.dispose();

        return image;
    }

    public Icon getIcon() {
        return WindowUtils.isParentWindowFocused(this) || fInactiveIcon == null
                ? super.getIcon() : fInactiveIcon;
    }

    @Override
    public void setIcon(Icon defaultIcon) {
        super.setIcon(defaultIcon);
        // if this class has already been initialized, ensure that the new icon matches the bounds
        // of the current mask.
        if (fMask != null) {
            checkIconMatchesMaskBounds(defaultIcon, new ImageIcon(fMask));
        }
    }

    public void setInactiveIcon(Icon inactiveIcon) {
        checkIconMatchesMaskBounds(inactiveIcon, new ImageIcon(fMask));
        fInactiveIcon = inactiveIcon;
    }

    @Override
    public void updateUI() {
        // install the custom ui delegate to track the icon rectangle and answer the contains
        // method.
        setUI(new CustomButtonUI());
    }

    private static void checkIconMatchesMaskBounds(Icon icon, Icon mask) {
        if (mask.getIconWidth() != icon.getIconWidth()
                || mask.getIconHeight() != icon.getIconHeight()) {
            throw new IllegalArgumentException("The mask must be the same size as the icon.");
        }
    }

    // CustomButtonUI implementation so that we can maintain the icon rectangle. //////////////////

    private class CustomButtonUI extends BasicButtonUI {

        private Rectangle fIconRect;

        private boolean maskContains(int x, int y) {
            return fIconRect != null && fIconRect.contains(x, y)
                    && fMask.getRaster().getSample(x - fIconRect.x, y - fIconRect.y, ALPHA_BAND) > 0;
        }

        @Override
        public boolean contains(JComponent c, int x, int y) {
            return maskContains(x, y);
        }

        @Override
        protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
            super.paintIcon(g, c, iconRect);
            // capture where the icon is being painted within the bounds of this button so we can
            // later use this information in the contains calculation.
            if (fIconRect == null || !fIconRect.equals(iconRect)) {
                // create a copy of the icon rectangle, as the given iconRect is a static variable
                // in BasicButtonUI that will be updated for each button painted.
                fIconRect = new Rectangle(iconRect);
            }
        }
    }

}