Swing/SpeechBalloonToolTipTail の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/SpeechBalloonToolTipTail へ行く。
- Swing/SpeechBalloonToolTipTail の差分を削除
--- 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 hreflang: href: https://java-swing-tips.blogspot.com/2023/04/change-tooltip-of-tab-in-jtabbedpane-to.html lang: en --- * 概要 [#summary] `JTabbedPane`のタブ用ツールヒントの形状をふきだしにし、そのしっぽの方向をタブ位置に応じて変更します。 #download(https://drive.google.com/uc?id=1WbgFj8zOssgmjFU6rsL0vXNgW5Lsw7OO) * サンプルコード [#sourcecode] #code(link){{ class BalloonToolTip extends JToolTip { private static final int SIZE = 4; private static final double ARC = 4d; private transient HierarchyListener listener; private transient Shape shape; @Override public void updateUI() { removeHierarchyListener(listener); super.updateUI(); setLayout(new BorderLayout()); 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); setBorder(BorderFactory.createEmptyBorder(SIZE, SIZE, SIZE, SIZE)); } @Override public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); d.width += SIZE; d.height += SIZE; 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 placement) { Insets i = getInsets(); Dimension d = getPreferredSize(); Path2D tail = new Path2D.Double(); double w = d.getWidth() - i.left - i.right - 1d; double h = d.getHeight() - i.top - i.bottom - 1d; double cx = w / 2d; double cy = h / 2d; switch (placement) { case SwingConstants.LEFT: tail.moveTo(0, cy - SIZE); tail.lineTo(-SIZE, cy); tail.lineTo(0, cy + SIZE); break; case SwingConstants.RIGHT: tail.moveTo(w, cy - SIZE); tail.lineTo(w + SIZE, cy); tail.lineTo(w, cy + SIZE); break; case SwingConstants.BOTTOM: tail.moveTo(cx - SIZE, h); tail.lineTo(cx, h + SIZE); tail.lineTo(cx + SIZE, h); break; default: // case SwingConstants.TOP: tail.moveTo(cx - SIZE, 0); tail.lineTo(cx, -SIZE); tail.lineTo(cx + SIZE, 0); } Area area = new Area(new RoundRectangle2D.Double(0, 0, w, h, ARC, ARC)); area.add(new Area(tail)); AffineTransform at = AffineTransform.getTranslateInstance(i.left, i.top); shape = at.createTransformedShape(area); } } }} * 解説 [#explanation] - `JToolTip`の形状変更は[[JToolTipの形状を吹き出し風に変更する>Swing/BalloonToolTip]]と同様の方法を使用 -- `JToolTip`のテキストが長くなると`JToolTip`を常に親`JFrame`内に表示される方向に設定していても`HeavyWeightWindow`が使用される可能性があるので`JWindow`を透明する設定もそのまま使用している - `JToolTip`が初回表示される前に`JToolTip#getVisibleRect()`を実行しても正しいサイズが取得できないので、代わりに`JToolTip#getPreferredSize()`を使用する必要がある -- `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(...)`を以下のようにオーバーライドして変更 -- `NimbusLookAndFeel`などで`JToolTip`の形が変更できない --- `UIDefault#put("ToolTip[Enabled].backgroundPainter", Painter<JToolTip>...);`などで背景を描画しない`Painter<JToolTip>`を設定する、または`JToolTip`に直接テキストを描画するのではなく`JLabel`を追加してテキストを描画すれば回避可能 --- `UIDefault#put("ToolTip[Enabled].backgroundPainter", Painter<JToolTip>...)`などで背景を描画しない`Painter<JToolTip>`を設定する、または`JToolTip`に直接テキストを描画するのではなく`JLabel`を追加してテキストを描画すれば回避可能 #code{{ JTabbedPane tabs = new JTabbedPane( SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT) { private transient BalloonToolTip tip; private final JLabel label = new JLabel(" ", CENTER); @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); label.setText(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(); LookAndFeel.installColorsAndFont( label, "ToolTip.background", "ToolTip.foreground", "ToolTip.font"); tip.add(label); tip.updateBalloonShape(getTabPlacement()); tip.setComponent(this); } return tip; } @Override public void updateUI() { tip = null; super.updateUI(); } }; }} * 参考リンク [#reference] - [[JToolTipの形状を吹き出し風に変更する>Swing/BalloonToolTip]] - [[JToolTipを半透明にする>Swing/TranslucentToolTips]] - [[JTabbedPaneのサムネイルをJToolTipで表示>Swing/TabThumbnail]] * コメント [#comment] #comment #comment