package org.docear.plugin.core.ui; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.List; import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.swing.Icon; import javax.swing.JPanel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; import sun.swing.SwingUtilities2; public class MultiLineActionLabel extends JPanel implements SwingConstants, Accessible { private static final long serialVersionUID = 1L; private String text = ""; private int verticalAlignment = CENTER; private int horizontalAlignment = LEADING; private int verticalTextPosition = CENTER; private int horizontalTextPosition = TRAILING; private Rectangle paintTextR = new Rectangle(); private Rectangle paintIconR = new Rectangle(); private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); private List<MultiLineActionLabel.ActionLabelItem> actionItems = new ArrayList<MultiLineActionLabel.ActionLabelItem>(); private final MouseAdapter mouseAdapter = new MouseAdapter(); public MultiLineActionLabel(String text) { setText(text); addMouseListener(mouseAdapter); addMouseMotionListener(mouseAdapter); } public void setText(String text) { String oldAccessibleName = null; if (accessibleContext != null) { oldAccessibleName = accessibleContext.getAccessibleName(); } String oldValue = this.text; this.text = parsedString(text); BasicHTML.updateRenderer(this, getText()); firePropertyChange("text", oldValue, getText()); if ((accessibleContext != null) && (accessibleContext.getAccessibleName() != oldAccessibleName)) { accessibleContext.firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, oldAccessibleName, accessibleContext.getAccessibleName()); } if (getText() == null || oldValue == null || !getText().equals(oldValue)) { revalidate(); repaint(); } } private String parsedString(final String str) { StringBuilder builder = new StringBuilder(); if (!BasicHTML.isHTMLString(text)) { builder.append("<html><body>"); } int currentPos = -1; int lastPos = 0; String text = str.replaceAll("[\n]", "<br/>"); actionItems.clear(); while ((currentPos = text.indexOf("<action cmd=\"", (currentPos + 1))) > -1) { builder.append(text.substring(lastPos, currentPos)); builder.append("<span style=\"color: #0000FF;\">"); lastPos = currentPos+"<action cmd=\"".length(); String actionCommand = text.substring(lastPos, text.indexOf("\"", lastPos)); lastPos = text.indexOf(">", lastPos)+1; currentPos = text.indexOf("</action>", (currentPos + 1)); ActionLabelItem item = new ActionLabelItem(text.substring(lastPos, currentPos)); item.setActionCommand(actionCommand); builder.append(item.getText()); builder.append("</span>"); lastPos = currentPos+"</action>".length(); actionItems.add(item); } if(lastPos < text.length()) { builder.append(text.substring(lastPos)); } if (!BasicHTML.isHTMLString(text)) { builder.append("</body></html>"); } return builder.toString(); } public String getText() { return text; } public int getVerticalAlignment() { return verticalAlignment; } public void setVerticalAlignment(int verticalAlignment) { this.verticalAlignment = verticalAlignment; } public int getHorizontalAlignment() { return horizontalAlignment; } public void setHorizontalAlignment(int horizontalAlignment) { this.horizontalAlignment = horizontalAlignment; } public int getVerticalTextPosition() { return verticalTextPosition; } public void setVerticalTextPosition(int verticalTextPosition) { this.verticalTextPosition = verticalTextPosition; } public int getHorizontalTextPosition() { return horizontalTextPosition; } public void setHorizontalTextPosition(int horizontalTextPosition) { this.horizontalTextPosition = horizontalTextPosition; } public void paint(Graphics g) { super.paint(g); String text = getText(); if (text == null) { return; } FontMetrics fm = SwingUtilities2.getFontMetrics(this, g); layout(fm, getWidth(), getHeight()); if (text != null) { View view = (View) getClientProperty(BasicHTML.propertyKey); if (view != null) { try { String clippedText = view.getDocument().getText(1, view.getDocument().getLength()); identifyActionAreas(clippedText, fm, SwingUtilities2.getFontMetrics(this, g, fm.getFont().deriveFont(Font.BOLD))); view.paint(g, paintTextR); } catch (Exception e) { } } } // g.setColor(Color.black); // for(ActionLabelItem item : actionItems) { // g.drawRect(item.getHotArea().x, item.getHotArea().y, item.getHotArea().width, item.getHotArea().height); // } } private void identifyActionAreas(final String text, FontMetrics fmDefault, FontMetrics fmAction) { int lastPos = 0; for(ActionLabelItem item : actionItems) { Rectangle rect = item.getHotArea(); int textPos = text.indexOf(item.getText(), lastPos); if(textPos > -1) { String sub = text.substring(0, textPos); rect.x = paintTextR.x + fmDefault.stringWidth(sub); rect.y = paintTextR.y; rect.width = fmDefault.stringWidth(item.getText()); rect.height = fmDefault.getHeight(); lastPos = textPos+item.getText().length(); } } } private String layout(FontMetrics fm, int width, int height) { Insets insets = getInsets(null); String text = getText(); Rectangle paintViewR = new Rectangle(); paintViewR.x = insets.left; paintViewR.y = insets.top; paintViewR.width = width - (insets.left + insets.right); paintViewR.height = height - (insets.top + insets.bottom); paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; return layoutCL(fm, text, null, paintViewR, paintIconR, paintTextR); } protected String layoutCL(FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR) { return SwingUtilities.layoutCompoundLabel(this, fontMetrics, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition, horizontalTextPosition, viewR, iconR, textR, 4); } public Dimension getPreferredSize() { String text = getText(); Insets insets = getInsets(null); Font font = getFont(); int dx = insets.left + insets.right; int dy = insets.top + insets.bottom; if (((text == null) || ((text != null) && (font == null)))) { return new Dimension(dx, dy); } else { FontMetrics fm = getFontMetrics(font); Rectangle iconR = new Rectangle(); Rectangle textR = new Rectangle(); Rectangle viewR = new Rectangle(); iconR.x = iconR.y = iconR.width = iconR.height = 0; textR.x = textR.y = textR.width = textR.height = 0; viewR.x = dx; viewR.y = dy; viewR.width = viewR.height = Short.MAX_VALUE; layoutCL(fm, text, null, viewR, iconR, textR); int x1 = Math.min(iconR.x, textR.x); int x2 = Math.max(iconR.x + iconR.width, textR.x + textR.width); int y1 = Math.min(iconR.y, textR.y); int y2 = Math.max(iconR.y + iconR.height, textR.y + textR.height); Dimension rv = new Dimension(x2 - x1, y2 - y1); rv.width += dx; rv.height += dy; return rv; } } /** * @return getPreferredSize() */ public Dimension getMinimumSize() { Dimension d = getPreferredSize(); View view = (View) getClientProperty(BasicHTML.propertyKey); if (view != null) { d.width -= view.getPreferredSpan(View.X_AXIS) - view.getMinimumSpan(View.X_AXIS); } return d; } /** * @return getPreferredSize() */ public Dimension getMaximumSize() { Dimension d = getPreferredSize(); View view = (View) getClientProperty(BasicHTML.propertyKey); if (view != null) { d.width += view.getMaximumSpan(View.X_AXIS) - view.getPreferredSpan(View.X_AXIS); } return d; } public int getBaseline(int width, int height) { super.getBaseline(width, height); String text = getText(); if (text == null || "".equals(text) || getFont() == null) { return -1; } FontMetrics fm = getFontMetrics(getFont()); layout(fm, width, height); return getHTMLBaseline(paintTextR.y, fm.getAscent(), paintTextR.width, paintTextR.height); } public void addActionListener(ActionListener listener) { if(!actionListeners.contains(listener)) { this.actionListeners.add(listener); } } public void removeActionListener(ActionListener listener) { this.actionListeners.remove(listener); } private int getHTMLBaseline(int y, int ascent, int width, int height) { View view = (View) getClientProperty(BasicHTML.propertyKey); if (view != null) { int baseline = BasicHTML.getHTMLBaseline(view, width, height); if (baseline < 0) { return baseline; } return y + baseline; } return y + ascent; } protected void fireActionEvent(String actionCommand) { ActionEvent event = new ActionEvent(this, 0, actionCommand); for(ActionListener listener : actionListeners) { listener.actionPerformed(event); } } protected ActionLabelItem getIntersectingItem(Point point) { for(ActionLabelItem item : actionItems) { if(item.getHotArea().contains(point)) { return item; } } return null; } protected ActionLabelItem getActionItem(Rectangle rect) { // TODO Auto-generated method stub return null; } class MouseAdapter implements MouseListener, MouseMotionListener { public void mouseMoved(MouseEvent e) { ActionLabelItem item = getIntersectingItem(e.getPoint()); if(item != null) { MultiLineActionLabel.this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { MultiLineActionLabel.this.setCursor(Cursor.getDefaultCursor()); } } public void mouseClicked(MouseEvent e) { ActionLabelItem item = getIntersectingItem(e.getPoint()); if(item != null) { fireActionEvent(item.getActionCommand()); } } public void mouseDragged(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } class ActionLabelItem { private Rectangle hotArea = null; private String actionCommand; private final String text; public ActionLabelItem(String text) { this.text = text; } public String getText() { return text; } public void setActionCommand(String actionCommand) { this.actionCommand = actionCommand; } public String getActionCommand() { return actionCommand; } public Rectangle getHotArea() { if(hotArea == null) { hotArea = new Rectangle(); } return hotArea; } public void setHotArea(Rectangle hotArea) { this.hotArea = hotArea; } } }