org.jdesktop.swingx.plaf.BuddyLayoutAndBorder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swingx-all Show documentation
Show all versions of swingx-all Show documentation
Fork of the inactive swingx-all library
package org.jdesktop.swingx.plaf;
import org.jdesktop.swingx.prompt.BuddySupport;
import org.jdesktop.swingx.prompt.BuddySupport.Position;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicBorders.MarginBorder;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class BuddyLayoutAndBorder implements LayoutManager, Border, PropertyChangeListener, UIResource {
private JTextField textField;
private Border borderDelegate;
/**
* Installs a {@link BuddyLayoutAndBorder} as a layout and border of the
* given text field. Registers a {@link PropertyChangeListener} to wrap any
* subsequently set border on the text field.
*/
protected void install(JTextField textField) {
uninstall();
this.textField = textField;
textField.setLayout(this);
replaceBorderIfNecessary();
textField.addPropertyChangeListener("border", this);
}
public Border getBorderDelegate() {
return borderDelegate;
}
/**
* Wraps and replaces the text fields default border with this object, to
* honor the button margins and sizes of the search, clear and popup buttons
* and the layout style.
*/
protected void replaceBorderIfNecessary() {
Border original = textField.getBorder();
if (!(original instanceof BuddyLayoutAndBorder)) {
borderDelegate = original;
textField.setBorder(this);
}
}
/**
* Does nothing.
*
* @see BuddySupport#add(Component, Position, JTextField)
*/
@Override
public void addLayoutComponent(String name, Component comp) {
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public Dimension preferredLayoutSize(Container parent) {
Dimension d = new Dimension();
// height of highest buddy.
for (Component c : BuddySupport.getLeft(textField)) {
d.height = Math.max(d.height, c.getPreferredSize().height);
}
for (Component c : BuddySupport.getRight(textField)) {
d.height = Math.max(d.height, c.getPreferredSize().height);
}
Insets insets = getRealBorderInsets();
d.height += insets.top + insets.bottom;
d.width += insets.left + insets.right;
Insets outerMargin = BuddySupport.getOuterMargin(textField);
if (outerMargin != null) {
d.width += outerMargin.left + outerMargin.right;
d.height += outerMargin.bottom + outerMargin.top;
}
return d;
}
/**
* Does nothing.
*
* @see BuddySupport#remove(javax.swing.JComponent, JTextField)
*/
@Override
public void removeLayoutComponent(Component comp) {
}
@Override
public void layoutContainer(Container parent) {
Rectangle visibleRect = getVisibleRect();
Dimension size;
for (Component comp : BuddySupport.getLeft(textField)) {
if (!comp.isVisible()) {
continue;
}
size = comp.getPreferredSize();
comp.setBounds(visibleRect.x, centerY(visibleRect, size), size.width, size.height);
visibleRect.x += size.width;
visibleRect.width -= size.width;
}
for (Component comp : BuddySupport.getRight(textField)) {
if (!comp.isVisible()) {
continue;
}
size = comp.getPreferredSize();
comp.setBounds(visibleRect.x + visibleRect.width - size.width, centerY(visibleRect, size), size.width, size.height);
visibleRect.width -= size.width;
}
}
protected int centerY(Rectangle rect, Dimension size) {
return (int) (rect.getCenterY() - size.height / 2);
}
/**
* @return the rectangle allocated by the text field, including the space
* allocated by the child components left and right, the text fields
* original border insets and the outer margin.
*/
protected Rectangle getVisibleRect() {
Rectangle alloc = SwingUtilities.getLocalBounds(textField);
substractInsets(alloc, getRealBorderInsets());
substractInsets(alloc, BuddySupport.getOuterMargin(textField));
return alloc;
}
private static void substractInsets(Rectangle alloc, Insets insets) {
if (insets != null) {
alloc.x += insets.left;
alloc.y += insets.top;
alloc.width -= insets.left + insets.right;
alloc.height -= insets.top + insets.bottom;
}
}
/**
* Returns the {@link Insets} of the original {@link Border} plus the space
* required by the child components.
*
* @see Border#getBorderInsets(Component)
*/
@Override
public Insets getBorderInsets(Component c) {
Insets insets;
if (borderDelegate != null) {
// Original insets are cloned to make it work in Mac OS X Aqua LnF.
// Seems that this LnF uses a shared insets instance which should
// not be modified.
// Include margin here
insets = (Insets) borderDelegate.getBorderInsets(textField).clone();
} else {
insets = new Insets(0, 0, 0, 0);
}
//somehow this happens sometimes
if (textField == null) {
return insets;
}
for (Component comp : BuddySupport.getLeft(textField)) {
insets.left += comp.isVisible() ? comp.getPreferredSize().width : 0;
}
for (Component comp : BuddySupport.getRight(textField)) {
insets.right += comp.isVisible() ? comp.getPreferredSize().width : 0;
}
Insets outerMargin = BuddySupport.getOuterMargin(textField);
if (outerMargin != null) {
insets.left += outerMargin.left;
insets.right += outerMargin.right;
insets.top += outerMargin.top;
insets.bottom += outerMargin.bottom;
}
return insets;
}
/**
* Returns the insets of the original border (without the margin! Beware of
* {@link MarginBorder}!).
*
* @return the insets of the border delegate
*/
public Insets getRealBorderInsets() {
if (borderDelegate == null) {
//SwingX 1287: null borders are possible and give no insets
return new Insets(0, 0, 0, 0);
}
Insets insets = borderDelegate.getBorderInsets(textField);
// for some reason, all LnFs add the margin to the insets.
// we want the insets without the margin, so substract the margin here!!
// TODO: consider checking, if the current border really includes the
// margin. Consider:
// 1. Not only MarginBorder adds margin
// 2. Calling getBorderInsets(null) is not appropriate, since some
// Borders can't handle null values.
Insets margin = textField.getMargin();
if (margin != null) {
insets.left -= margin.left;
insets.right -= margin.right;
insets.top -= margin.top;
insets.bottom -= margin.bottom;
}
return insets;
}
@Override
public boolean isBorderOpaque() {
if (borderDelegate == null) {
return false;
}
return borderDelegate.isBorderOpaque();
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
if (borderDelegate != null) {
borderDelegate.paintBorder(c, g, x, y, width, height);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
replaceBorderIfNecessary();
}
public void uninstall() {
if (textField != null) {
textField.removePropertyChangeListener("border", this);
if (textField.getBorder() == this) {
textField.setBorder(borderDelegate);
}
textField.setLayout(null);
textField = null;
}
}
@Override
public String toString() {
return String.format("%s (%s): %s", getClass().getName(), getBorderInsets(null), borderDelegate);
}
}