/******************************************************************************* * Copyright (c) 2000, 2017 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: * xored software, Inc. - initial API and Implementation (Andrei Sobolev) * xored software, Inc. - Patch 228846 (Alex Panchenko <alex@xored.com>) *******************************************************************************/ package org.eclipse.dltk.ui.infoviews; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IMember; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.ScriptUtils; import org.eclipse.dltk.internal.ui.editor.EditorUtility; import org.eclipse.dltk.internal.ui.editor.ScriptEditor; import org.eclipse.dltk.internal.ui.text.HTMLTextPresenter; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.eclipse.dltk.ui.PreferenceConstants; import org.eclipse.dltk.ui.ScriptElementLabels; import org.eclipse.dltk.ui.documentation.ScriptDocumentationAccess; import org.eclipse.dltk.ui.text.completion.HTMLPrinter; 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.DefaultInformationControl; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.TextSelection; 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.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds; import org.osgi.framework.Bundle; /** * Abstract view which shows documentation for a given model element. */ public abstract class AbstractDocumentationView 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 = "AbstractDocumentationView.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 = ScriptElementLabels.ALL_FULLY_QUALIFIED | ScriptElementLabels.M_APP_RETURNTYPE | ScriptElementLabels.F_APP_TYPE_SIGNATURE | ScriptElementLabels.M_PARAMETER_TYPES | ScriptElementLabels.M_PARAMETER_NAMES | ScriptElementLabels.M_EXCEPTIONS | ScriptElementLabels.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; /** The Browser widget */ private boolean fIsUsingBrowserWidget; private RGB fBackgroundColorRGB, fForegroundColorRGB; /** * 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$ 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. */ @Override 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<ISelectionChangedListener> fListeners = new ListenerList<>( ListenerList.IDENTITY); /** The widget. */ private Control fControl; /** * Creates a new selection provider. * * @param control * the widget */ public SelectionProvider(Control control) { fControl = control; if (fControl instanceof StyledText) { ((StyledText) fControl) .addSelectionListener(new SelectionAdapter() { @Override 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); for (ISelectionChangedListener listener : fListeners) listener.selectionChanged(event); } @Override public void addSelectionChangedListener( ISelectionChangedListener listener) { fListeners.add(listener); } @Override 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; } } @Override public void removeSelectionChangedListener( ISelectionChangedListener listener) { fListeners.remove(listener); } @Override public void setSelection(ISelection selection) { // not supported } } @Override 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 = this.getPreferenceStore();// JavaPlugin.getDefault().getPreferenceStore(); boolean doNotWarn = store.getBoolean(DO_NOT_WARN_PREFERENCE_KEY); if (WARNING_DIALOG_ENABLED && !doNotWarn) { String title = InfoViewMessages.ScriptdocView_error_noBrowser_title; String message = InfoViewMessages.ScriptdocView_error_noBrowser_message; String toggleMessage = InfoViewMessages.ScriptdocView_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() { @Override public void controlResized(ControlEvent e) { setInput(fText.getText()); } }); } initStyleSheet(); getViewSite().setSelectionProvider(new SelectionProvider(getControl())); } protected abstract IPreferenceStore getPreferenceStore(); protected abstract String getNature(); private static void initStyleSheet() { Bundle bundle = Platform.getBundle(DLTKUIPlugin.getPluginId()); URL styleSheetURL = bundle.getEntry("/DocumentationViewStyleSheet.css"); //$NON-NLS-1$ if (styleSheetURL == null) return; try { styleSheetURL = FileLocator.toFileURL(styleSheetURL); BufferedReader reader = new BufferedReader( new InputStreamReader(styleSheetURL.openStream())); StringBuffer buffer = new StringBuffer(200); String line = reader.readLine(); while (line != null) { buffer.append(line); buffer.append('\n'); line = reader.readLine(); } FontData fontData = JFaceResources.getFontRegistry().getFontData( PreferenceConstants.APPEARANCE_DOCUMENTATION_FONT)[0]; fgStyleSheet = org.eclipse.dltk.ui.text.completion.HTMLPrinter .convertTopLevelFont(buffer.toString(), fontData); } catch (IOException ex) { DLTKUIPlugin.log(ex); } } @Override protected void createActions() { super.createActions(); fSelectAllAction = new SelectAllAction(getControl(), (SelectionProvider) getSelectionProvider()); } @Override protected IAction getSelectAllAction() { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 if (fIsUsingBrowserWidget) return null; return fSelectAllAction; } @Override protected IAction getCopyToClipboardAction() { // FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022 if (fIsUsingBrowserWidget) return null; return super.getCopyToClipboardAction(); } @Override protected void setForeground(Color color) { getControl().setForeground(color); fForegroundColorRGB = color.getRGB(); } @Override protected void setBackground(Color color) { getControl().setBackground(color); // Apply style sheet fBackgroundColorRGB = color.getRGB(); if (getInput() == null) { StringBuffer buffer = new StringBuffer(); HTMLPrinter.insertPageProlog(buffer, 0, fBackgroundColorRGB, fForegroundColorRGB, fgStyleSheet); setInput(buffer.toString()); } else { setInput(computeInput(getInput())); } } @Override protected String getBackgroundColorKey() { return "org.eclipse.dltk.ui.ScriptdocView.backgroundColor"; //$NON-NLS-1$ } @Override protected void internalDispose() { fText = null; fBrowser = null; } @Override public void setFocus() { getControl().setFocus(); } @Override protected Object computeInput(Object input) { if (getControl() != null) { if (input instanceof KeywordInput) { return getScriptdocHtml((KeywordInput) input); } else if (input instanceof ModelElementArray) { final ModelElementArray array = (ModelElementArray) input; return getScriptdocHtmlDetailed(array.getElements()); } else if (input instanceof IModelElement) { final IModelElement je = (IModelElement) input; switch (je.getElementType()) { case IModelElement.SOURCE_MODULE: try { final ISourceModule module = (ISourceModule) je; return getScriptdocHtmlList(module.getChildren()); } catch (ModelException ex) { return null; } default: return getScriptdocHtml(je); } } } return null; } @Override 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 doc in HTML format. * * @param result * the Script elements for which to get the Javadoc * @return a string with the Javadoc in HTML format. */ private String getScriptdocHtml(KeywordInput keyword) { StringBuffer buffer = new StringBuffer(); try { Reader reader = ScriptDocumentationAccess.getKeywordDocumentation( getNature(), keyword.getContext(), keyword.getValue()); if (reader != null) { HTMLPrinter.addParagraph(buffer, reader); } } catch (ModelException ex) { DLTKUIPlugin.log(ex); return null; } if (buffer.length() > 0) { HTMLPrinter.insertPageProlog(buffer, 0, fgStyleSheet); HTMLPrinter.addPageEpilog(buffer); return buffer.toString(); } return null; } /** * Returns the Javadoc in HTML format. * * @param result * the Script elements for which to get the Javadoc * @return a string with the Javadoc in HTML format. */ private String getScriptdocHtmlDetailed(Object[] result) { final StringBuffer buffer = new StringBuffer(); final List<String> nodocs = new ArrayList<>(); for (int i = 0; i < result.length; i++) { final Object member = result[i]; Reader reader = ScriptDocumentationAccess .getHTMLContentReader(getNature(), member, true, true); if (reader != null) { buffer.append("<b>"); //$NON-NLS-1$ buffer.append(getInfoText(member)); buffer.append("</b>"); //$NON-NLS-1$ HTMLPrinter.addParagraph(buffer, reader); } else { if (member instanceof IModelElement) { nodocs.add(ScriptElementLabels.getDefault().getElementLabel( (IModelElement) member, LABEL_FLAGS | ScriptElementLabels.APPEND_FILE)); } else { // TODO } } } if (!nodocs.isEmpty()) { Collections.sort(nodocs); HTMLPrinter.addParagraph(buffer, InfoViewMessages.ScriptdocView_noAttachedInformationHeader); HTMLPrinter.startBulletList(buffer); for (String s : nodocs) { HTMLPrinter.addBullet(buffer, s); } HTMLPrinter.endBulletList(buffer); } return addPrologeEpilog(buffer); } /** * Returns the Javadoc in HTML format. * * @param result * the Script elements for which to get the Javadoc * @return a string with the Javadoc in HTML format. */ private String getScriptdocHtmlList(IModelElement[] result) { final StringBuffer buffer = new StringBuffer(); HTMLPrinter.startBulletList(buffer); for (int i = 0; i < result.length; i++) { final IModelElement curr = result[i]; if (curr instanceof IMember) { final IMember member = (IMember) curr; HTMLPrinter.addBullet(buffer, getInfoText(member)); } } HTMLPrinter.endBulletList(buffer); return addPrologeEpilog(buffer); } /** * Returns the Javadoc in HTML format. * * @param result * the Script elements for which to get the Javadoc * @return a string with the Javadoc in HTML format. */ private String getScriptdocHtml(IModelElement curr) { StringBuffer buffer = new StringBuffer(); if (curr instanceof IMember) { IMember member = (IMember) curr; // HTMLPrinter.addSmallHeader(buffer, getInfoText(member)); Reader reader = ScriptDocumentationAccess .getHTMLContentReader(getNature(), member, true, true); if (reader != null) { HTMLPrinter.addParagraph(buffer, reader); } else { // Provide hint why there's no Javadoc HTMLPrinter.addParagraph(buffer, InfoViewMessages.ScriptdocView_noAttachedInformation); } } return addPrologeEpilog(buffer); } private String addPrologeEpilog(StringBuffer buffer) { if (buffer.length() > 0) { HTMLPrinter.insertPageProlog(buffer, 0, fBackgroundColorRGB, fForegroundColorRGB, fgStyleSheet); HTMLPrinter.addPageEpilog(buffer); return buffer.toString(); } return null; } /** * The method is overridden to compare {@link ScriptEditor} nature with the * nature of this view. * * @see org.eclipse.dltk.ui.infoviews.AbstractInfoView#isValidWorkbenchPart(org.eclipse.ui.IWorkbenchPart) */ @Override protected boolean isValidWorkbenchPart(IWorkbenchPart part) { if (part instanceof IEditorPart) { final IEditorPart editor = (IEditorPart) part; final String editorNature = ScriptUtils.getNatureId(editor); if (editorNature != null) { return editorNature.equals(getNature()); } ISourceModule sourceModule = EditorUtility .getEditorInputModelElement(editor, false); if (sourceModule != null) { final IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(sourceModule); return toolkit != null && toolkit.getNatureId().equals(getNature()); } } return false; } /** * Gets the label for the given member. * * @param member * the Script member * @return a string containing the member's label */ private String getInfoText(Object member) { if (member instanceof IModelElement) { return ScriptElementLabels.getDefault() .getElementLabel((IModelElement) member, LABEL_FLAGS); } else { // TODO return null; } } @Override protected Control getControl() { if (fIsUsingBrowserWidget) return fBrowser; else return fText; } @Override protected String getHelpContextId() { // return IJavaHelpContextIds.JAVADOC_VIEW; // TODO: add help support here return ""; //$NON-NLS-1$ } }