blob: 5085fa4c4348af9e1f2dce6c309bd94ee10337bb [file] [log] [blame]
package org.eclipse.wst.sse.ui.internal.hyperlink;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
/**
* The is almost an exact copy of DefaultHyperlinkPresenter. However this
* hyperlink presenter works with the StructuredTextEditor's Highlighter
* instead of TextPresentation.
*
* The main difference is <code>text.redrawRange(offset, length, true);</code>
* is called instead of passing false for clearBackground. Also all mention of
* TextPresentation was removed since it does not really apply.
*
* @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter
*/
public class HighlighterHyperlinkPresenter implements IHyperlinkPresenter, PaintListener, ITextInputListener, IDocumentListener, IPropertyChangeListener {
/**
* A named preference that holds the color used for hyperlinks.
* <p>
* Value is of type <code>String</code>. A RGB color value encoded as a
* string using class <code>PreferenceConverter</code>
* </p>
*
* @see org.eclipse.jface.resource.StringConverter
* @see org.eclipse.jface.preference.PreferenceConverter
*/
public final static String HYPERLINK_COLOR = "hyperlinkColor"; //$NON-NLS-1$
/** The text viewer. */
private ITextViewer fTextViewer;
/** The hand cursor. */
private Cursor fCursor;
/** The link color. */
private Color fColor;
/** Tells whether to dispose the color on uninstall. */
private boolean fDisposeColor;
/** The currently active region. */
private IRegion fActiveRegion;
/** The currently active style range as position. */
private Position fRememberedPosition;
/** The optional preference store */
private IPreferenceStore fPreferenceStore;
/**
* Creates a new default hyperlink presenter which uses
* {@link #HYPERLINK_COLOR}to read the color from the given preference
* store.
*
* @param store
* the preference store
*/
public HighlighterHyperlinkPresenter(IPreferenceStore store) {
fPreferenceStore = store;
fDisposeColor = true;
}
/**
* Creates a new default hyperlink presenter.
*
* @param color
* the hyperlink color, to be disposed by the caller
*/
public HighlighterHyperlinkPresenter(Color color) {
fDisposeColor = false;
fColor = color;
}
public boolean canShowMultipleHyperlinks() {
return false;
}
public void showHyperlinks(IHyperlink[] hyperlinks) {
Assert.isLegal(hyperlinks != null && hyperlinks.length == 1);
highlightRegion(hyperlinks[0].getHyperlinkRegion());
activateCursor();
}
public void hideHyperlinks() {
repairRepresentation();
fRememberedPosition = null;
}
public void install(ITextViewer textViewer) {
Assert.isNotNull(textViewer);
fTextViewer = textViewer;
fTextViewer.addTextInputListener(this);
StyledText text = fTextViewer.getTextWidget();
if (text != null && !text.isDisposed()) {
text.addPaintListener(this);
if (fPreferenceStore != null)
fColor = createColor(fPreferenceStore, HYPERLINK_COLOR, text.getDisplay());
}
if (fPreferenceStore != null)
fPreferenceStore.addPropertyChangeListener(this);
}
public void uninstall() {
fTextViewer.removeTextInputListener(this);
if (fColor != null) {
if (fDisposeColor)
fColor.dispose();
fColor = null;
}
if (fCursor != null) {
fCursor.dispose();
fCursor = null;
}
StyledText text = fTextViewer.getTextWidget();
if (text != null && !text.isDisposed())
text.removePaintListener(this);
fTextViewer = null;
if (fPreferenceStore != null)
fPreferenceStore.removePropertyChangeListener(this);
}
public void setColor(Color color) {
Assert.isNotNull(fTextViewer);
fColor = color;
}
private void highlightRegion(IRegion region) {
if (region.equals(fActiveRegion))
return;
repairRepresentation();
StyledText text = fTextViewer.getTextWidget();
if (text == null || text.isDisposed())
return;
// Underline
int offset = 0;
int length = 0;
if (fTextViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
IRegion widgetRange = extension.modelRange2WidgetRange(region);
if (widgetRange == null)
return;
offset = widgetRange.getOffset();
length = widgetRange.getLength();
}
else {
offset = region.getOffset() - fTextViewer.getVisibleRegion().getOffset();
length = region.getLength();
}
// needs to clean background due to StructuredTextEditor's highlighter
text.redrawRange(offset, length, true);
// Invalidate region ==> apply text presentation
fActiveRegion = region;
if (fTextViewer instanceof ITextViewerExtension2)
((ITextViewerExtension2) fTextViewer).invalidateTextPresentation(region.getOffset(), region.getLength());
else
fTextViewer.invalidateTextPresentation();
}
private void activateCursor() {
StyledText text = fTextViewer.getTextWidget();
if (text == null || text.isDisposed())
return;
Display display = text.getDisplay();
if (fCursor == null)
fCursor = new Cursor(display, SWT.CURSOR_HAND);
text.setCursor(fCursor);
}
private void resetCursor() {
StyledText text = fTextViewer.getTextWidget();
if (text != null && !text.isDisposed())
text.setCursor(null);
if (fCursor != null) {
fCursor.dispose();
fCursor = null;
}
}
private void repairRepresentation() {
if (fActiveRegion == null)
return;
int offset = fActiveRegion.getOffset();
int length = fActiveRegion.getLength();
fActiveRegion = null;
resetCursor();
// Invalidate ==> remove applied text presentation
if (fTextViewer instanceof ITextViewerExtension2)
((ITextViewerExtension2) fTextViewer).invalidateTextPresentation(offset, length);
else
fTextViewer.invalidateTextPresentation();
// Remove underline
if (fTextViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
offset = extension.modelOffset2WidgetOffset(offset);
}
else {
offset -= fTextViewer.getVisibleRegion().getOffset();
}
try {
StyledText text = fTextViewer.getTextWidget();
// needs to clean background due to StructuredTextEditor's
// highlighter
text.redrawRange(offset, length, true);
}
catch (IllegalArgumentException x) {
// ignore - do not log
}
}
/*
* @see PaintListener#paintControl(PaintEvent)
*/
public void paintControl(PaintEvent event) {
if (fActiveRegion == null)
return;
StyledText text = fTextViewer.getTextWidget();
if (text == null || text.isDisposed())
return;
int offset = 0;
int length = 0;
if (fTextViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
IRegion widgetRange = extension.modelRange2WidgetRange(fActiveRegion);
if (widgetRange == null)
return;
offset = widgetRange.getOffset();
length = widgetRange.getLength();
}
else {
IRegion region = fTextViewer.getVisibleRegion();
if (!includes(region, fActiveRegion))
return;
offset = fActiveRegion.getOffset() - region.getOffset();
length = fActiveRegion.getLength();
}
// support for BIDI
Point minLocation = getMinimumLocation(text, offset, length);
Point maxLocation = getMaximumLocation(text, offset, length);
int x1 = minLocation.x;
int x2 = maxLocation.x - 1;
int y = minLocation.y + text.getLineHeight() - 1;
GC gc = event.gc;
if (fColor != null && !fColor.isDisposed())
gc.setForeground(fColor);
else if (fColor == null && !(offset < 0 && offset >= text.getCharCount())) {
StyleRange style = text.getStyleRangeAtOffset(offset);
if (style != null)
gc.setForeground(style.foreground);
}
gc.drawLine(x1, y, x2, y);
}
private Point getMinimumLocation(StyledText text, int offset, int length) {
int max = text.getCharCount();
Rectangle bounds = text.getBounds();
Point minLocation = new Point(bounds.width, bounds.height);
for (int i = 0; i <= length; i++) {
int k = offset + i;
if (k < 0 || k > max)
break;
Point location = text.getLocationAtOffset(k);
if (location.x < minLocation.x)
minLocation.x = location.x;
if (location.y < minLocation.y)
minLocation.y = location.y;
}
return minLocation;
}
private Point getMaximumLocation(StyledText text, int offset, int length) {
Point maxLocation = new Point(0, 0);
for (int i = 0; i <= length; i++) {
int k = offset + i;
if (k < 0 || k > text.getCharCount())
break;
Point location = text.getLocationAtOffset(k);
if (location.x > maxLocation.x)
maxLocation.x = location.x;
if (location.y > maxLocation.y)
maxLocation.y = location.y;
}
return maxLocation;
}
private boolean includes(IRegion region, IRegion position) {
return position.getOffset() >= region.getOffset() && position.getOffset() + position.getLength() <= region.getOffset() + region.getLength();
}
/*
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
if (fActiveRegion != null) {
fRememberedPosition = new Position(fActiveRegion.getOffset(), fActiveRegion.getLength());
try {
event.getDocument().addPosition(fRememberedPosition);
}
catch (BadLocationException x) {
fRememberedPosition = null;
}
}
}
/*
* @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
if (fRememberedPosition != null) {
if (!fRememberedPosition.isDeleted()) {
event.getDocument().removePosition(fRememberedPosition);
fActiveRegion = new Region(fRememberedPosition.getOffset(), fRememberedPosition.getLength());
fRememberedPosition = null;
StyledText widget = fTextViewer.getTextWidget();
if (widget != null && !widget.isDisposed()) {
widget.getDisplay().asyncExec(new Runnable() {
public void run() {
hideHyperlinks();
}
});
}
}
else {
fActiveRegion = null;
fRememberedPosition = null;
hideHyperlinks();
}
}
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument,
* org.eclipse.jface.text.IDocument)
*/
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
if (oldInput == null)
return;
hideHyperlinks();
oldInput.removeDocumentListener(this);
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument,
* org.eclipse.jface.text.IDocument)
*/
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
if (newInput == null)
return;
newInput.addDocumentListener(this);
}
/**
* Creates a color from the information stored in the given preference
* store.
*
* @param store
* the preference store
* @param key
* the key
* @param display
* the display
* @return the color or <code>null</code> if there is no such
* information available
*/
private Color createColor(IPreferenceStore store, String key, Display display) {
RGB rgb = null;
if (store.contains(key)) {
if (store.isDefault(key))
rgb = PreferenceConverter.getDefaultColor(store, key);
else
rgb = PreferenceConverter.getColor(store, key);
if (rgb != null)
return new Color(display, rgb);
}
return null;
}
/*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent event) {
if (!HYPERLINK_COLOR.equals(event.getProperty()))
return;
if (fDisposeColor && fColor != null && !fColor.isDisposed())
fColor.dispose();
fColor = null;
StyledText textWidget = fTextViewer.getTextWidget();
if (textWidget != null && !textWidget.isDisposed())
fColor = createColor(fPreferenceStore, HYPERLINK_COLOR, textWidget.getDisplay());
}
}