de.walware.statet.r.internal.ui.rhelp.RHelpInfoHoverCreator.java Source code

Java tutorial

Introduction

Here is the source code for de.walware.statet.r.internal.ui.rhelp.RHelpInfoHoverCreator.java

Source

/*=============================================================================#
 # Copyright (c) 2010-2015 Stephan Wahlbrink (WalWare.de) 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:
 #     Stephan Wahlbrink - initial API and implementation
 #=============================================================================*/

package de.walware.statet.r.internal.ui.rhelp;

import static de.walware.ecommons.ltk.ui.sourceediting.assist.IInfoHover.MODE_FOCUS;
import static org.eclipse.debug.ui.IDebugUIConstants.PREF_DETAIL_PANE_FONT;

import java.net.URI;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler2;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.browser.OpenWindowListener;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.TitleEvent;
import org.eclipse.swt.browser.TitleListener;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.services.IServiceLocator;

import de.walware.ecommons.ui.SharedUIResources;
import de.walware.ecommons.ui.actions.HandlerCollection;
import de.walware.ecommons.ui.actions.HandlerContributionItem;
import de.walware.ecommons.ui.actions.SimpleContributionItem;
import de.walware.ecommons.ui.mpbv.BrowserHandler.IBrowserProvider;
import de.walware.ecommons.ui.mpbv.BrowserHandler.NavigateBackHandler;
import de.walware.ecommons.ui.mpbv.BrowserHandler.NavigateForwardHandler;
import de.walware.ecommons.ui.util.InformationDispatchHandler;
import de.walware.ecommons.ui.util.LayoutUtil;
import de.walware.ecommons.ui.util.PixelConverter;
import de.walware.ecommons.ui.util.UIAccess;

import de.walware.statet.r.core.RCore;
import de.walware.statet.r.ui.RUI;

public class RHelpInfoHoverCreator extends AbstractReusableInformationControlCreator {

    public static final boolean isAvailable(final Composite parent) {
        return BrowserInformationControl.isAvailable(parent);
    }

    public static class Data {

        final Control control;

        final Object helpObject;
        final String httpUrl;

        public Data(final Control control, final Object helpObject, final String httpUrl) {
            this.control = control;
            this.helpObject = helpObject;
            this.httpUrl = httpUrl;
        }

    }

    private final int mode;

    public RHelpInfoHoverCreator(final int mode) {
        this.mode = mode;
    }

    @Override
    protected IInformationControl doCreateInformationControl(final Shell parent) {
        return ((this.mode & MODE_FOCUS) != 0) ? new RHelpInfoControl(parent, this.mode, true)
                : new RHelpInfoControl(parent, this.mode);
    }

}

