blob: 8b81034cf60a34b884078cbe12758b84967a532d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2009 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
* Genady Beryozkin <eclipse@genady.org> - [hovering] tooltip for constant string does not show constant value - https://bugs.eclipse.org/bugs/show_bug.cgi?id=85382
* BSI Business Systems Integration AG - adapted to Scout SDK
******************************************************************************/
package org.eclipse.scout.sdk.ui.fields.tooltip;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.javadoc.JavaDocLocations;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.JavadocContentAccess;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.scout.commons.StringUtility;
import org.eclipse.scout.sdk.compatibility.JavadocHoverUtility;
import org.eclipse.scout.sdk.ui.internal.ScoutSdkUi;
import org.eclipse.scout.sdk.util.type.TypeUtility;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.osgi.framework.Bundle;
/**
* Renders JavaDoc of a given {@link IMember}.
* <p>
* This class is based on {@link JavadocHover}.
*
* @author Andreas Hoegger
* @since 1.0.8 17.09.2010
*/
@SuppressWarnings("restriction")
public class JavadocTooltip extends AbstractTooltip {
/** Flags used to render a label in the text widget. */
private static final long LABEL_FLAGS = JavaElementLabels.ALL_FULLY_QUALIFIED
| JavaElementLabels.M_PRE_RETURNTYPE | JavaElementLabels.M_PARAMETER_TYPES | JavaElementLabels.M_PARAMETER_NAMES | JavaElementLabels.M_EXCEPTIONS
| JavaElementLabels.F_PRE_TYPE_SIGNATURE | JavaElementLabels.T_TYPE_PARAMETERS;
private IMember m_originalMember;
private IMember m_currentMember;
private Browser m_browser;
private String m_javaDoc;
public JavadocTooltip(Control sourceControl) {
super(sourceControl);
}
public void setMember(IMember member) {
m_originalMember = member;
resetCurrentMember();
}
private void resetCurrentMember() {
setCurrentMember(m_originalMember);
}
private void setCurrentMember(IMember member) {
if (m_currentMember != member) {
m_currentMember = member;
computeJavadoc();
}
}
@Override
protected void createContent(Composite parent) {
try {
m_browser = new Browser(parent, SWT.NONE);
m_browser.setJavascriptEnabled(true);
m_browser.setText(m_javaDoc);
GridData layoutData = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.FILL_BOTH);
layoutData.heightHint = 200;
layoutData.widthHint = 650;
m_browser.setLayoutData(layoutData);
m_browser.addLocationListener(JavaElementLinks.createLocationListener(new JavaElementLinks.ILinkHandler() {
@Override
public void handleTextSet() {
}
@Override
public void handleJavadocViewLink(IJavaElement target) {
}
@Override
public void handleInlineJavadocLink(IJavaElement target) {
if (target instanceof IMember) {
setCurrentMember((IMember) target);
}
}
@Override
public boolean handleExternalLink(URL url, Display display) {
return false;
}
@Override
public void handleDeclarationLink(IJavaElement target) {
}
}));
}
catch (SWTError swterr) {
// can happen if no browser is installed in the OS or if the browser cannot be found (not correctly registered).
// see http://www.eclipse.org/swt/faq.php#browserlinuxrcp
// and: https://bbs.archlinux.org/viewtopic.php?pid=266262#p266262
ScoutSdkUi.logError("Error creating Javadoc Tooltip", swterr);
if (m_browser != null) {
m_browser.dispose();
m_browser = null;
}
}
}
@Override
protected void show(int x, int y) {
resetCurrentMember();
if (!StringUtility.isNullOrEmpty(m_javaDoc)) {
super.show(x, y);
}
}
private void computeJavadoc() {
if (!TypeUtility.exists(m_currentMember)) {
return;
}
Job j = new Job("calculating java doc of '" + m_currentMember.getElementName() + "'") {
@Override
protected IStatus run(IProgressMonitor monitor) {
Reader contentReader = null;
try {
if (TypeUtility.exists(m_currentMember)) {
contentReader = JavadocContentAccess.getHTMLContentReader(m_currentMember, true, true);
if (contentReader != null) {
m_javaDoc = getJavadocHtml(new IJavaElement[]{m_currentMember});
if (getSourceControl() != null && !getSourceControl().isDisposed()) {
getSourceControl().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (m_browser != null && !m_browser.isDisposed()) {
m_browser.setText(m_javaDoc);
}
}
});
}
}
}
}
catch (Exception e) {
ScoutSdkUi.logWarning(e);
}
finally {
if (contentReader != null) {
try {
contentReader.close();
}
catch (Exception e) {
}
}
}
return Status.OK_STATUS;
}
};
j.setPriority(Job.DECORATE);
j.setSystem(true);
j.setUser(false);
j.schedule();
}
/**
* Returns the Javadoc in HTML format.
*
* @param result
* the Java elements for which to get the Javadoc
* @param activePart
* the active part if any
* @param selection
* the selection of the active site if any
* @param monitor
* a monitor to report progress to
* @return a string with the Javadoc in HTML format.
*/
private String getJavadocHtml(IJavaElement[] result) {
StringBuffer buffer = new StringBuffer();
int nResults = result.length;
if (nResults == 0) {
return null;
}
String base = null;
if (nResults > 1) {
for (int i = 0; i < result.length; i++) {
HTMLPrinter.startBulletList(buffer);
IJavaElement curr = result[i];
if (curr instanceof IMember || curr.getElementType() == IJavaElement.LOCAL_VARIABLE) {
HTMLPrinter.addBullet(buffer, getInfoText(curr, null, false));
}
HTMLPrinter.endBulletList(buffer);
}
}
else {
IJavaElement curr = result[0];
if (curr instanceof IMember) {
final IMember member = (IMember) curr;
String constantValue = null;
HTMLPrinter.addSmallHeader(buffer, getInfoText(member, constantValue, true));
Reader reader = null;
try {
String content = JavadocHoverUtility.getHtmlContent(member);
reader = content == null ? null : new StringReader(content);
// Provide hint why there's no Javadoc
if (reader == null && member.isBinary()) {
boolean hasAttachedJavadoc = JavaDocLocations.getJavadocBaseLocation(member) != null;
IPackageFragmentRoot root = (IPackageFragmentRoot) member.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
boolean hasAttachedSource = root != null && root.getSourceAttachmentPath() != null;
IOpenable openable = member.getOpenable();
boolean hasSource = openable.getBuffer() != null;
if (!hasAttachedSource && !hasAttachedJavadoc) {
reader = new StringReader("");
}
else if (!hasAttachedJavadoc && !hasSource) {
reader = new StringReader("");
}
else if (!hasAttachedSource) {
reader = new StringReader("");
}
else if (!hasSource) {
reader = new StringReader("");
}
}
else {
base = getBaseURL(member);
}
}
catch (CoreException ex) {
ScoutSdkUi.logError("unable to get javadoc html", ex);
}
if (reader != null) {
HTMLPrinter.addParagraph(buffer, reader);
}
}
else if (curr != null && (curr.getElementType() == IJavaElement.LOCAL_VARIABLE || curr.getElementType() == IJavaElement.TYPE_PARAMETER)) {
HTMLPrinter.addSmallHeader(buffer, getInfoText(curr, null, true));
}
}
boolean flushContent = true;
if (buffer.length() > 0 || flushContent) {
HTMLPrinter.insertPageProlog(buffer, 0, null, null, loadStyleSheet());
if (base != null) {
int endHeadIdx = buffer.indexOf("</head>"); //$NON-NLS-1$
buffer.insert(endHeadIdx, "\n<base href='" + base + "'>\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
return null;
}
/**
* Gets the label for the given member.
*
* @param member
* the Java member
* @param constantValue
* the constant value if any
* @param allowImage
* true if the java element image should be shown
* @return a string containing the member's label
*/
private String getInfoText(IJavaElement member, String constantValue, boolean allowImage) {
StringBuffer label = new StringBuffer(JavaElementLinks.getElementLabel(member, LABEL_FLAGS));
if (member.getElementType() == IJavaElement.FIELD && constantValue != null) {
label.append(constantValue);
}
String imageName = null;
try {
if (allowImage) {
URL imageUrl = JavaPlugin.getDefault().getImagesOnFSRegistry().getImageURL(member);
if (imageUrl != null) {
imageName = imageUrl.toExternalForm();
}
}
}
catch (Exception e) {
ScoutSdkUi.logWarning("could not load image for '" + member.getElementName() + "'.", e);
}
return JavadocHoverUtility.addImageAndLabel(member, imageName, label.toString());
}
private static String loadStyleSheet() {
Bundle bundle = JavaPlugin.getDefault().getBundle();
URL styleSheetURL = bundle.getEntry("/JavadocViewStyleSheet.css"); //$NON-NLS-1$
if (styleSheetURL == null) {
return null;
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(styleSheetURL.openStream()));
StringBuilder buffer = new StringBuilder(3000);
String line = reader.readLine();
while (line != null) {
buffer.append(line);
buffer.append('\n');
line = reader.readLine();
}
FontData fontData = JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
return HTMLPrinter.convertTopLevelFont(buffer.toString(), fontData);
}
catch (IOException ex) {
ScoutSdkUi.logError("unable to load javadoc stylesheet", ex);
return null;
}
finally {
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
}
}
}
}
/**
* Method copied from org.eclipse.jdt.internal.corext.javadoc.JavaDocLocations
*/
public static String getBaseURL(IMember element) throws JavaModelException {
if (element.isBinary()) {
// Source attachment usually does not include Javadoc resources
// => Always use the Javadoc location as base:
URL baseURL = JavaUI.getJavadocLocation(element, false);
if (baseURL != null) {
if ("jar".equals(baseURL.getProtocol())) {
// It's a JarURLConnection, which is not known to the browser widget.
// Let's start the help web server:
URL baseURL2 = PlatformUI.getWorkbench().getHelpSystem().resolve(baseURL.toExternalForm(), true);
if (baseURL2 != null) { // can be null if org.eclipse.help.ui is not available
baseURL = baseURL2;
}
}
return baseURL.toExternalForm();
}
}
else {
IResource resource = element.getResource();
if (resource != null) {
/*
* Too bad: Browser widget knows nothing about EFS and custom URL handlers,
* so IResource#getLocationURI() does not work in all cases.
* We only support the local file system for now.
* A solution could be https://bugs.eclipse.org/bugs/show_bug.cgi?id=149022 .
*/
IPath location = resource.getLocation();
if (location != null) {
return location.toFile().toURI().toString();
}
}
}
return null;
}
}