org.eclipse.jface.viewers.StyledString.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jface.viewers.StyledString.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.viewers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.TextStyle;

/**
 * A mutable string with styled ranges. All ranges mark substrings of the string
 * and do not overlap. Styles are applied using instances of {@link Styler} to
 * compute the result of {@link #getStyleRanges()}.
 *
 * The styled string can be built in the following two ways:
 * <ul>
 * <li>new strings with stylers can be appended</li>
 * <li>stylers can by applied to ranges of the existing string</li>
 * </ul>
 *
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 *
 * @since 3.4
 */
public class StyledString implements CharSequence {

    /**
     * A styler will be asked to apply its styles to one ore more ranges in the
     * {@link StyledString}.
     *
     */
    public static abstract class Styler {

        /**
         * Applies the styles represented by this object to the given textStyle.
         *
         * @param textStyle
         *            the {@link TextStyle} to modify
         */
        public abstract void applyStyles(TextStyle textStyle);
    }

    /**
     * A built-in styler using the {@link JFacePreferences#QUALIFIER_COLOR}
     * managed in the JFace color registry (See
     * {@link JFaceResources#getColorRegistry()}).
     */
    public static final Styler QUALIFIER_STYLER = createColorRegistryStyler(JFacePreferences.QUALIFIER_COLOR, null);

    /**
     * A built-in styler using the {@link JFacePreferences#COUNTER_COLOR}
     * managed in the JFace color registry (See
     * {@link JFaceResources#getColorRegistry()}).
     */
    public static final Styler COUNTER_STYLER = createColorRegistryStyler(JFacePreferences.COUNTER_COLOR, null);

    /**
     * A built-in styler using the {@link JFacePreferences#DECORATIONS_COLOR}
     * managed in the JFace color registry (See
     * {@link JFaceResources#getColorRegistry()}).
     */
    public static final Styler DECORATIONS_STYLER = createColorRegistryStyler(JFacePreferences.DECORATIONS_COLOR,
            null);

    /**
     * Creates a styler that takes the given foreground and background colors
     * from the JFace color registry.
     *
     * @param foregroundColorName
     *            the color name for the foreground color
     * @param backgroundColorName
     *            the color name for the background color
     *
     * @return the created style
     */
    public static Styler createColorRegistryStyler(String foregroundColorName, String backgroundColorName) {
        return new DefaultStyler(foregroundColorName, backgroundColorName);
    }

    private static final StyleRange[] EMPTY = new StyleRange[0];
    private StringBuilder fBuffer;
    private StyleRunList fStyleRuns;

    /**
     * Creates an empty {@link StyledString}.
     */
    public StyledString() {
        fBuffer = new StringBuilder();
        fStyleRuns = null;
    }

    /**
     * Creates an {@link StyledString} initialized with a string without
     * a style associated.
     *
     * @param string
     *            the string
     */
    public StyledString(String string) {
        this(string, null);
    }

    /**
     * Creates an {@link StyledString} initialized with a string and a
     * style.
     *
     * @param string
     *            the string
     * @param styler
     *            the styler for the string or <code>null</code> to not
     *            associated a styler.
     */
    public StyledString(String string, Styler styler) {
        this();
        append(string, styler);
    }

    /**
     * Returns the string of this {@link StyledString}.
     *
     * @return the current string of this {@link StyledString}.
     */
    public String getString() {
        return fBuffer.toString();
    }

    /**
     * Returns the string of this {@link StyledString}.
     *
     * @return the current string of this {@link StyledString}.
     */
    @Override
    public String toString() {
        return getString();
    }

    /**
     * Returns the length of the string of this {@link StyledString}.
     *
     * @return the length of the current string
     */
    @Override
    public int length() {
        return fBuffer.length();
    }

    /**
     * @since 3.12
     */
    @Override
    public CharSequence subSequence(int start, int end) {
        return fBuffer.subSequence(start, end);
    }

    /**
     * @since 3.12
     */
    @Override
    public char charAt(int index) {
        return fBuffer.charAt(index);
    }

    /**
     * Appends a string to the {@link StyledString}. The appended string
     * will have no associated styler.
     *
     * @param string
     *            the string to append
     * @return returns a reference to this object
     */
    public StyledString append(String string) {
        return append(string, null);
    }

    /**
     * Appends the string representation of the given character array
     * to the {@link StyledString}. The appended
     * character array will have no associated styler.
     *
     * @param chars
     *            the character array to append
     * @return returns a reference to this object
     */
    public StyledString append(char[] chars) {
        return append(chars, null);
    }

    /**
     * Appends the string representation of the given character
     * to the {@link StyledString}. The appended
     * character will have no associated styler.
     *
     * @param ch
     *            the character to append
     * @return returns a reference to this object
     */
    public StyledString append(char ch) {
        return append(String.valueOf(ch), null);
    }

