---
category: swing
folder: SpeechBalloonToolTipTail
title: JTabbedPaneのツールヒントをタブ位置に対応したふきだしに変更する
tags: [JToolTip, JTabbedPane]
author: aterai
pubdate: 2023-04-17T03:14:21+09:00
description: JTabbedPaneのタブ用ツールヒントの形状をふきだしにし、そのしっぽの方向をタブ位置に応じて変更します。
image: https://drive.google.com/uc?id=1WbgFj8zOssgmjFU6rsL0vXNgW5Lsw7OO
---
* 概要 [#summary]
`JTabbedPane`のタブ用ツールヒントの形状をふきだしにし、そのしっぽの方向をタブ位置に応じて変更します。

#download(https://drive.google.com/uc?id=1WbgFj8zOssgmjFU6rsL0vXNgW5Lsw7OO)

* サンプルコード [#sourcecode]
#code(link){{
class BalloonToolTip extends JToolTip {
  private static final int SIZE = 4;
  private transient HierarchyListener listener;
  private transient Shape shape;

  @Override public void updateUI() {
    removeHierarchyListener(listener);
    super.updateUI();
    listener = e -> {
      Component c = e.getComponent();
      if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0
           && c.isShowing()) {
        Optional.ofNullable(SwingUtilities.getWindowAncestor(c))
            .filter(w -> w.getType() == Window.Type.POPUP)
            .ifPresent(w -> w.setBackground(new Color(0x0, true)));
      }
    };
    addHierarchyListener(listener);
    setOpaque(false);
  }

  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 24;
    return d;
  }

  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(getBackground());
    g2.fill(shape);
    g2.setPaint(getForeground());
    g2.draw(shape);
    g2.dispose();
    super.paintComponent(g);
  }

  public void updateBalloonShape(int l) {
    Rectangle r = getVisibleRect();
    r.setSize(getPreferredSize());
    Path2D tail = new Path2D.Double();
    double w = r.getWidth() - 1d;
    double h = r.getHeight() - 1d;
    double arc = 10d;
    Shape bubble;
    switch (l) {
      case SwingConstants.LEFT:
        setBorder(BorderFactory.createEmptyBorder(2, 2 + SIZE, 2, 2));
        tail.moveTo(r.getMinX() + SIZE, r.getCenterY() - SIZE);
        tail.lineTo(r.getMinX(), r.getCenterY());
        tail.lineTo(r.getMinX() + SIZE, r.getCenterY() + SIZE);
        w -= SIZE;
        bubble = new RoundRectangle2D.Double(
            r.getMinX() + SIZE, r.getMinY(), w, h, arc, arc);
        break;
      case SwingConstants.RIGHT:
        setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2 + SIZE));
        tail.moveTo(r.getMaxX() - SIZE - 1d, r.getCenterY() - SIZE);
        tail.lineTo(r.getMaxX(), r.getCenterY());
        tail.lineTo(r.getMaxX() - SIZE - 1d, r.getCenterY() + SIZE);
        w -= SIZE;
        bubble = new RoundRectangle2D.Double(
            r.getMinX(), r.getMinY(), w, h, arc, arc);
        break;
      case SwingConstants.BOTTOM:
        setBorder(BorderFactory.createEmptyBorder(2, 2, 2 + SIZE, 2));
        tail.moveTo(r.getCenterX() - SIZE, r.getMaxY() - SIZE - 1d);
        tail.lineTo(r.getCenterX(), r.getMaxY());
        tail.lineTo(r.getCenterX() + SIZE, r.getMaxY() - SIZE - 1d);
        h -= SIZE;
        bubble = new RoundRectangle2D.Double(
            r.getMinX(), r.getMinY(), w, h, arc, arc);
        break;
      default: // case SwingConstants.TOP:
        setBorder(BorderFactory.createEmptyBorder(2 + SIZE, 2, 2, 2));
        tail.moveTo(r.getCenterX() - SIZE, r.getMinY() + SIZE);
        tail.lineTo(r.getCenterX(), r.getMinY());
        tail.lineTo(r.getCenterX() + SIZE, r.getMinY() + SIZE);
        h -= SIZE;
        bubble = new RoundRectangle2D.Double(
            r.getMinX(), r.getMinY() + SIZE, w, h, arc, arc);
    }
    Area area = new Area(bubble);
    area.add(new Area(tail));
    shape = area;
  }
}
}}

