org.isandlatech.plugins.rest.editor.ui.tooltip.RestInformationPresenter.java Source code

Java tutorial

Introduction

Here is the source code for org.isandlatech.plugins.rest.editor.ui.tooltip.RestInformationPresenter.java

Source

/*******************************************************************************
 * Copyright (c) 2011 isandlaTech, Thomas Calmant
 * 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:
 *    Thomas Calmant (isandlaTech) - initial API and implementation
 *******************************************************************************/

package org.isandlatech.plugins.rest.editor.ui.tooltip;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter;
import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenterExtension;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Drawable;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.EditorsUI;
import org.isandlatech.plugins.rest.RestPlugin;
import org.isandlatech.plugins.rest.editor.userassist.IInternalLinkListener;
import org.isandlatech.plugins.rest.editor.userassist.InternalHoverData;

/**
 * Tool tip presenter. Converts pseudo HTML text into a styled text
 * presentation.
 * 
 * @author Thomas Calmant
 */
public class RestInformationPresenter implements IInformationPresenter, IInformationPresenterExtension {

    /** Marker of attribute assignment */
    protected final static char ATTRIBUTE_ASSIGNMENT_MARKER = '=';

    /** Marker of a long attribute value (with spaces) */
    protected final static char LONG_ATTRIBUTE_VALUE_MARKER = '"';

    /** Text editor font (from platform preferences) */
    private static Font sEditorFont;

    /** Beginning of an HTML tag */
    protected final static char TAG_BEGIN = '<';

    /** End of an HTML tag */
    protected final static char TAG_END = '>';

    /** Marker of the end of an HTML tag zone */
    protected final static char TAG_END_MARKER = '/';

    /**
     * Prepares an information control creator.
     * 
     * @param aListener
     *            A link listener. Document and Region data will be null.
     * 
     * @return A new information control creator
     */
    public static IInformationControlCreator getCreator(final IInternalLinkListener aListener) {

        return getCreator(new InternalHoverData(aListener, null, null, false));
    }

    /**
     * Prepares an information control creator.
     * 
     * @param aAssociatedData
     *            Tool tip internal data
     * @return A new information control creator
     */
    public static IInformationControlCreator getCreator(final InternalHoverData aAssociatedData) {

        return new IInformationControlCreator() {

            @Override
            public IInformationControl createInformationControl(final Shell aParent) {

                // Prepare the presenter
                final RestInformationPresenter restPresenter = new RestInformationPresenter(aAssociatedData);

                // Prepare the information control
                final IInformationControl informationControl = new DefaultInformationControl(aParent,
                        EditorsUI.getTooltipAffordanceString(), restPresenter);

                // Link the presenter and the information control
                restPresenter.setInformationControl(informationControl);

                return informationControl;
            }
        };
    }

    /** The presented information control */
    private IInformationControl pInformationControl;

    /** The associated hover data */
    private final InternalHoverData pInternalHoverData;

    /**
     * Sets up the presenter
     * 
     * @param aHoverData
     *            The internal hover data
     */
    public RestInformationPresenter(final InternalHoverData aHoverData) {
        pInternalHoverData = aHoverData;

        if (sEditorFont == null) {
            // Find the editor font (used in pre-formatted text block)
            sEditorFont = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry()
                    .get(JFaceResources.TEXT_FONT);
        }
    }

