blob: 16081c1c40b1de72f83b3aefcac94a37ce696fcd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 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 com.ibm.icu.text.MessageFormat;
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 org.eclipse.jface.preference.IPreferenceStore;
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.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;
/**
* 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 {
/*
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
*/
public Object[] getElements(Object inputElement) {
return (Object[]) inputElement;
}
/*
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public void dispose() {
}
/*
* @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;
}
/*
* @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;
}
// /*
// * @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();
}
/*
* @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String)
*/
public void setInformation(String information) {
//replaced by IInformationControlExtension2#setInput(java.lang.Object)
}
/*
* @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
*/
public void setInput(Object input) {
fInput= (IHyperlink[]) input;
deferredCreateContent(fParent);
}
/*
* @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));
}
/*
* @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() {
/*
* @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());
}
});
}
/*
* @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;
}
/*
* @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
*/
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
return fSubjectRegion;
}
/*
* @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;
}
/*
* @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;
/*
* @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setInformationControl(org.eclipse.jface.text.IInformationControl)
*/
public void setInformationControl(IInformationControl control) {
fControl= control;
}
/*
* @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setSubjectControl(org.eclipse.swt.widgets.Control)
*/
public void setSubjectControl(Control subject) {
fSubjectControl= subject;
}
/*
* @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);
}
}
/*
* @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;
}
/*
* @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 <em>subject control</em>
* @param y the y coordinate, relative to the <em>subject control</em>
* @param controlBounds the bounds of the current control
*
* @return <code>true</code> 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);
}
/*
* @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
*/
public void keyPressed(KeyEvent e) {
}
/*
* @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);
}
/*
* @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);
}
/*
* @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);
}
}
/*
* @see org.eclipse.jface.text.AbstractInformationControlManager#hideInformationControl()
*/
protected void hideInformationControl() {
super.hideInformationControl();
if (fTextViewer instanceof IWidgetTokenOwner) {
((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this);
}
}
/*
* @see org.eclipse.jface.text.AbstractInformationControlManager#disposeInformationControl()
*/
public void disposeInformationControl() {
super.disposeInformationControl();
if (fTextViewer instanceof IWidgetTokenOwner) {
((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this);
}
}
/*
* @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner)
*/
public boolean requestWidgetToken(IWidgetTokenOwner owner) {
hideInformationControl();
return true;
}
/*
* @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;
}
/*
* @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);
}
/*
* @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);
}
/*
* @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#uninstall()
*/
public void uninstall() {
super.uninstall();
if (fTextViewer != null) {
fManager.dispose();
fTextViewer= null;
}
}
/*
* @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#canShowMultipleHyperlinks()
*/
public boolean canShowMultipleHyperlinks() {
return true;
}
/*
* @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#hideHyperlinks()
*/
public void hideHyperlinks() {
super.hideHyperlinks();
fHyperlinks= null;
}
/*
* @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;
}
}