blob: 0416008d1d579421166a15f4bff95fe03bf23e1d [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ui.components.labels;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.UI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.HashSet;
import java.util.Set;
/**
* @author kir
*/
public class LinkLabel<T> extends JLabel {
protected boolean myUnderline;
private LinkListener myLinkListener;
private T myLinkData;
private static final Set ourVisitedLinks = new HashSet();
private boolean myIsLinkActive;
private String myVisitedLinksKey;
private int myIconWidth;
private Icon myHoveringIcon;
private Icon myInactiveIcon;
private boolean myClickIsBeingProcessed;
private boolean myPaintDefaultIcon;
protected static final int DEFAULT_ICON_GAP = 2;
public LinkLabel() {
this("", AllIcons.Ide.Link);
}
public LinkLabel(String text, @Nullable Icon icon) {
this(text, icon, null, null, null);
}
public LinkLabel(String text, @Nullable Icon icon, @Nullable LinkListener aListener) {
this(text, icon, aListener, null, null);
}
public LinkLabel(String text, @Nullable Icon icon, @Nullable LinkListener aListener, @Nullable T aLinkData) {
this(text, icon, aListener, aLinkData, null);
}
public LinkLabel(String text,
@Nullable Icon icon,
@Nullable LinkListener<T> aListener,
@Nullable T aLinkData,
@Nullable String aVisitedLinksKey) {
super(text, icon, SwingConstants.LEFT);
setOpaque(false);
setListener(aListener, aLinkData);
myIconWidth = getIcon() == null ? 0 : getIcon().getIconWidth() + getIconTextGap();
myInactiveIcon = getIcon();
MyMouseHandler mouseHandler = new MyMouseHandler();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
myVisitedLinksKey = aVisitedLinksKey;
}
public void setHoveringIcon(Icon iconForHovering) {
myHoveringIcon = iconForHovering;
}
public void setListener(LinkListener listener, @Nullable T linkData) {
myLinkListener = listener;
myLinkData = linkData;
}
public void doClick() {
if (myClickIsBeingProcessed) return;
try {
myClickIsBeingProcessed = true;
if (myLinkListener != null) myLinkListener.linkSelected(this, myLinkData);
ourVisitedLinks.add(myVisitedLinksKey);
repaint();
}
finally {
myClickIsBeingProcessed = false;
}
}
public boolean isVisited() {
return myVisitedLinksKey != null && ourVisitedLinks.contains(myVisitedLinksKey);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Border border = getBorder();
int shiftX = 0;
int shiftY = 0;
if (border != null) {
shiftX = border.getBorderInsets(this).left;
shiftY = border.getBorderInsets(this).top;
}
setForeground(getTextColor());
super.paintComponent(g);
if (getText() != null) {
g.setColor(getTextColor());
int x = myIconWidth;
int y = getTextBaseLine();
if (myUnderline) {
int k = 1;
if (getFont().getSize() > 11) {
k += (getFont().getSize() - 11);
}
y += k;
int lineY = y + shiftY;
if (lineY >= getSize().height) {
lineY = getSize().height - 1;
}
if (getHorizontalAlignment() == LEFT) {
UIUtil.drawLine(g, x + shiftX, lineY, x + getFontMetrics(getFont()).stringWidth(getText()) + shiftX, lineY);
} else {
UIUtil.drawLine(g, getWidth() - 1 - getFontMetrics(getFont()).stringWidth(getText()) + shiftX, lineY,
getWidth() - 1 + shiftX, lineY);
}
}
if (myPaintDefaultIcon) {
int endX = myIconWidth + getFontMetrics(getFont()).stringWidth(getText());
int endY = getHeight() / 2 - AllIcons.Ide.Link.getIconHeight() / 2 + 1;
AllIcons.Ide.Link.paintIcon(this, g, endX + shiftX + DEFAULT_ICON_GAP, endY);
}
}
}
protected Color getTextColor() {
return myIsLinkActive ? getActive() : isVisited() ? getVisited() : getNormal();
}
public Dimension getPreferredSize() {
final Dimension size = super.getPreferredSize();
size.width += myPaintDefaultIcon ? AllIcons.Ide.Link.getIconWidth() + DEFAULT_ICON_GAP : 0;
return size;
}
public void removeNotify() {
super.removeNotify();
if (ScreenUtil.isStandardAddRemoveNotify(this))
disableUnderline();
}
private void setActive(boolean isActive) {
myIsLinkActive = isActive;
onSetActive(myIsLinkActive);
repaint();
}
protected void onSetActive(boolean active) {
}
private int getTextBaseLine() {
FontMetrics fm = getFontMetrics(getFont());
return getHeight() / 2 + (fm.getHeight() / 2 - fm.getDescent());
}
private boolean isInClickableArea(Point pt) {
Insets insets = getInsets(); // border is set
pt.translate(-insets.left, -insets.top);
if (getIcon() != null) {
if (pt.getX() < getIcon().getIconWidth() && pt.getY() < getIcon().getIconHeight()) {
return true;
}
}
if (getText() != null) {
FontMetrics fm = getFontMetrics(getFont());
int height = fm.getHeight() + 1;
int y = getHeight() / 2 - fm.getHeight() / 2;
int width = fm.stringWidth(getText());
if (myPaintDefaultIcon) {
width += AllIcons.Ide.Link.getIconWidth() + DEFAULT_ICON_GAP;
}
if (getHorizontalAlignment() == LEFT) {
return (new Rectangle(myIconWidth, y, width, height).contains(pt));
}
else {
return (new Rectangle(getWidth() - width - 1, y, getWidth() - 1, height).contains(pt));
}
}
return false;
}
private void enableUnderline() {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
myUnderline = true;
if (myHoveringIcon != null) {
setIcon(myHoveringIcon);
}
setStatusBarText(getStatusBarText());
repaint();
}
protected String getStatusBarText() {
return getToolTipText();
}
private void disableUnderline() {
setCursor(Cursor.getDefaultCursor());
myUnderline = false;
setIcon(myInactiveIcon);
setStatusBarText(null);
setActive(false);
}
private static void setStatusBarText(String statusBarText) {
if (ApplicationManager.getApplication() == null) return; // makes this component work in UIDesigner preview.
final Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (Project project : projects) {
StatusBar.Info.set(statusBarText, project);
}
}
public static void clearVisitedHistory() {
ourVisitedLinks.clear();
}
protected Color getVisited() {
return UI.getColor("link.visited.foreground");
}
protected Color getActive() {
return UI.getColor("link.pressed.foreground");
}
protected Color getNormal() {
return UI.getColor("link.foreground");
}
public void entered(MouseEvent e) {
enableUnderline();
}
public void exited(MouseEvent e) {
disableUnderline();
}
public void pressed(MouseEvent e) {
doClick(e);
}
private class MyMouseHandler extends MouseAdapter implements MouseMotionListener {
public void mousePressed(MouseEvent e) {
if (isInClickableArea(e.getPoint())) {
setActive(true);
}
}
public void mouseReleased(MouseEvent e) {
if (myIsLinkActive && isInClickableArea(e.getPoint())) {
doClick(e);
}
setActive(false);
}
public void mouseMoved(MouseEvent e) {
if (isInClickableArea(e.getPoint())) {
enableUnderline();
}
else {
disableUnderline();
}
}
public void mouseExited(MouseEvent e) {
disableUnderline();
}
public void mouseDragged(MouseEvent e) {
}
}
public void doClick(InputEvent e) {
doClick();
}
public void setDefaultIconPainted(boolean paintDefaultIcon) {
myPaintDefaultIcon = paintDefaultIcon;
}
}