    /**
     * Extract attributes (format: a=b c="d e")
     * 
     * @param aTagContent
     *            Content of the tag opening area (after the tag name)
     * @return The tag attributes as a map
     */
    protected Map<String, String> extractTagAttributes(final String aTagContent) {

        // Result map
        final Map<String, String> attributes = new HashMap<String, String>();

        // Attribute value offsets
        int valueStartIndex;
        int valueEndIndex;

        int valuationIndex = aTagContent.indexOf(ATTRIBUTE_ASSIGNMENT_MARKER);
        while (valuationIndex != -1) {

            // Find the attribute name start
            int attributeNameStart = aTagContent.lastIndexOf(' ', valuationIndex);
            if (attributeNameStart == -1) {
                // Use tag begin if needed
                attributeNameStart = 0;

            } else {
                // Avoid the found space
                attributeNameStart++;
            }

            if (valuationIndex + 1 >= aTagContent.length()) {
                // If the attribute can't have a value, stop there
                break;
            }

            // Extract the attribute name
            final String attributeName = aTagContent.substring(attributeNameStart, valuationIndex).trim();

            // Find attribute value
            if (aTagContent.charAt(valuationIndex + 1) == LONG_ATTRIBUTE_VALUE_MARKER) {
                // Attribute between quotes
                valueStartIndex = valuationIndex + 2;

                if (valueStartIndex >= aTagContent.length()) {
                    // If the attribute can't have a value, stop there
                    break;
                }

                valueEndIndex = aTagContent.indexOf(LONG_ATTRIBUTE_VALUE_MARKER, valueStartIndex);

            } else {
                // Simple attribute (space separation)
                valueStartIndex = valuationIndex + 1;
                valueEndIndex = aTagContent.indexOf(' ', valueStartIndex);
            }

            // Extract attribute value
            final String attributeValue = aTagContent.substring(valueStartIndex, valueEndIndex);

            // Put it to the attribute map
            attributes.put(attributeName, attributeValue);

            // Next loop
            valuationIndex = aTagContent.indexOf(ATTRIBUTE_ASSIGNMENT_MARKER, valuationIndex + 1);
        }

        return attributes;
    }

    /**
     * Treat single HTML tags (<br />
     * , ...)
     * 
     * @param aBuilder
     *            Result text builder (without HTML tags)
     * @param aTag
     *            Read tag name
     * @return A style range to apply (null if none)
     */
    protected StyleRange handleSingleTag(final StringBuilder aBuilder, final String aTag) {

        final String tagName = aTag.toLowerCase();

        if (tagName.equals("br")) {
            // <br /> are new lines
            aBuilder.append("\n");
        }

        // No style to apply
        return null;
    }

    /**
     * Handle a HTML tag
     * 
     * @param aBuilder
     *            Result text builder
     * @param aTag
     *            Read tag information
     * @return
     */
    protected StyleRange handleTag(final StringBuilder aBuilder, final HtmlTag aTag) {

        final String tagName = aTag.getTagName();

        // Default style attribute
        StyleRange style = new StyleRange();
        style.fontStyle = SWT.BOLD;
        style.start = aTag.getStartOffset();
        style.length = aBuilder.length() - aTag.getStartOffset();

        if (tagName.equals("a")) {
            // Link
            style.data = aTag.getAttributes().get("href");
            style.underline = true;
            style.underlineStyle = SWT.UNDERLINE_LINK;

            return style;

        } else if (tagName.equals("pre")) {
            // Pre-formatted text

            aBuilder.insert(aTag.getStartOffset(), "\n");
            // Don't forget to update the start offset after the inserted lines
            style.start += 1;

            aBuilder.append("\n\n");

            style.font = sEditorFont;
            return style;

        } else if (tagName.equals("h1")) {
            // HTML Title

            // Add an end of line (after title)
            aBuilder.append("\n");

            // Set up the style range
            style.fontStyle = SWT.BOLD;
            return style;

        } else if (tagName.equals("p")) {
            // Append a new line at the end of the paragraph
            aBuilder.append("\n");
            return null;

        } else if (tagName.equals("b")) {
            // Set up the style range
            style.fontStyle = SWT.BOLD;
            return style;

        } else if (tagName.equals("i")) {
            // Set up the style range
            style.fontStyle = SWT.ITALIC;
            return style;

        } else if (tagName.equals("u")) {
            // Set up the style range
            style.fontStyle = SWT.UNDERLINE_SINGLE;
            return style;

        }

        return null;
    }

