Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • software-control-system/libraries/java/projectutilities
1 result
Show changes
Commits on Source (2)
......@@ -35,9 +35,9 @@ import fr.soleil.lib.project.awt.ColorUtils;
*/
public class ArrowButton extends JButton implements MouseListener {
private static final long serialVersionUID = 5437073960702647214L;
private static final long serialVersionUID = -1363309152147062372L;
protected static final Color DEFAULT_BACKGROUND = new Color(102, 102, 153);
public static final Color DEFAULT_BACKGROUND = new Color(102, 102, 153);
public static final int DEFAULT_SIZE = 5;
protected static final double BRIGHTNESS_FACTOR = 0.3;
protected static final double STATE_FACTOR = 0.2;
......@@ -54,7 +54,6 @@ public class ArrowButton extends JButton implements MouseListener {
private int orientation;
private Color lightColor;
private Color darkColor;
private volatile boolean mouseOver;
public ArrowButton() {
this(UP);
......@@ -116,15 +115,17 @@ public class ArrowButton extends JButton implements MouseListener {
@Override
public void mouseEntered(MouseEvent e) {
mouseOver = true;
if (isEnabled()) {
getModel().setRollover(true);
repaint();
}
}
@Override
public void mouseExited(MouseEvent e) {
mouseOver = false;
if (getModel().isRollover()) {
getModel().setRollover(false);
}
if (isEnabled()) {
repaint();
}
......@@ -208,7 +209,7 @@ public class ArrowButton extends JButton implements MouseListener {
* @return Whether mouse is over this {@link ArrowButton}
*/
protected boolean isMouseOver() {
return mouseOver;
return getModel().isRollover();
}
@Override
......@@ -389,7 +390,8 @@ public class ArrowButton extends JButton implements MouseListener {
JFrame testFrame = new JFrame(ArrowButton.class.getSimpleName() + " test");
testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
testFrame.setContentPane(panel);
testFrame.setSize(300, 80);
testFrame.pack();
testFrame.setSize(300, testFrame.getHeight());
testFrame.setVisible(true);
}
}
/*
* This file is part of SwingUtilities.
*
* SwingUtilities 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.
*
* SwingUtilities 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 SwingUtilities. If not, see
* <https://www.gnu.org/licenses/>.
*/
package fr.soleil.lib.project.swing.border;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.Path2D;
import java.lang.ref.WeakReference;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import fr.soleil.lib.project.ObjectUtils;
import fr.soleil.lib.project.swing.text.DynamicForegroundLabel;
/**
* A titled border for which line color can be changed and adapted to title color.
*
* @author Rapha&euml;l GIRARDOT
*/
public class ColoredLineTitledBorder extends AbstractBorder {
private static final long serialVersionUID = 3133459972889696986L;
/** The default border line color to use. */
protected static final Color DEFAULT_LINE_COLOR;
static {
Border tmp = UIManager.getBorder("TitledBorder.border");
if (tmp instanceof LineBorder) {
Color tmpColor = ((LineBorder) tmp).getLineColor();
DEFAULT_LINE_COLOR = new Color(tmpColor.getRed(), tmpColor.getGreen(), tmpColor.getBlue());
} else {
DEFAULT_LINE_COLOR = Color.BLACK;
}
}
/** The default title color to use. */
protected static final Color DEFAULT_TITLE_COLOR = UIManager.getColor("TitledBorder.titleColor");
/** The default title font to use. */
protected static final Font DEFAULT_TITLE_FONT = UIManager.getFont("TitledBorder.font");
/** Use the default vertical orientation for the title text. */
public static final int DEFAULT_POSITION = TitledBorder.DEFAULT_POSITION;
public static final Object REGISTERED_DEFAULT_POSITION = UIManager.get("TitledBorder.position");
/** Position the title above the border's top line. */
public static final int ABOVE_TOP = TitledBorder.ABOVE_TOP;
/** String representation of the position the title above the border's top line. */
public static final String ABOVE_TOP_S = "ABOVE_TOP";
/** Position the title in the middle of the border's top line. */
public static final int TOP = TitledBorder.TOP;
/** String representation of the position the title in the middle of the border's top line. */
public static final String TOP_S = "TOP";
/** Position the title below the border's top line. */
public static final int BELOW_TOP = TitledBorder.BELOW_TOP;
/** String representation of the position the title below the border's top line. */
public static final String BELOW_TOP_S = "BELOW_TOP";
/** Position the title above the border's bottom line. */
public static final int ABOVE_BOTTOM = TitledBorder.ABOVE_BOTTOM;
/** String representation of the position the title above the border's bottom line. */
public static final String ABOVE_BOTTOM_S = "ABOVE_BOTTOM";
/** Position the title in the middle of the border's bottom line. */
public static final int BOTTOM = TitledBorder.BOTTOM;
/** String representation of the position the title in the middle of the border's bottom line. */
protected static final String BOTTOM_S = "BOTTOM";
/** Position the title below the border's bottom line. */
public static final int BELOW_BOTTOM = TitledBorder.BELOW_BOTTOM;
/** String representation of the position the title below the border's bottom line. */
public static final String BELOW_BOTTOM_S = "BELOW_BOTTOM";
/** Use the default justification for the title text. */
public static final int DEFAULT_JUSTIFICATION = TitledBorder.DEFAULT_JUSTIFICATION;
/** Position title text at the left side of the border line. */
public static final int LEFT = TitledBorder.LEFT;
/** Position title text in the center of the border line. */
public static final int CENTER = TitledBorder.CENTER;
/** Position title text at the right side of the border line. */
public static final int RIGHT = TitledBorder.RIGHT;
/**
* Position title text at the left side of the border line
* for left to right orientation, at the right side of the
* border line for right to left orientation.
*/
public static final int LEADING = TitledBorder.LEADING;
/**
* Position title text at the right side of the border line
* for left to right orientation, at the left side of the
* border line for right to left orientation.
*/
public static final int TRAILING = TitledBorder.TRAILING;
// Space between the border and the component's edge
protected static final int EDGE_SPACING = 2;
// Space between the border and text
protected static final int TEXT_SPACING = EDGE_SPACING;
// Horizontal insets of text that is left or right justified
protected static final int TEXT_HORIZONTAL_INSETS = 5;
protected String title;
protected SettableColorLineBorder border;
protected int titlePosition;
protected int titleJustification;
protected Font titleFont;
protected Color titleColor, lineColor;
protected boolean titleBackgroundAsLineColor, titleColorAsLineColor;
protected WeakReference<Component> lastComponentRef;
protected final TitleLabel label;
public ColoredLineTitledBorder() {
this(ObjectUtils.EMPTY_STRING, LEADING, DEFAULT_POSITION, null, null);
}
public ColoredLineTitledBorder(String title) {
this(title, LEADING, DEFAULT_POSITION, null, null);
}
public ColoredLineTitledBorder(String title, int titleJustification, int titlePosition) {
this(title, titleJustification, titlePosition, null, null);
}
public ColoredLineTitledBorder(String title, int titleJustification, int titlePosition, Font titleFont) {
this(title, titleJustification, titlePosition, titleFont, null);
}
public ColoredLineTitledBorder(String title, int titleJustification, int titlePosition, Font titleFont,
Color titleColor) {
this.title = title;
border = new SettableColorLineBorder(DEFAULT_LINE_COLOR, 1);
this.titleFont = titleFont == null ? DEFAULT_TITLE_FONT : titleFont;
setTitleJustification(titleJustification);
setTitlePosition(titlePosition);
this.titleColor = titleColor;
this.lineColor = border.getLineColor();
label = new TitleLabel();
label.setForeground(titleColor);
label.setOpaque(false);
label.setFont(DEFAULT_TITLE_FONT);
}
/**
* Returns the line color of the titled border.
*
* @return the line color of the titled border.
*/
public Color getLineColor() {
return border.getLineColor();
}
/**
* Sets the line color of the titled border.
*
* @param color the line color of the titled border.
*/
public void setLineColor(Color color) {
this.lineColor = color == null ? DEFAULT_LINE_COLOR : color;
border.setLineColor(this.lineColor);
repaintLastComponent();
}
/**
* Returns the line thickness of the titled border.
*
* @return the line thickness of the titled border.
*/
public int getLineThickness() {
return border.getThickness();
}
/**
* Sets the line thickness of the titled border.
*
* @param thickness the line thickness of the titled border.
*/
public void setLineThickness(int thickness) {
border.setThickness(thickness);
repaintLastComponent();
}
/**
* Returns the title of the titled border.
*
* @return the title of the titled border.
*/
public String getTitle() {
return title;
}
/**
* Sets the title of the titled border.
*
* @param title the title for the border.
*/
public void setTitle(String title) {
this.title = title;
repaintLastComponent();
}
/**
* Returns the title position of the titled border.
*
* @return the title position of the titled border.
*/
public int getTitlePosition() {
return titlePosition;
}
/**
* Sets the title position of the titled border.
*
* @param titlePosition the position for the border
*/
public void setTitlePosition(int titlePosition) {
switch (titlePosition) {
case ABOVE_TOP:
case TOP:
case BELOW_TOP:
case ABOVE_BOTTOM:
case BOTTOM:
case BELOW_BOTTOM:
case DEFAULT_POSITION:
this.titlePosition = titlePosition;
break;
default:
throw new IllegalArgumentException("Invalid title position: " + titlePosition);
}
}
/**
* Returns the title justification of the titled border.
*
* @return the title justification of the titled border.
*/
public int getTitleJustification() {
return titleJustification;
}
/**
* Sets the title justification of the titled border.
*
* @param titleJustification the justification for the border.
*/
public void setTitleJustification(int titleJustification) {
switch (titleJustification) {
case DEFAULT_JUSTIFICATION:
case LEFT:
case CENTER:
case RIGHT:
case LEADING:
case TRAILING:
this.titleJustification = titleJustification;
break;
default:
throw new IllegalArgumentException("Invalid title justification: " + titleJustification);
}
}
/**
* Returns the tooltip that should be displayed with title.
* <p>
* As a {@link Border} can't display its own tooltip, this tooltip is just a stored information.
* </p>
*
* @return A {@link String}.
*/
public String getTitleToolTip() {
return label.getToolTipText();
}
/**
* Sets the tooltip that should be displayed with title.
* <p>
* As a {@link Border} can't display its own tooltip, this tooltip is just a stored information.
* </p>
*
* @param text The tooltip to set.
*/
public void setTitleToolTip(String text) {
label.setToolTipText(text);
}
protected int getJustification(Component c) {
int justification = getTitleJustification();
switch (justification) {
case LEADING:
case DEFAULT_JUSTIFICATION:
justification = c == null || c.getComponentOrientation().isLeftToRight() ? LEFT : RIGHT;
break;
case TRAILING:
justification = c == null || c.getComponentOrientation().isLeftToRight() ? RIGHT : LEFT;
break;
}
return justification;
}
/**
* Returns the title font of the titled border.
*
* @return the title font of the titled border.
*/
public Font getTitleFont() {
return titleFont;
}
/**
* Sets the title font of the titled border.
*
* @param titleFont the font for the border title.
*/
public void setTitleFont(Font titleFont) {
this.titleFont = titleFont;
repaintLastComponent();
}
/**
* Returns the title color of the titled border.
*
* @return the title color of the titled border.
*/
public Color getTitleColor() {
return label.getForeground();
}
/**
* Sets the title color of the titled border.
*
* @param titleColor the color for the border title.
*/
public void setTitleColor(Color titleColor) {
this.titleColor = titleColor;
label.setForeground(titleColor);
repaintLastComponent();
}
/**
* Returns whether line color will automatically follow title background color.
*
* @return whether line color will automatically follow title background color.
*/
public boolean isTitleBackgroundAsLineColor() {
return titleBackgroundAsLineColor;
}
/**
* Sets whether line color will automatically follow title background color.
*
* @param backgroundAsLineColor whether line color will automatically follow title background color.
*/
public void setTitleBackgroundAsLineColor(boolean backgroundAsLineColor) {
this.titleBackgroundAsLineColor = backgroundAsLineColor;
repaintLastComponent();
}
/**
* Returns whether line color will automatically follow title color.
*
* @return whether line color will automatically follow title color.
*/
public boolean isTitleColorAsLineColor() {
return titleColorAsLineColor;
}
/**
* Sets whether line color will automatically follow title color.
*
* @param foregroundAsLineColor whether line color will automatically follow title color.
*/
public void setTitleColorAsLineColor(boolean foregroundAsLineColor) {
this.titleColorAsLineColor = foregroundAsLineColor;
repaintLastComponent();
}
/**
* Updates the label according to given {@link Component}.
*
* @param c The {@link Component}.
*/
protected void updateLabel(Component c) {
label.setText(getTitle());
label.setFont(getTitleFont());
if (c == null) {
label.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
label.setEnabled(true);
} else {
label.setComponentOrientation(c.getComponentOrientation());
label.setEnabled(c.isEnabled());
}
}
/**
* Returns the last known component that used this {@link ColoredLineTitledBorder}.
*
* @return A {@link Component}. Can be <code>null</code>.
*/
protected Component getLastComponent() {
Component c = ObjectUtils.recoverObject(lastComponentRef);
if (c instanceof JComponent) {
if (((JComponent) c).getBorder() != this) {
c = null;
}
} else {
c = null;
}
return c;
}
/**
* Repaint the component that uses this {@link ColoredLineTitledBorder} as border.
*/
protected void repaintLastComponent() {
Component c = getLastComponent();
if (c != null) {
c.repaint();
}
}
/**
* Recovers the title position to use, especially when that position was defined to {@value #DEFAULT_POSITION}.
*
* @return An <code>int</code>.
* @see #DEFAULT_POSITION
* @see #ABOVE_TOP
* @see #TOP
* @see #BELOW_TOP
* @see #ABOVE_BOTTOM
* @see #BOTTOM
* @see #BELOW_BOTTOM
*/
protected int getPosition() {
int position = getTitlePosition();
if (position == DEFAULT_POSITION) {
if (REGISTERED_DEFAULT_POSITION instanceof Integer) {
int i = ((Integer) REGISTERED_DEFAULT_POSITION).intValue();
if ((0 < i) && (i <= 6)) {
position = i;
} else {
position = TOP;
}
} else if (REGISTERED_DEFAULT_POSITION instanceof String) {
String s = ((String) REGISTERED_DEFAULT_POSITION).toUpperCase();
switch (s) {
case ABOVE_TOP_S:
position = ABOVE_TOP;
break;
case BELOW_TOP_S:
position = BELOW_TOP;
break;
case ABOVE_BOTTOM_S:
position = ABOVE_BOTTOM;
break;
case BOTTOM_S:
position = BOTTOM;
break;
case BELOW_BOTTOM_S:
position = BELOW_BOTTOM;
break;
case TOP_S:
default:
position = TOP;
break;
}
} else {
position = TOP;
}
}
return position;
}
/**
* Reinitialize the insets parameter with this Border's current Insets.
*
* @param c the component for which this border insets value applies
* @param insets the object to be reinitialized
*/
@Override
public Insets getBorderInsets(Component c, Insets insets) {
insets = border.getBorderInsets(c, insets);
String title = getTitle();
if ((title != null) && !title.isEmpty()) {
updateLabel(c);
Dimension size = label.getPreferredSize();
switch (getPosition()) {
case ABOVE_TOP:
insets.top += size.height - EDGE_SPACING;
break;
case TOP: {
if (insets.top < size.height) {
insets.top = size.height - EDGE_SPACING;
}
break;
}
case BELOW_TOP:
insets.top += size.height;
break;
case ABOVE_BOTTOM:
insets.bottom += size.height;
break;
case BOTTOM: {
if (insets.bottom < size.height) {
insets.bottom = size.height - EDGE_SPACING;
}
break;
}
case BELOW_BOTTOM:
insets.bottom += size.height - EDGE_SPACING;
break;
}
insets.top += EDGE_SPACING + TEXT_SPACING;
insets.left += EDGE_SPACING + TEXT_SPACING;
insets.right += EDGE_SPACING + TEXT_SPACING;
insets.bottom += EDGE_SPACING + TEXT_SPACING;
}
return insets;
}
@Override
public int getBaseline(Component c, int width, int height) {
if (width < 0) {
throw new IllegalArgumentException("Width must be \u2265 0");
}
if (height < 0) {
throw new IllegalArgumentException("Height must be \\u2265 0");
}
String title = getTitle();
int baseline;
if ((title != null) && !title.isEmpty()) {
int edge = EDGE_SPACING;
updateLabel(c);
Dimension size = label.getPreferredSize();
Insets insets = border.getBorderInsets(c, new Insets(0, 0, 0, 0));
baseline = label.getBaseline(size.width, size.height);
switch (getPosition()) {
case TOP:
// TOP: label is vertically centered with top line
insets.top = edge + (insets.top - size.height) / 2;
baseline = (insets.top < edge) ? baseline : baseline + insets.top;
break;
case BELOW_TOP:
// BELOW_TOP: label is below the top line
baseline += insets.top + edge;
break;
case ABOVE_BOTTOM:
// ABOVE_BOTTOM: label is above the bottom line.
baseline += height - size.height - insets.bottom - edge;
break;
case BOTTOM:
// BOTTOM: label is vertically centered with bottom line
insets.bottom = edge + (insets.bottom - size.height) / 2;
baseline = (insets.bottom < edge) ? baseline + height - size.height
: baseline + height - size.height + insets.bottom;
break;
case BELOW_BOTTOM:
// BELOW_BOTTOM: label is below the bottom line.
baseline += height - size.height;
break;
case ABOVE_TOP:
default:
// nothing to do: baseline is label's baseline (label is vertically at 0 position).
break;
}
} else {
baseline = -1;
}
return baseline;
}
@Override
public Component.BaselineResizeBehavior getBaselineResizeBehavior(Component c) {
Component.BaselineResizeBehavior behavior;
switch (getPosition()) {
case ABOVE_TOP:
case TOP:
case BELOW_TOP:
// When label is at the top, its baseline is relative to y-origin.
behavior = Component.BaselineResizeBehavior.CONSTANT_ASCENT;
break;
case ABOVE_BOTTOM:
case BOTTOM:
case BELOW_BOTTOM:
// When label is at the bottom, its baseline is relative to the height.
behavior = JComponent.BaselineResizeBehavior.CONSTANT_DESCENT;
break;
default:
// Other cases.
behavior = Component.BaselineResizeBehavior.OTHER;
break;
}
return behavior;
}
/**
*
* @param c the component for which this border is being painted
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param width the width of the painted border
* @param height the height of the painted border
* @return An <code>int[]</code>: <code>{borderX, borderY, borderWidth, borderHeight, labelX, labelY, labelWith,
* labelHeight, position}</code>, where <code>position</code> is the result of {@link #getPosition()}.
*/
protected int[] computeBorderBounds(Component c, int x, int y, int width, int height) {
updateLabel(c);
Dimension size = label.getPreferredSize();
Insets insets = border.getBorderInsets(c, new Insets(0, 0, 0, 0));
int borderX, borderY, borderWidth, borderHeight;
int labelX, labelY, labelWidth, labelHeight;
borderX = x + EDGE_SPACING;
borderY = y + EDGE_SPACING;
borderWidth = width - EDGE_SPACING - EDGE_SPACING;
borderHeight = height - EDGE_SPACING - EDGE_SPACING;
labelY = y;
labelHeight = size.height;
int position = getPosition();
switch (position) {
case ABOVE_TOP:
insets.left = 0;
insets.right = 0;
borderY += labelHeight - EDGE_SPACING;
borderHeight -= labelHeight - EDGE_SPACING;
break;
case TOP:
insets.top = EDGE_SPACING + insets.top / 2 - labelHeight / 2;
if (insets.top < EDGE_SPACING) {
borderY -= insets.top;
borderHeight += insets.top;
} else {
labelY += insets.top;
}
break;
case BELOW_TOP:
labelY += insets.top + EDGE_SPACING;
break;
case ABOVE_BOTTOM:
labelY += height - labelHeight - insets.bottom - EDGE_SPACING;
break;
case BOTTOM:
labelY += height - labelHeight;
insets.bottom = EDGE_SPACING + (insets.bottom - labelHeight) / 2;
if (insets.bottom < EDGE_SPACING) {
borderHeight += insets.bottom;
} else {
labelY -= insets.bottom;
}
break;
case BELOW_BOTTOM:
insets.left = 0;
insets.right = 0;
labelY += height - labelHeight;
borderHeight -= labelHeight - EDGE_SPACING;
break;
}
insets.left += EDGE_SPACING + TEXT_HORIZONTAL_INSETS;
insets.right += EDGE_SPACING + TEXT_HORIZONTAL_INSETS;
labelX = x;
labelWidth = width - insets.left - insets.right;
if (labelWidth > size.width) {
labelWidth = size.width;
}
switch (getJustification(c)) {
case LEFT:
labelX += insets.left;
break;
case RIGHT:
labelX += width - insets.right - labelWidth;
break;
case CENTER:
labelX += (width - labelWidth) / 2;
break;
}
return new int[] { borderX, borderY, borderWidth, borderHeight, labelX, labelY, labelWidth, labelHeight,
position };
}
/**
* Returns the {@link Rectangle} that represents the title label bounds for given {@link Component} at given
* coordinates.
*
* @param c The {@link Component}.
* @param x The border start abscissa.
* @param y The border start ordinate.
* @param width The border width.
* @param height The border height.
* @return A {@link Rectangle}.
*/
public Rectangle getLabelBounds(Component c, int x, int y, int width, int height) {
Rectangle labelBounds;
String title = getTitle();
if ((title == null) || (title.isEmpty())) {
labelBounds = new Rectangle(0, 0, 0, 0);
} else {
int[] bounds = computeBorderBounds(c, x, y, width, height);
labelBounds = new Rectangle(bounds[4], bounds[5], bounds[6], bounds[7]);
}
return labelBounds;
}
/**
* Sets the title label background color.
*
* @param bg The color to set.
*/
public void setLabelBackground(Color bg) {
label.setBackground(bg);
label.setOpaque(bg != null);
repaintLastComponent();
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
String title = getTitle();
if ((title != null) && !title.isEmpty()) {
int[] bounds = computeBorderBounds(c, x, y, width, height);
int borderX = bounds[0], borderY = bounds[1], borderWidth = bounds[2], borderHeight = bounds[3];
int labelX = bounds[4], labelY = bounds[5], labelWidth = bounds[6], labelHeight = bounds[7];
int position = bounds[8];
if ((position != TOP) && (position != BOTTOM)) {
border.paintBorder(c, g, borderX, borderY, borderWidth, borderHeight);
} else {
Graphics g2 = g.create();
if (g2 instanceof Graphics2D) {
Graphics2D g2d = (Graphics2D) g2;
Path2D path = new Path2D.Float();
path.append(new Rectangle(borderX, borderY, borderWidth, labelY - borderY), false);
path.append(new Rectangle(borderX, labelY, labelX - borderX - TEXT_SPACING, labelHeight), false);
path.append(new Rectangle(labelX + labelWidth + TEXT_SPACING, labelY,
borderX - labelX + borderWidth - labelWidth - TEXT_SPACING, labelHeight), false);
path.append(new Rectangle(borderX, labelY + labelHeight, borderWidth,
borderY - labelY + borderHeight - labelHeight), false);
g2d.clip(path);
}
border.paintBorder(c, g2, borderX, borderY, borderWidth, borderHeight);
g2.dispose();
}
g.translate(labelX, labelY);
label.setSize(labelWidth, labelHeight);
label.paint(g);
g.translate(-labelX, -labelY);
} else if (border != null) {
border.paintBorder(c, g, x, y, width, height);
}
lastComponentRef = c == null ? null : new WeakReference<>(c);
}
// ///////////// //
// Inner classes //
// ///////////// //
/**
* {@link DynamicForegroundLabel} dedicated to displaying the title of a {@link ColoredLineTitledBorder}.
*/
protected class TitleLabel extends DynamicForegroundLabel {
private static final long serialVersionUID = 8647951261313807955L;
public TitleLabel() {
super();
}
public TitleLabel(Icon image, int horizontalAlignment) {
super(image, horizontalAlignment);
}
public TitleLabel(Icon image) {
super(image);
}
public TitleLabel(String text, Icon icon, int horizontalAlignment) {
super(text, icon, horizontalAlignment);
}
public TitleLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
public TitleLabel(String text) {
super(text);
}
@Override
public void setOpaque(boolean isOpaque) {
super.setOpaque(isOpaque);
Color fg = titleColor;
if (fg == null) {
if (isOpaque) {
super.setForeground(null);
} else {
super.setForeground(DEFAULT_TITLE_COLOR);
}
}
}
@Override
protected void updateForeground() {
super.updateForeground();
if (isTitleColorAsLineColor()) {
border.setLineColor(getForeground());
} else if (!isTitleBackgroundAsLineColor()) {
border.setLineColor(lineColor);
}
}
@Override
public void setBackground(Color bg) {
super.setBackground(bg);
if (isTitleBackgroundAsLineColor()) {
border.setLineColor(bg);
}
}
}
}
/*
* This file is part of SwingUtilities.
*
* SwingUtilities 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.
*
* SwingUtilities 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 SwingUtilities. If not, see
* <https://www.gnu.org/licenses/>.
*/
package fr.soleil.lib.project.swing.border;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Path2D;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import fr.soleil.lib.project.ObjectUtils;
import fr.soleil.lib.project.swing.ArrowButton;
/**
* A {@link ColoredLineTitledBorder} with a button to fold/unfold some content.
*
* @author Rapha&euml;l GIRARDOT
*/
public class FoldableBorder extends ColoredLineTitledBorder implements MouseListener, MouseMotionListener {
private static final long serialVersionUID = -167592519370626180L;
protected static final Dimension ZERO_SIZE = new Dimension(0, 0);
/** Property key when bolder folded aspect changed */
public static final String BORDER_FOLDED = "borderFolded";
protected final PropertyChangeSupport changeSupport;
protected final ArrowButton arrowButton;
protected boolean titleColorAsButtonColor;
protected int buttonPosition;
public FoldableBorder() {
this(ObjectUtils.EMPTY_STRING, LEADING, DEFAULT_POSITION, null, null);
}
public FoldableBorder(String title) {
this(title, LEADING, DEFAULT_POSITION, null, null);
}
public FoldableBorder(String title, int titleJustification, int titlePosition) {
this(title, titleJustification, titlePosition, null, null);
}
public FoldableBorder(String title, int titleJustification, int titlePosition, Font titleFont) {
this(title, titleJustification, titlePosition, titleFont, null);
}
public FoldableBorder(String title, int titleJustification, int titlePosition, Font titleFont, Color titleColor) {
super(title, titleJustification, titlePosition, titleFont, titleColor);
changeSupport = new PropertyChangeSupport(this);
arrowButton = new ArrowButton(ArrowButton.DOWN);
buttonPosition = RIGHT;
updateButtonSize();
}
protected void updateButtonSize() {
arrowButton.setSize(titleFont.getSize(), titleFont.getSize());
}
protected Dimension getLabelSize(String title) {
return title == null || title.isEmpty() ? ZERO_SIZE : label.getPreferredSize();
}
protected int getRefHeight(Dimension size) {
return Math.max(size.height, arrowButton.getHeight());
}
protected int getRefHeight(String title) {
return getRefHeight(getLabelSize(title));
}
protected void updateButtonColor(Color titleColor, boolean titleColorAsButtonColor) {
if (titleColorAsButtonColor) {
arrowButton.setForeground(titleColor == null ? ArrowButton.DEFAULT_BACKGROUND : titleColor);
} else {
arrowButton.setForeground(ArrowButton.DEFAULT_BACKGROUND);
}
}
/**
* Returns whether title color will be applied to button too.
*
* @return A <code>boolean</code>.
*/
public boolean isTitleColorAsButtonColor() {
return titleColorAsButtonColor;
}
/**
* Sets whether title color should be applied to button too.
*
* @param titleColorAsButtonColor Whether title color should be applied to button too.
*/
public void setTitleColorAsButtonColor(boolean titleColorAsButtonColor) {
this.titleColorAsButtonColor = titleColorAsButtonColor;
updateButtonColor(getTitleColor(), titleColorAsButtonColor);
}
@Override
public void setTitleColor(Color titleColor) {
updateButtonColor(titleColor, isTitleColorAsButtonColor());
super.setTitleColor(titleColor);
}
/**
* Returns the button position relative to title.
*
* @return An <code>int</code>.
* <p>
* Can be any of these:
* <ul>
* <li>{@link ColoredLineTitledBorder#LEFT} or {@link ColoredLineTitledBorder#LEADING}: the button
* is placed before the title, on its left.</li>
* <li>{@link ColoredLineTitledBorder#RIGHT} or {@link ColoredLineTitledBorder#TRAILING}: the button
* is placed after the title, on its right.</li>
* </ul>
* </p>
*/
public int getButtonPosition() {
return buttonPosition;
}
/**
* Sets the button position.
*
* @param buttonPosition The button position to set.
* <p>
* Can be any of these:
* <ul>
* <li>{@link ColoredLineTitledBorder#LEFT} or {@link ColoredLineTitledBorder#LEADING}: the button
* will be placed before the title, on its left.</li>
* <li>{@link ColoredLineTitledBorder#RIGHT} or {@link ColoredLineTitledBorder#TRAILING}: the button
* will be placed after the title, on its right.</li>
* </ul>
* </p>
*/
public void setButtonPosition(int buttonPosition) {
if (buttonPosition != this.buttonPosition) {
switch (buttonPosition) {
case LEFT:
case RIGHT:
case LEADING:
case TRAILING:
this.buttonPosition = buttonPosition;
repaintLastComponent();
break;
}
}
}
/**
* Reinitialize the insets parameter with this Border's current Insets.
*
* @param c the component for which this border insets value applies
* @param insets the object to be reinitialized
*/
@Override
public Insets getBorderInsets(Component c, Insets insets) {
insets = border.getBorderInsets(c, insets);
String title = getTitle();
updateLabel(c);
int refHeight = getRefHeight(title);
switch (getPosition()) {
case ABOVE_TOP:
insets.top += refHeight - EDGE_SPACING;
break;
case TOP: {
if (insets.top < refHeight) {
insets.top = refHeight - EDGE_SPACING;
}
break;
}
case BELOW_TOP:
insets.top += refHeight;
break;
case ABOVE_BOTTOM:
insets.bottom += refHeight;
break;
case BOTTOM: {
if (insets.bottom < refHeight) {
insets.bottom = refHeight - EDGE_SPACING;
}
break;
}
case BELOW_BOTTOM:
insets.bottom += refHeight - EDGE_SPACING;
break;
}
insets.top += EDGE_SPACING + TEXT_SPACING;
insets.left += EDGE_SPACING + TEXT_SPACING;
insets.right += EDGE_SPACING + TEXT_SPACING;
insets.bottom += EDGE_SPACING + TEXT_SPACING;
return insets;
}
@Override
public int getBaseline(Component c, int width, int height) {
if (c == null) {
throw new NullPointerException("Component can't be null");
}
if (width < 0) {
throw new IllegalArgumentException("Width must be \u2265 0");
}
if (height < 0) {
throw new IllegalArgumentException("Height must be \\u2265 0");
}
String title = getTitle();
int baseline;
int edge = EDGE_SPACING;
updateLabel(c);
Dimension size = getLabelSize(title);
int refHeight = getRefHeight(size);
Insets insets = border.getBorderInsets(c, new Insets(0, 0, 0, 0));
baseline = label.getBaseline(size.width == 0 ? arrowButton.getWidth() : size.width, refHeight);
switch (getPosition()) {
case TOP:
// TOP: label is vertically centered with top line
insets.top = edge + (insets.top - refHeight) / 2;
baseline = (insets.top < edge) ? baseline : baseline + insets.top;
break;
case BELOW_TOP:
// BELOW_TOP: label is below the top line
baseline += insets.top + edge;
break;
case ABOVE_BOTTOM:
// ABOVE_BOTTOM: label is above the bottom line.
baseline += height - refHeight - insets.bottom - edge;
break;
case BOTTOM:
// BOTTOM: label is vertically centered with bottom line
insets.bottom = edge + (insets.bottom - refHeight) / 2;
baseline = (insets.bottom < edge) ? baseline + height - refHeight
: baseline + height - refHeight + insets.bottom;
break;
case BELOW_BOTTOM:
// BELOW_BOTTOM: label is below the bottom line.
baseline += height - refHeight;
break;
case ABOVE_TOP:
default:
// nothing to do: baseline is label's baseline (label is vertically at 0 position).
break;
}
return baseline;
}
@Override
public Component.BaselineResizeBehavior getBaselineResizeBehavior(Component c) {
Component.BaselineResizeBehavior behavior;
switch (getPosition()) {
case ABOVE_TOP:
case TOP:
case BELOW_TOP:
// When label is at the top, its baseline is relative to y-origin.
behavior = Component.BaselineResizeBehavior.CONSTANT_ASCENT;
break;
case ABOVE_BOTTOM:
case BOTTOM:
case BELOW_BOTTOM:
// When label is at the bottom, its baseline is relative to the height.
behavior = JComponent.BaselineResizeBehavior.CONSTANT_DESCENT;
break;
default:
// Other cases.
behavior = Component.BaselineResizeBehavior.OTHER;
break;
}
return behavior;
}
/**
*
* @param c the component for which this border is being painted
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param width the width of the painted border
* @param height the height of the painted border
* @return An <code>int[]</code>: <code>{borderX, borderY, borderWidth, borderHeight, labelX, labelY, labelWith,
* labelHeight, buttonX, buttonY, buttonWidth, buttonHeight, position}</code>, where
* <code>position</code> is the result of {@link #getPosition()}.
*/
@Override
protected int[] computeBorderBounds(Component c, int x, int y, int width, int height) {
updateLabel(c);
Dimension size = label.getPreferredSize();
Insets insets = border.getBorderInsets(c, new Insets(0, 0, 0, 0));
int borderX, borderY, borderWidth, borderHeight;
int labelX, labelY, labelWidth, labelHeight;
int buttonX, buttonY, buttonWidth, buttonHeight;
borderX = x + EDGE_SPACING;
borderY = y + EDGE_SPACING;
borderWidth = width - EDGE_SPACING - EDGE_SPACING;
borderHeight = height - EDGE_SPACING - EDGE_SPACING;
labelY = y;
labelHeight = size.height;
int refHeight = getRefHeight(size);
int position = getPosition();
switch (position) {
case ABOVE_TOP:
insets.left = 0;
insets.right = 0;
borderY += refHeight - EDGE_SPACING;
borderHeight -= refHeight - EDGE_SPACING;
break;
case TOP:
insets.top = EDGE_SPACING + insets.top / 2 - refHeight / 2;
if (insets.top < EDGE_SPACING) {
borderY -= insets.top;
borderHeight += insets.top;
} else {
labelY += insets.top;
}
break;
case BELOW_TOP:
labelY += insets.top + EDGE_SPACING;
break;
case ABOVE_BOTTOM:
labelY += height - refHeight - insets.bottom - EDGE_SPACING;
break;
case BOTTOM:
labelY += height - refHeight;
insets.bottom = EDGE_SPACING + (insets.bottom - refHeight) / 2;
if (insets.bottom < EDGE_SPACING) {
borderHeight += insets.bottom;
} else {
labelY -= insets.bottom;
}
break;
case BELOW_BOTTOM:
insets.left = 0;
insets.right = 0;
labelY += height - refHeight;
borderHeight -= refHeight - EDGE_SPACING;
break;
}
insets.left += EDGE_SPACING + TEXT_HORIZONTAL_INSETS;
insets.right += EDGE_SPACING + TEXT_HORIZONTAL_INSETS;
labelX = x;
labelWidth = width - insets.left - insets.right;
if (labelWidth > size.width) {
labelWidth = size.width;
}
if (labelWidth < 0) {
labelWidth = 0;
}
switch (getJustification(c)) {
case LEFT:
labelX += insets.left;
break;
case RIGHT:
labelX += width - insets.right - labelWidth;
break;
case CENTER:
labelX += (width - labelWidth) / 2;
break;
}
if (labelX < 0) {
labelX = 0;
}
int buttonPosition = getButtonPosition();
buttonWidth = arrowButton.getWidth();
buttonHeight = arrowButton.getHeight();
if (buttonHeight < labelHeight) {
buttonY = labelY + (labelHeight - buttonHeight) / 2;
} else {
if (buttonHeight > labelHeight && labelHeight > 0) {
buttonHeight = labelHeight;
buttonWidth = buttonHeight;
arrowButton.setSize(buttonWidth, buttonHeight);
}
buttonY = labelY;
}
if (buttonPosition == TRAILING || buttonPosition == RIGHT) {
buttonX = labelX;
if (labelWidth > 0) {
buttonX += labelWidth + TEXT_SPACING;
}
} else {
buttonX = labelX;
labelX = buttonX + buttonWidth;
if (labelWidth > 0) {
labelX += TEXT_SPACING;
}
}
return new int[] { borderX, borderY, borderWidth, borderHeight, labelX, labelY, labelWidth, labelHeight,
buttonX, buttonY, buttonWidth, buttonHeight, position };
}
/**
* Returns the {@link Rectangle} that represents the title label bounds for given {@link Component} at given
* coordinates.
*
* @param c The {@link Component}.
* @param x The border start abscissa.
* @param y The border start ordinate.
* @param width The border width.
* @param height The border height.
* @return A {@link Rectangle}.
*/
@Override
public Rectangle getLabelBounds(Component c, int x, int y, int width, int height) {
Rectangle labelBounds;
int[] bounds = computeBorderBounds(c, x, y, width, height);
labelBounds = new Rectangle(bounds[4], bounds[5], bounds[6], bounds[7]);
return labelBounds;
}
/**
* Returns the {@link Rectangle} that represents the fold button bounds for given {@link Component} at given
* coordinates.
*
* @param c The {@link Component}.
* @param x The border start abscissa.
* @param y The border start ordinate.
* @param width The border width.
* @param height The border height.
* @return A {@link Rectangle}.
*/
public Rectangle getButtonBounds(Component c, int x, int y, int width, int height) {
Rectangle buttonBounds;
int[] bounds = computeBorderBounds(c, x, y, width, height);
buttonBounds = new Rectangle(bounds[8], bounds[9], bounds[10], bounds[11]);
return buttonBounds;
}
public boolean isFolded() {
return arrowButton.getOrientation() == ArrowButton.LEFT;
}
public void setFolded(boolean folded) {
boolean formerFolded = isFolded();
if (formerFolded != folded) {
arrowButton.setOrientation(folded ? ArrowButton.LEFT : ArrowButton.DOWN);
repaintLastComponent();
changeSupport.firePropertyChange(BORDER_FOLDED, formerFolded, folded);
}
}
protected boolean isButtonEvent(MouseEvent e) {
boolean buttonEvent;
Component c = e.getComponent();
if (c == null) {
buttonEvent = false;
} else {
Rectangle bounds = getButtonBounds(c, 0, 0, c.getWidth(), c.getHeight());
int buttonX = bounds.x, buttonY = bounds.y, buttonWidth = bounds.width, buttonHeight = bounds.height;
int x = e.getX(), y = e.getY();
buttonEvent = x >= buttonX && x < buttonX + buttonWidth && y >= buttonY && y < buttonY + buttonHeight;
}
return buttonEvent;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(propertyName, listener);
}
protected void setButtonPressed(boolean pressed) {
ButtonModel model = arrowButton.getModel();
if ((model != null) && (model.isPressed() != pressed)) {
model.setPressed(pressed);
repaintLastComponent();
}
}
protected void setButtonHover(boolean hover) {
ButtonModel model = arrowButton.getModel();
if ((model != null) && (model.isRollover() != hover)) {
model.setRollover(hover);
repaintLastComponent();
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (isButtonEvent(e)) {
setFolded(!isFolded());
}
}
@Override
public void mousePressed(MouseEvent e) {
if (isButtonEvent(e)) {
setButtonPressed(true);
}
}
@Override
public void mouseReleased(MouseEvent e) {
setButtonPressed(false);
}
@Override
public void mouseEntered(MouseEvent e) {
if (isButtonEvent(e)) {
setButtonHover(true);
}
}
@Override
public void mouseExited(MouseEvent e) {
setButtonPressed(false);
setButtonHover(false);
}
@Override
public void mouseDragged(MouseEvent e) {
boolean button = isButtonEvent(e);
setButtonPressed(button);
setButtonHover(button);
}
@Override
public void mouseMoved(MouseEvent e) {
boolean button = isButtonEvent(e);
setButtonHover(button);
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
arrowButton.setEnabled(c == null ? true : c.isEnabled());
int[] bounds = computeBorderBounds(c, x, y, width, height);
int borderX = bounds[0], borderY = bounds[1], borderWidth = bounds[2], borderHeight = bounds[3];
int labelX = bounds[4], labelY = bounds[5], labelWidth = bounds[6], labelHeight = bounds[7];
int buttonX = bounds[8], buttonY = bounds[9], buttonWidth = bounds[10], buttonHeight = bounds[11];
int position = getPosition();
int refHeight = Math.max(labelHeight, buttonHeight);
if (border != null) {
if ((position != TOP) && (position != BOTTOM)) {
border.paintBorder(c, g, borderX, borderY, borderWidth, borderHeight);
} else {
Graphics g2 = g.create();
if (g2 instanceof Graphics2D) {
int minX, maxX, lastWidth;
if (labelX < buttonX) {
minX = labelX;
maxX = buttonX;
lastWidth = buttonWidth;
} else {
minX = buttonX;
maxX = labelX;
lastWidth = labelWidth;
}
Graphics2D g2d = (Graphics2D) g2;
Path2D path = new Path2D.Float();
path.append(new Rectangle(borderX, borderY, borderWidth, labelY - borderY), false);
path.append(new Rectangle(borderX, labelY, minX - borderX - TEXT_SPACING, refHeight), false);
path.append(new Rectangle(maxX + lastWidth + TEXT_SPACING, labelY,
borderX - maxX + borderWidth - lastWidth - 2 * TEXT_SPACING, refHeight), false);
path.append(new Rectangle(borderX, labelY + refHeight, borderWidth,
borderY - labelY + borderHeight - refHeight), false);
g2d.clip(path);
}
border.paintBorder(c, g2, borderX, borderY, borderWidth, borderHeight);
g2.dispose();
}
}
g.translate(labelX, labelY);
label.setSize(labelWidth, labelHeight);
label.paint(g);
g.translate(-labelX, -labelY);
g.translate(buttonX, buttonY);
arrowButton.paint(g);
g.translate(-buttonX, -buttonY);
lastComponentRef = c == null ? null : new WeakReference<>(c);
}
}