Problem: Let’s assume that we know the available width and we should determine what is the required height for JTextComponent within that limitation.
This problem arises immediately, at least in three cases in java:
- Variable row height table cells
- Tooltip size
- Message dialog size
In all of these cases, it is necessary sometimes to know actual height based into width, otherwise layout cannot be done correctly. For example, if we have tooltip with arbitary text, and we want to display this tooltip above the mouse cursor, so that maximum width is not exceeded and bottom-left corner of the tip is nicely adjusted into cursor location.
Luckily there is solution:
Text Height Calculator
[code lang=”java”]
public static final class HeightCalculator {
private final CellRendererPane mRenderPanel = new CellRendererPane();
private final JPanel mPanel = new JPanel(new BorderLayout());
private final Box mBox = new Box(BoxLayout.X_AXIS);
private static HeightCalculator mInstance;
public static HeightCalculator getInstance() {
if (mInstance == null) {
mInstance = new HeightCalculator();
}
return mInstance;
}
public HeightCalculator() {
// RenderPanel works as ”root” of the hierarchy
// Both box & panel are seemingly needed; didn’t work when trying to
// discard either one of them.
mRenderPanel.addNotify();
mBox.add(mPanel);
mRenderPanel.add(mBox);
}
public int getHeight(final JTextComponent pComponent, final int pWidth) {
mPanel.add(pComponent, BorderLayout.NORTH);
mBox.setSize(pWidth, Integer.MAX_VALUE);
// First validate triggers width calculation (with minimun height)
// Second validate triggers height calculation (with pWidth)
mBox.validate();
mBox.invalidate();
mBox.validate();
int height = pComponent.getHeight();
mPanel.remove(pComponent);
// validate() after remove; otherwise next getInstance().getHeight() call will fail.
mBox.validate();
return height;
}
}
[/code]
And then some test code…
Tooltip with Max Tip Width
[code lang=”java”]
public static final class CustomToolTip extends JToolTip {
private final int mWidth;
public CustomToolTip(int pWidth) {
mWidth = pWidth;
}
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
if (d.width > mWidth) {
Font font = getFont();
String origTip = super.getTipText();
String tip = TextUtil.toHTML(origTip);
if (tip != origTip) {
setTipText(tip);
}
JTextPane field = new JTextPane();
field.setContentType(”text/html”);
HTMLDocument doc = (HTMLDocument)field.getDocument();
String style = ”body { font : ” + font.getFontName() + ”; font-size: ” + font.getSize() + ”}”;
doc.getStyleSheet().addRule(style);
field.setText(tip);
int height = HeightCalculator.getInstance().getHeight(field, mWidth);
d = new Dimension(mWidth, height);
}
return d;
}
}
public static final class TipPanel extends JPanel {
@Override
public JToolTip createToolTip() {
CustomToolTip tip = new CustomToolTip(100);
tip.setComponent(this);
return tip;
}
}
[/code]
I performed some testing for the logic with Sun JDK 1.6.0_02, for JTextArea, JTextPane and JEditorPane, with single and multiline line plain text, and HTML text with line breaks. Based into this experiment, logic seems to work, however, some optimizations might be still needed.
NOTE: This preferred height calculation logic is crafted based into following references: