/******************************************************************************* * Copyright (c) 2000, 2015 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.wst.jsdt.internal.ui.infoviews; 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.runtime.Assert; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.MessageDialogWithToggle; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPartitioningException; import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.wst.jsdt.core.IClassFile; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.ILocalVariable; import org.eclipse.wst.jsdt.core.IMember; import org.eclipse.wst.jsdt.core.IOpenable; import org.eclipse.wst.jsdt.core.IPackageFragmentRoot; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.internal.corext.javadoc.JavaDocLocations; import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.wst.jsdt.internal.ui.text.html.HTMLPrinter; import org.eclipse.wst.jsdt.internal.ui.text.html.HTMLTextPresenter; import org.eclipse.wst.jsdt.ui.JSdocContentAccess; import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels; import org.eclipse.wst.jsdt.ui.PreferenceConstants; import org.eclipse.wst.jsdt.ui.text.IJavaScriptPartitions; import org.osgi.framework.Bundle; /** * View which shows Javadoc for a given Java element. * * FIXME: As of 3.0 selectAll() and getSelection() is not working * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 * * */ public class JavadocView extends AbstractInfoView { /** * Preference key for the preference whether to show a dialog * when the SWT Browser widget is not available. * */ private static final String DO_NOT_WARN_PREFERENCE_KEY= "JavadocView.error.doNotWarn"; //$NON-NLS-1$ // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=73558 private static final boolean WARNING_DIALOG_ENABLED= false; /** Flags used to render a label in the text widget. */ private static final long LABEL_FLAGS= JavaScriptElementLabels.ALL_FULLY_QUALIFIED | JavaScriptElementLabels.M_PRE_RETURNTYPE | JavaScriptElementLabels.M_PARAMETER_TYPES | JavaScriptElementLabels.M_PARAMETER_NAMES | JavaScriptElementLabels.M_EXCEPTIONS | JavaScriptElementLabels.F_PRE_TYPE_SIGNATURE | JavaScriptElementLabels.T_TYPE_PARAMETERS; /** The HTML widget. */ private Browser fBrowser; /** The text widget. */ private StyledText fText; /** The information presenter. */ private DefaultInformationControl.IInformationPresenter fPresenter; /** The text presentation. */ private TextPresentation fPresentation= new TextPresentation(); /** The select all action */ private SelectAllAction fSelectAllAction; /** The style sheet (css) */ private static String fgStyleSheet; /** * <code>true</code> once the style sheet has been loaded. * */ private static boolean fgStyleSheetLoaded= false; /** The Browser widget */ private boolean fIsUsingBrowserWidget; private RGB fBackgroundColorRGB; /** * The font listener. * */ private IPropertyChangeListener fFontListener; /** * The Javadoc view's select all action. */ private class SelectAllAction extends Action { /** The control. */ private Control fControl; /** The selection provider. */ private SelectionProvider fSelectionProvider; /** * Creates the action. * * @param control the widget * @param selectionProvider the selection provider */ public SelectAllAction(Control control, SelectionProvider selectionProvider) { super("selectAll"); //$NON-NLS-1$ Assert.isNotNull(control); Assert.isNotNull(selectionProvider); fControl= control; fSelectionProvider= selectionProvider; // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 setEnabled(!fIsUsingBrowserWidget); setText(InfoViewMessages.SelectAllAction_label); setToolTipText(InfoViewMessages.SelectAllAction_tooltip); setDescription(InfoViewMessages.SelectAllAction_description); PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IAbstractTextEditorHelpContextIds.SELECT_ALL_ACTION); } /** * Selects all in the view. */ public void run() { if (fControl instanceof StyledText) ((StyledText)fControl).selectAll(); else { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 // ((Browser)fControl).selectAll(); if (fSelectionProvider != null) fSelectionProvider.fireSelectionChanged(); } } } /** * The Javadoc view's selection provider. */ private static class SelectionProvider implements ISelectionProvider { /** The selection changed listeners. */ private ListenerList fListeners= new ListenerList(ListenerList.IDENTITY); /** The widget. */ private Control fControl; /** * Creates a new selection provider. * * @param control the widget */ public SelectionProvider(Control control) { Assert.isNotNull(control); fControl= control; if (fControl instanceof StyledText) { ((StyledText)fControl).addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { fireSelectionChanged(); } }); } else { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 // ((Browser)fControl).addSelectionListener(new SelectionAdapter() { // public void widgetSelected(SelectionEvent e) { // fireSelectionChanged(); // } // }); } } /** * Sends a selection changed event to all listeners. */ public void fireSelectionChanged() { ISelection selection= getSelection(); SelectionChangedEvent event= new SelectionChangedEvent(this, selection); Object[] selectionChangedListeners= fListeners.getListeners(); for (int i= 0; i < selectionChangedListeners.length; i++) ((ISelectionChangedListener)selectionChangedListeners[i]).selectionChanged(event); } /* * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ public void addSelectionChangedListener(ISelectionChangedListener listener) { fListeners.add(listener); } /* * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() */ public ISelection getSelection() { if (fControl instanceof StyledText) { IDocument document= new Document(((StyledText)fControl).getSelectionText()); return new TextSelection(document, 0, document.getLength()); } else { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 return StructuredSelection.EMPTY; } } /* * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ public void removeSelectionChangedListener(ISelectionChangedListener listener) { fListeners.remove(listener); } /* * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) */ public void setSelection(ISelection selection) { // not supported } } /* * @see AbstractInfoView#internalCreatePartControl(Composite) */ protected void internalCreatePartControl(Composite parent) { try { fBrowser= new Browser(parent, SWT.NONE); fIsUsingBrowserWidget= true; } catch (SWTError er) { /* The Browser widget throws an SWTError if it fails to * instantiate properly. Application code should catch * this SWTError and disable any feature requiring the * Browser widget. * Platform requirements for the SWT Browser widget are available * from the SWT FAQ web site. */ IPreferenceStore store= JavaScriptPlugin.getDefault().getPreferenceStore(); boolean doNotWarn= store.getBoolean(DO_NOT_WARN_PREFERENCE_KEY); if (WARNING_DIALOG_ENABLED && !doNotWarn) { String title= InfoViewMessages.JavadocView_error_noBrowser_title; String message= InfoViewMessages.JavadocView_error_noBrowser_message; String toggleMessage= InfoViewMessages.JavadocView_error_noBrowser_doNotWarn; MessageDialogWithToggle dialog= MessageDialogWithToggle.openError(parent.getShell(), title, message, toggleMessage, false, null, null); if (dialog.getReturnCode() == Window.OK) store.setValue(DO_NOT_WARN_PREFERENCE_KEY, dialog.getToggleState()); } fIsUsingBrowserWidget= false; } if (!fIsUsingBrowserWidget) { fText= new StyledText(parent, SWT.V_SCROLL | SWT.H_SCROLL); fText.setEditable(false); fPresenter= new HTMLTextPresenter(false); fText.addControlListener(new ControlAdapter() { /* * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent) */ public void controlResized(ControlEvent e) { setInput(fText.getText()); } }); } initStyleSheet(); listenForFontChanges(); getViewSite().setSelectionProvider(new SelectionProvider(getControl())); } /** * Registers a listener for the Java editor font. * * */ private void listenForFontChanges() { fFontListener= new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (PreferenceConstants.APPEARANCE_JAVADOC_FONT.equals(event.getProperty())) { fgStyleSheetLoaded= false; // trigger reloading, but make sure other listeners have already run, so that // the style sheet gets reloaded only once. final Display display= getSite().getPage().getWorkbenchWindow().getWorkbench().getDisplay(); if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { if (!display.isDisposed()) { initStyleSheet(); refresh(); } } }); } } } }; JFaceResources.getFontRegistry().addListener(fFontListener); } private static void initStyleSheet() { if (fgStyleSheetLoaded) return; fgStyleSheetLoaded= true; fgStyleSheet= loadStyleSheet(); } private static String loadStyleSheet() { Bundle bundle= Platform.getBundle(JavaScriptPlugin.getPluginId()); URL styleSheetURL= bundle.getEntry("/JavadocViewStyleSheet.css"); //$NON-NLS-1$ if (styleSheetURL == null) return null; try { styleSheetURL= FileLocator.toFileURL(styleSheetURL); StringBuffer buffer= new StringBuffer(200); BufferedReader reader= new BufferedReader(new InputStreamReader(styleSheetURL.openStream())); try { String line= reader.readLine(); while (line != null) { buffer.append(line); buffer.append('\n'); line= reader.readLine(); } } finally { reader.close(); } FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; return HTMLPrinter.convertTopLevelFont(buffer.toString(), fontData); } catch (IOException ex) { JavaScriptPlugin.log(ex); return null; } } /* * @see AbstractInfoView#createActions() */ protected void createActions() { super.createActions(); fSelectAllAction= new SelectAllAction(getControl(), (SelectionProvider)getSelectionProvider()); } /* * @see org.eclipse.wst.jsdt.internal.ui.infoviews.AbstractInfoView#getSelectAllAction() * */ protected IAction getSelectAllAction() { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 if (fIsUsingBrowserWidget) return null; return fSelectAllAction; } /* * @see org.eclipse.wst.jsdt.internal.ui.infoviews.AbstractInfoView#getCopyToClipboardAction() * */ protected IAction getCopyToClipboardAction() { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 if (fIsUsingBrowserWidget) return null; return super.getCopyToClipboardAction(); } /* * @see AbstractInfoView#setForeground(Color) */ protected void setForeground(Color color) { getControl().setForeground(color); } /* * @see AbstractInfoView#setBackground(Color) */ protected void setBackground(Color color) { getControl().setBackground(color); fBackgroundColorRGB= color.getRGB(); refresh(); } /** * Refreshes the view. * * */ private void refresh() { IJavaScriptElement input= getInput(); if (input == null) { StringBuffer buffer= new StringBuffer(""); //$NON-NLS-1$ HTMLPrinter.insertPageProlog(buffer, 0, fBackgroundColorRGB, fgStyleSheet); setInput(buffer.toString()); } else { setInput(computeInput(input)); } } /* * @see org.eclipse.wst.jsdt.internal.ui.infoviews.AbstractInfoView#getBackgroundColorKey() * */ protected String getBackgroundColorKey() { return "org.eclipse.wst.jsdt.ui.JavadocView.backgroundColor"; //$NON-NLS-1$ } /* * @see AbstractInfoView#internalDispose() */ protected void internalDispose() { fText= null; fBrowser= null; if (fFontListener != null) { JFaceResources.getFontRegistry().removeListener(fFontListener); fFontListener= null; } } /* * @see org.eclipse.ui.part.WorkbenchPart#setFocus() */ public void setFocus() { getControl().setFocus(); } /* * @see AbstractInfoView#computeInput(Object) */ protected Object computeInput(Object input) { if (getControl() == null || ! (input instanceof IJavaScriptElement)) return null; IJavaScriptElement je= (IJavaScriptElement)input; String javadocHtml; switch (je.getElementType()) { case IJavaScriptElement.JAVASCRIPT_UNIT: try { javadocHtml= getJavadocHtml(((IJavaScriptUnit)je).getTypes()); } catch (JavaScriptModelException ex) { javadocHtml= null; } break; case IJavaScriptElement.CLASS_FILE: javadocHtml= getJavadocHtml(new IJavaScriptElement[] {((IClassFile)je).getType()}); break; default: javadocHtml= getJavadocHtml(new IJavaScriptElement[] { je }); } if (javadocHtml == null) return ""; //$NON-NLS-1$ return javadocHtml; } /* * @see AbstractInfoView#setInput(Object) */ protected void setInput(Object input) { String javadocHtml= (String)input; if (fIsUsingBrowserWidget) { if (javadocHtml != null && javadocHtml.length() > 0) { boolean RTL= (getSite().getShell().getStyle() & SWT.RIGHT_TO_LEFT) != 0; if (RTL) { StringBuffer buffer= new StringBuffer(javadocHtml); HTMLPrinter.insertStyles(buffer, new String[] { "direction:rtl" } ); //$NON-NLS-1$ javadocHtml= buffer.toString(); } } fBrowser.setText(javadocHtml); } else { fPresentation.clear(); Rectangle size= fText.getClientArea(); try { javadocHtml= ((DefaultInformationControl.IInformationPresenterExtension)fPresenter).updatePresentation(getSite().getShell(), javadocHtml, fPresentation, size.width, size.height); } catch (IllegalArgumentException ex) { // the javadoc might no longer be valid return; } fText.setText(javadocHtml); TextPresentation.applyTextPresentation(fPresentation, fText); } } /** * Returns the Javadoc in HTML format. * * @param result the Java elements for which to get the Javadoc * @return a string with the Javadoc in HTML format. */ private String getJavadocHtml(IJavaScriptElement[] result) { StringBuffer buffer= new StringBuffer(); int nResults= result.length; if (nResults == 0) return null; if (nResults > 1) { for (int i= 0; i < result.length; i++) { HTMLPrinter.startBulletList(buffer); IJavaScriptElement curr= result[i]; if (curr instanceof IMember) HTMLPrinter.addBullet(buffer, getInfoText((IMember) curr)); HTMLPrinter.endBulletList(buffer); } } else { IJavaScriptElement curr= result[0]; if (curr instanceof IMember) { IMember member= (IMember) curr; // HTMLPrinter.addSmallHeader(buffer, getInfoText(member)); Reader reader; try { reader= JSdocContentAccess.getHTMLContentReader(member, true, true); // Provide hint why there's no Javadoc if (reader == null && member.isBinary()) { boolean hasAttachedJavadoc= JavaDocLocations.getJavadocBaseLocation(member) != null; IPackageFragmentRoot root= (IPackageFragmentRoot)member.getAncestor(IJavaScriptElement.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(InfoViewMessages.JavadocView_noAttachments); else if (!hasAttachedJavadoc && !hasSource) reader= new StringReader(InfoViewMessages.JavadocView_noAttachedJavadoc); else if (!hasAttachedSource) reader= new StringReader(InfoViewMessages.JavadocView_noAttachedSource); else if (!hasSource) reader= new StringReader(InfoViewMessages.JavadocView_noInformation); } } catch (JavaScriptModelException ex) { reader= new StringReader(InfoViewMessages.JavadocView_error_gettingJavadoc); JavaScriptPlugin.log(ex.getStatus()); } if (reader != null) { HTMLPrinter.addParagraph(buffer, reader); } } else if (curr != null && curr.getElementType() == IJavaScriptElement.LOCAL_VARIABLE) { Reader reader = null; try { reader= JSdocContentAccess.getHTMLContentReader((ILocalVariable)curr, false, true); } catch (JavaScriptModelException e) { reader= new StringReader(InfoViewMessages.JavadocView_error_gettingJavadoc); JavaScriptPlugin.log(e.getStatus()); } if (reader != null) { HTMLPrinter.addParagraph(buffer, reader); } else { HTMLPrinter.addSmallHeader(buffer, InfoViewMessages.JavadocView_noInformation); } } } boolean flushContent= true; if (buffer.length() > 0 || flushContent) { HTMLPrinter.insertPageProlog(buffer, 0, fBackgroundColorRGB, fgStyleSheet); HTMLPrinter.addPageEpilog(buffer); return buffer.toString(); } return null; } /** * Gets the label for the given member. * * @param member the Java member * @return a string containing the member's label */ private String getInfoText(IMember member) { return JavaScriptElementLabels.getElementLabel(member, LABEL_FLAGS); } /* * @see org.eclipse.wst.jsdt.internal.ui.infoviews.AbstractInfoView#isIgnoringNewInput(org.eclipse.wst.jsdt.core.IJavaScriptElement, org.eclipse.jface.viewers.ISelection) * */ protected boolean isIgnoringNewInput(IJavaScriptElement je, IWorkbenchPart part, ISelection selection) { if (super.isIgnoringNewInput(je, part, selection) && part instanceof ITextEditor && selection instanceof ITextSelection) { ITextEditor editor= (ITextEditor)part; IDocumentProvider docProvider= editor.getDocumentProvider(); if (docProvider == null) return false; IDocument document= docProvider.getDocument(editor.getEditorInput()); if (!(document instanceof IDocumentExtension3)) return false; try { int offset= ((ITextSelection)selection).getOffset(); String partition= ((IDocumentExtension3)document).getContentType(IJavaScriptPartitions.JAVA_PARTITIONING, offset, false); return partition != IJavaScriptPartitions.JAVA_DOC; } catch (BadPartitioningException ex) { return false; } catch (BadLocationException ex) { return false; } } return false; } /* * @see AbstractInfoView#findSelectedJavaElement(IWorkbenchPart) */ protected IJavaScriptElement findSelectedJavaElement(IWorkbenchPart part, ISelection selection) { IJavaScriptElement element; try { element= super.findSelectedJavaElement(part, selection); if (element == null && part instanceof JavaEditor && selection instanceof ITextSelection) { JavaEditor editor= (JavaEditor)part; ITextSelection textSelection= (ITextSelection)selection; IDocumentProvider documentProvider= editor.getDocumentProvider(); if (documentProvider == null) return null; IDocument document= documentProvider.getDocument(editor.getEditorInput()); if (document == null) return null; ITypedRegion typedRegion= TextUtilities.getPartition(document, IJavaScriptPartitions.JAVA_PARTITIONING, textSelection.getOffset(), false); if (IJavaScriptPartitions.JAVA_DOC.equals(typedRegion.getType())) return TextSelectionConverter.getElementAtOffset((JavaEditor)part, textSelection); else return null; } else return element; } catch (JavaScriptModelException e) { return null; } catch (BadLocationException e) { return null; } } /* * @see AbstractInfoView#getControl() */ protected Control getControl() { if (fIsUsingBrowserWidget) return fBrowser; else return fText; } /* * @see org.eclipse.wst.jsdt.internal.ui.infoviews.AbstractInfoView#getHelpContextId() * */ protected String getHelpContextId() { return IJavaHelpContextIds.JAVADOC_VIEW; } }