    /**
     * Appends a string with styles to the {@link StyledString}.
     *
     * @param string
     *            the string to append
     * @return returns a reference to this object
     */
    public StyledString append(StyledString string) {
        if (string.length() == 0) {
            return this;
        }

        int offset = fBuffer.length();
        fBuffer.append(string.toString());

        List<StyleRun> otherRuns = string.fStyleRuns;
        if (otherRuns != null && !otherRuns.isEmpty()) {
            for (int i = 0; i < otherRuns.size(); i++) {
                StyleRun curr = otherRuns.get(i);
                if (i == 0 && curr.offset != 0) {
                    appendStyleRun(null, offset); // appended string will
                    // start with the default
                    // color
                }
                appendStyleRun(curr.style, offset + curr.offset);
            }
        } else {
            appendStyleRun(null, offset); // appended string will start with
            // the default color
        }
        return this;
    }

    /**
     * Appends the string representation of the given character
     * with a style to the {@link StyledString}. The
     * appended character will have the given style associated.
     *
     * @param ch
     *            the character to append
     * @param styler
     *            the styler to use for styling the character to append or
     *            <code>null</code> if no styler should be associated with the
     *            appended character
     * @return returns a reference to this object
     */
    public StyledString append(char ch, Styler styler) {
        return append(String.valueOf(ch), styler);
    }

    /**
     * Appends a string with a style to the {@link StyledString}. The
     * appended string will be styled using the given styler.
     *
     * @param string
     *            the string to append
     * @param styler
     *            the styler to use for styling the string to append or
     *            <code>null</code> if no styler should be associated with the
     *            appended string.
     * @return returns a reference to this object
     */
    public StyledString append(String string, Styler styler) {
        if (string.length() == 0)
            return this;

        int offset = fBuffer.length(); // the length before appending
        fBuffer.append(string);
        appendStyleRun(styler, offset);
        return this;
    }

    /**
     * Appends the string representation of the given character array
     * with a style to the {@link StyledString}. The
     * appended character array will be styled using the given styler.
     *
     * @param chars
     *            the character array to append
     * @param styler
     *            the styler to use for styling the character array to append or
     *            <code>null</code> if no styler should be associated with the
     *            appended character array
     * @return returns a reference to this object
     */
    public StyledString append(char[] chars, Styler styler) {
        if (chars.length == 0)
            return this;

        int offset = fBuffer.length(); // the length before appending
        fBuffer.append(chars);
        appendStyleRun(styler, offset);
        return this;
    }

    /**
     * Inserts the character at the given offset. The inserted character will
     * get the styler that is already at the given offset.
     *
     * @param ch
     *            the character to insert
     * @param offset
     *            the insertion index
     * @return returns a reference to this object
     * @throws StringIndexOutOfBoundsException
     *             if <code>offset</code> is less than zero, or if <code>offset</code>
     *             is greater than the length of this object
     * @since 3.5
     */
    public StyledString insert(char ch, int offset) throws StringIndexOutOfBoundsException {
        if (offset < 0 || offset > fBuffer.length()) {
            throw new StringIndexOutOfBoundsException("Invalid offset (" + offset + ")"); //$NON-NLS-1$//$NON-NLS-2$
        }
        if (hasRuns()) {
            int runIndex = findRun(offset);
            if (runIndex < 0) {
                runIndex = -runIndex - 1;
            } else {
                runIndex = runIndex + 1;
            }
            List<StyleRun> styleRuns = getStyleRuns();
            final int size = styleRuns.size();
            for (int i = runIndex; i < size; i++) {
                StyleRun run = styleRuns.get(i);
                run.offset++;
            }
        }
        fBuffer.insert(offset, ch);
        return this;
    }

