Initial cut of generalized hyperlink feature
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
index 255a6f5..b229716 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
@@ -89,6 +89,8 @@
* <li>{@link org.eclipse.jface.text.ITextViewerExtension5}since version 3.0
* extending the visible region concept with explicit handling and conversation
* of widget and model coordinates.</li>
+ * <li>{@link org.eclipse.jface.text.ITextViewerExtension6}since version 3.1
+ * extending the text viewer with the ability to detect hyperlinks.</li>
* </ul>
*
* Clients may implement this interface and its extension interfaces or use the
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java
new file mode 100644
index 0000000..bed2905
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text;
+
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+
+/**
+ * Extension interface for {@link org.eclipse.jface.text.ITextViewer}.
+ * Introduces the concept of text hyperlinks.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @see org.eclipse.jface.text.hyperlink.IHyperlink
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkDetector
+ * @since 3.1
+ */
+public interface ITextViewerExtension6 {
+
+ /**
+ * Sets this viewer's hyperlinkDetectors for the given content type.
+ *
+ * @param hyperlinkDetectors the new list of hyperlink detectors, must not be empty
+ * @throws IllegalArgumentException if hyperlinkDetectors is <code>null</code> or empty
+ */
+ void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors) throws IllegalArgumentException;
+
+ /**
+ * Sets whether hyperlinking is enabled or not.
+ *
+ * @param state <code>true</code> if enabled
+ */
+ void setHyperlinksEnabled(boolean state);
+
+ /**
+ * Sets the hyperlink state mask.
+ *
+ * @param hyperlinkStateMask the hyperlink state mask
+ */
+ public void setHyperlinkStateMask(int hyperlinkStateMask);
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java
new file mode 100644
index 0000000..b268acb
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java
@@ -0,0 +1,490 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+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.widgets.Display;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+
+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.ITextPresentationListener;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension2;
+import org.eclipse.jface.text.ITextViewerExtension4;
+import org.eclipse.jface.text.ITextViewerExtension5;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextPresentation;
+
+
+/**
+ * The default hyperlink controller underlines the
+ * link and colors the line and the text with
+ * the given color.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class DefaultHyperlinkController implements IHyperlinkController, ITextPresentationListener, 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
+ * @since 3.1
+ */
+ 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 controller which uses
+ * {@link #HYPERLINK_COLOR} to read the color from the given preference store.
+ *
+ * @param store the preference store
+ */
+ public DefaultHyperlinkController(IPreferenceStore store) {
+ fPreferenceStore= store;
+ fDisposeColor= true;
+ }
+
+ /**
+ * Creates a new default hyperlink controller.
+ *
+ * @param color the hyperlink color, to be disposed by the caller
+ */
+ public DefaultHyperlinkController(Color color) {
+ fDisposeColor= false;
+ fColor= color;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#canShowMultipleHyperlinks()
+ * @since 3.1
+ */
+ public boolean canShowMultipleHyperlinks() {
+ return false;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#activate(org.eclipse.jdt.internal.ui.javaeditor.IHyperlink[])
+ * @since 3.1
+ */
+ public void activate(IHyperlink[] hyperlinks) {
+ Assert.isLegal(hyperlinks != null && hyperlinks.length == 1);
+ highlightRegion(hyperlinks[0].getRegion());
+ activateCursor();
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#deactivate()
+ * @since 3.1
+ */
+ public void deactivate() {
+ repairRepresentation();
+ fRememberedPosition= null;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#install(org.eclipse.jface.text.ITextViewer)
+ * @since 3.1
+ */
+ public void install(ITextViewer textViewer) {
+ Assert.isNotNull(textViewer);
+ fTextViewer= textViewer;
+ fTextViewer.addTextInputListener(this);
+ if (fTextViewer instanceof ITextViewerExtension4)
+ ((ITextViewerExtension4)fTextViewer).addTextPresentationListener(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);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#uninstall()
+ * @since 3.1
+ */
+ 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);
+
+ if (fTextViewer instanceof ITextViewerExtension4)
+ ((ITextViewerExtension4)fTextViewer).removeTextPresentationListener(this);
+ fTextViewer= null;
+
+ if (fPreferenceStore != null)
+ fPreferenceStore.removePropertyChangeListener(this);
+ }
+
+ public void setColor(Color color) {
+ Assert.isNotNull(fTextViewer);
+ fColor= color;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
+ * @since 3.1
+ */
+ public void applyTextPresentation(TextPresentation textPresentation) {
+ if (fActiveRegion == null)
+ return;
+ IRegion region= textPresentation.getExtent();
+ if (fActiveRegion.getOffset() + fActiveRegion.getLength() >= region.getOffset() && region.getOffset() + region.getLength() > fActiveRegion.getOffset())
+ textPresentation.mergeStyleRange(new StyleRange(fActiveRegion.getOffset(), fActiveRegion.getLength(), fColor, null));
+ }
+
+ 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();
+ }
+ text.redrawRange(offset, length, false);
+
+ // 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();
+
+ text.redrawRange(offset, length, false);
+ } 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= minLocation.x + maxLocation.x - minLocation.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) {
+ 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) {
+ Point minLocation= new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+ for (int i= 0; i <= length; i++) {
+ Point location= text.getLocationAtOffset(offset + i);
+
+ 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(Integer.MIN_VALUE, Integer.MIN_VALUE);
+
+ for (int i= 0; i <= length; i++) {
+ Point location= text.getLocationAtOffset(offset + i);
+
+ 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() {
+ deactivate();
+ }
+ });
+ }
+
+ } else {
+ fActiveRegion= null;
+ fRememberedPosition= null;
+ deactivate();
+ }
+ }
+ }
+
+ /*
+ * @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;
+ deactivate();
+ 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)
+ * @since 3.1
+ */
+ 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());
+ }
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java
new file mode 100644
index 0000000..a8c8878
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java
@@ -0,0 +1,456 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension5;
+import org.eclipse.jface.text.Region;
+
+/**
+ * Default implementation of a hyperlink manager.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class DefaultHyperlinkManager implements IHyperlinkManager, KeyListener, MouseListener, MouseMoveListener, FocusListener {
+
+ /**
+ * Detection strategy.
+ */
+ private static final class DETECTION_STRATEGY {
+
+ String fName;
+
+ private DETECTION_STRATEGY(String name) {
+ fName= name;
+ }
+
+ /*
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return fName;
+ }
+ }
+
+
+ /**
+ * A named preference that controls if hyperlinks are turned on or off.
+ * <p>
+ * Value is of type <code>Boolean</code>.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final String HYPERLINKS_ENABLED= "hyperlinksEnabled"; //$NON-NLS-1$
+
+ /**
+ * A named preference that controls the key modifier for hyperlinks.
+ * <p>
+ * Value is of type <code>String</code>.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final String HYPERLINK_KEY_MODIFIER= "hyperlinkKeyModifier"; //$NON-NLS-1$
+
+ /**
+ * A named preference that controls the key modifier mask for hyperlinks.
+ * The value is only used if the value of <code>EHYPERLINK_KEY_MODIFIER</code>
+ * cannot be resolved to valid SWT modifier bits.
+ * <p>
+ * Value is of type <code>String</code>.
+ * </p>
+ *
+ * @see #HYPERLINK_KEY_MODIFIER
+ * @since 3.1
+ */
+ public static final String HYPERLINK_KEY_MODIFIER_MASK= "hyperlinkKeyModifierMask"; //$NON-NLS-1$
+
+ /**
+ * The first detected hyperlink is passed to the
+ * hyperlink controller and no further detector
+ * is consulted.
+ */
+ public static final DETECTION_STRATEGY FIRST= new DETECTION_STRATEGY("first"); //$NON-NLS-1$
+
+ /**
+ * All detected hyperlinks from all detectors are collected
+ * and passed to the hyperlink controller.
+ * <p>
+ * This strategy is only allowed if {@link IHyperlinkController#canShowMultipleHyperlinks()}
+ * returns <code>true</code>.
+ * </p>
+ */
+ public static final DETECTION_STRATEGY ALL= new DETECTION_STRATEGY("all"); //$NON-NLS-1$
+
+ /**
+ * All detected hyperlinks from all detectors are collected
+ * and all those with the longest region are passed to the
+ * hyperlink controller.
+ * <p>
+ * This strategy is only allowed if {@link IHyperlinkController#canShowMultipleHyperlinks()}
+ * returns <code>true</code>.
+ * </p>
+ */
+ public static final DETECTION_STRATEGY LONGEST_REGION_ALL= new DETECTION_STRATEGY("all with same longest region"); //$NON-NLS-1$
+
+ /**
+ * All detected hyperlinks from all detectors are collected
+ * and form all those with the longest region only the first
+ * one is passed to the hyperlink controller.
+ */
+ public static final DETECTION_STRATEGY LONGEST_REGION_FIRST= new DETECTION_STRATEGY("first with longest region"); //$NON-NLS-1$
+
+
+ /** The text viewer on which this hyperlink manager works. */
+ private ITextViewer fTextViewer;
+ /** The session is active. */
+ private boolean fActive;
+ /** The key modifier mask. */
+ private int fKeyModifierMask;
+ /** The active hyperlinks. */
+ private IHyperlink[] fActiveHyperlinks;
+ /** The hyperlink detectors. */
+ private IHyperlinkDetector[] fHyperlinkDetectors;
+ /** The hyperlink controller. */
+ private IHyperlinkController fHyperlinkController;
+ /** The detection strategy. */
+ private final DETECTION_STRATEGY fDetectionStrategy;
+
+
+ /**
+ * Creates a new hyperlink manager.
+ *
+ * @param detectionStrategy the detection strategy one of {{@link #ALL}, {@link #FIRST}, {@link #LONGEST_REGION_ALL}, {@link #LONGEST_REGION_FIRST}}
+ */
+ public DefaultHyperlinkManager(DETECTION_STRATEGY detectionStrategy) {
+ Assert.isNotNull(detectionStrategy);
+ fDetectionStrategy= detectionStrategy;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#install(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.hyperlink.IHyperlinkController, org.eclipse.jface.text.hyperlink.IHyperlinkDetector[], int)
+ * @since 3.1
+ */
+ public void install(ITextViewer textViewer, IHyperlinkController hyperlinkController, IHyperlinkDetector[] hyperlinkDetectors, int keyModifierMask) {
+ Assert.isNotNull(textViewer);
+ Assert.isNotNull(hyperlinkController);
+ fTextViewer= textViewer;
+ fHyperlinkController= hyperlinkController;
+ Assert.isLegal(!fHyperlinkController.canShowMultipleHyperlinks() && (fDetectionStrategy == FIRST || fDetectionStrategy == LONGEST_REGION_FIRST));
+ setHyperlinkDetectors(hyperlinkDetectors);
+ setKeyModifierMask(keyModifierMask);
+
+ StyledText text= fTextViewer.getTextWidget();
+ if (text == null || text.isDisposed())
+ return;
+
+ text.addKeyListener(this);
+ text.addMouseListener(this);
+ text.addMouseMoveListener(this);
+ text.addFocusListener(this);
+
+ fHyperlinkController.install(fTextViewer);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#setHyperlinkDetectors(org.eclipse.jface.text.hyperlink.IHyperlinkDetector[])
+ * @since 3.1
+ */
+ public void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors) {
+ Assert.isTrue(hyperlinkDetectors != null && hyperlinkDetectors.length > 0);
+ if (fHyperlinkDetectors == null)
+ fHyperlinkDetectors= hyperlinkDetectors;
+ else {
+ synchronized (fHyperlinkDetectors) {
+ fHyperlinkDetectors= hyperlinkDetectors;
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#setKeyModifierMask(int)
+ * @since 3.1
+ */
+ public void setKeyModifierMask(int keyModifierMask) {
+ fKeyModifierMask= keyModifierMask;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#uninstall()
+ * @since 3.1
+ */
+ public void uninstall() {
+ deactivate();
+
+ StyledText text= fTextViewer.getTextWidget();
+ if (text != null && !text.isDisposed()) {
+ text.removeKeyListener(this);
+ text.removeMouseListener(this);
+ text.removeMouseMoveListener(this);
+ text.removeFocusListener(this);
+ }
+ fHyperlinkController.uninstall();
+
+ fHyperlinkController= null;
+ fTextViewer= null;
+ fHyperlinkDetectors= null;
+ }
+
+ protected void deactivate() {
+ if (!fActive)
+ return;
+
+ fHyperlinkController.deactivate();
+ fActive= false;
+ }
+
+ protected IHyperlink[] findHyperlinks() {
+ int offset= getCurrentTextOffset();
+ if (offset == -1)
+ return null;
+
+ IRegion region= new Region(offset, 0);
+ List allHyperlinks= new ArrayList(fHyperlinkDetectors.length * 2);
+ synchronized (fHyperlinkDetectors) {
+ for (int i= 0, length= fHyperlinkDetectors.length; i < length; i++) {
+ IHyperlink[] hyperlinks= fHyperlinkDetectors[i].detectHyperlinks(fTextViewer, region);
+ if (hyperlinks == null)
+ continue;
+
+ Assert.isLegal(hyperlinks.length > 0);
+
+ if (fDetectionStrategy == FIRST) {
+ if (hyperlinks.length == 1)
+ return hyperlinks;
+ return new IHyperlink[] {hyperlinks[0]};
+ }
+ allHyperlinks.addAll(Arrays.asList(hyperlinks));
+ }
+ }
+
+ if (allHyperlinks.isEmpty())
+ return null;
+
+ if (fDetectionStrategy != ALL) {
+ int maxLength= computeLongestHyperlinkLength(allHyperlinks);
+ Iterator iter= new ArrayList(allHyperlinks).iterator();
+ while (iter.hasNext()) {
+ IHyperlink hyperlink= (IHyperlink)iter.next();
+ if (hyperlink.getRegion().getLength() < maxLength)
+ allHyperlinks.remove(hyperlink);
+ }
+ }
+
+ if (fDetectionStrategy == LONGEST_REGION_FIRST)
+ return new IHyperlink[] {(IHyperlink)allHyperlinks.get(0)};
+
+ return (IHyperlink[])allHyperlinks.toArray(new IHyperlink[allHyperlinks.size()]);
+
+ }
+
+ protected int computeLongestHyperlinkLength(List hyperlinks) {
+ Assert.isLegal(hyperlinks != null && !hyperlinks.isEmpty());
+ Iterator iter= hyperlinks.iterator();
+ int length= Integer.MIN_VALUE;
+ while (iter.hasNext()) {
+ IRegion region= ((IHyperlink)iter.next()).getRegion();
+ if (region.getLength() < length)
+ continue;
+ length= region.getLength();
+ }
+ return length;
+ }
+
+ protected int getCurrentTextOffset() {
+
+ try {
+ StyledText text= fTextViewer.getTextWidget();
+ if (text == null || text.isDisposed())
+ return -1;
+
+ Display display= text.getDisplay();
+ Point absolutePosition= display.getCursorLocation();
+ Point relativePosition= text.toControl(absolutePosition);
+
+ int widgetOffset= text.getOffsetAtLocation(relativePosition);
+ if (fTextViewer instanceof ITextViewerExtension5) {
+ ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer;
+ return extension.widgetOffset2ModelOffset(widgetOffset);
+ }
+
+ return widgetOffset + fTextViewer.getVisibleRegion().getOffset();
+
+ } catch (IllegalArgumentException e) {
+ return -1;
+ }
+ }
+
+ /*
+ * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
+ */
+ public void keyPressed(KeyEvent event) {
+
+ if (fActive) {
+ deactivate();
+ return;
+ }
+
+ if (event.keyCode != fKeyModifierMask) {
+ deactivate();
+ return;
+ }
+
+ fActive= true;
+
+// removed for #25871
+//
+// ITextViewer viewer= getSourceViewer();
+// if (viewer == null)
+// return;
+//
+// IRegion region= getCurrentTextRegion(viewer);
+// if (region == null)
+// return;
+//
+// highlightRegion(viewer, region);
+// activateCursor(viewer);
+ }
+
+ /*
+ * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent)
+ */
+ public void keyReleased(KeyEvent event) {
+
+ if (!fActive)
+ return;
+
+ deactivate();
+ }
+
+ /*
+ * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
+ */
+ public void mouseDoubleClick(MouseEvent e) {
+
+ }
+
+ /*
+ * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
+ */
+ public void mouseDown(MouseEvent event) {
+
+ if (!fActive)
+ return;
+
+ if (event.stateMask != fKeyModifierMask) {
+ deactivate();
+ return;
+ }
+
+ if (event.button != 1) {
+ deactivate();
+ return;
+ }
+ }
+
+ /*
+ * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
+ */
+ public void mouseUp(MouseEvent e) {
+
+ if (!fActive) {
+ fActiveHyperlinks= null;
+ return;
+ }
+
+ if (e.button != 1)
+ fActiveHyperlinks= null;
+
+ deactivate();
+
+ if (fActiveHyperlinks != null)
+ fActiveHyperlinks[0].open();
+ }
+
+ /*
+ * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
+ */
+ public void mouseMove(MouseEvent event) {
+
+ if (event.widget instanceof Control && !((Control) event.widget).isFocusControl()) {
+ deactivate();
+ return;
+ }
+
+ if (!fActive) {
+ if (event.stateMask != fKeyModifierMask)
+ return;
+ // modifier was already pressed
+ fActive= true;
+ }
+
+ StyledText text= fTextViewer.getTextWidget();
+ if (text == null || text.isDisposed()) {
+ deactivate();
+ return;
+ }
+
+ if ((event.stateMask & SWT.BUTTON1) != 0 && text.getSelectionCount() != 0) {
+ deactivate();
+ return;
+ }
+
+ fActiveHyperlinks= findHyperlinks();
+ if (fActiveHyperlinks == null || fActiveHyperlinks.length == 0) {
+ fHyperlinkController.deactivate();
+ return;
+ }
+
+ fHyperlinkController.activate(fActiveHyperlinks);
+ }
+
+ /*
+ * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent)
+ */
+ public void focusGained(FocusEvent e) {}
+
+ /*
+ * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
+ */
+ public void focusLost(FocusEvent event) {
+ deactivate();
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java
new file mode 100644
index 0000000..e23322c
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.IRegion;
+
+
+/**
+ * Represents a hyperlink.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IHyperlink {
+ IRegion getRegion();
+
+ /**
+ * Optional label for this type of hyperlink.
+ * <p>
+ * This type label can be used by {@link IHyperlinkController}s
+ * which show several hyperlinks at once.
+ * </p>
+ *
+ * @return the type label or <code>null</code> if none.
+ */
+ String getTypeLabel();
+
+ /**
+ * Optional text for this hyperlink.
+ * <p>
+ * This can be used in situations where there are
+ * several targets for the same hyperlink location.
+ * </p>
+ *
+ * @return the text or <code>null</code> if none.
+ */
+ String getText();
+
+ /**
+ * Opens the given hyperlink.
+ */
+ void open();
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java
new file mode 100644
index 0000000..a773dd1
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.ITextViewer;
+
+
+/**
+ * A hyperlink controller displays the given hyperlinks on
+ * the installed text viewer and allows to select one
+ * on of the hyperlinks.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IHyperlinkController {
+
+ /**
+ * Tells whether this controller is able to handle
+ * more than one hyperlink.
+ *
+ * @return <code>true</code> if this controller can handle more than one hyperlink
+ */
+ boolean canShowMultipleHyperlinks();
+
+ /**
+ * Activates the this hyperlink controller on the installed
+ * text viewer for the given hyperlinks.
+ *
+ * @param hyperlinks the hyperlinks to show
+ * @throws IllegalArgumentException if
+ * <ul>
+ * <li><code>hyperlinks</code> is empty</li>
+ * <li>{@link #canShowMultipleHyperlinks()} returns <code>false</code> and <code>hyperlinks</code> contains more than one element</li>
+ * </ul>
+ */
+ void activate(IHyperlink[] hyperlinks) throws IllegalArgumentException;
+
+ /**
+ * Deactivates this hyperlink controller.
+ */
+ void deactivate();
+
+ /**
+ * Installs this hyperlink controller on the given text viewer.
+ *
+ * @param textViewer the text viewer
+ */
+ void install(ITextViewer textViewer);
+
+ /**
+ * Uninstalls this hyperlink controller.
+ */
+ void uninstall();
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java
new file mode 100644
index 0000000..eb70de3
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+
+
+/**
+ * A hyperlink detector tries to find a hyperlink at
+ * a given location in a given text viewer.
+ * <p>
+ * Hyperlink detectors can be contributed via the
+ * <code>hyperlinkDetectors</code> extension point.
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IHyperlinkDetector {
+
+ /**
+ * Tries to detect hyperlinks for the given region in
+ * the given text viewer and returns them.
+ * <p>
+ * In most of the cases only one hyperlink should be returned.
+ * </p>
+ * @param textViewer the text viewer on which the hover popup should be shown
+ * @param region the text range in the text viewer which is used to detect the hyperlinks
+ * @return the hyperlinks or <code>null</code> if no hyperlink was detected
+ */
+ IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region);
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java
new file mode 100644
index 0000000..5d34924
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.ITextViewer;
+
+/**
+ * A hyperlink manager for a text viewer detects
+ * {@link org.eclipse.jface.text.hyperlink.IHyperlink hyperlinks} with the given
+ * {@link org.eclipse.jface.text.hyperlink.IHyperlinkDetector hyperlink detectors}
+ * and displays the links using a {@link org.eclipse.jface.text.hyperlink.IHyperlinkController hyperlink controller}.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IHyperlinkManager {
+
+ /**
+ * Installs this hyperlink manager on the given text viewer.
+ *
+ * @param textViewer the text viewer
+ * @param hyperlinkController the hyperlink controller
+ * @param hyperlinkDetectors the hyperlink detectors
+ * @param keyModifierMask the modifier mask which in combination with the left mouse button triggers the hyperlink mode
+ */
+ void install(ITextViewer textViewer, IHyperlinkController hyperlinkController, IHyperlinkDetector[] hyperlinkDetectors, int keyModifierMask);
+
+ /**
+ * Uninstalls this hyperlink manager from the text viewer.
+ */
+ void uninstall();
+
+ /**
+ * Sets the hyperlink detectors for this hyperlink manager.
+ * <p>
+ * It is allowed to called this method after this
+ * hyperlink manger has been installed.
+ * </p>
+ *
+ * @param hyperlinkDetectors
+ */
+ void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors);
+
+ /**
+ * Sets the SWT key modifier mask which in combination
+ * with the left mouse button triggers the hyperlink mode.
+ * <p>
+ * It is allowed to call this method after this
+ * hyperlink manger has been installed.
+ * </p>
+ *
+ * @param modifierMask the modifier mask
+ */
+ void setKeyModifierMask(int modifierMask);
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java
new file mode 100644
index 0000000..d91a135
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.program.Program;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IRegion;
+
+
+/**
+ * URL hyperlink.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class URLHyperlink implements IHyperlink {
+
+ private String fURLString;
+ private IRegion fRegion;
+
+ /**
+ * Creates a new URL hyperlink.
+ *
+ * @param region
+ * @param urlString
+ */
+ public URLHyperlink(IRegion region, String urlString) {
+ Assert.isNotNull(urlString);
+ Assert.isNotNull(region);
+
+ fRegion= region;
+ fURLString= urlString;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getRegion()
+ * @since 3.1
+ */
+ public IRegion getRegion() {
+ return fRegion;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#open()
+ * @since 3.1
+ */
+ public void open() {
+ if (fURLString != null) {
+ String platform= SWT.getPlatform();
+ if ("motif".equals(platform) || "gtk".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$
+ Program program= Program.findProgram("html"); //$NON-NLS-1$
+ if (program == null)
+ program= Program.findProgram("htm"); //$NON-NLS-1$
+ if (program != null)
+ program.execute(fURLString);
+ } else
+ Program.launch(fURLString);
+ fURLString= null;
+ return;
+ }
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getTypeLabel()
+ * @since 3.1
+ */
+ public String getTypeLabel() {
+ return null;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getText()
+ * @since 3.1
+ */
+ public String getText() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java
new file mode 100644
index 0000000..add1b35
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Region;
+
+
+
+/**
+ * URL hyperlink detector.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class URLHyperlinkDetector implements IHyperlinkDetector {
+
+ private ITextViewer fTextViewer;
+
+
+ /**
+ * Creates a new URL hyperlink detector.
+ *
+ * @param textViewer the text viewer in which to detect the hyperlink
+ */
+ public URLHyperlinkDetector(ITextViewer textViewer) {
+ Assert.isNotNull(textViewer);
+ fTextViewer= textViewer;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkDetector#detectHyperlink(org.eclipse.jface.text.IRegion)
+ * @since 3.1
+ */
+ public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region) {
+ if (region == null || fTextViewer == null)
+ return null;
+
+ IDocument document= fTextViewer.getDocument();
+
+ int offset= region.getOffset();
+
+ String urlString= null;
+ if (document == null)
+ return null;
+
+ IRegion lineInfo;
+ String line;
+ try {
+ lineInfo= document.getLineInformationOfOffset(offset);
+ line= document.get(lineInfo.getOffset(), lineInfo.getLength());
+ } catch (BadLocationException ex) {
+ return null;
+ }
+
+ int offsetInLine= offset - lineInfo.getOffset();
+
+ int urlSeparatorOffset= line.indexOf("://"); //$NON-NLS-1$
+ if (urlSeparatorOffset < 0)
+ return null;
+
+ // URL protocol (left to "://")
+ int urlOffsetInLine= urlSeparatorOffset;
+ char ch;
+ do {
+ urlOffsetInLine--;
+ ch= ' ';
+ if (urlOffsetInLine > -1)
+ ch= line.charAt(urlOffsetInLine);
+ } while (!Character.isWhitespace(ch));
+ urlOffsetInLine++;
+
+ // Right to "://"
+ StringTokenizer tokenizer= new StringTokenizer(line.substring(urlSeparatorOffset + 3));
+ if (!tokenizer.hasMoreTokens())
+ return null;
+
+ int urlLength= tokenizer.nextToken().length() + 3 + urlSeparatorOffset - urlOffsetInLine;
+ if (offsetInLine < urlOffsetInLine || offsetInLine > urlOffsetInLine + urlLength)
+ return null;
+
+ // Set and validate URL string
+ try {
+ urlString= line.substring(urlOffsetInLine, urlOffsetInLine + urlLength);
+ new URL(urlString);
+ } catch (MalformedURLException ex) {
+ urlString= null;
+ return null;
+ }
+
+ IRegion urlRegion= new Region(lineInfo.getOffset() + urlOffsetInLine, urlLength);
+ return new IHyperlink[] {new URLHyperlink(urlRegion, urlString)};
+ }
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
index 03cfe6f..b584a8b 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
@@ -43,6 +43,7 @@
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.formatter.IContentFormatterExtension;
import org.eclipse.jface.text.formatter.IFormattingContext;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.projection.ChildDocument;
@@ -348,6 +349,14 @@
setHoverControlCreator(configuration.getInformationControlCreator(this));
+ if (configuration.getHyperlinksEnabled(this)) {
+ setHyperlinkController(configuration.getHyperlinkController(this));
+ setHyperlinkStateMask(configuration.getHyperlinkStateMask(this));
+ IHyperlinkDetector[] hyperlinkDetectors= configuration.getHyperlinkDetectors(this);
+ if (hyperlinkDetectors != null)
+ setHyperlinkDetectors(hyperlinkDetectors);
+ }
+
// install content type specific plug-ins
String[] types= configuration.getConfiguredContentTypes(this);
for (int i= 0; i < types.length; i++) {
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
index b81849c..5af2704 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
@@ -11,8 +11,11 @@
package org.eclipse.jface.text.source;
+import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
+import org.eclipse.jface.preference.IPreferenceStore;
+
import org.eclipse.jface.text.DefaultAutoIndentStrategy;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.DefaultTextDoubleClickStrategy;
@@ -28,6 +31,10 @@
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.formatter.IContentFormatter;
+import org.eclipse.jface.text.hyperlink.DefaultHyperlinkController;
+import org.eclipse.jface.text.hyperlink.IHyperlinkController;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.URLHyperlinkDetector;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
@@ -324,4 +331,58 @@
public String getConfiguredDocumentPartitioning(ISourceViewer sourceViewer) {
return IDocumentExtension3.DEFAULT_PARTITIONING;
}
+
+ /**
+ * Returns the whether hyperlinks are enabled in the
+ * given source viewer.
+ * This implementation always returns the <code>true</code>.
+ *
+ * @param sourceViewer the source viewer to be configured by this configuration
+ * @return <code>true</code> if hyperlinks are enabled
+ * @since 3.1
+ */
+ public boolean getHyperlinksEnabled(ISourceViewer sourceViewer) {
+ return true;
+ }
+
+ /**
+ * Returns the hyperlink detectors which be used to detect hyperlinks
+ * in the given source viewer. This
+ * implementation always returns an array with an URL hyperlink detector.
+ *
+ * @param sourceViewer the source viewer to be configured by this configuration
+ * @return an array with hyperlink detectors or <code>null</code> if no hyperlink support should be installed
+ * @since 3.1
+ */
+ public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) {
+ if (sourceViewer == null)
+ return null;
+
+ return new IHyperlinkDetector[] {new URLHyperlinkDetector(sourceViewer)};
+ }
+
+ /**
+ * Returns the hyperlink controller for the given source viewer.
+ * This implementation always returns the {@link DefaultHyperlinkController}.
+ *
+ * @param sourceViewer the source viewer to be configured by this configuration
+ * @return the hyperlink controller or <code>null</code> if no hyperlink support should be installed
+ * @since 3.1
+ */
+ public IHyperlinkController getHyperlinkController(ISourceViewer sourceViewer) {
+ return new DefaultHyperlinkController((IPreferenceStore)null);
+ }
+
+ /**
+ * Returns the SWT event state mask which in combination
+ * with the left mouse button activates hyperlinking.
+ * This implementation always returns the {@link SWT#MOD1}.
+ *
+ * @param sourceViewer the source viewer to be configured by this configuration
+ * @return the state mask for
+ * @since 3.1
+ */
+ public int getHyperlinkStateMask(ISourceViewer sourceViewer) {
+ return SWT.CTRL;
+ }
}