/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.dialogs;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.util.EncodingUtils;
public class StyledLink extends Canvas {
private static final int LINE_SPACING = 1;
private static final int PARAGRAPH_SPACING = 7;
private static class StyledParagraph {
TextLayout text;
int top;
}
private String text = ""; //$NON-NLS-1$
private boolean styled;
private StyledParagraph[] paragraphs = null;
private int cachedWidth = -621735;
private Point cachedSize = null;
private boolean hovered = false;
private Font boldFont = null;
private Font italicFont = null;
private Font boldItalicFont = null;
private ListenerList listeners = new ListenerList();
public StyledLink(Composite parent, int style) {
super(parent, style | SWT.DOUBLE_BUFFERED);
this.styled = (style & SWT.SIMPLE) == 0;
super.setFont(FontUtils.getFont(JFaceResources.DEFAULT_FONT));
super.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
drawParagraphs(e.gc);
}
});
addMouseListener(new MouseListener() {
boolean pressed = false;
public void mouseUp(MouseEvent e) {
Rectangle r = getBounds();
if (pressed && e.x >= 0 && e.x < r.width && e.y >= 0
&& e.y < r.height) {
fireLinkActivated(createLinkEvent(e));
}
pressed = false;
}
public void mouseDown(MouseEvent e) {
pressed = true;
}
public void mouseDoubleClick(MouseEvent e) {
pressed = false;
}
});
addMouseTrackListener(new MouseTrackListener() {
public void mouseHover(MouseEvent e) {
setHovered(true);
}
public void mouseExit(MouseEvent e) {
setHovered(false);
}
public void mouseEnter(MouseEvent e) {
setHovered(true);
}
});
}
private void fireLinkActivated(HyperlinkEvent event) {
for (Object listener : listeners.getListeners()) {
((IHyperlinkListener) listener).linkActivated(event);
}
}
private HyperlinkEvent createLinkEvent(MouseEvent e) {
HyperlinkEvent event = new HyperlinkEvent(this, null, getText(),
e.stateMask);
event.display = e.display;
event.time = e.time;
return event;
}
public void addHyperlinkListener(IHyperlinkListener listener) {
listeners.add(listener);
}
public void removeHyperlinkListener(IHyperlinkListener listener) {
listeners.remove(listener);
}
@Override
public void setFont(Font font) {
checkWidget();
releaseLines();
super.setFont(font);
}
@Override
public void setBackground(Color color) {
checkWidget();
releaseLines();
super.setBackground(color);
}
@Override
public void setForeground(Color color) {
checkWidget();
releaseLines();
super.setForeground(color);
}
@Override
public void setEnabled(boolean enabled) {
checkWidget();
releaseLines();
super.setEnabled(enabled);
}
private void releaseLines() {
StyledParagraph[] ls = paragraphs;
paragraphs = null;
if (ls != null) {
for (int i = 0; i < ls.length; i++) {
ls[i].text.dispose();
}
}
}
private void ensureParagraphs(int wHint) {
if (paragraphs != null)
return;
if (styled) {
paragraphs = parseStyledText(text, wHint);
} else {
paragraphs = parseSimpleText(text, wHint);
}
cachedSize = new Point(0, 0);
if (paragraphs.length == 0) {
if (wHint >= 0)
cachedSize.x = wHint;
} else {
for (StyledParagraph p : paragraphs) {
Rectangle pBounds = p.text.getBounds();
cachedSize.x = Math.max(cachedSize.x, pBounds.width);
cachedSize.y = p.top + pBounds.height;
}
}
}
private StyledParagraph[] parseStyledText(String text, int wHint) {
List<StyledParagraph> paragraphs = new ArrayList<StyledParagraph>(
text.length() / 2);
int contentStart = text.indexOf("<form>"); //$NON-NLS-1$
if (contentStart >= 0) {
contentStart += 6;
int contentEnd = text.indexOf("</form>", contentStart); //$NON-NLS-1$
if (contentEnd < 0)
contentEnd = text.length();
String content = text.substring(contentStart, contentEnd);
int pStart = 0, pEnd = 0;
int top = 0;
String pText;
StringBuilder pBuffer;
List<StyleRange> styles;
boolean bold = false, italic = false;
while (pStart < content.length()) {
if ("<p>".equals(content.substring(pStart, pStart + 3))) { //$NON-NLS-1$
pStart += 3;
}
pEnd = content.indexOf("</p>", pStart); //$NON-NLS-1$
if (pEnd >= 0) {
pText = content.substring(pStart, pEnd);
pEnd += 4;
} else {
pEnd = content.indexOf("<p>", pStart); //$NON-NLS-1$
if (pEnd >= 0) {
pText = content.substring(pStart, pEnd);
pEnd += 3;
} else {
pEnd = content.length();
pText = content.substring(pStart, pEnd);
}
}
pStart = pEnd;
pBuffer = new StringBuilder(pText.length());
styles = new ArrayList<StyleRange>(pText.length());
int start = 0;
int end = 0;
while (start < pText.length()) {
if ("<b>".equals(pText.substring(start, start + 3))) { //$NON-NLS-1$
bold = true;
start += 3;
} else if ("<i>".equals(pText.substring(start, start + 3))) { //$NON-NLS-1$
italic = true;
start += 3;
} else if ("</b>".equals(pText.substring(start, start + 4))) { //$NON-NLS-1$
bold = false;
start += 4;
} else if ("</i>".equals(pText.substring(start, start + 4))) { //$NON-NLS-1$
italic = false;
start += 4;
} else if (pText.charAt(start) == '<') {
end = pText.indexOf('>', start + 1);
if (end < 0) {
end = pText.length();
} else {
end += 1;
}
start = end;
} else {
end = pText.indexOf('<', start);
if (end < 0)
end = pText.length();
int styleStart = pBuffer.length();
pBuffer.append(unescape(pText.substring(start, end)));
int styleEnd = pBuffer.length();
styles.add(newStyleRange(bold, italic, styleStart,
styleEnd));
start = end;
}
}
if (pBuffer.length() == 0) {
top += PARAGRAPH_SPACING;
} else {
StyledParagraph paragraph = new StyledParagraph();
paragraph.text = new TextLayout(getDisplay());
paragraph.text.setFont(getFont());
paragraph.text.setAlignment(SWT.LEFT);
paragraph.text.setSpacing(LINE_SPACING);
paragraph.text.setText(pBuffer.toString());
for (StyleRange style : styles) {
paragraph.text.setStyle(style, style.start, style.start
+ style.length);
}
paragraph.text.setWidth(wHint);
paragraph.top = top;
paragraphs.add(paragraph);
top += paragraph.text.getBounds().height
+ PARAGRAPH_SPACING;
}
}
}
return paragraphs.toArray(new StyledParagraph[paragraphs.size()]);
}
private StyledParagraph[] parseSimpleText(String text, int wHint) {
List<StyledParagraph> paragraphs = new ArrayList<StyledLink.StyledParagraph>(
text.length() / 2);
int start = 0;
int end = 0;
int top = 0;
StyledParagraph paragraph;
while (start < text.length()) {
if (text.charAt(start) == '\r') {
if (start + 1 < text.length() && text.charAt(start + 1) == '\n') {
start += 2;
} else {
start += 1;
}
top += PARAGRAPH_SPACING;
} else if (text.charAt(start) == '\n') {
start += 1;
top += PARAGRAPH_SPACING;
} else {
end = Math.min(text.indexOf('\n', start),
text.indexOf('\r', start));
if (end < 0)
end = text.length();
if (end > start) {
paragraph = new StyledParagraph();
paragraph.text = new TextLayout(getDisplay());
paragraph.text.setFont(getFont());
paragraph.text.setAlignment(SWT.LEFT);
paragraph.text.setSpacing(LINE_SPACING);
paragraph.text.setText(text.substring(start, end));
paragraph.text.setStyle(newStyle(false, false), 0, end
- start);
paragraph.text.setWidth(wHint);
paragraph.top = top;
paragraphs.add(paragraph);
top += paragraph.text.getBounds().height;
}
start = end;
}
}
return paragraphs.toArray(new StyledParagraph[paragraphs.size()]);
}
private String unescape(String text) {
return EncodingUtils.unescape(text);
}
private StyleRange newStyleRange(boolean bold, boolean italic, int start,
int end) {
StyleRange style = new StyleRange();
applyStyle(style, bold, italic);
style.start = start;
style.length = end - start;
return style;
}
private TextStyle newStyle(boolean bold, boolean italic) {
TextStyle style = new TextStyle();
applyStyle(style, bold, italic);
return style;
}
private void applyStyle(TextStyle style, boolean bold, boolean italic) {
style.background = getBackground();
style.foreground = getForeground();
style.underline = isEnabled() && hovered;
style.underlineColor = getForeground();
style.underlineStyle = SWT.UNDERLINE_LINK;
style.strikeout = false;
style.strikeoutColor = null;
if (bold && italic) {
style.font = getBoldItalicFont();
} else if (bold) {
style.font = getBoldFont();
} else if (italic) {
style.font = getItalicFont();
} else {
style.font = getFont();
}
}
private Font getBoldFont() {
if (boldFont == null) {
boldFont = new Font(getDisplay(), FontUtils.bold(getFont()
.getFontData(), true));
}
return boldFont;
}
private Font getItalicFont() {
if (italicFont == null) {
italicFont = new Font(getDisplay(), FontUtils.italic(getFont()
.getFontData(), true));
}
return italicFont;
}
private Font getBoldItalicFont() {
if (boldItalicFont == null) {
boldItalicFont = new Font(getDisplay(), FontUtils.bold(
FontUtils.italic(getFont().getFontData(), true), true));
}
return boldItalicFont;
}
private void drawParagraphs(GC gc) {
ensureParagraphs(cachedWidth);
gc.setAntialias(SWT.ON);
gc.setTextAntialias(SWT.ON);
for (StyledParagraph p : paragraphs) {
p.text.draw(gc, 0, p.top);
}
}
private void setHovered(boolean hovered) {
hovered = hovered && isEnabled();
if (hovered == this.hovered)
return;
this.hovered = hovered;
releaseLines();
redraw();
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
checkWidget();
if (changed || cachedWidth != wHint || paragraphs == null
|| cachedSize == null) {
releaseLines();
ensureParagraphs(wHint);
cachedWidth = wHint;
}
return new Point(cachedSize.x, cachedSize.y);
}
@Override
public void dispose() {
releaseLines();
if (boldFont != null) {
boldFont.dispose();
boldFont = null;
}
if (italicFont != null) {
italicFont.dispose();
italicFont = null;
}
if (boldItalicFont != null) {
boldItalicFont.dispose();
boldItalicFont = null;
}
super.dispose();
}
public void setText(String text) {
checkWidget();
if (text == null)
SWT.error(SWT.ERROR_NULL_ARGUMENT);
if (text.equals(this.text))
return;
this.text = text;
releaseLines();
redraw();
}
public String getText() {
return text;
}
}