### Eclipse Workspace Patch 1.0 #P org.eclipse.ui.editors Index: src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java,v retrieving revision 1.41 diff -u -r1.41 TextSourceViewerConfiguration.java --- src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java 26 Feb 2008 18:09:49 -0000 1.41 +++ src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java 11 Mar 2008 10:45:55 -0000 @@ -16,15 +16,10 @@ import java.util.StringTokenizer; import java.util.Map.Entry; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Shell; - import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IAdaptable; - import org.eclipse.jface.action.Action; import org.eclipse.jface.preference.IPreferenceStore; - import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.DefaultTextHover; import org.eclipse.jface.text.IInformationControl; @@ -34,9 +29,9 @@ import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.IUndoManager; import org.eclipse.jface.text.TextViewerUndoManager; -import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter; import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter; +import org.eclipse.jface.text.hyperlink.MultipleHyperlinkPresenter; import org.eclipse.jface.text.quickassist.IQuickAssistAssistant; import org.eclipse.jface.text.quickassist.QuickAssistAssistant; import org.eclipse.jface.text.reconciler.IReconciler; @@ -47,7 +42,9 @@ import org.eclipse.jface.text.source.IAnnotationHover; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; - +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.AnnotationPreference; import org.eclipse.ui.texteditor.HyperlinkDetectorRegistry; @@ -299,9 +296,9 @@ */ public IHyperlinkPresenter getHyperlinkPresenter(ISourceViewer sourceViewer) { if (fPreferenceStore == null) - return super.getHyperlinkPresenter(sourceViewer); + return new MultipleHyperlinkPresenter(new RGB(0, 0, 255)); - return new DefaultHyperlinkPresenter(fPreferenceStore); + return new MultipleHyperlinkPresenter(fPreferenceStore); } /** #P org.eclipse.jface.text Index: src/org/eclipse/jface/text/TextViewer.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java,v retrieving revision 1.191 diff -u -r1.191 TextViewer.java --- src/org/eclipse/jface/text/TextViewer.java 4 Feb 2008 11:24:10 -0000 1.191 +++ src/org/eclipse/jface/text/TextViewer.java 11 Mar 2008 10:45:57 -0000 @@ -21,7 +21,20 @@ import java.util.regex.PatternSyntaxException; import org.eclipse.core.runtime.Assert; - +import org.eclipse.jface.internal.text.NonDeletingPositionUpdater; +import org.eclipse.jface.text.hyperlink.HyperlinkManager; +import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; +import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension; +import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter; +import org.eclipse.jface.text.hyperlink.HyperlinkManager.DETECTION_STRATEGY; +import org.eclipse.jface.text.projection.ChildDocument; +import org.eclipse.jface.text.projection.ChildDocumentManager; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.LineBackgroundEvent; import org.eclipse.swt.custom.LineBackgroundListener; @@ -61,20 +74,6 @@ import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; -import org.eclipse.jface.internal.text.NonDeletingPositionUpdater; -import org.eclipse.jface.text.hyperlink.HyperlinkManager; -import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; -import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension; -import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter; -import org.eclipse.jface.text.projection.ChildDocument; -import org.eclipse.jface.text.projection.ChildDocumentManager; -import org.eclipse.jface.viewers.IPostSelectionProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.Viewer; - /** * SWT based implementation of {@link ITextViewer} and its extension interfaces. @@ -3230,11 +3229,7 @@ * @return the average character width of this viewer's widget */ final protected int getAverageCharWidth() { - GC gc= new GC(fTextWidget); - gc.setFont(fTextWidget.getFont()); - int increment= gc.getFontMetrics().getAverageCharWidth(); - gc.dispose(); - return increment; + return JFaceTextUtil.getAverageCharWidth(getTextWidget()); } /* @@ -5366,7 +5361,8 @@ */ private void ensureHyperlinkManagerInstalled() { if (fHyperlinkDetectors != null && fHyperlinkDetectors.length > 0 && fHyperlinkPresenter != null && fHyperlinkManager == null) { - fHyperlinkManager= new HyperlinkManager(HyperlinkManager.FIRST); + DETECTION_STRATEGY strategy= fHyperlinkPresenter.canShowMultipleHyperlinks() ? HyperlinkManager.ALL : HyperlinkManager.FIRST; + fHyperlinkManager= new HyperlinkManager(strategy); fHyperlinkManager.install(this, fHyperlinkPresenter, fHyperlinkDetectors, fHyperlinkStateMask); } } Index: src/org/eclipse/jface/text/JFaceTextUtil.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextUtil.java,v retrieving revision 1.4 diff -u -r1.4 JFaceTextUtil.java --- src/org/eclipse/jface/text/JFaceTextUtil.java 11 Jul 2007 13:08:25 -0000 1.4 +++ src/org/eclipse/jface/text/JFaceTextUtil.java 11 Mar 2008 10:45:56 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2007 IBM Corporation and others. + * Copyright (c) 2006, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,10 +10,13 @@ *******************************************************************************/ package org.eclipse.jface.text; -import org.eclipse.swt.custom.StyledText; - import org.eclipse.jface.text.source.ILineRange; import org.eclipse.jface.text.source.LineRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; /** * A collection of JFace Text functions. @@ -276,4 +279,76 @@ return lastPossiblePixel <= lastVisiblePixel; } + /** + * Determines graphical area covered by the given text region in + * the given viewer. + * + * @param region the region whose graphical extend must be computed + * @param textViewer the text viewer containing the region + * @return the graphical extend of the given region in the given viewer + * + * @since 3.4 + */ + public static Rectangle computeArea(IRegion region, ITextViewer textViewer) { + int start= 0; + int end= 0; + IRegion widgetRegion= modelRange2WidgetRange(region, textViewer); + if (widgetRegion != null) { + start= widgetRegion.getOffset(); + end= start + widgetRegion.getLength(); + } + + StyledText styledText= textViewer.getTextWidget(); + Rectangle bounds; + if (end > 0 && start < end) + bounds= styledText.getTextBounds(start, end - 1); + else { + Point loc= styledText.getLocationAtOffset(start); + bounds= new Rectangle(loc.x, loc.y, getAverageCharWidth(textViewer.getTextWidget()), styledText.getLineHeight(start)); + } + + return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); + } + + /** + * Translates a given region of the text viewer's document into + * the corresponding region of the viewer's widget. + * + * @param region the document region + * @param textViewer the viewer containing the region + * @return the corresponding widget region + * + * @since 3.4 + */ + private static IRegion modelRange2WidgetRange(IRegion region, ITextViewer textViewer) { + if (textViewer instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5) textViewer; + return extension.modelRange2WidgetRange(region); + } + + IRegion visibleRegion= textViewer.getVisibleRegion(); + int start= region.getOffset() - visibleRegion.getOffset(); + int end= start + region.getLength(); + if (end > visibleRegion.getLength()) + end= visibleRegion.getLength(); + + return new Region(start, end - start); + } + + /** + * Returns the average character width of the controls font + * + * @param control the control to calculate the average char width for + * @return the average character width of the controls font + * + * @since 3.4 + */ + public static int getAverageCharWidth(Control control) { + GC gc= new GC(control); + gc.setFont(control.getFont()); + int increment= gc.getFontMetrics().getAverageCharWidth(); + gc.dispose(); + return increment; + } + } Index: src/org/eclipse/jface/text/TextViewerHoverManager.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java,v retrieving revision 1.35 diff -u -r1.35 TextViewerHoverManager.java --- src/org/eclipse/jface/text/TextViewerHoverManager.java 4 Feb 2008 11:24:29 -0000 1.35 +++ src/org/eclipse/jface/text/TextViewerHoverManager.java 11 Mar 2008 10:45:57 -0000 @@ -15,7 +15,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; - +import org.eclipse.jface.text.information.IInformationProviderExtension2; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; @@ -23,8 +23,6 @@ import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; -import org.eclipse.jface.text.information.IInformationProviderExtension2; - /** * This manager controls the layout, content, and visibility of an information @@ -145,7 +143,7 @@ return; } - final Rectangle area= computeArea(region); + final Rectangle area= JFaceTextUtil.computeArea(region, fTextViewer); if (area == null || area.isEmpty()) { setInformation(null, null); return; @@ -277,57 +275,6 @@ } } - /** - * Determines graphical area covered by the given text region. - * - * @param region the region whose graphical extend must be computed - * @return the graphical extend of the given region - */ - private Rectangle computeArea(IRegion region) { - - int start= 0; - int end= 0; - IRegion widgetRegion= modelRange2WidgetRange(region); - if (widgetRegion != null) { - start= widgetRegion.getOffset(); - end= start + widgetRegion.getLength(); - } - - StyledText styledText= fTextViewer.getTextWidget(); - Rectangle bounds; - if (end > 0 && start < end) - bounds= styledText.getTextBounds(start, end - 1); - else { - Point loc= styledText.getLocationAtOffset(start); - bounds= new Rectangle(loc.x, loc.y, fTextViewer.getAverageCharWidth(), styledText.getLineHeight(start)); - } - - return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); - } - - /** - * Translates a given region of the text viewer's document into - * the corresponding region of the viewer's widget. - * - * @param region the document region - * @return the corresponding widget region - * @since 2.1 - */ - private IRegion modelRange2WidgetRange(IRegion region) { - if (fTextViewer instanceof ITextViewerExtension5) { - ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer; - return extension.modelRange2WidgetRange(region); - } - - IRegion visibleRegion= fTextViewer.getVisibleRegion(); - int start= region.getOffset() - visibleRegion.getOffset(); - int end= start + region.getLength(); - if (end > visibleRegion.getLength()) - end= visibleRegion.getLength(); - - return new Region(start, end - start); - } - /* * @see org.eclipse.jface.text.AbstractInformationControlManager#showInformationControl(org.eclipse.swt.graphics.Rectangle) */ Index: src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java =================================================================== RCS file: src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java diff -N src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,719 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.hyperlink; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.AbstractInformationControl; +import org.eclipse.jface.text.AbstractInformationControlManager; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IInformationControlExtension2; +import org.eclipse.jface.text.IInformationControlExtension3; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.IWidgetTokenKeeper; +import org.eclipse.jface.text.IWidgetTokenKeeperExtension; +import org.eclipse.jface.text.IWidgetTokenOwner; +import org.eclipse.jface.text.IWidgetTokenOwnerExtension; +import org.eclipse.jface.text.JFaceTextUtil; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.util.Geometry; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.IOpenListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.OpenEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +import com.ibm.icu.text.MessageFormat; + + +/** + * A hyperlink presenter capable of showing multiple hyperlinks in a hover. + * + * @since 3.4 + */ +public class MultipleHyperlinkPresenter extends DefaultHyperlinkPresenter { + + /** + * An information control capable of showing a list of hyperlinks. The hyperlinks can be opened. + */ + private static class LinkListInformationControl extends AbstractInformationControl implements IInformationControlExtension2 { + + private static final class LinkConentenProvider implements IStructuredContentProvider { + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) + */ + public Object[] getElements(Object inputElement) { + return (Object[]) inputElement; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + public void dispose() { + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object) + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + + private static final class LinkLabelProvider extends ColumnLabelProvider { + + private final IHyperlink fDefaultLink; + + public LinkLabelProvider(IHyperlink defaultLink) { + fDefaultLink= defaultLink; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ColumnLabelProvider#getText(java.lang.Object) + */ + public String getText(Object element) { + IHyperlink link= (IHyperlink) element; + + String text= link.getHyperlinkText(); + if (text == null) + text= HyperlinkMessages.getString("LinkListInformationControl.unknownLink"); //$NON-NLS-1$ + + if (link == fDefaultLink) { + text= MessageFormat.format(HyperlinkMessages.getString("LinkListInformationControl.defaultLinkPattern"), new Object[] { text }); //$NON-NLS-1$ + } + + return text; + } + +// /* (non-Javadoc) +// * @see org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider#getStyledText(java.lang.Object) +// */ +// public StyledStringBuilder getStyledText(Object element) { +// String text= getText(element); +// StyledStringBuilder result= new StyledStringBuilder(text); +// if (element == fDefaultLink) { +// result.setStyle(text.length() - 10, 10, new Styler() { +// public void applyStyles(TextStyle textStyle) { +// textStyle.foreground= Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY); +// } +// }); +// } +// +// return result; +// } + } + + private final MultipleHyperlinkHoverManager fManager; + + private IHyperlink[] fInput; + private Composite fParent; + private Table fTable; + + /** + * Creates a link list information control with the given shell as parent. + * + * @param parentShell the parent shell + * @param manager + */ + public LinkListInformationControl(Shell parentShell, MultipleHyperlinkHoverManager manager) { + super(parentShell, true); + fManager= manager; + + create(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String) + */ + public void setInformation(String information) { + //replaced by IInformationControlExtension2#setInput(java.lang.Object) + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object) + */ + public void setInput(Object input) { + fInput= (IHyperlink[]) input; + deferredCreateContent(fParent); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#createContent(org.eclipse.swt.widgets.Composite) + */ + protected void createContent(Composite parent) { + fParent= new Composite(parent, SWT.NONE); + + GridLayout layout= new GridLayout(1, false); + layout.marginWidth= 0; + fParent.setLayout(layout); + fParent.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#computeSizeHint() + */ + public Point computeSizeHint() { + Point preferedSize= getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + + Point constraints= getSizeConstraints(); + if (constraints == null) + return preferedSize; + + if (fTable.getVerticalBar() == null || fTable.getHorizontalBar() == null) + return Geometry.min(constraints, preferedSize); + + int scrollBarWidth= fTable.getVerticalBar().getSize().x; + int scrollBarHeight= fTable.getHorizontalBar().getSize().y; + + int width; + if (preferedSize.y - scrollBarHeight < constraints.y) { + width= preferedSize.x - scrollBarWidth; + } else { + width= Math.min(preferedSize.x, constraints.x); + } + + int height; + if (preferedSize.x - scrollBarWidth < constraints.x) { + height= preferedSize.y - scrollBarHeight; + } else { + height= Math.min(preferedSize.y, constraints.y); + } + + return new Point(width, height); + } + + private void deferredCreateContent(Composite parent) { + fTable= new Table(parent, SWT.SINGLE); + GridData data= new GridData(SWT.FILL, SWT.FILL, true, true); + fTable.setLayoutData(data); + fTable.setLinesVisible(false); + fTable.setHeaderVisible(false); + fTable.setBackground(parent.getBackground()); + + final TableViewer viewer= new TableViewer(fTable); + viewer.setContentProvider(new LinkConentenProvider()); + viewer.setLabelProvider(new LinkLabelProvider(fInput[0])); + viewer.setInput(fInput); + + registerQuickViewTableListeners(viewer); + + getShell().addShellListener(new ShellAdapter() { + + /* (non-Javadoc) + * @see org.eclipse.swt.events.ShellAdapter#shellActivated(org.eclipse.swt.events.ShellEvent) + */ + public void shellActivated(ShellEvent e) { + if (viewer.getTable().getSelectionCount() == 0) { + viewer.getTable().setSelection(0); + } + + viewer.getTable().setFocus(); + } + }); + } + + private void registerQuickViewTableListeners(final TableViewer viewer) { + final Table table= viewer.getTable(); + + table.addMouseMoveListener(new MouseMoveListener() { + TableItem fLastItem= null; + + public void mouseMove(MouseEvent e) { + if (table.equals(e.getSource())) { + Object o= table.getItem(new Point(e.x, e.y)); + if (o instanceof TableItem) { + TableItem item= (TableItem) o; + if (!o.equals(fLastItem)) { + fLastItem= (TableItem) o; + table.setSelection(new TableItem[] { fLastItem }); + } else if (e.y < table.getItemHeight() / 4) { + // Scroll up + int index= table.indexOf(item); + if (index > 0) { + fLastItem= table.getItem(index - 1); + table.setSelection(new TableItem[] { fLastItem }); + } + } else if (e.y > table.getBounds().height - table.getItemHeight() / 4) { + // Scroll down + int index= table.indexOf(item); + if (index < table.getItemCount() - 1) { + fLastItem= table.getItem(index + 1); + table.setSelection(new TableItem[] { fLastItem }); + } + } + } + } + } + }); + + table.addMouseListener(new MouseAdapter() { + public void mouseUp(MouseEvent e) { + + if (table.getSelectionCount() < 1) + return; + + if (e.button != 1) + return; + + if (table.equals(e.getSource())) { + Object o= table.getItem(new Point(e.x, e.y)); + TableItem selection= table.getSelection()[0]; + if (selection.equals(o)) { + openLink((IHyperlink) selection.getData()); + } + } + } + }); + + viewer.addOpenListener(new IOpenListener() { + public void open(OpenEvent event) { + StructuredSelection selection= (StructuredSelection) event.getSelection(); + openLink((IHyperlink) selection.getFirstElement()); + } + }); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControlExtension#hasContents() + */ + public boolean hasContents() { + return true; + } + + /** + * Opens the given link + * + * @param link the link to open + */ + private void openLink(IHyperlink link) { + fManager.hideInformationControl(); + + link.open(); + } + } + + private class MultipleHyperlinkHover implements ITextHover, ITextHoverExtension { + + /** + * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) + * @deprecated + */ + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int) + */ + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + return fSubjectRegion; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) + */ + public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { + return fHyperlinks; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator() + */ + public IInformationControlCreator getHoverControlCreator() { + return new IInformationControlCreator() { + public IInformationControl createInformationControl(Shell parent) { + return new LinkListInformationControl(parent, fManager); + } + }; + } + } + + private static class MultipleHyperlinkHoverManager extends AbstractInformationControlManager implements IWidgetTokenKeeper, IWidgetTokenKeeperExtension { + + private class Closer implements IInformationControlCloser, Listener, KeyListener { + + private Control fSubjectControl; + private Display fDisplay; + private IInformationControl fControl; + private Rectangle fSubjectArea; + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setInformationControl(org.eclipse.jface.text.IInformationControl) + */ + public void setInformationControl(IInformationControl control) { + fControl= control; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setSubjectControl(org.eclipse.swt.widgets.Control) + */ + public void setSubjectControl(Control subject) { + fSubjectControl= subject; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#start(org.eclipse.swt.graphics.Rectangle) + */ + public void start(Rectangle subjectArea) { + fSubjectArea= subjectArea; + + fDisplay= fSubjectControl.getDisplay(); + if (!fDisplay.isDisposed()) { + fDisplay.addFilter(SWT.FocusOut, this); + fDisplay.addFilter(SWT.MouseMove, this); + fTextViewer.getTextWidget().addKeyListener(this); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#stop() + */ + public void stop() { + if (fDisplay != null && !fDisplay.isDisposed()) { + fDisplay.removeFilter(SWT.FocusOut, this); + fDisplay.removeFilter(SWT.MouseMove, this); + fTextViewer.getTextWidget().removeKeyListener(this); + } + + fSubjectArea= null; + } + + /* (non-Javadoc) + * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) + */ + public void handleEvent(Event event) { + switch (event.type) { + case SWT.FocusOut: + if (!fControl.isFocusControl()) + disposeInformationControl(); + break; + case SWT.MouseMove: + handleMouseMove(event); + break; + } + } + + /** + * Handle mouse movement events. + * + * @param event the event + */ + private void handleMouseMove(Event event) { + if (!(event.widget instanceof Control)) + return; + + if (fControl.isFocusControl()) + return; + + Control eventControl= (Control) event.widget; + + //transform coordinates to subject control: + Point mouseLoc= event.display.map(eventControl, fSubjectControl, event.x, event.y); + + if (fSubjectArea.contains(mouseLoc)) + return; + + if (inKeepUpZone(mouseLoc.x, mouseLoc.y, ((IInformationControlExtension3) fControl).getBounds())) + return; + + hideInformationControl(); + } + + /** + * Tests whether a given mouse location is within the keep-up zone. + * The hover should not be hidden as long as the mouse stays inside this zone. + * + * @param x the x coordinate, relative to the subject control + * @param y the y coordinate, relative to the subject control + * @param controlBounds the bounds of the current control + * + * @return true iff the mouse event occurred in the keep-up zone + */ + private boolean inKeepUpZone(int x, int y, Rectangle controlBounds) { + Rectangle iControlBounds= fSubjectControl.getDisplay().map(null, fSubjectControl, controlBounds); + Rectangle totalBounds= Geometry.copy(iControlBounds); + + // FIXME: should maybe use convex hull, not bounding box + totalBounds.add(fSubjectArea); + return totalBounds.contains(x, y); + } + + /* (non-Javadoc) + * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent) + */ + public void keyPressed(KeyEvent e) { + } + + /* (non-Javadoc) + * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent) + */ + public void keyReleased(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + fControl.setFocus(); + return; + } + + hideInformationControl(); + } + } + + /** + * Priority of the hover managed by this manager. + * Default value: One higher then for the hovers + * managed by TextViewerHoverManager. + */ + private static final int WIDGET_TOKEN_PRIORITY= 1; + + private final MultipleHyperlinkHover fHover; + private final ITextViewer fTextViewer; + private Closer fCloser; + + /** + * Create a new MultipleHyperlinkHoverManager. The MHHM can show and hide + * the given MultipleHyperlinkHover inside the given ITextViewer. + * + * @param hover the hover to manage + * @param viewer the viewer to show the hover in + */ + public MultipleHyperlinkHoverManager(MultipleHyperlinkHover hover, ITextViewer viewer) { + super(hover.getHoverControlCreator()); + + fHover= hover; + fTextViewer= viewer; + + fCloser= new Closer(); + setCloser(fCloser); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager#computeInformation() + */ + protected void computeInformation() { + IRegion region= fHover.getHoverRegion(fTextViewer, -1); + if (region == null) { + setInformation(null, null); + return; + } + + Rectangle area= JFaceTextUtil.computeArea(region, fTextViewer); + if (area == null || area.isEmpty()) { + setInformation(null, null); + return; + } + + Object information= fHover.getHoverInfo2(fTextViewer, region); + setCustomInformationControlCreator(fHover.getHoverControlCreator()); + setInformation(information, area); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager#showInformationControl(org.eclipse.swt.graphics.Rectangle) + */ + protected void showInformationControl(Rectangle subjectArea) { + if (fTextViewer instanceof IWidgetTokenOwnerExtension) { + if (((IWidgetTokenOwnerExtension) fTextViewer).requestWidgetToken(this, WIDGET_TOKEN_PRIORITY)) + super.showInformationControl(subjectArea); + } else if (fTextViewer instanceof IWidgetTokenOwner) { + if (((IWidgetTokenOwner) fTextViewer).requestWidgetToken(this)) + super.showInformationControl(subjectArea); + } else { + super.showInformationControl(subjectArea); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager#hideInformationControl() + */ + protected void hideInformationControl() { + super.hideInformationControl(); + + if (fTextViewer instanceof IWidgetTokenOwner) { + ((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControlManager#disposeInformationControl() + */ + public void disposeInformationControl() { + super.disposeInformationControl(); + + if (fTextViewer instanceof IWidgetTokenOwner) { + ((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner) + */ + public boolean requestWidgetToken(IWidgetTokenOwner owner) { + hideInformationControl(); + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner, int) + */ + public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) { + if (priority < WIDGET_TOKEN_PRIORITY) + return false; + + hideInformationControl(); + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#setFocus(org.eclipse.jface.text.IWidgetTokenOwner) + */ + public boolean setFocus(IWidgetTokenOwner owner) { + return false; + } + } + + private ITextViewer fTextViewer; + + private IHyperlink[] fHyperlinks; + private Region fSubjectRegion; + private MultipleHyperlinkHoverManager fManager; + + /** + * Creates a new multiple hyperlink presenter which uses + * {@link #HYPERLINK_COLOR} to read the color from the given preference store. + * + * @param store the preference store + */ + public MultipleHyperlinkPresenter(IPreferenceStore store) { + super(store); + } + + /** + * Creates a new multiple hyperlink presenter. + * + * @param color the hyperlink color, to be disposed by the caller + */ + public MultipleHyperlinkPresenter(RGB color) { + super(color); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#install(org.eclipse.jface.text.ITextViewer) + */ + public void install(ITextViewer viewer) { + super.install(viewer); + fTextViewer= viewer; + + fManager= new MultipleHyperlinkHoverManager(new MultipleHyperlinkHover(), fTextViewer); + fManager.install(viewer.getTextWidget()); + fManager.setSizeConstraints(100, 12, false, true); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#uninstall() + */ + public void uninstall() { + super.uninstall(); + + if (fTextViewer != null) { + fManager.dispose(); + + fTextViewer= null; + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#canShowMultipleHyperlinks() + */ + public boolean canShowMultipleHyperlinks() { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#hideHyperlinks() + */ + public void hideHyperlinks() { + super.hideHyperlinks(); + + fHyperlinks= null; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#showHyperlinks(org.eclipse.jface.text.hyperlink.IHyperlink[]) + */ + public void showHyperlinks(IHyperlink[] hyperlinks) { + super.showHyperlinks(new IHyperlink[] { hyperlinks[0] }); + + if (equals(fHyperlinks, hyperlinks)) + return; + + fManager.disposeInformationControl(); + fSubjectRegion= null; + fHyperlinks= hyperlinks; + + if (hyperlinks.length == 1) + return; + + int start= hyperlinks[0].getHyperlinkRegion().getOffset(); + int end= start + hyperlinks[0].getHyperlinkRegion().getLength(); + + for (int i= 1; i < hyperlinks.length; i++) { + int hstart= hyperlinks[i].getHyperlinkRegion().getOffset(); + int hend= hstart + hyperlinks[i].getHyperlinkRegion().getLength(); + + start= Math.min(start, hstart); + end= Math.max(end, hend); + } + + fSubjectRegion= new Region(start, end - start); + + fManager.showInformation(); + } + + private boolean equals(IHyperlink[] oldLinks, IHyperlink[] newLinks) { + if (oldLinks == null) + return false; + + if (oldLinks.length != newLinks.length) + return false; + + for (int i= 0; i < newLinks.length; i++) { + if (!oldLinks[i].getHyperlinkRegion().equals(newLinks[i].getHyperlinkRegion())) + return false; + } + + return true; + } +}