Google Tag Manager

2024/12/31

Create a multi-line JToolTip using JTextArea's automatic line wrapping

Code

class LineWrapToolTip extends JToolTip {
  private static final double JAVA17 = 17.0;
  private static final JLabel MEASURER = new JLabel(" ");
  private static final int TIP_WIDTH = 200;
  private final JTextArea textArea = new JTextArea(0, 20);

  protected LineWrapToolTip() {
    super();
    textArea.setLineWrap(true);
    textArea.setWrapStyleWord(true);
    textArea.setOpaque(true);
    // textArea.setColumns(20);
    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() {
    Dimension d = getLayout().preferredLayoutSize(this);
    Dimension dim;
    String version = System.getProperty("java.specification.version");
    if (Double.parseDouble(version) >= JAVA17) {
      dim = getTextAreaSize17(d);
    } else {
      dim = getTextAreaSize8(d);
    }
    return dim;
  }

  private Dimension getTextAreaSize8(Dimension d) {
    Font font = textArea.getFont();
    MEASURER.setFont(font);
    MEASURER.setText(textArea.getText());
    Insets i = getInsets();
    int pad = getTextAreaPaddingWidth(i);
    // d.width = Math.min(d.width, MEASURER.getPreferredSize().width + pad);
    d.width = Math.min(TIP_WIDTH, MEASURER.getPreferredSize().width + pad);

    // JDK-8226513 JEditorPane is shown with incorrect size - Java Bug System
    // https://bugs.openjdk.org/browse/JDK-8226513
    AttributedString as = new AttributedString(textArea.getText());
    as.addAttribute(TextAttribute.FONT, font);
    AttributedCharacterIterator aci = as.getIterator();
    FontMetrics fm = textArea.getFontMetrics(font);
    FontRenderContext frc = fm.getFontRenderContext();
    LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
    float y = 0f;
    while (lbm.getPosition() < aci.getEndIndex()) {
      TextLayout tl = lbm.nextLayout(TIP_WIDTH);
      y += tl.getDescent() + tl.getLeading() + tl.getAscent();
    }
    d.height = (int) y + getTextAreaPaddingHeight(i);
    return d;
  }

  private Dimension getTextAreaSize17(Dimension d) {
    MEASURER.setFont(textArea.getFont());
    MEASURER.setText(textArea.getText());
    int pad = getTextAreaPaddingWidth(getInsets());
    d.width = Math.min(d.width, MEASURER.getPreferredSize().width + pad);
    return d;
  }

  private int getTextAreaPaddingWidth(Insets i) {
    // @see BasicTextUI.java
    // margin required to show caret in the rightmost position
    int caretMargin = -1;
    Object property = UIManager.get("Caret.width");
    if (property instanceof Number) {
      caretMargin = ((Number) property).intValue();
    }
    property = textArea.getClientProperty("caretWidth");
    if (property instanceof Number) {
      caretMargin = ((Number) property).intValue();
    }
    if (caretMargin < 0) {
      caretMargin = 1;
    }
    Insets ti = textArea.getInsets();
    return i.left + i.right + ti.left + ti.right + caretMargin;
    // Insets tm = textArea.getMargin();
    // return i.left + i.right + ti.left + ti.right + tm.left + tm.right;
  }

  private int getTextAreaPaddingHeight(Insets i) {
    Insets ti = textArea.getInsets();
    return i.top + i.bottom + ti.top + ti.bottom;
  }

  @Override public void setTipText(String tipText) {
    String oldValue = textArea.getText();
    textArea.setText(tipText);
    firePropertyChange("tiptext", oldValue, tipText);
    if (!Objects.equals(oldValue, tipText)) {
      revalidate();
      repaint();
    }
  }

  @Override public String getTipText() {
    return Optional.ofNullable(textArea).map(JTextArea::getText).orElse(null);
  }
}

References

No comments:

Post a Comment