Imagine a component has a dynamic tooltip.
It may be quite long. In that case, the user wants it to be split into multiple lines. The end goal is to never cut a tooltip with a screen edge. For example, on screens with low resolution long tooltips may exceed the total screen width.
How can I achieve it?
Remember, the text is dynamic, I can't wrap it in <html></html>
and scatter <br>
tags here and there.
package demos.text.field;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import java.awt.Container;
public class TextFieldDemo {
public static void main(String[] args) {
Container mainPanel = createMainPanel();
JFrame frame = new JFrame("Text field demo");
frame.setContentPane(mainPanel);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static JPanel createMainPanel() {
JPanel panel = new JPanel();
panel.add(createTextField());
return panel;
}
private static JTextField createTextField() {
JTextField textField = new JTextField("Text field with a tooltip");
textField.setToolTipText("A " + "very ".repeat(10) + "long tooltip that must be split");
return textField;
}
}
Java 8.
How about adding a JTextArea
with JTextArea#setLineWrap(true) to the JToolTip
?
This JTextArea
will automatically wrap the text if it exceeds the width set by JTextArea.html#setColumns(...).
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import javax.swing.*;
public class LineWrapToolTipTest {
private Component makeUI() {
String very = String.join(" ", Collections.nCopies(10, "very"));
String txt = "A " + very + " long tooltip that must be line wrap";
JButton b1 = new JButton("JToolTip(Default)");
b1.setToolTipText(txt + ": 1");
JButton b2 = makeButton("LineWrapToolTip short");
b2.setToolTipText("short: 2");
JButton b3 = makeButton("LineWrapToolTip long");
b3.setToolTipText(txt + ": 3");
JPanel p = new JPanel();
p.add(b1);
p.add(b2);
p.add(b3);
return p;
}
private static JButton makeButton(String title) {
return new JButton(title) {
private transient JToolTip tip;
@Override public JToolTip createToolTip() {
if (tip == null) {
tip = new LineWrapToolTip();
tip.setComponent(this);
}
return tip;
}
@Override public String getToolTipText(MouseEvent e) {
String txt = super.getToolTipText(e);
EventQueue.invokeLater(() ->
Optional.ofNullable(SwingUtilities.getWindowAncestor(tip))
.filter(w -> w.getType() == Window.Type.POPUP)
.ifPresent(Window::pack));
return txt;
}
};
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new LineWrapToolTipTest().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class LineWrapToolTip extends JToolTip {
private final JTextArea textArea = new JTextArea(0, 20);
private final JLabel label = new JLabel(" ");
protected LineWrapToolTip() {
super();
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
// textArea.setColumns(20);
textArea.setOpaque(true);
LookAndFeel.installColorsAndFont(
textArea, "ToolTip.background", "ToolTip.foreground", "ToolTip.font");
setLayout(new BorderLayout());
add(textArea);
}
@Override public final void setLayout(LayoutManager mgr) {
super.setLayout(mgr);
}
@Override public final Component add(Component comp) {
return super.add(comp);
}
@Override public Dimension getPreferredSize() {
// return getLayout().preferredLayoutSize(this);
Dimension d = getLayout().preferredLayoutSize(this);
label.setText(textArea.getText());
Insets i = getInsets();
Insets ti = textArea.getInsets();
Insets tm = textArea.getMargin();
int pad = i.left + i.right + ti.left + ti.right + tm.left + tm.right;
d.width = Math.min(d.width, label.getPreferredSize().width + pad);
return d;
}
@Override public void setTipText(String tipText) {
String oldValue = textArea.getText();
if (!Objects.equals(oldValue, tipText)) {
textArea.setText(tipText);
firePropertyChange("tiptext", oldValue, tipText);
revalidate();
repaint();
}
}
@Override public String getTipText() {
return Optional.ofNullable(textArea).map(JTextArea::getText).orElse(null);
}
}