/*******************************************************************************
* Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.model.ui.widgets;
import java.util.ArrayList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.jboss.tools.common.util.SwtUtil;
public class TextAndReferenceComponent extends Canvas implements PaintListener, MouseMoveListener {
int defaultWidth = 100;
int width;
String text = ""; //$NON-NLS-1$
Line[] lines = new Line[0];
int lineHeight = 12;
ReferenceListener listener;
Font plain;
Font bold;
Token[] tokens = new Token[0];
public TextAndReferenceComponent(Composite parent, int style) {
super (parent, style);
addPaintListener(this);
addMouseMoveListener(this);
addMouseListener(new MA());
plain = getFont();
FontData d = plain.getFontData()[0];
bold = new Font(null, d.getName(), d.getHeight(), d.getStyle() | SWT.BOLD);
SwtUtil.bindDisposal(bold, this);
}
public void mouseMove(MouseEvent e) {
int c = (overReference(e.x, e.y)) ? SWT.CURSOR_HAND : SWT.CURSOR_ARROW;
setCursor(getShell().getDisplay().getSystemCursor(c));
}
private boolean overReference(int x, int y) {
Token t = findToken(x, y);
return t != null && t.isReference;
}
public Point computeSize(int wHint, int hHint, boolean changed) {
width = (wHint == SWT.DEFAULT) ? defaultWidth : wHint;
GC g = new GC(this);
g.setFont(getFont());
lines = Tokenizer.breakIntoLines(g, tokens, width);
g.dispose();
return new Point(width + 10, lines.length * lineHeight + 6);
}
public int getStringWidth(Font font, String str){
GC g = new GC(this);
int size = getStringWidth(g, font, str);
g.dispose();
return size;
}
public static int getStringWidth(GC g, Font font, String str){
int size = 0;
g.setFont(font);
for(int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
size += g.getCharWidth(c) + 1;
if(c == ' ') size += 1;
}
return size;
}
public void setText(String text, int width) {
this.text = text;
defaultWidth = width;
this.width = width;
tokens = Tokenizer.tokenize(text, plain, bold);
pack();
}
public void paintControl(PaintEvent ev) {
if(lines.length == 0) return;
GC g = ev.gc;
Font f = getFont();
g.setFont(f);
for (int i = 0; i < lines.length; i++) {
int y = 2 + i * lineHeight;
lines[i].paint(tokens, g, y);
}
}
public void addReferenceListener(ReferenceListener listener) {
this.listener = listener;
}
class MA extends MouseAdapter {
public void mouseDown(MouseEvent e) {
Token t = findToken(e.x, e.y);
if(t != null && listener != null && t.isReference) {
listener.referenceSelected(t.text);
}
}
}
Token findToken(int x, int y) {
for (int i = 0; i < tokens.length; i++)
if(tokens[i].contains(x, y)) return tokens[i];
return null;
}
}
class Context {
ArrayList<Token> l = new ArrayList<Token>();
StringBuffer word = new StringBuffer();
boolean isReference = false;
boolean isBold = false;
String text;
int i = -1;
char c = '\0';
Font plain;
Font bold;
boolean nextChar() {
++i;
if(i >= text.length()) return false;
c = text.charAt(i);
return true;
}
void appendToWord() {
word.append(c);
}
boolean isReferenceStart() {
boolean b = (c == '<' && text.indexOf("<a>", i) == i && !isReference); //$NON-NLS-1$
if(b) {
getLastToken();
isReference = true;
i += 2;
}
return b;
}
boolean isReferenceEnd() {
boolean b = (c == '<' && text.indexOf("</a>", i) == i && isReference); //$NON-NLS-1$
if(b) {
getLastToken();
isReference = false;
i += 3;
}
return b;
}
boolean isBoldStart() {
boolean b = (c == '<' && text.indexOf("<b>", i) == i && !isBold); //$NON-NLS-1$
if(b) {
getLastToken();
isBold = true;
i += 2;
}
return b;
}
boolean isBoldEnd() {
boolean b = (c == '<' && text.indexOf("</b>", i) == i && isBold); //$NON-NLS-1$
if(b) {
getLastToken();
isBold = false;
i += 3;
}
return b;
}
boolean isWordEnd() {
boolean b = (c == ' ');
if(b) {
getLastToken();
}
return b;
}
boolean isBreakLine() {
boolean b = (c == '\n');
if(b) {
getLastToken();
l.add(Token.createBreakToken());
}
return b;
}
String readLastWord() {
String w = word.toString();
word.setLength(0);
return w;
}
Token getLastToken() {
String w = readLastWord();
if(w.length() == 0) return null;
Token t = Token.createToken(w, isReference, (isBold) ? bold : plain);
l.add(t);
return t;
}
}
class Tokenizer {
public static Token[] tokenize(String text, Font plain, Font bold) {
Context context = new Context();
context.bold = bold;
context.plain = plain;
context.text = text;
while(context.nextChar()) {
if(context.isBreakLine()) {
} else if(context.isWordEnd()) {
} else if(context.isReferenceStart()) {
} else if(context.isReferenceEnd()) {
} else if(context.isBoldStart()) {
} else if(context.isBoldEnd()) {
} else {
context.appendToWord();
}
}
context.getLastToken();
return (Token[])context.l.toArray(new Token[0]);
}
public static Line[] breakIntoLines(GC g, Token[] tokens, int width) {
ArrayList<Line> l = new ArrayList<Line>();
Line line = null;
int x = 0;
for (int i = 0; i < tokens.length; i++) {
if(line == null) {
line = new Line();
line.firstToken = i;
line.lastToken = i;
x = 0;
}
if(tokens[i].isBreak) {
l.add(line);
line = null;
} else {
int dx = TextAndReferenceComponent.getStringWidth(g, tokens[i].font, tokens[i].text);
if(x > 0 && x + 5 + dx > width) {
l.add(line);
line = null;
--i;
} else {
line.lastToken = i;
if(x == 0) x = dx; else x = x + 5 + dx;
}
}
}
if(line != null) l.add(line);
return (Line[])l.toArray(new Line[0]);
}
}
class Line {
int firstToken = -1;
int lastToken = -1;
public void paint(Token[] tokens, GC gc, int y) {
int x = 0;
for (int i = firstToken; i <= lastToken && i < tokens.length; i++) {
x = tokens[i].paint(gc, y, x) + 4;
}
}
}
class Token {
String text = ""; //$NON-NLS-1$
boolean isReference = false;
boolean isBreak = false;
Rectangle r = new Rectangle(0, 0, 0, 0);
Font font;
public int paint(GC g, int y, int x) {
if(isBreak) return x;
g.setFont(font);
r.x = x - 2;
r.y = y;
int color = (isReference) ? SWT.COLOR_BLUE : SWT.COLOR_BLACK;
g.setForeground(Display.getDefault().getSystemColor(color));
g.drawString(text, x, y);
int w = TextAndReferenceComponent.getStringWidth(g, g.getFont(), text);
int x1 = x;
int x2 = x1 + w + 2;
int y1 = y + g.getFont().getFontData()[0].getHeight() + 4;
if(isReference) g.drawLine(x1 - 2, y1, x2, y1);
r.x = x1;
r.y = y;
r.width = x2 - x1 + 2;
r.height = y1 - y;
return x1 + w;
}
public boolean contains(int x, int y) {
return x >= r.x && x <= r.x + r.width && y >= r.y && y < r.y + r.height;
}
public static Token createBreakToken() {
Token t = new Token();
t.isBreak = true;
return t;
}
public static Token createToken(String text, boolean reference, Font font) {
Token t = new Token();
t.text = text;
t.isReference = reference;
t.font = font;
return t;
}
}