* 解説 [#explanation]
- `JToolTip`の形状変更は[[JToolTipの形状を吹き出し風に変更する>Swing/BalloonToolTip]]と同様の方法を使用
-- `JToolTip`のテキストが長くなると`JToolTip`を常に親`JFrame`内に表示される方向に設定していても`HeavyWeightWindow`が使用される可能性があるので`JWindow`を透明する設定もそのまま使用している
- `JToolTip`が初回表示される前に`JToolTip#getVisibleRect()`を実行しても正しいサイズが取得できないので、代わりに`JToolTip#getPreferredSize()`を使用する必要がある
- `JTabbedPane.TOP`
-- ふきだしのしっぽの三角形を上辺中央に描画し、対象タブ領域の下辺中央に三角形の頂点が接するように`JToolTip`を配置
- `JTabbedPane.BOTTOM`
-- ふきだしのしっぽの三角形を下辺中央に描画し、対象タブ領域の上辺中央に三角形の頂点が接するように`JToolTip`を配置
- `JTabbedPane.LEFT`
-- ふきだしのしっぽの三角形を左辺中央に描画し、対象タブ領域の右辺中央に三角形の頂点が接するように`JToolTip`を配置
- `JTabbedPane.RIGHT`
-- ふきだしのしっぽの三角形を右辺中央に描画し、対象タブ領域の左辺中央に三角形の頂点が接するように`JToolTip`を配置
- `JToolTip`の表示位置は`JTabbedPane#getToolTipLocation(...)`を以下のようにオーバーライドして変更

#code{{
JTabbedPane tabs = new JTabbedPane() {
  private transient BalloonToolTip tip;

  @Override public Point getToolTipLocation(MouseEvent e) {
    int idx = indexAtLocation(e.getX(), e.getY());
    String txt = idx >= 0 ? getToolTipTextAt(idx) : null;
    return Optional.ofNullable(txt).map(toolTipText -> {
      JToolTip tips = createToolTip();
      tips.setTipText(toolTipText);
      if (tips instanceof BalloonToolTip) {
        ((BalloonToolTip) tips).updateBalloonShape(getTabPlacement());
      }
      return getToolTipPoint(
          getBoundsAt(idx), tips.getPreferredSize());
    }).orElse(null);
  }

  private Point getToolTipPoint(Rectangle r, Dimension d) {
    double dx;
    double dy;
    switch (getTabPlacement()) {
      case LEFT:
        dx = r.getMaxX();
        dy = r.getCenterY() - d.getHeight() / 2d;
        break;
      case RIGHT:
        dx = r.getMinX() - d.width;
        dy = r.getCenterY() - d.getHeight() / 2d;
        break;
      case BOTTOM:
        dx = r.getCenterX() - d.getWidth() / 2d;
        dy = r.getMinY() - d.height;
        break;
      default: // case TOP:
        dx = r.getCenterX() - d.getWidth() / 2d;
        dy = r.getMaxY();
    }
    return new Point((int) (dx + .5), (int) (dy + .5));
  }

  @Override public JToolTip createToolTip() {
    if (tip == null) {
      tip = new BalloonToolTip();
      tip.updateBalloonShape(getTabPlacement());
      tip.setComponent(this);
    }
    return tip;
  }
};
}}

* 参考リンク [#reference]
- [[JToolTipの形状を吹き出し風に変更する>Swing/BalloonToolTip]]
- [[JToolTipを半透明にする>Swing/TranslucentToolTips]]
- [[JTabbedPaneのサムネイルをJToolTipで表示>Swing/TabThumbnail]]

* コメント [#comment]
#comment
#comment