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