Skip to content

Instantly share code, notes, and snippets.

@hakanai
Created February 7, 2014 12:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hakanai/8861831 to your computer and use it in GitHub Desktop.
Save hakanai/8861831 to your computer and use it in GitHub Desktop.
Modified version of AquaLayoutStyle.java with one correction and some additional debug output
/*
* Haqua - a collection of hacks to work around issues in the Aqua look and feel
* Copyright (C) 2014 Trejkaz, Haqua Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.trypticon.haqua;
import sun.swing.DefaultLayoutStyle;
import javax.swing.AbstractButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.Panel;
import java.util.HashMap;
import java.util.Map;
/**
* <p>An implementation of {@code LayoutStyle} for Mac OS X Tiger.</p>
*
* <p>The information used for this layout style comes from:
* http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/</p>
*
* <p>This code was taken (by Haqua) from the Swing Layout project, which was in turn derived from Quaqua.
* The changes made here are as follows:</p>
* <ul>
* <li>Changed to fit the {@link javax.swing.LayoutStyle} API which is now in Swing.</li>
* <li>Removed {@code EMPTY_INSETS} constant. {@link Insets} are mutable and the caller was receiving
* a shared instance.</li>
* <li>Updated code style to fit this project (automatically.)</li>
* </ul>
*
* @author Werner Randelshofer
* @author trejkaz
*/
class AquaLayoutStyle extends DefaultLayoutStyle {
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",
// TODO: This is most likely wrong for non-default buttons, which seem to have smaller margins
new Insets(5, 6, 3, 6)
},
{ "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<String, ComponentInsets> RELATED_GAPS = createInsetsMap(relatedGapDefinitions);
/**
* The unrelatedGaps map defines the preferred gaps
* of one party of two unrelated components.
*/
private final static Map<String, ComponentInsets> 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<String, ComponentInsets> CONTAINER_GAPS = createInsetsMap(containerGapDefinitions);
/**
* The indentGaps map defines the preferred indentation
* for components that are indented after the specified component.
*/
private final static Map<String, ComponentInsets> INDENT_GAPS = createInsetsMap(indentGapDefinitions);
/**
* The visualMargins map defines the preferred indentation
* for components that are indented after the specified component.
*/
private final static Map<String, ComponentInsets> 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) {
Map<String, ComponentInsets> map = new HashMap<>();
for (Object[] definition : definitions) {
int keys = 0;
while (keys < definition.length && (definition[keys] instanceof String)) {
keys++;
}
Insets[] values = new Insets[definition.length - keys];
for (int j = keys; j < definition.length; j++) {
values[j - keys] = (Insets) definition[j];
}
for (int j = 0; j < keys; j++) {
String key = (String) definition[j];
int subIndex = key.indexOf('.');
if (subIndex == -1) {
ComponentInsets componentInsets = map.get(key);
if (componentInsets == null) {
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 = map.get(parentKey);
if (componentInsets == null) {
componentInsets = new ComponentInsets();
map.put(parentKey, componentInsets);
}
componentInsets.addSubInsets(subkey,
new ComponentInsets(values));
}
}
}
return map;
}
@Override
public int getPreferredGap(JComponent component1, JComponent component2, ComponentPlacement type, int position, Container parent) {
// Check args
super.getPreferredGap(component1, component2, type, position, parent);
int result;
// Compute gap
if (type == ComponentPlacement.INDENT) {
// Compute gap
if (position == SwingConstants.EAST || position == SwingConstants.WEST) {
int gap = getButtonGap(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;
}
// 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.
// ^-- TODO: Says the comment, but if I put a button on a red background, it looks transparent to me.
result = Math.max(0, result);
System.err.println(String.format("getPreferredGap(%s, %s, %s, %s, %s) -> %s", component1.getName(), component2.getName(), type, positionToString(position), parent.getName(), result));
return result;
}
@SuppressWarnings("StringEquality") // used to compare UI class ID instances, supposedly safe
private Insets getPreferredGap(JComponent component, ComponentPlacement 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);
}
@Override
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.
// ^-- TODO: Says the comment, but since this isn't Quaqua, is this valid logic?
if (component instanceof JRadioButton) {
result--;
}
break;
case SwingConstants.EAST :
result -= visualMargin.right;
break;
case SwingConstants.WEST :
result -= visualMargin.left;
default :
break;
}
// Aqua does not support negative gaps, because all its components are opaque.
// ^-- TODO: Says the comment, but if I put a button on a red background, it looks transparent to me.
result = Math.max(0, result);
System.err.println(String.format("getContainerGap(%s, %s, %s) -> %s", component.getName(), positionToString(position), parent.getName(), result));
return 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);
}
@Override
protected int getButtonGap(JComponent source, JComponent target, int position, int offset) {
return super.getButtonGap(source, target, position, offset);
}
@Override
protected int getButtonGap(JComponent source, int position, int offset) {
return super.getButtonGap(source, position, offset);
}
@Override
public int getButtonGap(JComponent c, int position) {
return super.getButtonGap(c, position);
}
@Override
protected int getIndent(JComponent c, int position) {
return super.getIndent(c, position);
}
@SuppressWarnings("StringEquality") // used to compare UI class ID instances, supposedly safe
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);
}
/**
* Diagnostics method to convert the position value to a string for logging.
*
* @param position the position.
* @return a string representation of the position.
*/
private static String positionToString(int position) {
switch (position) {
case SwingConstants.NORTH:
return "NORTH";
case SwingConstants.SOUTH:
return "SOUTH";
case SwingConstants.EAST:
return "EAST";
case SwingConstants.WEST:
return "WEST";
default:
throw new IllegalArgumentException("Invalid position: " + position);
}
}
/**
* 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 {
private Map<String, ComponentInsets> children;
private Insets[] insets;
private ComponentInsets() {
}
private ComponentInsets(Insets[] insets) {
this.insets = insets;
}
private void setInsets(Insets[] insets) {
this.insets = insets;
}
private Insets[] getInsets() {
return insets;
}
private Insets getInsets(int size) {
if (insets == null) {
return EMPTY_INSETS;
}
return insets[size];
}
private void addSubInsets(String subKey, ComponentInsets subInsets) {
if (children == null) {
children = new HashMap<>(5);
}
children.put(subKey, subInsets);
}
private ComponentInsets getSubInsets(String subKey) {
return (children == null) ? null : children.get(subKey);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment