This works fine, but as I am using Substance L&F, I ran into an unsupported featured concerning the foreground colour using html.
First, let’s see how substance displays the text in a button.
The foreground color depend of the button state (default,pressed,armed,…)
There is an animated transition when switching state.
When using html as text the htmlView won’t behave like normal text, I mean by that, that the foreground color will not change because the rendering is delegated to the basicHtml View and not to substance.
SubstanceButtonUI default state with HTML text
SubstanceButtonUI rollover state with HTML text, here you can barely see the text
SubstanceButtonUI default state with normal text
In most of the substance LAF, it’s not big a problem as the foreground color doesn’t change with the button state. But with some LAF as SubstanceMagellanLookAndFeel there is a problem as the text become barely readable during the rollover state.
And even if it stay readable, it just seem strange that some buttons have a black foreground when rollover and some a white one as it’s the case for my custom substance LAF.
So the plan was to make a custom SubstanceButtonUI to manage multiple text lines on JButton, without using the basic html rendering, using \n to split the text.
There are 2 things to do:
To split the text when it’s changing
To paint the text
Splitting the text
Cache of the lines
private String[] splittedText = null;
private class TextChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if (evt.getNewValue() != null && !evt.getNewValue().toString().isEmpty()) {
SubstanceMultiLineButtonUI.this.splittedText = evt.getNewValue().toString().split("\n");
} else {
SubstanceMultiLineButtonUI.this.splittedText = null;
}
SubstanceMultiLineButtonUI.this.button.repaint();
}
};
Install the text change listener
@Override
protected void installListeners(final AbstractButton b) {
super.installListeners(b);
this.textChangeListener = new TextChangeListener();
b.addPropertyChangeListener("text", this.textChangeListener);
}
Painting:
Loop through the lines
Find the correct position to paint each line
Paint the line using the paintButtonText from substanceButtonUI
Find the correct position for the icon and paint it.
for (final String s : this.splittedText) {
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = b.getWidth() - (i.right + viewRect.x);
viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
String line = SwingUtilities.layoutCompoundLabel(c, fm, s, b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());
// find the icon location
if (b.getIcon() != null) {
if (trueIconRect.x != 0) {
trueIconRect.x = Math.min(trueIconRect.x, iconRect.x);
} else {
trueIconRect.x = iconRect.x;
}
trueIconRect.y = iconRect.y;
trueIconRect.width = iconRect.width;
trueIconRect.height = iconRect.height;
}
//compute the text Y position
double part = fontMetrics.getStringBounds(line, g2).getHeight();
double y = 0.5 * this.button.getHeight();
y -= part * 0.5 * this.splittedText.length;
y += part * lineNumber;
textRect.y = (int) y;
//deletegate to substance
this.paintButtonText(g2, b, textRect, line);
lineNumber++;
}
//paint the icon
if (b.getIcon() != null) {
if (this.splittedText==null||this.splittedText.length==0) {
// if no text
SwingUtilities.layoutCompoundLabel(c, fontMetrics, "", b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(),viewRect, trueIconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());
}
paintIcon(g2, c, trueIconRect);
}
SubstanceMultiLineButtonUI default state
SubstanceMultiLineButtonUI rollover state
To use this UI on a button:
JButton b = new JButton("Do this\nand that");
b.setUI(new SubstanceMultiLineButtonUI(b));