| /** |
| * 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(); |
| } |
| } |