package org.japura.gui;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.View;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
/**
* Button with a tooltip function.
* <P>
* The tooltip text is wrapped with a defined width.
* <P>
* Copyright (C) 2009-2015 Carlos Eduardo Leite de Andrade
* <P>
* This library is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* <P>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
* <P>
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <A
* HREF="www.gnu.org/licenses/">www.gnu.org/licenses/</A>
* <P>
* For more information, contact: <A HREF="www.japura.org">www.japura.org</A>
* <P>
*
* @author Carlos Eduardo Leite de Andrade
*/
public class ToolTipButton extends JPanel {
private static final long serialVersionUID = 6L;
public final static Color DEFAULT_BACKGROUND = new Color(255, 255, 220);
private Color borderColor = Color.BLACK;
private Color toolTipBackground;
private Icon image;
private Icon imageMouseOver;
private int toolTipWrapWidth = 300;
private JLabel imageComponent;
private Insets margin;
private int borderThickness = 2;
private String text;
private ToolTipButtonTimer timer;
private JPopupMenu popup;
private Rectangle2D popupBounds;
private JComponent extraComponent;
private ToolTipButtonAnchor extraComponentAnchor;
/**
* Constructor
*
* @param image {@link Icon} - image for tooltip button
*/
public ToolTipButton(Icon image) {
this(image, null, null);
}
/**
* Constructor
*
* @param image {@link Icon} - image for tooltip button
* @param tooltip {@link String} - tooltip text
*/
public ToolTipButton(Icon image, String tooltip) {
this(image, null, tooltip);
}
/**
* Constructor
*
* @param image {@link Icon} - image for tooltip button
* @param imageMouseOver {@link Icon} - image for mouse over tooltip button
* @param tooltip {@link String} - tooltip text
*/
public ToolTipButton(Icon image, Icon imageMouseOver, String tooltip) {
this(image, imageMouseOver, tooltip, null, null);
}
/**
* Constructor
*
* @param image {@link Icon} - image for tooltip button
* @param imageMouseOver {@link Icon} - image for mouse over tooltip button
* @param tooltip {@link String} - tooltip text
* @param extraComponent an extra component for the hint
* @param extraComponentAnchor the BordeLayout constraint for the extra
* component
*/
public ToolTipButton(Icon image, Icon imageMouseOver, String tooltip,
JComponent extraComponent, ToolTipButtonAnchor extraComponentAnchor) {
this.extraComponent = extraComponent;
this.extraComponentAnchor = extraComponentAnchor;
setTooltipMargin(null);
setLayout(new GridBagLayout());
imageComponent = new JLabel(image);
add(imageComponent);
this.image = image;
this.imageMouseOver = imageMouseOver;
this.toolTipBackground = ToolTipButton.DEFAULT_BACKGROUND;
setText(tooltip);
setOpaque(false);
this.timer = new ToolTipButtonTimer();
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
timer.startNextAction(NextAction.SHOW);
}
@Override
public void mouseClicked(MouseEvent e) {
timer.stop();
showTooltip();
}
@Override
public void mouseExited(MouseEvent e) {
timer.startNextAction(NextAction.DISPOSE);
}
});
if (imageMouseOver != null) {
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
imageComponent.setIcon(ToolTipButton.this.imageMouseOver);
}
@Override
public void mouseExited(MouseEvent e) {
imageComponent.setIcon(ToolTipButton.this.image);
}
});
}
}
public String getText() {
return text;
}
public void setText(String text) {
if (text == null) {
text = "";
}
this.text = text;
}
@Override
public void setToolTipText(String text) {
setText(text);
}
public int getDisposeTime() {
return timer.getDisposeTime();
}
public void setDisposeTime(int time) {
timer.setDisposeTime(time);
}
public int getShowTime() {
return timer.getShowTime();
}
public void setShowTime(int time) {
timer.setShowTime(time);
}
public Insets getTooltipMargin() {
return margin;
}
public void setTooltipMargin(Insets margin) {
if (margin == null) {
margin = new Insets(10, 10, 10, 10);
}
margin.bottom = (Math.max(margin.bottom, 0));
margin.top = (Math.max(margin.top, 0));
margin.right = (Math.max(margin.right, 0));
margin.left = (Math.max(margin.left, 0));
this.margin = margin;
}
/**
* Set the border width.
*
* @param thickness an integer specifying the width in pixels
*/
public void setTooltipBorderThickness(int thickness) {
borderThickness = Math.max(0, thickness);
}
/**
* Get the border width.
*
* @return an integer specifying the width in pixels
*/
public int getTooltipBorderThickness() {
return borderThickness;
}
public Color getTooltipBorderColor() {
return borderColor;
}
public void setTooltipBorderColor(Color borderColor) {
this.borderColor = borderColor;
}
public Color getToolTipBackground() {
return toolTipBackground;
}
public void setToolTipBackground(Color toolTipBackground) {
this.toolTipBackground = toolTipBackground;
}
/**
* Get the tooltip text wrap width.
*
* @return int
*/
public int getToolTipWrapWidth() {
return toolTipWrapWidth;
}
/**
* Set the tooltip text wrap width.
* <P>
* The minimal value is 200
*
* @param toolTipWrapWidth int
*/
public void setToolTipWrapWidth(int toolTipWrapWidth) {
this.toolTipWrapWidth = Math.max(200, toolTipWrapWidth);
}
private void tryDisposeTooltip() {
if (popup != null) {
if (popup.isShowing()) {
Point cursorLocation = MouseInfo.getPointerInfo().getLocation();
if (popupBounds.contains(cursorLocation) == false) {
disposeTooltip();
}
}
else {
disposeTooltip();
}
}
}
/**
* Dispose the tooltip
*/
public void disposeTooltip() {
if (popup != null) {
timer.stop();
popup.setVisible(false);
popup = null;
}
}
/**
* Show the tooltip.
*/
private void showTooltip() {
Wrap tooltipLabel = new Wrap(toolTipWrapWidth);
tooltipLabel.setForeground(getForeground());
tooltipLabel.setFont(ToolTipButton.this.getFont());
tooltipLabel.setText(text);
JPanel panel = new JPanel();
panel.setBackground(toolTipBackground);
panel.setLayout(new BorderLayout());
panel.add(tooltipLabel, BorderLayout.CENTER);
if (extraComponent != null && extraComponentAnchor != null) {
if (extraComponentAnchor.equals(ToolTipButtonAnchor.SOUTH)) {
panel.add(extraComponent, BorderLayout.SOUTH);
}
else if (extraComponentAnchor.equals(ToolTipButtonAnchor.NORTH)) {
panel.add(extraComponent, BorderLayout.NORTH);
}
else if (extraComponentAnchor.equals(ToolTipButtonAnchor.EAST)) {
panel.add(extraComponent, BorderLayout.EAST);
}
else if (extraComponentAnchor.equals(ToolTipButtonAnchor.WEST)) {
panel.add(extraComponent, BorderLayout.WEST);
}
}
Border out = BorderFactory.createLineBorder(borderColor, borderThickness);
Border in =
BorderFactory.createEmptyBorder(margin.top, margin.left, margin.bottom,
margin.right);
Border border = BorderFactory.createCompoundBorder(out, in);
panel.setBorder(border);
popup = new JPopupMenu();
popup.setBorder(BorderFactory.createEmptyBorder());
popup.add(panel);
popup.addMouseListener(new MouseAdapter() {
@Override
public void mouseExited(MouseEvent e) {
tryDisposeTooltip();
}
});
popup.pack();
popup.show(ToolTipButton.this, 0, getHeight());
Point point = popup.getLocationOnScreen();
Dimension dim = popup.getSize();
popupBounds =
new Rectangle2D.Double(point.getX(), point.getY(), dim.getWidth(),
dim.getHeight());
}
private static class Wrap extends JTextPane {
private static final long serialVersionUID = 2849635511260534304L;
private View view;
private int width;
public Wrap(int width) {
this.width = width;
setText("");
setContentType("text/html");
setOpaque(false);
setEditable(false);
setHighlighter(null);
setBorder(null);
setBackground(new Color(0, 0, 0, 0));
}
private void setHTMLFont(Font font) {
MutableAttributeSet attrs = getInputAttributes();
StyleConstants.setFontFamily(attrs, font.getFamily());
StyleConstants.setFontSize(attrs, font.getSize());
// StyleConstants.setItalic(attrs, (font.getStyle() & Font.ITALIC) != 0);
// StyleConstants.setBold(attrs, (font.getStyle() & Font.BOLD) != 0);
StyleConstants.setForeground(attrs, getForeground());
StyledDocument doc = getStyledDocument();
doc.setCharacterAttributes(0, doc.getLength() + 1, attrs, false);
}
@Override
public void setText(String t) {
super.setText(t);
view = javax.swing.plaf.basic.BasicHTML.createHTMLView(Wrap.this, t);
setHTMLFont(getFont());
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
view.setSize(width, 0);
Insets insets = getInsets();
Insets margin = getMargin();
float w =
view.getPreferredSpan(View.X_AXIS) + insets.left + insets.right
+ margin.left + margin.right;
float h =
view.getPreferredSpan(View.Y_AXIS) + insets.bottom + insets.top
+ margin.bottom + margin.top;
return new Dimension((int) Math.ceil(w), (int) Math.ceil(h));
}
}
private enum NextAction {
SHOW,
DISPOSE;
}
private class ToolTipButtonTimer extends Timer implements ActionListener {
private int showTime = 800;
private int disposeTime = 200;
private NextAction nextAction = NextAction.SHOW;
public ToolTipButtonTimer() {
super(0, null);
addActionListener(this);
setRepeats(false);
}
public void setShowTime(int showTime) {
this.showTime = showTime;
}
public int getShowTime() {
return showTime;
}
public void setDisposeTime(int disposeTime) {
this.disposeTime = disposeTime;
}
public int getDisposeTime() {
return disposeTime;
}
public void startNextAction(NextAction nextAction) {
stop();
this.nextAction = nextAction;
if (nextAction.equals(NextAction.SHOW)) {
setInitialDelay(getShowTime());
}
else if (nextAction.equals(NextAction.DISPOSE)) {
setInitialDelay(getDisposeTime());
}
start();
}
@Override
public void actionPerformed(ActionEvent e) {
if (nextAction.equals(NextAction.SHOW)) {
showTooltip();
}
else if (nextAction.equals(NextAction.DISPOSE)) {
tryDisposeTooltip();
}
}
}
public static enum ToolTipButtonAnchor {
SOUTH,
NORTH,
EAST,
WEST;
}
}