net.miginfocom.swing.SwingComponentWrapper Maven / Gradle / Ivy
Show all versions of miglayout-swing Show documentation
package net.miginfocom.swing;
/*
* License (BSD):
* ==============
*
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
* Neither the name of the MiG InfoCom AB nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* @version 1.0
* @author Mikael Grev, MiG InfoCom AB
* Date: 2006-sep-08
*/
import net.miginfocom.layout.ComponentWrapper;
import net.miginfocom.layout.ContainerWrapper;
import net.miginfocom.layout.LayoutUtil;
import net.miginfocom.layout.PlatformDefaults;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.IdentityHashMap;
import java.util.StringTokenizer;
/**
*/
public class SwingComponentWrapper implements ComponentWrapper
{
private static boolean maxSet = false;
private static boolean vp = true;
/** Debug color for component bounds outline.
*/
private static final Color DB_COMP_OUTLINE = new Color(0, 0, 200);
/** Property to use in LAF settings and as JComponent client property
* to specify the visual padding.
*
*/
private static final String VISUAL_PADDING_PROPERTY = net.miginfocom.layout.PlatformDefaults.VISUAL_PADDING_PROPERTY;
private final Component c;
private int compType = TYPE_UNSET;
private Boolean bl = null;
private boolean prefCalled = false;
public SwingComponentWrapper(Component c)
{
this.c = c;
}
@Override
public final int getBaseline(int width, int height)
{
int h = height;
int[] visPad = getVisualPadding();
if (h < 0) {
h = c.getHeight();
} else if (visPad != null) {
h = height + visPad[0] + visPad[2];
}
int baseLine = c.getBaseline(Math.max(0, width < 0 ? c.getWidth() : width), Math.max(0, h));
if (baseLine != -1 && visPad != null)
baseLine -= visPad[0];
return baseLine;
}
@Override
public final Object getComponent()
{
return c;
}
/** Cache.
*/
private final static IdentityHashMap FM_MAP = new IdentityHashMap(4);
private final static Font SUBST_FONT = new Font("sansserif", Font.PLAIN, 11);
@Override
public final float getPixelUnitFactor(boolean isHor)
{
switch (PlatformDefaults.getLogicalPixelBase()) {
case PlatformDefaults.BASE_FONT_SIZE:
Font font = c.getFont();
FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT);
Point.Float p = FM_MAP.get(fm);
if (p == null) {
Rectangle2D r = fm.getStringBounds("X", c.getGraphics());
p = new Point.Float(((float) r.getWidth()) / 6f, ((float) r.getHeight()) / 13.27734375f);
FM_MAP.put(fm, p);
}
return isHor ? p.x : p.y;
case PlatformDefaults.BASE_SCALE_FACTOR:
Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor();
float scaleFactor = (s != null) ? s : 1f;
// If the current look and feel is able to scale and provides
// an scale factor, then use it.
Object lafScaleFactorObj = UIManager.get( "laf.scaleFactor" );
if( lafScaleFactorObj instanceof Number ) {
float lafScaleFactor = ((Number)lafScaleFactorObj).floatValue();
return scaleFactor * lafScaleFactor;
}
// Swing in Java 9 scales automatically using the system scale factor(s) that the
// user can change in the system settings (Windows: Control Panel; Mac: System Preferences).
// Each connected screen may use its own scale factor
// (e.g. 1.5 for primary 4K 40inch screen and 1.0 for secondary HD screen).
float screenScale = isJava9orLater
? 1f // use system scale factor(s)
: (float) (isHor ? getHorizontalScreenDPI() : getVerticalScreenDPI()) / (float) PlatformDefaults.getDefaultDPI();
return scaleFactor * screenScale;
default:
return 1f;
}
}
private static boolean isJava9orLater;
static {
try {
// Java 9 version-String Scheme: http://openjdk.java.net/jeps/223
StringTokenizer st = new StringTokenizer(System.getProperty("java.version"), "._-+");
int majorVersion = Integer.parseInt(st.nextToken());
isJava9orLater = majorVersion >= 9;
} catch (Exception e) {
// Java 8 or older
}
}
// /** Cache.
// */
// private final static IdentityHashMap FM_MAP2 = new IdentityHashMap(4);
// private final static Font SUBST_FONT2 = new Font("sansserif", Font.PLAIN, 11);
//
// public float getDialogUnit(boolean isHor)
// {
// Font font = c.getFont();
// FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT2);
// Point.Float dluP = FM_MAP2.get(fm);
// if (dluP == null) {
// float w = fm.charWidth('X') / 4f;
// int ascent = fm.getAscent();
// float h = (ascent > 14 ? ascent : ascent + (15 - ascent) / 3) / 8f;
//
// dluP = new Point.Float(w, h);
// FM_MAP2.put(fm, dluP);
// }
// return isHor ? dluP.x : dluP.y;
// }
@Override
public final int getX()
{
return c.getX();
}
@Override
public final int getY()
{
return c.getY();
}
@Override
public final int getHeight()
{
return c.getHeight();
}
@Override
public final int getWidth()
{
return c.getWidth();
}
@Override
public final int getScreenLocationX()
{
Point p = new Point();
SwingUtilities.convertPointToScreen(p, c);
return p.x;
}
@Override
public final int getScreenLocationY()
{
Point p = new Point();
SwingUtilities.convertPointToScreen(p, c);
return p.y;
}
@Override
public final int getMinimumHeight(int sz)
{
if (prefCalled == false) {
c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize();
prefCalled = true;
}
return c.getMinimumSize().height;
}
@Override
public final int getMinimumWidth(int sz)
{
if (prefCalled == false) {
c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize();
prefCalled = true;
}
return c.getMinimumSize().width;
}
@Override
public final int getPreferredHeight(int sz)
{
// If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
c.setBounds(c.getX(), c.getY(), sz, 1);
return c.getPreferredSize().height;
}
@Override
public final int getPreferredWidth(int sz)
{
// If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
c.setBounds(c.getX(), c.getY(), 1, sz);
return c.getPreferredSize().width;
}
@Override
public final int getMaximumHeight(int sz)
{
if (!isMaxSet(c))
return Integer.MAX_VALUE;
return c.getMaximumSize().height;
}
@Override
public final int getMaximumWidth(int sz)
{
if (!isMaxSet(c))
return Integer.MAX_VALUE;
return c.getMaximumSize().width;
}
private boolean isMaxSet(Component c)
{
return c.isMaximumSizeSet();
}
@Override
public final ContainerWrapper getParent()
{
Container p = c.getParent();
return p != null ? new SwingContainerWrapper(p) : null;
}
@Override
public final int getHorizontalScreenDPI() {
try {
return c.getToolkit().getScreenResolution();
} catch (HeadlessException ex) {
return PlatformDefaults.getDefaultDPI();
}
}
@Override
public final int getVerticalScreenDPI()
{
try {
return c.getToolkit().getScreenResolution();
} catch (HeadlessException ex) {
return PlatformDefaults.getDefaultDPI();
}
}
@Override
public final int getScreenWidth()
{
try {
return c.getToolkit().getScreenSize().width;
} catch (HeadlessException ex) {
return 1024;
}
}
@Override
public final int getScreenHeight()
{
try {
return c.getToolkit().getScreenSize().height;
} catch (HeadlessException ex) {
return 768;
}
}
@Override
public final boolean hasBaseline()
{
if (bl == null) {
try {
if(c instanceof JLabel && ((JComponent)c).getClientProperty(BasicHTML.propertyKey) != null) {
bl = Boolean.FALSE;
}else {
bl = getBaseline(8192, 8192) > -1; // Use large number but don't risk overflow or exposing size bugs with Integer.MAX_VALUE
}
// Removed since OTHER is sometimes returned even though there is a valid baseline (e.g. an empty JComboBox)
// if (c.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.OTHER) {
// bl = Boolean.FALSE;
// } else {
// Removed since it made some components layout themselves to the minimum size and that stuck after that. E.g. JLabel with HTML content and white spaces would be very tall.
// Dimension d = c.getMinimumSize();
// bl = getBaseline(d.width, d.height) > -1;
// }
} catch (Throwable ex) {
bl = Boolean.FALSE;
}
}
return bl;
}
@Override
public final String getLinkId()
{
return c.getName();
}
@Override
public final void setBounds(int x, int y, int width, int height)
{
c.setBounds(x, y, width, height);
}
@Override
public boolean isVisible()
{
return c.isVisible();
}
@Override
public final int[] getVisualPadding()
{
int[] padding = null;
if (isVisualPaddingEnabled()) {
//First try "visualPadding" client property
if (c instanceof JComponent) {
JComponent component = (JComponent) c;
Object padValue = component.getClientProperty(VISUAL_PADDING_PROPERTY);
if (padValue instanceof int[] ) {
//client property value could be an int[]
padding = (int[]) padValue;
} else if (padValue instanceof Insets) {
//OR client property value could be an Insets
Insets padInsets = (Insets) padValue;
padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
}
if (padding == null) {
//No client property set on the individual JComponent,
// so check for a LAF setting for the component type.
String classID;
switch (getComponentType(false)) {
case TYPE_BUTTON:
Border border = component.getBorder();
if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
Object buttonType = component.getClientProperty("JButton.buttonType");
if (buttonType == null) {
classID = component.getHeight() < 33 ? "Button" : "Button.bevel";
} else {
classID = "Button." + buttonType;
}
if (((AbstractButton) component).getIcon() != null)
classID += ".icon";
} else {
classID = "Button";
}
} else {
classID = "";
}
break;
case TYPE_CHECK_BOX:
border = component.getBorder();
if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
Object size = component.getClientProperty("JComponent.sizeVariant");
if (size != null && size.toString().equals("regular") == false) {
size = "." + size;
} else {
size = "";
}
if (component instanceof JRadioButton) {
classID = "RadioButton" + size;
} else if (component instanceof JCheckBox) {
classID = "CheckBox" + size;
} else {
classID = "ToggleButton" + size;
}
} else {
classID = "";
}
break;
case TYPE_COMBO_BOX:
if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
if (((JComboBox) component).isEditable()) {
Object isSquare = component.getClientProperty("JComboBox.isSquare");
if (isSquare != null && isSquare.toString().equals("true")) {
classID = "ComboBox.editable.isSquare";
} else {
classID = "ComboBox.editable";
}
} else {
Object isSquare = component.getClientProperty("JComboBox.isSquare");
Object isPopDown = component.getClientProperty("JComboBox.isPopDown");
if (isSquare != null && isSquare.toString().equals("true")) {
classID = "ComboBox.isSquare";
} else if (isPopDown != null && isPopDown.toString().equals("true")) {
classID = "ComboBox.isPopDown";
} else {
classID = "ComboBox";
}
}
} else {
classID = "ComboBox";
}
break;
case TYPE_CONTAINER:
classID = "Container";
break;
case TYPE_IMAGE:
classID = "Image";
break;
case TYPE_LABEL:
classID = "Label";
break;
case TYPE_LIST:
classID = "List";
break;
case TYPE_PANEL:
classID = "Panel";
break;
case TYPE_PROGRESS_BAR:
classID = "ProgressBar";
break;
case TYPE_SCROLL_BAR:
classID = "ScrollBar";
break;
case TYPE_SCROLL_PANE:
classID = "ScrollPane";
break;
case TYPE_SEPARATOR:
classID = "Separator";
break;
case TYPE_SLIDER:
classID = "Slider";
break;
case TYPE_SPINNER:
classID = "Spinner";
break;
case TYPE_TABLE:
classID = "Table";
break;
case TYPE_TABBED_PANE:
classID = "TabbedPane";
break;
case TYPE_TEXT_AREA:
classID = "TextArea";
break;
case TYPE_TEXT_FIELD:
border = component.getBorder();
if (!component.isOpaque() && border != null && border.getClass().getSimpleName().equals("AquaTextFieldBorder")) {
classID = "TextField";
} else {
classID = "";
}
break;
case TYPE_TREE:
classID = "Tree";
break;
case TYPE_UNKNOWN:
classID = "Other";
break;
case TYPE_UNSET:
default:
classID = "";
break;
}
padValue = PlatformDefaults.getDefaultVisualPadding(classID + "." + VISUAL_PADDING_PROPERTY);
if (padValue instanceof int[]) {
//client property value could be an int[]
padding = (int[]) padValue;
} else if (padValue instanceof Insets) {
//OR client property value could be an Insets
Insets padInsets = (Insets) padValue;
padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
}
}
}
}
return padding;
}
/**
* @deprecated Java 1.4 is not supported anymore
*/
public static boolean isMaxSizeSetOn1_4()
{
return maxSet;
}
/**
* @deprecated Java 1.4 is not supported anymore
*/
public static void setMaxSizeSetOn1_4(boolean b)
{
maxSet = b;
}
public static boolean isVisualPaddingEnabled()
{
return vp;
}
public static void setVisualPaddingEnabled(boolean b)
{
vp = b;
}
@Override
public final void paintDebugOutline(boolean showVisualPadding)
{
if (c.isShowing() == false)
return;
Graphics2D g = (Graphics2D) c.getGraphics();
if (g == null)
return;
g.setPaint(DB_COMP_OUTLINE);
g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 4f}, 0));
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
if (showVisualPadding && isVisualPaddingEnabled()) {
int[] padding = getVisualPadding();
if (padding != null) {
g.setColor(Color.GREEN);
g.drawRect(padding[1], padding[0], (getWidth() - 1) - (padding[1] + padding[3]), (getHeight() - 1) - (padding[0] + padding[2]));
}
}
}
@Override
public int getComponentType(boolean disregardScrollPane)
{
if (compType == TYPE_UNSET)
compType = checkType(disregardScrollPane);
return compType;
}
@Override
public int getLayoutHashCode()
{
Dimension d = c.getMaximumSize();
int hash = d.width + (d.height << 5);
d = c.getPreferredSize();
hash += (d.width << 10) + (d.height << 15);
d = c.getMinimumSize();
hash += (d.width << 20) + (d.height << 25);
if (c.isVisible())
hash += 1324511;
String id = getLinkId();
if (id != null)
hash += id.hashCode();
return hash;
}
private int checkType(boolean disregardScrollPane)
{
Component c = this.c;
if (disregardScrollPane) {
if (c instanceof JScrollPane) {
c = ((JScrollPane) c).getViewport().getView();
} else if (c instanceof ScrollPane) {
c = ((ScrollPane) c).getComponent(0);
}
}
if (c instanceof JTextField || c instanceof TextField) {
return TYPE_TEXT_FIELD;
} else if (c instanceof JLabel || c instanceof Label) {
return TYPE_LABEL;
} else if (c instanceof JCheckBox || c instanceof JRadioButton || c instanceof Checkbox) {
return TYPE_CHECK_BOX;
} else if (c instanceof AbstractButton || c instanceof Button) {
return TYPE_BUTTON;
} else if (c instanceof JComboBox || c instanceof Choice) {
return TYPE_COMBO_BOX;
} else if (c instanceof JTextComponent || c instanceof TextComponent) {
return TYPE_TEXT_AREA;
} else if (c instanceof JPanel || c instanceof Canvas) {
return TYPE_PANEL;
} else if (c instanceof JList || c instanceof List) {
return TYPE_LIST;
} else if (c instanceof JTable) {
return TYPE_TABLE;
} else if (c instanceof JSeparator) {
return TYPE_SEPARATOR;
} else if (c instanceof JSpinner) {
return TYPE_SPINNER;
} else if (c instanceof JTabbedPane) {
return TYPE_TABBED_PANE;
} else if (c instanceof JProgressBar) {
return TYPE_PROGRESS_BAR;
} else if (c instanceof JSlider) {
return TYPE_SLIDER;
} else if (c instanceof JScrollPane) {
return TYPE_SCROLL_PANE;
} else if (c instanceof JScrollBar || c instanceof Scrollbar) {
return TYPE_SCROLL_BAR;
} else if (c instanceof Container) { // only AWT components is not containers.
return TYPE_CONTAINER;
}
return TYPE_UNKNOWN;
}
@Override
public final int hashCode()
{
return getComponent().hashCode();
}
@Override
public final boolean equals(Object o)
{
if (o instanceof ComponentWrapper == false)
return false;
return c.equals(((ComponentWrapper) o).getComponent());
}
@Override
public int getContentBias()
{
return c instanceof JTextArea || c instanceof JEditorPane || (c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty("migLayout.dynamicAspectRatio"))) ? LayoutUtil.HORIZONTAL : -1;
}
}