package org.jdesktop.layout; import javax.swing.border.EmptyBorder; import java.awt.*; import javax.swing.*; import java.util.*; class AquaLayoutStyle extends LayoutStyle { private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); /** Mini size style. */ private final static int MINI = 0; /** Small size style. */ private final static int SMALL = 1; /** Regular size style. */ private final static int REGULAR = 2; /** * The containerGapDefinitions array defines the preferred insets (child * gaps) of a parent container towards one of its child components. * * Note: As of now, we do not yet specify the preferred gap from a child to * its parent. Therefore we may not be able to treat all special cases. * * This array is used to initialize the containerGaps HashMap. * * The array has the following structure, which is supposed to be a a * compromise between legibility and code size. * containerGapDefinitions[0..n] = preferred insets for some parent UI's * containerGapDefinitions[][0..m-3] = name of parent UI, optionally * followed by a full stop and a style name containerGapDefinitions[][m-2] = * mini insets containerGapDefinitions[][m-1] = small insets * containerGapDefinitions[][m] = regular insets */ private final static Object[][] containerGapDefinitions = { // Format: // { list of parent UI's, // mini insets, small insets, regular insets } { "TabbedPaneUI", new Insets(6, 10, 10, 10), new Insets(6, 10, 10, 12), new Insets(12, 20, 20, 20) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_27 // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_26 // note for small and mini size: leave 8 to 10 pixels on top // note for regular size: leave only 12 pixel at top if tabbed pane // UI { "RootPaneUI", new Insets(8, 10, 10, 10), new Insets(8, 10, 10, 12), new Insets(14, 20, 20, 20) }, // These child gaps are used for all other components { "default", new Insets(8, 10, 10, 10), new Insets(8, 10, 10, 12), new Insets(14, 20, 20, 20) }, }; /** * The relatedGapDefinitions table defines the preferred gaps of one party * of two related components. * * The effective preferred gap is the maximum of the preferred gaps of both * parties. * * This array is used to initialize the relatedGaps HashMap. * * The array has the following structure, which is supposed to be a a * compromise between legibility and code size. * containerGapDefinitions[0..n] = preferred gaps for a party of a two * related UI's containerGapDefinitions[][0..m-3] = name of UI optionally * followed by a full stop and a style name containerGapDefinitions[][m-2] = * mini insets containerGapDefinitions[][m-1] = small insets * containerGapDefinitions[][m] = regular insets */ private final static Object[][] relatedGapDefinitions = { // Format: // { list of UI's, // mini insets, small insets, regular insets } // Push Button: // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF104 { "ButtonUI", "ButtonUI.push", "ButtonUI.text", "ToggleButtonUI.push", "ToggleButtonUI.text", new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12) }, // Metal Button // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF187 { "ButtonUI.metal", "ToggleButtonUI.metal", new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8), new Insets(12, 12, 12, 12) }, // Bevel Button (Rounded and Square) // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112 { "ButtonUI.bevel", "ButtonUI.toggle", "ButtonUI.square", "ToggleButtonUI", "ToggleButtonUI.bevel", "ToggleButtonUI.square", "ToggleButtonUI.toggle", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // Bevel Button (Rounded and Square) // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112 { "ButtonUI.bevel.largeIcon", "ToggleButtonUI.bevel.largeIcon", new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8) }, // Icon Button // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF189 { "ButtonUI.icon", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, { "ButtonUI.icon.largeIcon", new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8) }, // Round Button // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF191 { "ButtonUI.round", "ToggleButtonUI.round", new Insets(12, 12, 12, 12), new Insets(12, 12, 12, 12), new Insets(12, 12, 12, 12) }, // Help Button // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF193 { "ButtonUI.help", new Insets(12, 12, 12, 12), new Insets(12, 12, 12, 12), new Insets(12, 12, 12, 12) }, // Segmented Control // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF196 { "ButtonUI.toggleCenter", "ToggleButtonUI.toggleCenter", new Insets(8, 0, 8, 0), new Insets(10, 0, 10, 0), new Insets(12, 0, 12, 0) }, { "ButtonUI.toggleEast", "ToggleButtonUI.toggleEast", new Insets(8, 0, 8, 8), new Insets(10, 0, 10, 10), new Insets(12, 0, 12, 12) }, { "ButtonUI.toggleWest", "ToggleButtonUI.toggleWest", new Insets(8, 8, 8, 0), new Insets(10, 10, 10, 0), new Insets(12, 12, 12, 0) }, { "ButtonUI.toolBarTab", "ToggleButtonUI.toolBarTab", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // Color Well Button // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF213 { "ButtonUI.colorWell", "ToggleButtonUI.colorWell", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198 // FIXME - The following values are given in the AHIG. // In reality, the values further below seem to be more appropriate. // Which ones are right? // { "CheckBoxUI", new Insets(7, 5, 7, 5), new Insets(8, 6, 8, 6), // new Insets(8, 8, 8, 8) }, { "CheckBoxUI", new Insets(6, 5, 6, 5), new Insets(7, 6, 7, 6), new Insets(7, 6, 7, 6) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198 { "ComboBoxUI.editable", new Insets(8, 5, 8, 5), new Insets(10, 6, 10, 6), new Insets(12, 8, 12, 8) }, { "ComboBoxUI.uneditable", new Insets(6, 5, 6, 5), new Insets(8, 6, 8, 6), new Insets(10, 8, 10, 8) }, // There is no spacing given for labels. // This comes from playing with IB. // We use the values here, which is the minimum of the spacing of // all // other components. { "LabelUI", new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8) }, // ? spacing not given { "ListUI", new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6) }, // ? spacing not given { "PanelUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_5.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF106 // ? spacing not given { "ProgressBarUI", new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-BIAHBFAD { "RadioButtonUI", new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF114 // ? spacing not given. We use the same as for text fields. { "ScrollPaneUI", new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_8.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF214 // ? spacing not given // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_2.html#//apple_ref/doc/uid/20000957-TP30000360-CHDEACGD { "SeparatorUI", new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF115 { "SliderUI.horizontal", new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12) }, { "SliderUI.vertical", new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF204 { "SpinnerUI", new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-CHDDBIJE // ? spacing not given { "SplitPaneUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF105 // ? spacing not given { "TabbedPaneUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, { "TableUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // ? spacing not given { "TextAreaUI", "EditorPaneUI", "TextPaneUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF225 { "TextFieldUI", "FormattedTextFieldUI", "PasswordFieldUI", new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10) }, // ? spacing not given { "TreeUI", new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0) }, }; private final static Object[][] unrelatedGapDefinitions = { // UI, mini, small, regular { "ButtonUI.help", new Insets(24, 24, 24, 24), new Insets(24, 24, 24, 24), new Insets(24, 24, 24, 24) }, { "default", new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12), new Insets(14, 14, 14, 14) }, }; /** * The indentGapDefinitions table defines the preferred indentation for * components that are indented after the specified component. * * This array is used to initialize the indentGaps HashMap. * * The array has the following structure, which is supposed to be a a * compromise between legibility and code size. indentGapDefinitions[0..n] = * preferred gaps for a party of a two related UI's * indentGapDefinitions[][0..m-3] = name of UI optionally followed by a full * stop and a style name indentGapDefinitions[][m-2] = mini insets * indentGapDefinitions[][m-1] = small insets indentGapDefinitions[][m] = * regular insets */ private final static Object[][] indentGapDefinitions = { // UI, mini, small, regular // The Aqua L&F does not scale button images of check boxes and // radio // buttons. Therefore we use to the same horizontal indents for all // sizes. { "CheckBoxUI", "RadioButtonUI", new Insets(16, 24, 16, 24), new Insets(20, 24, 20, 24), new Insets(24, 24, 24, 24) }, { "default", new Insets(16, 16, 16, 16), new Insets(20, 20, 20, 20), new Insets(24, 24, 24, 24) }, }; /** * The visualMarginDefinition table defines the visually perceived margin of * the components. * * This array is used to initialize the visualMargins HashMap. * * The array has the following structure, which is supposed to be a a * compromise between legibility and code size. * visualMarginDefinitions[0..n] = preferred gaps for a party of a two * related UI's visualMarginDefinitions[][0..m-1] = name of UI optionally * followed by a full stop and a style name containerGapDefinitions[][m] = * visual margins */ private final static Object[][] visualMarginDefinitions = { // UI, regular { "ButtonUI", "ButtonUI.text", "ToggleButtonUI", "ToggleButtonUI.text", new Insets(5, 3, 3, 3) }, { "ButtonUI.icon", "ToggleButtonUI.icon", new Insets(5, 2, 3, 2) }, { "ButtonUI.toolbar", "ToggleButtonUI.toolbar", new Insets(0, 0, 0, 0) }, { "CheckBoxUI", new Insets(4, 4, 3, 3) }, { "ComboBoxUI", new Insets(2, 3, 4, 3) }, { "DesktopPaneUI", new Insets(0, 0, 0, 0) }, { "EditorPaneUI", "TextAreaUI", "TextPaneUI", new Insets(0, 0, 0, 0) }, { "FormattedTextFieldUI", "PasswordFieldUI", "TextFieldUI", new Insets(0, 0, 0, 0) }, { "LabelUI", new Insets(0, 0, 0, 0) }, { "ListUI", new Insets(0, 0, 0, 0) }, { "PanelUI", new Insets(0, 0, 0, 0) }, { "ProgressBarUI", "ProgressBarUI.horizontal", new Insets(0, 2, 4, 2) }, { "ProgressBarUI.vertical", new Insets(2, 0, 2, 4) }, { "RadioButtonUI", new Insets(4, 4, 3, 3) }, { "ScrollBarUI", new Insets(0, 0, 0, 0) }, { "ScrollPaneUI", new Insets(0, 0, 0, 0) }, { "SpinnerUI", new Insets(0, 0, 0, 0) }, { "SeparatorUI", new Insets(0, 0, 0, 0) }, { "SplitPaneUI", new Insets(0, 0, 0, 0) }, { "SliderUI", "SliderUI.horizontal", new Insets(3, 6, 3, 6) }, { "SliderUI.vertical", new Insets(6, 3, 6, 3) }, { "TabbedPaneUI", "TabbedPaneUI.top", new Insets(5, 7, 10, 7) }, { "TabbedPaneUI.bottom", new Insets(4, 7, 5, 7) }, { "TabbedPaneUI.left", new Insets(4, 6, 10, 7) }, { "TabbedPaneUI.right", new Insets(4, 7, 10, 6) }, { "TableUI", new Insets(0, 0, 0, 0) }, { "TreeUI", new Insets(0, 0, 0, 0) }, { "default", new Insets(0, 0, 0, 0) }, }; /** * The relatedGaps map defines the preferred gaps of one party of two * related components. */ private final static Map RELATED_GAPS = createInsetsMap(relatedGapDefinitions); /** * The unrelatedGaps map defines the preferred gaps of one party of two * unrelated components. */ private final static Map UNRELATED_GAPS = createInsetsMap(unrelatedGapDefinitions); /** * The containerGaps map defines the preferred insets (child gaps) of a * parent component towards one of its children. */ private final static Map CONTAINER_GAPS = createInsetsMap(containerGapDefinitions); /** * The indentGaps map defines the preferred indentation for components that * are indented after the specified component. */ private final static Map INDENT_GAPS = createInsetsMap(indentGapDefinitions); /** * The visualMargins map defines the preferred indentation for components * that are indented after the specified component. */ private final static Map VISUAL_MARGINS = createInsetsMap(visualMarginDefinitions); /** * Creates a map for the specified definitions array. * <p> * The key for the map is the name of the UI, for example, ButtonUI, with a * value of ComponentInsets. Each ComponentInsets may have sub styles. */ // private static Map<String,ComponentInsets> createInsetsMap(Object[][] // definitions) { private static Map createInsetsMap(Object[][] definitions) { Map map = new HashMap(); for (int i = 0; i < definitions.length; i++) { int keys = 0; while (keys < definitions[i].length && (definitions[i][keys] instanceof String)) { keys++; } Insets[] values = new Insets[definitions[i].length - keys]; for (int j = keys; j < definitions[i].length; j++) { values[j - keys] = (Insets) definitions[i][j]; } for (int j = 0; j < keys; j++) { String key = (String) definitions[i][j]; int subindex = key.indexOf('.'); if (subindex == -1) { ComponentInsets componentInsets = (ComponentInsets) map .get(key); if (componentInsets == null) { componentInsets = new ComponentInsets(values); map.put(key, new ComponentInsets(values)); } else { assert (componentInsets.getInsets() == null); componentInsets.setInsets(values); } } else { String subkey = key.substring(subindex + 1); String parentKey = key.substring(0, subindex); ComponentInsets componentInsets = (ComponentInsets) map .get(parentKey); if (componentInsets == null) { componentInsets = new ComponentInsets(); map.put(parentKey, componentInsets); } componentInsets.addSubinsets(subkey, new ComponentInsets( values)); } } } return map; } public static void main(String[] args) { JButton button = new JButton(); button.putClientProperty("JButton.buttonType", "metal"); JButton button2 = new JButton(); LayoutStyle style = new AquaLayoutStyle(); int gap = style.getPreferredGap(button, button2, LayoutStyle.RELATED, SwingConstants.EAST, null); System.err.println("gap= " + gap); button.putClientProperty("JButton.buttonType", "square"); button2.putClientProperty("JButton.buttonType", "square"); gap = style.getPreferredGap(button, button2, LayoutStyle.RELATED, SwingConstants.EAST, null); System.err.println("gap= " + gap); } /** * Creates a new instance. */ public AquaLayoutStyle() { } /** * Returns the amount of space to use between two components. The return * value indicates the distance to place <code>component2</code> relative to * <code>component1</code>. For example, the following returns the amount of * space to place between <code>component2</code> and * <code>component1</code> when <code>component2</code> is placed vertically * above <code>component1</code>: * * <pre> * int gap = getPreferredGap(component1, component2, LayoutStyle.RELATED, * SwingConstants.NORTH, parent); * </pre> * * The <code>type</code> parameter indicates the type of gap being * requested. It can be one of the following values: * <table> * <tr> * <td><code>RELATED</code> * <td>If the two components will be contained in the same parent and are * showing similar logically related items, use <code>RELATED</code>. * <tr> * <td><code>UNRELATED</code> * <td>If the two components will be contained in the same parent but show * logically unrelated items use <code>UNRELATED</code>. * <tr> * <td><code>INDENT</code> * <td>Used to obtain the preferred distance to indent a component relative * to another. For example, if you want to horizontally indent a JCheckBox * relative to a JLabel use <code>INDENT</code>. This is only useful for the * horizontal axis. * </table> * <p> * It's important to note that some look and feels may not distinguish * between <code>RELATED</code> and <code>UNRELATED</code>. * <p> * The return value is not intended to take into account the current size * and position of <code>component2</code> or <code>component1</code>. The * return value may take into consideration various properties of the * components. For example, the space may vary based on font size, or the * preferred size of the component. * * @param component1 * the <code>JComponent</code> <code>component2</code> is being * placed relative to * @param component2 * the <code>JComponent</code> being placed * @param type * how the two components are being placed * @param position * the position <code>component2</code> is being placed relative * to <code>component1</code>; one of * <code>SwingConstants.NORTH</code>, * <code>SwingConstants.SOUTH</code>, * <code>SwingConstants.EAST</code> or * <code>SwingConstants.WEST</code> * @param parent * the parent of <code>component2</code>; this may differ from * the actual parent and may be null * @return the amount of space to place between the two components * @throws IllegalArgumentException * if <code>position</code> is not one of * <code>SwingConstants.NORTH</code>, * <code>SwingConstants.SOUTH</code>, * <code>SwingConstants.EAST</code> or * <code>SwingConstants.WEST</code>; <code>type</code> not one * of <code>INDENT</code>, <code>RELATED</code> or * <code>UNRELATED</code>; or <code>component1</code> or * <code>component2</code> is null */ public int getPreferredGap(JComponent component1, JComponent component2, int type, int position, Container parent) { // Check args super.getPreferredGap(component1, component2, type, position, parent); int result; // Compute gap if (type == INDENT) { // Compute gap if (position == SwingConstants.EAST || position == SwingConstants.WEST) { int gap = getButtonChildIndent(component1, position); if (gap != 0) { return gap; } } int sizeStyle = getSizeStyle(component1); Insets gap1 = getPreferredGap(component1, type, sizeStyle); switch (position) { case SwingConstants.NORTH: result = gap1.bottom; break; case SwingConstants.SOUTH: result = gap1.top; break; case SwingConstants.EAST: result = gap1.left; break; case SwingConstants.WEST: default: result = gap1.right; break; } int raw = result; // Compensate for visual margin Insets visualMargin2 = getVisualMargin(component2); switch (position) { case SwingConstants.NORTH: result -= visualMargin2.bottom; break; case SwingConstants.SOUTH: result -= visualMargin2.top; break; case SwingConstants.EAST: result -= visualMargin2.left; break; case SwingConstants.WEST: result -= visualMargin2.right; default: break; } } else { // Compute gap int sizeStyle = Math.min(getSizeStyle(component1), getSizeStyle(component2)); Insets gap1 = getPreferredGap(component1, type, sizeStyle); Insets gap2 = getPreferredGap(component2, type, sizeStyle); switch (position) { case SwingConstants.NORTH: result = Math.max(gap1.top, gap2.bottom); break; case SwingConstants.SOUTH: result = Math.max(gap1.bottom, gap2.top); break; case SwingConstants.EAST: result = Math.max(gap1.right, gap2.left); break; case SwingConstants.WEST: default: result = Math.max(gap1.left, gap2.right); break; } // Compensate for visual margin Insets visualMargin1 = getVisualMargin(component1); Insets visualMargin2 = getVisualMargin(component2); switch (position) { case SwingConstants.NORTH: result -= visualMargin1.top + visualMargin2.bottom; break; case SwingConstants.SOUTH: result -= visualMargin1.bottom + visualMargin2.top; break; case SwingConstants.EAST: result -= visualMargin1.right + visualMargin2.left; break; case SwingConstants.WEST: result -= visualMargin1.left + visualMargin2.right; default: break; } } // Aqua does not support negative gaps, because all its components are // opaque return Math.max(0, result); } private Insets getPreferredGap(JComponent component, int type, int sizeStyle) { Map gapMap; switch (type) { case INDENT: gapMap = INDENT_GAPS; break; case RELATED: gapMap = RELATED_GAPS; break; case UNRELATED: default: gapMap = UNRELATED_GAPS; break; } String uid = component.getUIClassID(); String style = null; // == is ok here as Strings from Swing get interned, if for some reason // need .equals then must deal with null. if (uid == "ButtonUI" || uid == "ToggleButtonUI") { style = (String) component.getClientProperty("JButton.buttonType"); } else if (uid == "ProgressBarUI") { style = (((JProgressBar) component).getOrientation() == JProgressBar.HORIZONTAL) ? "horizontal" : "vertical"; } else if (uid == "SliderUI") { style = (((JSlider) component).getOrientation() == JProgressBar.HORIZONTAL) ? "horizontal" : "vertical"; } else if (uid == "TabbedPaneUI") { switch (((JTabbedPane) component).getTabPlacement()) { case JTabbedPane.TOP: style = "top"; break; case JTabbedPane.LEFT: style = "left"; break; case JTabbedPane.BOTTOM: style = "bottom"; break; case JTabbedPane.RIGHT: style = "right"; break; } } else if (uid == "ComboBoxUI") { style = ((JComboBox) component).isEditable() ? "editable" : "uneditable"; } return getInsets(gapMap, uid, style, sizeStyle); } /** * Returns the amount of space to position a component inside its parent. * * @param component * the <code>Component</code> being positioned * @param position * the position <code>component</code> is being placed relative * to its parent; one of <code>SwingConstants.NORTH</code>, * <code>SwingConstants.SOUTH</code>, * <code>SwingConstants.EAST</code> or * <code>SwingConstants.WEST</code> * @param parent * the parent of <code>component</code>; this may differ from the * actual parent and may be null * @return the amount of space to place between the component and specified * edge * @throws IllegalArgumentException * if <code>position</code> is not one of * <code>SwingConstants.NORTH</code>, * <code>SwingConstants.SOUTH</code>, * <code>SwingConstants.EAST</code> or * <code>SwingConstants.WEST</code>; or <code>component</code> * is null */ public int getContainerGap(JComponent component, int position, Container parent) { int result; int sizeStyle = Math.min(getSizeStyle(component), getSizeStyle(parent)); // Compute gap Insets gap = getContainerGap(parent, sizeStyle); switch (position) { case SwingConstants.NORTH: result = gap.top; break; case SwingConstants.SOUTH: result = gap.bottom; break; case SwingConstants.EAST: result = gap.right; break; case SwingConstants.WEST: default: result = gap.left; break; } // Compensate for visual margin Insets visualMargin = getVisualMargin(component); switch (position) { case SwingConstants.NORTH: result -= visualMargin.top; break; case SwingConstants.SOUTH: result -= visualMargin.bottom; // Radio buttons in Quaqua are 1 pixel too high, in order // to align their baselines with other components, when no // baseline aware layout manager is used. if (component instanceof JRadioButton) { result--; } break; case SwingConstants.EAST: result -= visualMargin.left; break; case SwingConstants.WEST: result -= visualMargin.right; default: break; } // Aqua does not support negative gaps, because all its components are // opaque return Math.max(0, result); } private Insets getContainerGap(Container container, int sizeStyle) { String uid; if (container instanceof JComponent) { uid = ((JComponent) container).getUIClassID(); } else if (container instanceof Dialog) { uid = "Dialog"; } else if (container instanceof Frame) { uid = "Frame"; } else if (container instanceof java.applet.Applet) { uid = "Applet"; } else if (container instanceof Panel) { uid = "Panel"; } else { uid = "default"; } // FIXME insert style code here for JInternalFrame with palette style return getInsets(CONTAINER_GAPS, uid, null, sizeStyle); } private Insets getInsets(Map gapMap, String uid, String style, int sizeStyle) { if (uid == null) { uid = "default"; } ComponentInsets componentInsets = (ComponentInsets) gapMap.get(uid); if (componentInsets == null) { componentInsets = (ComponentInsets) gapMap.get("default"); if (componentInsets == null) { return EMPTY_INSETS; } } else if (style != null) { ComponentInsets subInsets = componentInsets.getSubinsets(style); if (subInsets != null) { componentInsets = subInsets; } } return componentInsets.getInsets(sizeStyle); } private Insets getVisualMargin(JComponent component) { String uid = component.getUIClassID(); String style = null; if (uid == "ButtonUI" || uid == "ToggleButtonUI") { style = (String) component.getClientProperty("JButton.buttonType"); } else if (uid == "ProgressBarUI") { style = (((JProgressBar) component).getOrientation() == JProgressBar.HORIZONTAL) ? "horizontal" : "vertical"; } else if (uid == "SliderUI") { style = (((JSlider) component).getOrientation() == JProgressBar.HORIZONTAL) ? "horizontal" : "vertical"; } else if (uid == "TabbedPaneUI") { switch (((JTabbedPane) component).getTabPlacement()) { case JTabbedPane.TOP: style = "top"; break; case JTabbedPane.LEFT: style = "left"; break; case JTabbedPane.BOTTOM: style = "bottom"; break; case JTabbedPane.RIGHT: style = "right"; break; } } Insets gap = getInsets(VISUAL_MARGINS, uid, style, 0); // Take into account different positions of the button icon if (uid == "RadioButtonUI" || uid == "CheckBoxUI") { switch (((AbstractButton) component).getHorizontalTextPosition()) { case SwingConstants.RIGHT: gap = new Insets(gap.top, gap.right, gap.bottom, gap.left); break; case SwingConstants.CENTER: gap = new Insets(gap.top, gap.right, gap.bottom, gap.right); break; /* * case SwingConstants.LEFT : break; */ default: gap = new Insets(gap.top, gap.left, gap.bottom, gap.right); } if (component.getBorder() instanceof EmptyBorder) { gap.left -= 2; gap.right -= 2; gap.top -= 2; gap.bottom -= 2; } } return gap; } /** * Returns the size style of a specified component. * * @return REGULAR, SMALL or MINI. */ private int getSizeStyle(Component c) { // Aqua components have a different style depending on the // font size used. // 13 Point = Regular // 11 Point = Small // 9 Point = Mini if (c == null) { return REGULAR; } Font font = c.getFont(); if (font == null) { return REGULAR; } int fontSize = font.getSize(); return (fontSize >= 13) ? REGULAR : ((fontSize > 9) ? SMALL : MINI); } /** * ComponentInsets is used to manage the Insets for a specific Component * type. Each ComponentInsets may also have children (sub) ComponentInsets. * Subinsets are used to represent different styles a component may have. * For example, a Button may not a set of insets, as well as insets when it * has a style of metal. */ private static class ComponentInsets { // Map<String,ComponentInsets> private Map children; private Insets[] insets; public ComponentInsets() { } public ComponentInsets(Insets[] insets) { this.insets = insets; } public void setInsets(Insets[] insets) { this.insets = insets; } public Insets[] getInsets() { return insets; } public Insets getInsets(int size) { if (insets == null) { return EMPTY_INSETS; } return insets[size]; } void addSubinsets(String subkey, ComponentInsets subinsets) { if (children == null) { children = new HashMap(5); } children.put(subkey, subinsets); } ComponentInsets getSubinsets(String subkey) { return (children == null) ? null : (ComponentInsets) children .get(subkey); } } }