| /******************************************************************************* |
| * Copyright (c) 2008 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.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 { |
| |
| /** |
| * 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 StringBuffer fBuffer; |
| private StyleRunList fStyleRuns; |
| |
| /** |
| * Creates an empty {@link StyledString}. |
| */ |
| public StyledString() { |
| fBuffer = new StringBuffer(); |
| 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}. |
| */ |
| public String toString() { |
| return getString(); |
| } |
| |
| /** |
| * Returns the length of the string of this {@link StyledString}. |
| * |
| * @return the length of the current string |
| */ |
| public int length() { |
| return fBuffer.length(); |
| } |
| |
| /** |
| * 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 otherRuns = string.fStyleRuns; |
| if (otherRuns != null && !otherRuns.isEmpty()) { |
| for (int i = 0; i < otherRuns.size(); i++) { |
| StyleRun curr = (StyleRun) 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; |
| } |
| |
| /** |
| * 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) { |
| 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; |
| } |
| if (!hasRuns() || getLastRun().offset <= offset) { |
| appendStyleRun(styler, offset); |
| if (offset + length != fBuffer.length()) { |
| appendStyleRun(null, 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.getRun(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.getRun(startRun); |
| styleRun.style = styler; |
| } else { |
| startRun = -(startRun + 1); |
| |
| Styler prevStyle = startRun > 0 ? fStyleRuns.getRun(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 res = new ArrayList(); |
| |
| List styleRuns = getStyleRuns(); |
| int offset = 0; |
| Styler style = null; |
| for (int i = 0; i < styleRuns.size(); i++) { |
| StyleRun curr = (StyleRun) styleRuns.get(i); |
| 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 (StyleRange[]) 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.getRun(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.getRun(fStyleRuns.size() - 1); |
| } |
| |
| private List 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; |
| } |
| |
| public String toString() { |
| return "Offset " + offset + ", style: " + style; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| } |
| |
| private static class StyleRunList extends ArrayList { |
| private static final long serialVersionUID = 123L; |
| |
| public StyleRunList() { |
| super(3); |
| } |
| |
| public StyleRun getRun(int index) { |
| return (StyleRun) get(index); |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| } |