class RHelpInfoControl extends AbstractInformationControl
        implements IInformationControlExtension2, IPropertyChangeListener, OpenWindowListener, LocationListener,
        ProgressListener, TitleListener, IBrowserProvider {

    /** Action id (command) to navigate one page back. */
    protected static final String NAVIGATE_BACK_ID = IWorkbenchCommandConstants.NAVIGATE_BACK;
    /** Action id (command) to navigate one page forward. */
    protected static final String NAVIGATE_FORWARD_ID = IWorkbenchCommandConstants.NAVIGATE_FORWARD;

    /**
     * Cached scroll bar width and height
     */
    private static Point gScrollBarSize;

    private final int fMode;

    private RHelpLabelProvider fLabelProvider;

    private Composite fContentComposite;
    private Label fTitleImage;
    private StyledText fTitleText;
    private Browser fInfoBrowser;

    private final HandlerCollection fHandlerCollection = new HandlerCollection();

    private boolean fLayoutWorkaround;
    private boolean fLayoutHint;

    private RHelpInfoHoverCreator.Data fInput;
    private boolean fInputChanged;

    private boolean fLoadingCompleted;
    private String fBrowserTitle;

    private boolean fHide;

    RHelpInfoControl(final Shell shell, final int mode) {
        super(shell, ""); //$NON-NLS-1$
        assert ((mode & MODE_FOCUS) == 0);
        fMode = mode;

        JFaceResources.getFontRegistry().addListener(this);
        create();
    }

    RHelpInfoControl(final Shell shell, final int mode, final boolean dummy) {
        super(shell, new ToolBarManager(SWT.FLAT));
        assert ((mode & MODE_FOCUS) != 0);
        fMode = mode;

        create();
    }

    @Override
    public void setInput(final Object input) {
        fInputChanged = true;
        if (input instanceof RHelpInfoHoverCreator.Data) {
            fInput = (RHelpInfoHoverCreator.Data) input;
        } else {
            fInput = null;
        }
    }

    @Override
    public boolean hasContents() {
        return (fInput != null);
    }

    @Override
    protected void createContent(final Composite parent) {
        fContentComposite = new Composite(parent, SWT.NONE) {
            @Override
            public Point computeSize(final int width, final int height, final boolean changed) {
                return super.computeSize(width, height, changed || width != getSize().x);
            }
        };
        fContentComposite.setBackgroundMode(SWT.INHERIT_FORCE);

        final GridLayout gridLayout = LayoutUtil.createCompositeGrid(2);
        gridLayout.horizontalSpacing = (int) ((gridLayout.horizontalSpacing) / 1.5);
        fContentComposite.setLayout(gridLayout);

        final int vIndent = Math.max(1, LayoutUtil.defaultVSpacing() / 4);
        final int hIndent = Math.max(3, LayoutUtil.defaultHSpacing() / 3);

        { // Title image
            fTitleImage = new Label(fContentComposite, SWT.NULL);
            final Image image = SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID);
            fTitleImage.setImage(image);

            final GridData textGd = new GridData(SWT.FILL, SWT.TOP, false, false);
            fTitleText = new StyledText(fContentComposite, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP) {
                @Override
                public Point computeSize(int width, final int height, final boolean changed) {
                    if (!fLayoutHint && width <= 0 && fContentComposite.getSize().x > 0) {
                        width = fContentComposite.getSize().x - LayoutUtil.defaultHMargin()
                                - fTitleImage.getSize().x - LayoutUtil.defaultHSpacing() - 10;
                    }

                    final Point size = super.computeSize(width, -1, true);
                    //               if (width >= 0) {
                    //                  size.x = Math.min(size.x, width);
                    //               }
                    return size;
                }
            };

            fTitleText.setFont(JFaceResources.getDialogFont());
            final GC gc = new GC(fTitleText);
            final FontMetrics fontMetrics = gc.getFontMetrics();
            final GridData imageGd = new GridData(SWT.FILL, SWT.TOP, false, false);
            imageGd.horizontalIndent = hIndent - 2;
            final int textHeight = fontMetrics.getAscent() + fontMetrics.getLeading();
            final int imageHeight = image.getBounds().height;
            final int shift = Math.max(3, (int) ((fontMetrics.getDescent()) / 1.5));
            if (textHeight + shift < imageHeight) {
                imageGd.verticalIndent = vIndent + shift;
                textGd.verticalIndent = vIndent + (imageHeight - textHeight);
            } else {
                imageGd.verticalIndent = vIndent + (textHeight - imageHeight) + shift;
                textGd.verticalIndent = vIndent;
            }
            fTitleImage.setLayoutData(imageGd);
            fTitleText.setLayoutData(textGd);
            fLayoutWorkaround = true;

            gc.dispose();
        }

        fInfoBrowser = new Browser(fContentComposite, SWT.NONE);

        fInfoBrowser.addOpenWindowListener(this);
        fInfoBrowser.addLocationListener(this);
        fInfoBrowser.addProgressListener(this);
        fInfoBrowser.addTitleListener(this);

        fInfoBrowser.addOpenWindowListener(new OpenWindowListener() {
            @Override
            public void open(final WindowEvent event) {
                event.required = true;
            }
        });
        // Disable context menu
        fInfoBrowser.setMenu(new Menu(getShell(), SWT.NONE));

        final GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
        //      gd.widthHint = LayoutUtil.hintWidth(fInfoText, INFO_FONT, 50);
        fInfoBrowser.setLayoutData(gd);

        fInfoBrowser.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(final KeyEvent e) {
                if (e.character == SWT.ESC) {
                    dispose();
                }
            }

            @Override
            public void keyReleased(final KeyEvent e) {
            }
        });

        setBackgroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        setForegroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));

        if ((fMode & MODE_FOCUS) != 0) {
            initActions(fHandlerCollection);
            final ToolBarManager toolBarManager = getToolBarManager();
            contributeToActionBars(PlatformUI.getWorkbench(), toolBarManager, fHandlerCollection);
            toolBarManager.update(true);
        }
        updateInput();
    }

    protected void initActions(final HandlerCollection handlers) {
        {
            final IHandler2 handler = new NavigateBackHandler(this);
            handlers.add(NAVIGATE_BACK_ID, handler);
            //         handlerService.activateHandler(NAVIGATE_BACK_ID, handler);
            //         handlerService.activateHandler(IWorkbenchCommandConstants.NAVIGATE_BACKWARD_HISTORY, handler);
        }
        {
            final IHandler2 handler = new NavigateForwardHandler(this);
            handlers.add(NAVIGATE_FORWARD_ID, handler);
            //         handlerService.activateHandler(NAVIGATE_FORWARD_ID, handler);
            //         handlerService.activateHandler(IWorkbenchCommandConstants.NAVIGATE_FORWARD_HISTORY, handler);
        }
    }

    protected void contributeToActionBars(final IServiceLocator serviceLocator, final ToolBarManager toolBarManager,
            final HandlerCollection handlers) {
        toolBarManager.add(new HandlerContributionItem(new CommandContributionItemParameter(serviceLocator, null,
                NAVIGATE_BACK_ID, HandlerContributionItem.STYLE_PUSH), handlers.get(NAVIGATE_BACK_ID)));
        toolBarManager.add(new HandlerContributionItem(new CommandContributionItemParameter(serviceLocator, null,
                NAVIGATE_FORWARD_ID, HandlerContributionItem.STYLE_PUSH), handlers.get(NAVIGATE_FORWARD_ID)));
        toolBarManager.add(new SimpleContributionItem(RUI.getImageDescriptor(RUI.IMG_OBJ_R_HELP_SEARCH), null,
                "Show in R Help View", "V") {
            @Override
            protected void execute() throws ExecutionException {
                if (UIAccess.isOkToUse(fInfoBrowser)) {
                    try {
                        String url = fInfoBrowser.getUrl();
                        final IWorkbenchPage page = UIAccess.getActiveWorkbenchPage(true);
                        final RHelpView view = (RHelpView) page.showView(RUI.R_HELP_VIEW_ID);
                        final String browseUrl = RCore.getRHelpManager().toHttpUrl(url, null,
                                RHelpUIServlet.BROWSE_TARGET);
                        if (browseUrl != null) {
                            url = browseUrl;
                        }
                        view.openUrl(url, null);
                    } catch (final Exception e) {
                    }
                }
            }
        });
    }

    @Override
    public Browser getBrowser() {
        return fInfoBrowser;
    }

    @Override
    public void showMessage(final IStatus status) {
    }

    @Override
    public void changing(final LocationEvent event) {
        if (event.location.startsWith("http://")) { //$NON-NLS-1$
            try {
                if (RCore.getRHelpManager().isDynamic(new URI(event.location))) {
                    return;
                }
            } catch (final Exception e) {
            }
        }
        if (event.location.equals("about:blank")) { //$NON-NLS-1$
            return;
        }
        event.doit = false;
    }

    @Override
    public void changed(final LocationEvent event) {
        if (!event.top) {
            return;
        }
        final String url = fInfoBrowser.getUrl();
        final Object obj = RCore.getRHelpManager().getContentOfUrl(url);
        updateTitle(obj, fBrowserTitle);
    }

    @Override
    public void changed(final ProgressEvent event) {
        fHandlerCollection.update(null);
    }

    @Override
    public void completed(final ProgressEvent event) {
        fLoadingCompleted = true;
        fHandlerCollection.update(null);
    }

    @Override
    public void changed(final TitleEvent event) {
        String title = event.title;
        if (title == null) {
            title = ""; //$NON-NLS-1$
        } else if (title.startsWith("http://")) { //$NON-NLS-1$
            final int idx = title.lastIndexOf('/');
            if (idx >= 0) {
                title = title.substring(idx + 1);
            }
        }
        fBrowserTitle = title;
    }

    @Override
    public void open(final WindowEvent event) {
        event.required = true;
    }

    @Override
    public void setBackgroundColor(final Color background) {
        super.setBackgroundColor(background);
        fContentComposite.setBackground(background);
        fInfoBrowser.setBackground(background);
    }

    @Override
    public void setForegroundColor(final Color foreground) {
        super.setForegroundColor(foreground);
        fContentComposite.setForeground(foreground);
        fTitleText.setForeground(foreground);
        fInfoBrowser.setForeground(foreground);
    }

    @Override
    public void setSize(final int width, final int height) {
        fInfoBrowser.setRedraw(false); // avoid flickering
        try {
            super.setSize(width, height);
        } finally {
            fInfoBrowser.setRedraw(true);
        }
    }

    @Override
    public Rectangle computeTrim() {
        final Rectangle trim = super.computeTrim();

        final Rectangle textTrim = fInfoBrowser.computeTrim(0, 0, 0, 0);
        if ((fMode & MODE_FOCUS) != 0 && textTrim.width == 0) {
            if (gScrollBarSize == null) {
                final Text text = new Text(fContentComposite, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
                gScrollBarSize = new Point(text.getVerticalBar().getSize().x, text.getHorizontalBar().getSize().y);
                text.dispose();
            }
            textTrim.x = 0;
            textTrim.y = 0;
            textTrim.width = gScrollBarSize.x;
            textTrim.height = gScrollBarSize.y;
        }
        trim.x += textTrim.x;
        trim.y += textTrim.y;
        trim.width += textTrim.width;
        trim.height += textTrim.height;

        return trim;
    }

    @Override
    public Point computeSizeHint() {
        updateInput();
        final Point sizeConstraints = getSizeConstraints();
        final Rectangle trim = computeTrim();

        fTitleText.setFont(JFaceResources.getDialogFont());
        final PixelConverter converter = new PixelConverter(fTitleText);
        int widthHint = converter.convertWidthInCharsToPixels(60);
        final GC gc = new GC(fContentComposite);
        gc.setFont(JFaceResources.getTextFont());
        widthHint = Math.max(widthHint, gc.getFontMetrics().getAverageCharWidth() * 60);
        gc.dispose();

        final int heightHint = fTitleText.getLineHeight() * 12;

        final int widthMax = ((sizeConstraints != null && sizeConstraints.x != SWT.DEFAULT) ? sizeConstraints.x
                : widthHint + 100) - trim.width;
        final int heightMax = ((sizeConstraints != null && sizeConstraints.y != SWT.DEFAULT) ? sizeConstraints.y
                : fTitleText.getLineHeight() * 12) - trim.height;

        final Point size = new Point(widthHint, heightHint);
        size.y += LayoutUtil.defaultVSpacing();
        size.x = Math.max(Math.min(size.x, widthMax), 200) + trim.width;
        size.y = Math.max(Math.min(size.y, heightMax), 100) + trim.height;
        return size;
    }

    @Override
    public Point computeSizeConstraints(final int widthInChars, final int heightInChars) {
        final int width = LayoutUtil.hintWidth(fTitleText, JFaceResources.DIALOG_FONT, widthInChars);
        final int lineHeight = fTitleText.getLineHeight();

        return new Point(width, lineHeight * heightInChars + LayoutUtil.defaultVSpacing());
    }

    @Override
    public void setVisible(final boolean visible) {
        if (visible) {
            fHide = false;
            updateInput();

            final Display display = Display.getCurrent();
            display.timerExec(200, new Runnable() {
                @Override
                public void run() {
                    fLoadingCompleted = true;
                }
            });
            while (!fLoadingCompleted) {
                // Drive the event loop to process the events required to load the browser widget's contents:
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }

            if (fHide) {
                return;
            }

            if (fLayoutWorkaround) {
                fContentComposite.layout(true, true);
                fLayoutWorkaround = false;
            }

            if (Platform.OS_WIN32.equals(SWT.getPlatform())) {
                final Shell shell = getShell();
                if (shell != null) {
                    shell.moveAbove(null);
                }
            }
        } else {
            fHide = true;
        }
        super.setVisible(visible);
    }

    @Override
    public void setFocus() {
        fInfoBrowser.setFocus();
    }

    private void updateInput() {
        if (fInfoBrowser == null || !fInputChanged) {
            return;
        }
        if (fLabelProvider == null) {
            fLabelProvider = new RHelpLabelProvider(
                    RHelpLabelProvider.WITH_TITLE | RHelpLabelProvider.WITH_QUALIFIER | RHelpLabelProvider.HEADER);
        }
        fLoadingCompleted = false;
        fInputChanged = false;
        fBrowserTitle = null;
        updateTitle(fInput.helpObject, null);
        if (fInput != null && fInput.httpUrl != null) {
            String url = fInput.httpUrl;
            if ((fMode & MODE_FOCUS) == 0) { // disable scrollbars
                url += "?style=hover"; //$NON-NLS-1$
            }
            fInfoBrowser.setUrl(url);
        } else {
            fInfoBrowser.setUrl("about:blank"); //$NON-NLS-1$
        }
        if ((fMode & MODE_FOCUS) == 0) {
            setStatusText((fInput.control != null && fInput.control.isFocusControl())
                    ? InformationDispatchHandler.getAffordanceString(fMode)
                    : ""); //$NON-NLS-1$
        }
    }

    private void updateTitle(final Object helpObject, final String alt) {
        if (helpObject != null) {
            final Image image = fLabelProvider.getImage(helpObject);
            fTitleImage.setImage((image != null) ? image
                    : SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID));
            final StyledString styleString = fLabelProvider.getStyledText(helpObject);
            fTitleText.setText(styleString.getString());
            fTitleText.setStyleRanges(styleString.getStyleRanges());
        } else {
            fTitleImage.setImage(SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID));
            fTitleText.setText((alt != null) ? alt : ""); //$NON-NLS-1$
        }
    }

    @Override
    public IInformationControlCreator getInformationPresenterControlCreator() {
        // enriched mode
        return new RHelpInfoHoverCreator(fMode | MODE_FOCUS);
    }

    @Override
    public void propertyChange(final PropertyChangeEvent event) {
        final String property = event.getProperty();
        if (property.equals(PREF_DETAIL_PANE_FONT) || property.equals(JFaceResources.DEFAULT_FONT)) {
            dispose();
        }
    }

    @Override
    public void dispose() {
        JFaceResources.getFontRegistry().removeListener(this);
        fHandlerCollection.dispose();
        super.dispose();
    }

}