    /**
     * Sets a styler to use for the given source range. The range must be
     * subrange of actual string of this {@link StyledString}. Stylers
     * previously set for that range will be overwritten.
     *
     * @param offset
     *            the start offset of the range
     * @param length
     *            the length of the range
     * @param styler
     *            the styler to set
     *
     * @throws StringIndexOutOfBoundsException
     *             if <code>start</code> is less than zero, or if offset plus
     *             length is greater than the length of this object.
     */
    public void setStyle(int offset, int length, Styler styler) throws StringIndexOutOfBoundsException {
        if (offset < 0 || offset + length > fBuffer.length()) {
            throw new StringIndexOutOfBoundsException("Invalid offset (" + offset + ") or length (" + length + ")"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
        }
        if (length == 0) {
            return;
        }
        final StyleRun lastRun = getLastRun();
        if (lastRun == null || lastRun.offset <= offset) {
            Styler lastStyler = lastRun == null ? null : lastRun.style;
            appendStyleRun(styler, offset);
            if (offset + length != fBuffer.length()) {
                appendStyleRun(lastStyler, offset + length);
            }
            return;
        }

        int endRun = findRun(offset + length);
        if (endRun >= 0) {
            // run with the same end index, nothing to change
        } else {
            endRun = -(endRun + 1);
            if (offset + length < fBuffer.length()) {
                Styler prevStyle = endRun > 0 ? fStyleRuns.get(endRun - 1).style : null;
                fStyleRuns.add(endRun, new StyleRun(offset + length, prevStyle));
            }
        }

        int startRun = findRun(offset);
        if (startRun >= 0) {
            // run with the same start index
            StyleRun styleRun = fStyleRuns.get(startRun);
            styleRun.style = styler;
        } else {
            startRun = -(startRun + 1);

            Styler prevStyle = startRun > 0 ? fStyleRuns.get(startRun - 1).style : null;
            if (isDifferentStyle(prevStyle, styler) || (startRun == 0 && styler != null)) {
                fStyleRuns.add(startRun, new StyleRun(offset, styler));
                endRun++; // endrun is moved one back
            } else {
                startRun--; // we use the previous
            }
        }
        if (startRun + 1 < endRun) {
            fStyleRuns.removeRange(startRun + 1, endRun);
        }
    }

    /**
     * Returns an array of {@link StyleRange} resulting from applying all
     * associated stylers for this string builder.
     *
     * @return an array of all {@link StyleRange} resulting from applying the
     *         stored stylers to this string.
     */
    public StyleRange[] getStyleRanges() {
        if (hasRuns()) {
            ArrayList<StyleRange> res = new ArrayList<>();

            int offset = 0;
            Styler style = null;
            for (StyleRun curr : getStyleRuns()) {
                if (isDifferentStyle(curr.style, style)) {
                    if (curr.offset > offset && style != null) {
                        res.add(createStyleRange(offset, curr.offset, style));
                    }
                    offset = curr.offset;
                    style = curr.style;
                }
            }
            if (fBuffer.length() > offset && style != null) {
                res.add(createStyleRange(offset, fBuffer.length(), style));
            }
            return res.toArray(new StyleRange[res.size()]);
        }
        return EMPTY;
    }

    private int findRun(int offset) {
        // method assumes that fStyleRuns is not null
        int low = 0;
        int high = fStyleRuns.size() - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            StyleRun styleRun = fStyleRuns.get(mid);
            if (styleRun.offset < offset) {
                low = mid + 1;
            } else if (styleRun.offset > offset) {
                high = mid - 1;
            } else {
                return mid; // key found
            }
        }
        return -(low + 1); // key not found.
    }

    private StyleRange createStyleRange(int start, int end, Styler style) {
        StyleRange styleRange = new StyleRange();
        styleRange.start = start;
        styleRange.length = end - start;
        style.applyStyles(styleRange);
        return styleRange;
    }

    private boolean hasRuns() {
        return fStyleRuns != null && !fStyleRuns.isEmpty();
    }

    private void appendStyleRun(Styler style, int offset) {
        StyleRun lastRun = getLastRun();
        if (lastRun != null && lastRun.offset == offset) {
            lastRun.style = style;
            return;
        }

        if (lastRun == null && style != null || lastRun != null && isDifferentStyle(style, lastRun.style)) {
            getStyleRuns().add(new StyleRun(offset, style));
        }
    }

    private boolean isDifferentStyle(Styler style1, Styler style2) {
        if (style1 == null) {
            return style2 != null;
        }
        return !style1.equals(style2);
    }

    private StyleRun getLastRun() {
        if (fStyleRuns == null || fStyleRuns.isEmpty()) {
            return null;
        }
        return fStyleRuns.get(fStyleRuns.size() - 1);
    }

    private List<StyleRun> getStyleRuns() {
        if (fStyleRuns == null)
            fStyleRuns = new StyleRunList();
        return fStyleRuns;
    }

    private static class StyleRun {
        public int offset;
        public Styler style;

        public StyleRun(int offset, Styler style) {
            this.offset = offset;
            this.style = style;
        }

        @Override
        public String toString() {
            return "Offset " + offset + ", style: " + style; //$NON-NLS-1$//$NON-NLS-2$
        }
    }

    private static class StyleRunList extends ArrayList<StyleRun> {
        private static final long serialVersionUID = 123L;

        public StyleRunList() {
            super(3);
        }

        @Override
        public void removeRange(int fromIndex, int toIndex) {
            super.removeRange(fromIndex, toIndex);
        }
    }

    private static class DefaultStyler extends Styler {
        private final String fForegroundColorName;
        private final String fBackgroundColorName;

        public DefaultStyler(String foregroundColorName, String backgroundColorName) {
            fForegroundColorName = foregroundColorName;
            fBackgroundColorName = backgroundColorName;
        }

        @Override
        public void applyStyles(TextStyle textStyle) {
            ColorRegistry colorRegistry = JFaceResources.getColorRegistry();
            if (fForegroundColorName != null) {
                textStyle.foreground = colorRegistry.get(fForegroundColorName);
            }
            if (fBackgroundColorName != null) {
                textStyle.background = colorRegistry.get(fBackgroundColorName);
            }
        }
    }

}