001/*
002 * $Id: IconBorder.java 4147 2012-02-01 17:13:24Z 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
022package org.jdesktop.swingx.border;
023
024import java.awt.Component;
025import java.awt.ComponentOrientation;
026import java.awt.Graphics;
027import java.awt.Insets;
028import java.awt.Rectangle;
029import java.io.Serializable;
030
031import javax.swing.Icon;
032import javax.swing.SwingConstants;
033import javax.swing.border.Border;
034
035import org.jdesktop.beans.JavaBean;
036import org.jdesktop.swingx.icon.EmptyIcon;
037
038/**
039 * {@code IconBorder} creates a border that places an {@code Icon} in the border
040 * on the horizontal axis. The border does not add any additional insets other
041 * than the inset required to produce the space for the icon. If additional
042 * insets are required, users should create a
043 * {@link javax.swing.border.CompoundBorder compund border}.
044 * <p>
045 * This border is useful when attempting to add {@code Icon}s to pre-existing
046 * components without requiring specialty painting.
047 * 
048 * @author Amy Fowler
049 * @author Karl Schaefer
050 * 
051 * @version 1.1
052 */
053@JavaBean
054public class IconBorder implements Border, Serializable {
055
056    /**
057     * An empty icon.
058     */
059    public static final Icon EMPTY_ICON = new EmptyIcon();
060    private int padding;
061    private Icon icon;
062    private int iconPosition;
063    private Rectangle iconBounds = new Rectangle();
064    
065    /**
066     * Creates an {@code IconBorder} with an empty icon in a trailing position
067     * with a padding of 4.
068     * 
069     * @see #EMPTY_ICON
070     */
071    public IconBorder() {
072        this(null);
073    }
074
075    /**
076     * Creates an {@code IconBorder} with the specified icon in a trailing
077     * position with a padding of 4.
078     * 
079     * @param validIcon
080     *            the icon to set. This may be {@code null} to represent an
081     *            empty icon.
082     * @see #EMPTY_ICON
083     */
084    public IconBorder(Icon validIcon) {
085        this(validIcon, SwingConstants.TRAILING);
086    }
087
088    /**
089     * Creates an {@code IconBorder} with the specified constraints and a
090     * padding of 4.
091     * 
092     * @param validIcon
093     *            the icon to set. This may be {@code null} to represent an
094     *            empty icon.
095     * @param iconPosition
096     *            the position to place the icon relative to the component
097     *            contents. This must be one of the following
098     *            {@code SwingConstants}:
099     *            <ul>
100     *            <li>{@code LEADING}</li>
101     *            <li>{@code TRAILING}</li>
102     *            <li>{@code EAST}</li>
103     *            <li>{@code WEST}</li>
104     *            </ul>
105     * @throws IllegalArgumentException
106     *             if {@code iconPosition} is not a valid position.
107     * @see #EMPTY_ICON
108     */
109    public IconBorder(Icon validIcon, int iconPosition) {
110        this(validIcon, iconPosition, 4);
111    }
112    
113    /**
114     * Creates an {@code IconBorder} with the specified constraints. If
115     * {@code validIcon} is {@code null}, {@code EMPTY_ICON} is used instead.
116     * If {@code padding} is negative, then the border does not use padding.
117     * 
118     * @param validIcon
119     *            the icon to set. This may be {@code null} to represent an
120     *            empty icon.
121     * @param iconPosition
122     *            the position to place the icon relative to the component
123     *            contents. This must be one of the following
124     *            {@code SwingConstants}:
125     *            <ul>
126     *            <li>{@code LEADING}</li>
127     *            <li>{@code TRAILING}</li>
128     *            <li>{@code EAST}</li>
129     *            <li>{@code WEST}</li>
130     *            </ul>
131     * @param padding
132     *            the padding to surround the icon with. All non-positive values
133     *            set the padding to 0.
134     * @throws IllegalArgumentException
135     *             if {@code iconPosition} is not a valid position.
136     * @see #EMPTY_ICON
137     */
138    public IconBorder(Icon validIcon, int iconPosition, int padding) {
139        setIcon(validIcon);
140        setPadding(padding);
141        setIconPosition(iconPosition);
142    }
143
144    private boolean isValidPosition(int position) {
145        boolean result = false;
146        
147        switch (position) {
148        case SwingConstants.LEADING:
149        case SwingConstants.TRAILING:
150        case SwingConstants.EAST:
151        case SwingConstants.WEST:
152            result = true;
153            break;
154        default:
155            result = false;
156        }
157        
158        return result;
159    }
160    
161    /**
162     * {@inheritDoc}
163     */
164    public Insets getBorderInsets(Component c) {
165        int horizontalInset = icon.getIconWidth() + (2 * padding);
166        int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
167        if (iconPosition == SwingConstants.EAST) {
168            return new Insets(0, 0, 0, horizontalInset);
169        }
170        return new Insets(0, horizontalInset, 0, 0);
171    }
172
173    /**
174     * Sets the icon for this border.
175     * 
176     * @param validIcon
177     *            the icon to set.  This may be {@code null} to represent an
178     *            empty icon.
179     * @see #EMPTY_ICON
180     */
181    public void setIcon(Icon validIcon) {
182        this.icon = validIcon == null ? EMPTY_ICON : validIcon;
183    }
184    
185    /**
186     * This border is not opaque.
187     * 
188     * @return always returns {@code false}
189     */
190    public boolean isBorderOpaque() {
191        return false;
192    }
193
194    /**
195     * {@inheritDoc}
196     */
197    public void paintBorder(Component c, Graphics g, int x, int y, int width,
198        int height) {
199        int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
200        if (iconPosition == SwingConstants.NORTH_EAST) {
201            iconBounds.y = y + padding;
202            iconBounds.x = x + width - padding - icon.getIconWidth();
203        } else if (iconPosition == SwingConstants.EAST) {    // EAST
204            iconBounds.y = y
205                + ((height - icon.getIconHeight()) / 2);
206            iconBounds.x = x + width - padding - icon.getIconWidth();
207        } else if (iconPosition == SwingConstants.WEST) {
208            iconBounds.y = y
209                + ((height - icon.getIconHeight()) / 2);
210            iconBounds.x = x + padding;
211        }
212        iconBounds.width = icon.getIconWidth();
213        iconBounds.height = icon.getIconHeight();
214        icon.paintIcon(c, g, iconBounds.x, iconBounds.y);
215    }
216
217    /**
218     * Returns EAST or WEST depending on the ComponentOrientation and 
219     * the given postion LEADING/TRAILING this method has no effect for other
220     * position values
221     */
222    private int bidiDecodeLeadingTrailing(ComponentOrientation c, int position) {
223        if(position == SwingConstants.TRAILING) {
224            if(!c.isLeftToRight()) {
225                return SwingConstants.WEST;
226            }
227            return SwingConstants.EAST;
228        }
229        if(position == SwingConstants.LEADING) {
230            if(c.isLeftToRight()) {
231                return SwingConstants.WEST;
232            }
233            return SwingConstants.EAST;
234        }
235        return position;
236    }
237
238    /**
239     * Gets the padding surrounding the icon.
240     * 
241     * @return the padding for the icon. This value is guaranteed to be
242     *         nonnegative.
243     */
244    public int getPadding() {
245        return padding;
246    }
247
248    /**
249     * Sets the padding around the icon.
250     * 
251     * @param padding
252     *            the padding to set. If {@code padding < 0}, then
253     *            {@code padding} will be set to {@code 0}.
254     */
255    public void setPadding(int padding) {
256        this.padding = padding < 0 ? 0 : padding;
257    }
258
259    /**
260     * Returns the position to place the icon (relative to the component contents).
261     * 
262     * @return one of the following {@code SwingConstants}:
263     *        <ul>
264     *          <li>{@code LEADING}</li>
265     *          <li>{@code TRAILING}</li>
266     *          <li>{@code EAST}</li>
267     *          <li>{@code WEST}</li>
268     *        </ul>
269     */
270    public int getIconPosition() {
271        return iconPosition;
272    }
273
274    /**
275     * Sets the position to place the icon (relative to the component contents).
276     * 
277     * @param iconPosition must be one of the following {@code SwingConstants}:
278     *        <ul>
279     *          <li>{@code LEADING}</li>
280     *          <li>{@code TRAILING}</li>
281     *          <li>{@code EAST}</li>
282     *          <li>{@code WEST}</li>
283     *        </ul>
284     * @throws IllegalArgumentException
285     *             if {@code iconPosition} is not a valid position.
286     */
287    public void setIconPosition(int iconPosition) {
288        if (!isValidPosition(iconPosition)) {
289            throw new IllegalArgumentException("Invalid icon position");
290        }
291        this.iconPosition = iconPosition;
292    }
293
294}