1/*
2 * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package sun.swing;
26
27import java.awt.Container;
28import java.awt.Insets;
29import javax.swing.*;
30import javax.swing.LayoutStyle.ComponentPlacement;
31import javax.swing.border.Border;
32import javax.swing.plaf.UIResource;
33
34/**
35 * An implementation of <code>LayoutStyle</code> that returns 6 for related
36 * components, otherwise 12.  This class also provides helper methods for
37 * subclasses.
38 *
39 */
40public class DefaultLayoutStyle extends LayoutStyle {
41    private static final DefaultLayoutStyle INSTANCE =
42            new DefaultLayoutStyle();
43
44    public static LayoutStyle getInstance() {
45        return INSTANCE;
46    }
47
48    @Override
49    public int getPreferredGap(JComponent component1, JComponent component2,
50            ComponentPlacement type, int position, Container parent) {
51        if (component1 == null || component2 == null || type == null) {
52            throw new NullPointerException();
53        }
54
55        checkPosition(position);
56
57        if (type == ComponentPlacement.INDENT &&
58                (position == SwingConstants.EAST ||
59                 position == SwingConstants.WEST)) {
60            int indent = getIndent(component1, position);
61            if (indent > 0) {
62                return indent;
63            }
64        }
65        return (type == ComponentPlacement.UNRELATED) ? 12 : 6;
66    }
67
68    @Override
69    public int getContainerGap(JComponent component, int position,
70                               Container parent) {
71        if (component == null) {
72            throw new NullPointerException();
73        }
74        checkPosition(position);
75        return 6;
76    }
77
78    /**
79     * Returns true if the classes identify a JLabel and a non-JLabel
80     * along the horizontal axis.
81     */
82    protected boolean isLabelAndNonlabel(JComponent c1, JComponent c2,
83                                         int position) {
84        if (position == SwingConstants.EAST ||
85                position == SwingConstants.WEST) {
86            boolean c1Label = (c1 instanceof JLabel);
87            boolean c2Label = (c2 instanceof JLabel);
88            return ((c1Label || c2Label) && (c1Label != c2Label));
89        }
90        return false;
91    }
92
93    /**
94     * For some look and feels check boxs and radio buttons typically
95     * don't paint the border, yet they have padding for a border.  Look
96     * and feel guidelines generally don't include this space.  Use
97     * this method to subtract this space from the specified
98     * components.
99     *
100     * @param source First component
101     * @param target Second component
102     * @param position Position doing layout along.
103     * @param offset Ideal offset, not including border/margin
104     * @return offset - border/margin around the component.
105     */
106    protected int getButtonGap(JComponent source, JComponent target,
107                               int position, int offset) {
108        offset -= getButtonGap(source, position);
109        if (offset > 0) {
110            offset -= getButtonGap(target, flipDirection(position));
111        }
112        if (offset < 0) {
113            return 0;
114        }
115        return offset;
116    }
117
118    /**
119     * For some look and feels check boxs and radio buttons typically
120     * don't paint the border, yet they have padding for a border.  Look
121     * and feel guidelines generally don't include this space.  Use
122     * this method to subtract this space from the specified
123     * components.
124     *
125     * @param source Component
126     * @param position Position doing layout along.
127     * @param offset Ideal offset, not including border/margin
128     * @return offset - border/margin around the component.
129     */
130    protected int getButtonGap(JComponent source, int position, int offset) {
131        offset -= getButtonGap(source, position);
132        return Math.max(offset, 0);
133    }
134
135    /**
136     * If <code>c</code> is a check box or radio button, and the border is
137     * not painted this returns the inset along the specified axis.
138     */
139    public int getButtonGap(JComponent c, int position) {
140        String classID = c.getUIClassID();
141        if ((classID == "CheckBoxUI" || classID == "RadioButtonUI") &&
142                !((AbstractButton)c).isBorderPainted()) {
143            Border border = c.getBorder();
144            if (border instanceof UIResource) {
145                return getInset(c, position);
146            }
147        }
148        return 0;
149    }
150
151    private void checkPosition(int position) {
152        if (position != SwingConstants.NORTH &&
153                position != SwingConstants.SOUTH &&
154                position != SwingConstants.WEST &&
155                position != SwingConstants.EAST) {
156            throw new IllegalArgumentException();
157        }
158    }
159
160    protected int flipDirection(int position) {
161        switch(position) {
162        case SwingConstants.NORTH:
163            return SwingConstants.SOUTH;
164        case SwingConstants.SOUTH:
165            return SwingConstants.NORTH;
166        case SwingConstants.EAST:
167            return SwingConstants.WEST;
168        case SwingConstants.WEST:
169            return SwingConstants.EAST;
170        }
171        assert false;
172        return 0;
173    }
174
175    /**
176     * Returns the amount to indent the specified component if it's
177     * a JCheckBox or JRadioButton.  If the component is not a JCheckBox or
178     * JRadioButton, 0 will be returned.
179     */
180    protected int getIndent(JComponent c, int position) {
181        String classID = c.getUIClassID();
182        if (classID == "CheckBoxUI" || classID == "RadioButtonUI") {
183            AbstractButton button = (AbstractButton)c;
184            Insets insets = c.getInsets();
185            Icon icon = getIcon(button);
186            int gap = button.getIconTextGap();
187            if (isLeftAligned(button, position)) {
188                return insets.left + icon.getIconWidth() + gap;
189            } else if (isRightAligned(button, position)) {
190                return insets.right + icon.getIconWidth() + gap;
191            }
192        }
193        return 0;
194    }
195
196    private Icon getIcon(AbstractButton button) {
197        Icon icon = button.getIcon();
198        if (icon != null) {
199            return icon;
200        }
201        String key = null;
202        if (button instanceof JCheckBox) {
203            key = "CheckBox.icon";
204        } else if (button instanceof JRadioButton) {
205            key = "RadioButton.icon";
206        }
207        if (key != null) {
208            Object oIcon = UIManager.get(key);
209            if (oIcon instanceof Icon) {
210                return (Icon)oIcon;
211            }
212        }
213        return null;
214    }
215
216    private boolean isLeftAligned(AbstractButton button, int position) {
217        if (position == SwingConstants.WEST) {
218            boolean ltr = button.getComponentOrientation().isLeftToRight();
219            int hAlign = button.getHorizontalAlignment();
220            return ((ltr && (hAlign == SwingConstants.LEFT ||
221                             hAlign == SwingConstants.LEADING)) ||
222                    (!ltr && (hAlign == SwingConstants.TRAILING)));
223        }
224        return false;
225    }
226
227    private boolean isRightAligned(AbstractButton button, int position) {
228        if (position == SwingConstants.EAST) {
229            boolean ltr = button.getComponentOrientation().isLeftToRight();
230            int hAlign = button.getHorizontalAlignment();
231            return ((ltr && (hAlign == SwingConstants.RIGHT ||
232                             hAlign == SwingConstants.TRAILING)) ||
233                    (!ltr && (hAlign == SwingConstants.LEADING)));
234        }
235        return false;
236    }
237
238    private int getInset(JComponent c, int position) {
239        return getInset(c.getInsets(), position);
240    }
241
242    private int getInset(Insets insets, int position) {
243        if (insets == null) {
244            return 0;
245        }
246        switch(position) {
247        case SwingConstants.NORTH:
248            return insets.top;
249        case SwingConstants.SOUTH:
250            return insets.bottom;
251        case SwingConstants.EAST:
252            return insets.right;
253        case SwingConstants.WEST:
254            return insets.left;
255        }
256        assert false;
257        return 0;
258    }
259}
260