概要

JToolTipJTextAreaを配置して割当て幅に収まりきらない長さの行を自動的に折り返します。

サンプルコード

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());
    // @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 i = getInsets();
    Insets ti = textArea.getInsets();
    int pad = i.left + i.right + ti.left + ti.right + caretMargin;
    // 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);
  }
}
View in GitHub: Java, Kotlin

解説

  • JTextArea#setLineWrap(true)JTextArea#setWrapStyleWord(true)で行の折り返しを設定したJTextAreaJToolTipに配置
  • JComponent#getToolTipText(...)をオーバーライドしてJToolTipが親JFrameの外に表示されて重量ポップアップウィンドウ(共用)が使用される場合は現在のJToolTipの推奨サイズに合わせてリサイズされるようWindow#pack()を実行
JTextField field = new JTextField(20) {
  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 tipText = getText();
    EventQueue.invokeLater(() ->
        Optional.ofNullable(SwingUtilities.getWindowAncestor(tip))
            .filter(w -> w.getType() == Window.Type.POPUP)
            .ifPresent(Window::pack));
    return tipText;
  }
};
  • 文字列の長さが割当て幅に収まるかどうかはJLabelに文字列を設定してその推奨サイズと比較して判断
  • 割当て幅はJTextArea#setColumns(...)で設定
    • Windows 10 + Java 1.8.0_432環境では一度JToolTipが重量ポップアップウィンドウとして表示されるとJTextAreaに配置する文字列が変更されてもその推奨サイズが正しく更新されない?ため割り当て幅は200px固定で設定
      • Java 8.0.432: NG
  • 初めてJTextAreaを表示すると推奨サイズの高さも正しい値が取得できないため、LineBreakMeasurerを使用してJTextAreaとは別に高さを計算して使用

参考リンク

コメント