    /**
     * Converts the given HTML pseudo-code to style range
     * 
     * @param aHtml
     *            A HTML pseudo-code
     * @param aPresentation
     *            The styled text presentation (to be updated)
     * @return The styled text content (without HTML tags)
     */
    protected String html2TextPresentation(final String aHtml, final TextPresentation aPresentation) {

        // Result string builder
        final StringBuilder builder = new StringBuilder(aHtml.length());

        // Styles list
        final List<StyleRange> stylesList = new ArrayList<StyleRange>();

        // Stack of HTML tags
        final Stack<HtmlTag> tagStack = new Stack<HtmlTag>();

        // Parsing area
        int tagStart = aHtml.indexOf(TAG_BEGIN);
        int tagEnd = 0;
        int oldTagEnd = 0;

        if (tagStart != -1) {
            // First tag begins after some text...
            builder.append(aHtml.substring(0, tagStart));
        }

        while (tagStart != -1) {

            // Search the end of the current tag
            tagEnd = aHtml.indexOf(TAG_END, tagStart);

            // End of tag not found, append the "tag" content
            if (tagEnd == -1) {
                tagEnd = tagStart;
                break;
            }

            boolean needsInsertion = false;

            // Extract the tag
            String tag = aHtml.substring(tagStart + 1, tagEnd).trim();

            if (tag.charAt(tag.length() - 1) == TAG_END_MARKER) {
                // Test single tags (<br />, ...)
                tag = tag.substring(0, tag.length() - 1).trim();

                // Apply its handler
                StyleRange style = handleSingleTag(builder, tag);
                if (style != null) {
                    stylesList.add(style);
                }

                needsInsertion = true;

            } else if (tag.charAt(0) == TAG_END_MARKER) {
                // End of a tag area
                tag = tag.substring(1).trim();

                final HtmlTag poppedTag = tagStack.pop();
                if (!tag.equalsIgnoreCase(poppedTag.getTagName())) {

                    RestPlugin.logWarning("Error parsing an HTML styled text - " + tag + " found instead of "
                            + poppedTag.getTagName() + " at " + tagStart);
                    // Stop on first error
                    return null;
                }

                // Append text between the current text index and the current
                // one
                builder.append(aHtml.substring(oldTagEnd, tagStart));

                StyleRange style = handleTag(builder, poppedTag);
                if (style != null) {
                    stylesList.add(style);
                }

            } else {
                // Beginning of a tag area
                HtmlTag foundTag;

                // Extract tag name
                int tagNameEnd = tag.indexOf(' ');

                if (tagNameEnd == -1) {
                    // Simple tag
                    foundTag = new HtmlTag(tag, builder.length());

                } else {
                    // Tag with attributes (and without a space after the '<'
                    foundTag = new HtmlTag(tag.substring(0, tagNameEnd), builder.length());

                    // Read attributes in a map
                    foundTag.getAttributes().putAll(extractTagAttributes(tag.substring(tagNameEnd + 1).trim()));
                }

                tagStack.push(foundTag);
            }

            // Continue to parse...
            tagStart = aHtml.indexOf(TAG_BEGIN, tagEnd);
            oldTagEnd = tagEnd + 1;

            if (needsInsertion && tagStart != -1) {
                // Append text between the current text index and the current
                // one, if necessary
                builder.append(aHtml.substring(oldTagEnd, tagStart));
                needsInsertion = false;
            }
        }

        // Append the end of the string
        builder.append(aHtml.substring(oldTagEnd));

        // Apply computed styles
        for (StyleRange style : stylesList) {
            aPresentation.addStyleRange(style);
        }

        return builder.toString();
    }

    /**
     * Sets the presented information control. Allows the link listener to
     * dispose it.
     * 
     * @param aInformationControl
     *            The presented information control
     */
    public void setInformationControl(final IInformationControl aInformationControl) {

        pInformationControl = aInformationControl;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter
     * #updatePresentation(org.eclipse.swt.widgets.Display, java.lang.String,
     * org.eclipse.jface.text.TextPresentation, int, int)
     */
    @Override
    public String updatePresentation(final Display display, final String hoverInfo,
            final TextPresentation presentation, final int maxWidth, final int maxHeight) {

        return updatePresentation((Drawable) display, hoverInfo, presentation, maxWidth, maxHeight);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.text.DefaultInformationControl.
     * IInformationPresenterExtension
     * #updatePresentation(org.eclipse.swt.graphics.Drawable, java.lang.String,
     * org.eclipse.jface.text.TextPresentation, int, int)
     */
    @Override
    public String updatePresentation(final Drawable aDrawable, final String aHoverInfo,
            final TextPresentation aPresentation, final int aMaxWidth, final int aMaxHeight) {

        if (aDrawable instanceof StyledText) {

            // Set up the link listener
            final StyledTextLinkListener listener = new StyledTextLinkListener(pInformationControl,
                    pInternalHoverData);
            listener.registerTo((StyledText) aDrawable);

            // Convert pseudo-HTML to TextPresentation styles
            return html2TextPresentation(aHoverInfo, aPresentation);
        }

        return null;
    }
}