blob: eefd2b347baec8d757d915713400cc17757a6992 [file] [log] [blame]
/**
* Copyright (c) 2012 Eclipse contributors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*/
package org.eclipse.emf.common.ui.viewer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import org.eclipse.emf.common.ui.CommonUIPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.InformationControlReplacer;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.internal.text.html.BrowserInformationControlInput;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractHoverInformationControlManager;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.text.IInformationControlExtension3;
import org.eclipse.jface.text.IInformationControlExtension4;
import org.eclipse.jface.text.IInformationControlExtension5;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerRow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
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.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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;
/**
* A utility class for showing rich JDT-style HTML content in tool tip hovers.
* This class is final to avoid long term API commitments.
* If you feel the need to specialize it, please open a bugzilla to explain what your use case and requirements.
*
* @since 2.9
*/
@SuppressWarnings("restriction")
public final class ColumnViewerInformationControlToolTipSupport
{
/**
* The default font used within the hovers.
* It will be the same as JDT's Javadoc font, if available, or the default {@link JFaceResources#DIALOG_FONT dialog} otherwise.
*/
protected static final String DEFAULT_FONT = JFaceResources.getFontRegistry().hasValueFor("org.eclipse.jdt.ui.javadocfont") ? "org.eclipse.jdt.ui.javadocfont" : JFaceResources.DIALOG_FONT;
/**
* A listener that's "path URI" aware.
* I.e., a URI of the form "path:{/<index>}+" will be interpreted to
* {@link StructuredViewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) select} and reveal the {@link StructuredViewer}'s content tree's corresponding object.
*/
public static class PathLocationListener implements LocationListener
{
protected StructuredViewer viewer;
public PathLocationListener(StructuredViewer viewer)
{
this.viewer = viewer;
}
public URI getURI(Object object)
{
IContentProvider contentProvider = viewer.getContentProvider();
if (contentProvider instanceof IStructuredContentProvider)
{
IStructuredContentProvider structuredContentProvider = (IStructuredContentProvider)contentProvider;
List<Object> elements = Arrays.asList(structuredContentProvider.getElements(viewer.getInput()));
if (structuredContentProvider instanceof ITreeContentProvider)
{
return traverse(elements, (ITreeContentProvider)structuredContentProvider, object);
}
else
{
int index = elements.indexOf(object);
if (index != -1)
{
return URI.createURI("path:/" + index);
}
}
}
return null;
}
protected URI traverse(List<Object> elements, ITreeContentProvider treeContentProvider, Object object)
{
int index = elements.indexOf(object);
if (index != -1)
{
return URI.createURI("path:/" + index);
}
Object parent = treeContentProvider.getParent(object);
if (parent != null)
{
URI uri = traverse(elements, treeContentProvider, parent);
if (uri != null)
{
List<Object> children = Arrays.asList(treeContentProvider.getChildren(parent));
index = children.indexOf(object);
if (index != -1)
{
return uri.appendSegment(Integer.toString(index));
}
}
}
return null;
}
public void changing(LocationEvent event)
{
URI uri = URI.createURI(event.location);
if ("path".equals(uri.scheme()))
{
IContentProvider contentProvider = viewer.getContentProvider();
if (contentProvider instanceof IStructuredContentProvider)
{
IStructuredContentProvider structuredContentProvider = (IStructuredContentProvider)contentProvider;
traverse(structuredContentProvider, uri.segments(), 0);
}
event.doit = false;
}
}
protected void traverse(IStructuredContentProvider structuredContentProvider, String[] segments, int index)
{
Object[] elements = structuredContentProvider.getElements(viewer.getInput());
int i = Integer.parseInt(segments[index++]);
Object object = elements[i];
if (index != segments.length && structuredContentProvider instanceof ITreeContentProvider)
{
ITreeContentProvider treeContentProvider = (ITreeContentProvider)structuredContentProvider;
traverse(treeContentProvider, object, segments, index);
}
else
{
setSelection(object);
}
}
protected void traverse(ITreeContentProvider treeContentProvider, Object object, String[] segments, int index)
{
Object[] children = treeContentProvider.getChildren(object);
int i = Integer.parseInt(segments[index++]);
Object child = children[i];
if (index != segments.length)
{
traverse(treeContentProvider, child, segments, index);
}
else
{
setSelection(child);
}
}
protected void setSelection(Object object)
{
viewer.setSelection(new StructuredSelection(object), true);
viewer.getControl().setFocus();
}
public void changed(LocationEvent event)
{
// Ignore.
}
}
static protected String defaultStyleSheet;
protected ColumnViewer viewer;
protected Control control;
protected LocationListener locationListener;
protected ViewerCell currentCell;
protected String text;
protected Font font;
protected Color backgroundColor;
protected Color foregroundColor;
/**
* An interface that provides access to the underlying control so that we're able give it focus.
*/
public interface AccessibleInformationControl extends IInformationControl, IInformationControlExtension, IInformationControlExtension2, IInformationControlExtension3, IInformationControlExtension4, IInformationControlExtension5
{
public Control getControl();
}
protected AbstractReusableInformationControlCreator replacementInformationControlCreator =
new AbstractReusableInformationControlCreator()
{
@Override
protected IInformationControl doCreateInformationControl(Shell parent)
{
IInformationControl informationControl;
if (BrowserInformationControl.isAvailable(parent))
{
BrowserInformationControl browserInformationControl = new BrowserInformationControl(parent, getSymbolicFont(), true);
browserInformationControl.addLocationListener(locationListener);
informationControl = browserInformationControl;
}
else
{
informationControl = new DefaultInformationControl(parent, (ToolBarManager)null);
}
if (foregroundColor != null)
{
informationControl.setForegroundColor(foregroundColor);
}
if (backgroundColor != null)
{
informationControl.setBackgroundColor(backgroundColor);
}
return informationControl;
}
};
protected AbstractReusableInformationControlCreator reusableInformationControlCreator =
new AbstractReusableInformationControlCreator()
{
@Override
protected IInformationControl doCreateInformationControl(Shell parent)
{
IInformationControl informationControl;
if (BrowserInformationControl.isAvailable(parent))
{
class AccessibleBrowserInformationControl extends BrowserInformationControl implements AccessibleInformationControl
{
public AccessibleBrowserInformationControl(Shell parent, String symbolicFontName, String statusFieldText)
{
super(parent, symbolicFontName, statusFieldText);
}
@Override
public IInformationControlCreator getInformationPresenterControlCreator()
{
return replacementInformationControlCreator;
}
public Control getControl()
{
return getShell();
}
}
AccessibleBrowserInformationControl accessibleBrowserInformationControl = new AccessibleBrowserInformationControl(parent, getSymbolicFont(), CommonUIPlugin.INSTANCE.getString("_UI_PressF2ForFocus_label"));
informationControl = accessibleBrowserInformationControl;
}
else
{
class AccessibleDefaultInformationControl extends DefaultInformationControl implements AccessibleInformationControl
{
public AccessibleDefaultInformationControl(Shell parent, String statusFieldText)
{
super(parent, statusFieldText);
}
public void setInput(Object input)
{
if (input instanceof BrowserInformationControlInput)
{
setInformation(((BrowserInformationControlInput)input).getHtml());
}
else
{
setInformation((String)input);
}
}
public Control getControl()
{
return getShell();
}
}
informationControl = new AccessibleDefaultInformationControl(parent, CommonUIPlugin.INSTANCE.getString("_UI_PressF2ForFocus_label"));
}
if (foregroundColor != null)
{
informationControl.setForegroundColor(foregroundColor);
}
if (backgroundColor != null)
{
informationControl.setBackgroundColor(backgroundColor);
}
return informationControl;
}
};
protected AbstractHoverInformationControlManager hoverInformationControlManager =
new AbstractHoverInformationControlManager(reusableInformationControlCreator)
{
protected IInformationControlCloser closer;
protected InformationControlReplacer replacer;
{
replacer =
new InformationControlReplacer(replacementInformationControlCreator)
{
{
this.setCloser(new Closer());
}
class Closer implements IInformationControlCloser, ControlListener, MouseListener, KeyListener, FocusListener, Listener
{
protected boolean isActive;
protected Display display;
protected Control subjectControl;
protected IInformationControl informationControl;
public void setSubjectControl(Control control)
{
subjectControl = control;
}
public void setInformationControl(IInformationControl control)
{
this.informationControl = control;
}
public void start(Rectangle informationArea)
{
if (!isActive)
{
isActive = true;
if (subjectControl != null && !subjectControl.isDisposed())
{
subjectControl.addControlListener(this);
subjectControl.addMouseListener(this);
subjectControl.addKeyListener(this);
}
if (informationControl != null)
{
informationControl.addFocusListener(this);
}
display = subjectControl.getDisplay();
if (!display.isDisposed())
{
display.addFilter(SWT.MouseMove, this);
display.addFilter(SWT.FocusOut, this);
}
}
}
public void stop()
{
if (isActive)
{
isActive = false;
if (subjectControl != null && !subjectControl.isDisposed())
{
subjectControl.removeControlListener(this);
subjectControl.removeMouseListener(this);
subjectControl.removeKeyListener(this);
}
if (informationControl != null)
{
informationControl.removeFocusListener(this);
}
if (display != null && !display.isDisposed())
{
display.removeFilter(SWT.MouseMove, this);
display.removeFilter(SWT.FocusOut, this);
}
display = null;
}
}
public void controlResized(ControlEvent event)
{
hideInformationControl();
}
public void controlMoved(ControlEvent event)
{
hideInformationControl();
}
public void mouseDown(MouseEvent event)
{
hideInformationControl();
}
public void mouseUp(MouseEvent event)
{
// Ignore.
}
public void mouseDoubleClick(MouseEvent event)
{
hideInformationControl();
}
public void keyPressed(KeyEvent event)
{
hideInformationControl();
}
public void keyReleased(KeyEvent event)
{
// Ignore.
}
public void focusGained(FocusEvent event)
{
// Ignore.
}
public void focusLost(FocusEvent event)
{
if (display != null && !display.isDisposed())
{
display.asyncExec
(new Runnable()
{
public void run()
{
hideInformationControl();
}
});
}
}
public void handleEvent(Event event)
{
if (event.type == SWT.MouseMove)
{
if (event.widget instanceof Control && event.widget.isDisposed())
{
if (informationControl != null && !informationControl.isFocusControl() && informationControl instanceof IInformationControlExtension3)
{
Rectangle controlBounds = ((IInformationControlExtension3)informationControl).getBounds();
if (controlBounds != null)
{
Point mouseLocation = event.display.map((Control)event.widget, null, event.x, event.y);
if (!controlBounds.contains(mouseLocation))
{
hideInformationControl();
}
}
}
else
{
if (display != null && !display.isDisposed())
{
display.removeFilter(SWT.MouseMove, this);
}
}
}
}
else if (event.type == SWT.FocusOut)
{
if (informationControl != null && !informationControl.isFocusControl())
{
hideInformationControl();
}
}
}
}
};
getInternalAccessor().setInformationControlReplacer(replacer);
}
@Override
protected void setCloser(IInformationControlCloser closer)
{
this.closer = closer;
super.setCloser(closer);
}
@Override
protected boolean canClearDataOnHide()
{
return false;
}
@Override
@SuppressWarnings("deprecation")
protected void computeInformation()
{
MouseEvent hoverEvent = getHoverEvent();
Event event = new Event();
event.x = hoverEvent.x;
event.y = hoverEvent.y;
if (shouldCreateToolTip(event))
{
StringBuffer buffer = new StringBuffer(text);
String styleSheet = getStyleSheet();
FontData fontData = JFaceResources.getFontRegistry().getFontData(getSymbolicFont())[0];
styleSheet = HTMLPrinter.convertTopLevelFont(styleSheet, fontData);
HTMLPrinter.insertPageProlog(buffer, 0, foregroundColor == null ? null : foregroundColor.getRGB(), backgroundColor == null ? null : backgroundColor.getRGB(), styleSheet);
HTMLPrinter.addPageEpilog(buffer);
final String html = buffer.toString();
setInformation
(new BrowserInformationControlInput(null)
{
@Override
public String getHtml()
{
return html;
}
@Override
public String getInputName()
{
return "";
}
@Override
public Object getInputElement()
{
return text;
}
},
currentCell.getBounds());
currentCell = null;
}
else
{
setInformation(null, null);
}
}
protected KeyListener keyListener =
new KeyListener()
{
public void keyReleased(KeyEvent event)
{
if (event.keyCode == SWT.F2)
{
IInformationControl informationControl = getInformationControl();
if (informationControl instanceof AccessibleInformationControl)
{
Control myControl = ((AccessibleInformationControl)informationControl).getControl();
Event mouseEvent = new Event();
mouseEvent.display = control.getDisplay();
mouseEvent.widget = myControl;
mouseEvent.type = SWT.MouseUp;
((Listener)closer).handleEvent(mouseEvent);
event.doit = false;
}
}
}
public void keyPressed(KeyEvent event)
{
// Ignore.
}
};
@Override
public void install(Control subjectControl)
{
Control oldSubjectControl = getSubjectControl();
if (oldSubjectControl != null && !oldSubjectControl.isDisposed())
{
oldSubjectControl.removeKeyListener(keyListener);
}
if (subjectControl != null)
{
subjectControl.addKeyListener(keyListener);
}
super.install(subjectControl);
getInternalAccessor().getInformationControlReplacer().install(subjectControl);
}
@Override
public void dispose()
{
Control subjectControl = getSubjectControl();
if (subjectControl != null && !subjectControl.isDisposed())
{
subjectControl.removeKeyListener(keyListener);
}
replacer.dispose();
super.dispose();
}
};
public ColumnViewerInformationControlToolTipSupport(ColumnViewer viewer, LocationListener locationListener)
{
this.viewer = viewer;
this.control = viewer.getControl();
this.locationListener = locationListener == null ? new PathLocationListener(viewer) : locationListener;
hoverInformationControlManager.install(control);
}
protected String getSymbolicFont()
{
if (font == null)
{
return DEFAULT_FONT;
}
else
{
FontData[] fontData = font.getFontData();
FontRegistry fontRegistry = JFaceResources.getFontRegistry();
for (Object name : fontRegistry.getKeySet())
{
FontData[] registerFontData = fontRegistry.getFontData((String)name);
if (Arrays.equals(fontData, registerFontData))
{
return (String)name;
}
}
return DEFAULT_FONT;
}
}
public void showInformation()
{
hoverInformationControlManager.showInformation();
}
/**
* @see AbstractHoverInformationControlManager#setSizeConstraints(int, int, boolean, boolean)
* @since 2.19
*/
public void setSizeConstraints(int widthInChar, int heightInChar, boolean enforceAsMinimalSize, boolean enforceAsMaximalSize)
{
hoverInformationControlManager.setSizeConstraints(widthInChar, heightInChar, enforceAsMinimalSize, enforceAsMaximalSize);
}
public String getStyleSheet()
{
if (defaultStyleSheet == null)
{
URL styleSheetURL = CommonUIPlugin.getPlugin().getBundle().getEntry("EMFCommonUIHoverStyleSheet.css");
if (styleSheetURL != null)
{
BufferedReader reader = null;
try
{
reader = new BufferedReader(new InputStreamReader(styleSheetURL.openStream()));
StringBuffer buffer = new StringBuffer(1500);
String line = reader.readLine();
while (line != null)
{
buffer.append(line);
buffer.append('\n');
line = reader.readLine();
}
defaultStyleSheet = buffer.toString();
}
catch (IOException ex)
{
return "";
}
finally
{
try
{
if (reader != null)
reader.close();
}
catch (IOException exception)
{
// Ignore
}
}
}
}
return defaultStyleSheet;
}
protected void toolTipShow(Shell tip, Event event)
{
if (!tip.isDisposed())
{
currentCell = getToolTipArea(event);
tip.setVisible(true);
}
}
protected ViewerCell getToolTipArea(Event event)
{
return viewer.getCell(new Point(event.x, event.y));
}
protected boolean shouldCreateToolTip(Event event)
{
ViewerCell cell = getToolTipArea(event);
if (cell != null && !cell.equals(currentCell))
{
control.setToolTipText("");
currentCell = cell;
ViewerRow row = cell.getViewerRow();
if (row != null)
{
Object element = row.getItem().getData();
CellLabelProvider labelProvider = viewer.getLabelProvider(cell.getColumnIndex());
text = labelProvider.getToolTipText(element);
boolean useNative = labelProvider.useNativeToolTip(element);
if (useNative || text == null)
{
control.setToolTipText(text);
}
else
{
foregroundColor = labelProvider.getToolTipForegroundColor(element);
backgroundColor = labelProvider.getToolTipBackgroundColor(element);
font = labelProvider.getToolTipFont(element);
return text != null;
}
}
}
else
{
currentCell = cell;
}
return false;
}
public void dispose()
{
hoverInformationControlManager.dispose();
}
}