blob: 0c2cfb519e51174d8e7d8c1d5313a0856d4a123f [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}
}