Code
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);
}
}
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();
}
};
References