/** * 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 v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.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 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(); } 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(); } }