blob: d38b0790102cfce5a9e37b0a60726a6fbe855fda [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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.text;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.core.runtime.Assert;
/**
* Describes the presentation styles for a section of an indexed text such as a
* document or string. A text presentation defines a default style for the whole
* section and in addition style differences for individual subsections. Text
* presentations can be narrowed down to a particular result window. All methods
* are result window aware, i.e. ranges outside the result window are always
* ignored.
* <p>
* All iterators provided by a text presentation assume that they enumerate non
* overlapping, consecutive ranges inside the default range. Thus, all these
* iterators do not include the default range. The default style range must be
* explicitly asked for using <code>getDefaultStyleRange</code>.
*/
public class TextPresentation {
/**
* Applies the given presentation to the given text widget. Helper method.
*
* @param presentation the style information
* @param text the widget to which to apply the style information
* @since 2.0
*/
public static void applyTextPresentation(TextPresentation presentation, StyledText text) {
StyleRange[] ranges= new StyleRange[presentation.getDenumerableRanges()];
int i= 0;
Iterator<StyleRange> e= presentation.getAllStyleRangeIterator();
while (e.hasNext())
ranges[i++]= e.next();
text.setStyleRanges(ranges);
}
/**
* Enumerates all the <code>StyleRange</code>s included in the presentation.
*/
class FilterIterator implements Iterator<StyleRange> {
/** The index of the next style range to be enumerated */
protected int fIndex;
/** The upper bound of the indices of style ranges to be enumerated */
protected int fLength;
/** Indicates whether ranges similar to the default range should be enumerated */
protected boolean fSkipDefaults;
/** The result window */
protected IRegion fWindow;
/**
* <code>skipDefaults</code> tells the enumeration to skip all those style ranges
* which define the same style as the presentation's default style range.
*
* @param skipDefaults <code>false</code> if ranges similar to the default range should be enumerated
*/
protected FilterIterator(boolean skipDefaults) {
fSkipDefaults= skipDefaults;
fWindow= fResultWindow;
fIndex= getFirstIndexInWindow(fWindow);
fLength= getFirstIndexAfterWindow(fWindow);
if (fSkipDefaults)
computeIndex();
}
@Override
public StyleRange next() {
try {
StyleRange r= fRanges.get(fIndex++);
return createWindowRelativeRange(fWindow, r);
} catch (ArrayIndexOutOfBoundsException x) {
throw new NoSuchElementException();
} finally {
if (fSkipDefaults)
computeIndex();
}
}
@Override
public boolean hasNext() {
return fIndex < fLength;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Returns whether the given object should be skipped.
*
* @param o the object to be checked
* @return <code>true</code> if the object should be skipped by the iterator
*/
protected boolean skip(Object o) {
StyleRange r= (StyleRange) o;
return r.similarTo(fDefaultRange);
}
/**
* Computes the index of the styled range that is the next to be enumerated.
*/
protected void computeIndex() {
while (fIndex < fLength && skip(fRanges.get(fIndex)))
++ fIndex;
}
}
/** The style information for the range covered by the whole presentation */
private StyleRange fDefaultRange;
/** The member ranges of the presentation */
private ArrayList<StyleRange> fRanges;
/** A clipping region against which the presentation can be clipped when asked for results */
private IRegion fResultWindow;
/**
* The optional extent for this presentation.
* @since 3.0
*/
private IRegion fExtent;
/**
* Creates a new empty text presentation.
*/
public TextPresentation() {
fRanges= new ArrayList<>(50);
}
/**
* Creates a new empty text presentation. <code>sizeHint</code> tells the expected size of this
* presentation.
*
* @param sizeHint the expected size of this presentation, must be positive
*/
public TextPresentation(int sizeHint) {
Assert.isTrue(sizeHint > 0);
fRanges= new ArrayList<>(sizeHint);
}
/**
* Creates a new empty text presentation with the given extent. <code>sizeHint</code> tells the
* expected size of this presentation.
*
* @param extent the extent of the created <code>TextPresentation</code>
* @param sizeHint the expected size of this presentation, must be positive
* @since 3.0
*/
public TextPresentation(IRegion extent, int sizeHint) {
this(sizeHint);
Assert.isNotNull(extent);
fExtent= extent;
}
/**
* Sets the result window for this presentation. When dealing with
* this presentation all ranges which are outside the result window
* are ignored. For example, the size of the presentation is 0
* when there is no range inside the window even if there are ranges
* outside the window. All methods are aware of the result window.
*
* @param resultWindow the result window
*/
public void setResultWindow(IRegion resultWindow) {
fResultWindow= resultWindow;
}
/**
* Set the default style range of this presentation.
* The default style range defines the overall area covered
* by this presentation and its style information.
*
* @param range the range describing the default region
*/
public void setDefaultStyleRange(StyleRange range) {
fDefaultRange= range;
}
/**
* Returns this presentation's default style range. The returned <code>StyleRange</code>
* is relative to the start of the result window.
*
* @return this presentation's default style range
*/
public StyleRange getDefaultStyleRange() {
StyleRange range= createWindowRelativeRange(fResultWindow, fDefaultRange);
if (range == null)
return null;
return (StyleRange)range.clone();
}
/**
* Add the given range to the presentation. The range must be a
* subrange of the presentation's default range.
*
* @param range the range to be added
*/
public void addStyleRange(StyleRange range) {
checkConsistency(range);
fRanges.add(range);
}
/**
* Replaces the given range in this presentation. The range must be a
* subrange of the presentation's default range.
*
* @param range the range to be added
* @since 3.0
*/
public void replaceStyleRange(StyleRange range) {
applyStyleRange(range, false);
}
/**
* Merges the given range into this presentation. The range must be a
* subrange of the presentation's default range.
*
* @param range the range to be added
* @since 3.0
*/
public void mergeStyleRange(StyleRange range) {
applyStyleRange(range, true);
}
/**
* Applies the given range to this presentation. The range must be a
* subrange of the presentation's default range.
*
* @param range the range to be added
* @param merge <code>true</code> if the style should be merged instead of replaced
* @since 3.0
*/
private void applyStyleRange(StyleRange range, boolean merge) {
if (range.length == 0)
return;
checkConsistency(range);
int start= range.start;
int length= range.length;
int end= start + length;
if (fRanges.size() == 0) {
StyleRange defaultRange= getDefaultStyleRange();
if (defaultRange == null)
defaultRange= range;
defaultRange.start= start;
defaultRange.length= length;
applyStyle(range, defaultRange, merge);
fRanges.add(defaultRange);
} else {
IRegion rangeRegion= new Region(start, length);
int first= getFirstIndexInWindow(rangeRegion);
if (first == fRanges.size()) {
StyleRange defaultRange= getDefaultStyleRange();
if (defaultRange == null)
defaultRange= range;
defaultRange.start= start;
defaultRange.length= length;
applyStyle(range, defaultRange, merge);
fRanges.add(defaultRange);
return;
}
int last= getFirstIndexAfterWindow(rangeRegion);
for (int i= first; i < last && length > 0; i++) {
StyleRange current= fRanges.get(i);
int currentStart= current.start;
int currentEnd= currentStart + current.length;
if (end <= currentStart) {
fRanges.add(i, range);
return;
}
if (start >= currentEnd)
continue;
StyleRange currentCopy= null;
if (end < currentEnd)
currentCopy= (StyleRange)current.clone();
if (start < currentStart) {
// Apply style to new default range and add it
StyleRange defaultRange= getDefaultStyleRange();
if (defaultRange == null)
defaultRange= new StyleRange();
defaultRange.start= start;
defaultRange.length= currentStart - start;
applyStyle(range, defaultRange, merge);
fRanges.add(i, defaultRange);
i++; last++;
// Apply style to first part of current range
current.length= Math.min(end, currentEnd) - currentStart;
applyStyle(range, current, merge);
}
if (start >= currentStart) {
// Shorten the current range
current.length= start - currentStart;
// Apply the style to the rest of the current range and add it
if (current.length > 0) {
current= (StyleRange)current.clone();
i++; last++;
fRanges.add(i, current);
}
applyStyle(range, current, merge);
current.start= start;
current.length= Math.min(end, currentEnd) - start;
}
if (end < currentEnd) {
// Add rest of current range
currentCopy.start= end;
currentCopy.length= currentEnd - end;
i++; last++;
fRanges.add(i, currentCopy);
}
// Update range
range.start= currentEnd;
range.length= Math.max(end - currentEnd, 0);
start= range.start;
length= range.length;
}
if (length > 0) {
// Apply style to new default range and add it
StyleRange defaultRange= getDefaultStyleRange();
if (defaultRange == null)
defaultRange= range;
defaultRange.start= start;
defaultRange.length= end - start;
applyStyle(range, defaultRange, merge);
fRanges.add(last, defaultRange);
}
}
}
/**
* Replaces the given ranges in this presentation. Each range must be a
* subrange of the presentation's default range. The ranges must be ordered
* by increasing offset and must not overlap (but may be adjacent).
*
* @param ranges the ranges to be added
* @since 3.0
*/
public void replaceStyleRanges(StyleRange[] ranges) {
applyStyleRanges(ranges, false);
}
/**
* Merges the given ranges into this presentation. Each range must be a
* subrange of the presentation's default range. The ranges must be ordered
* by increasing offset and must not overlap (but may be adjacent).
*
* @param ranges the ranges to be added
* @since 3.0
*/
public void mergeStyleRanges(StyleRange[] ranges) {
applyStyleRanges(ranges, true);
}
/**
* Applies the given ranges to this presentation. Each range must be a
* subrange of the presentation's default range. The ranges must be ordered
* by increasing offset and must not overlap (but may be adjacent).
*
* @param ranges the ranges to be added
* @param merge <code>true</code> if the style should be merged instead of replaced
* @since 3.0
*/
private void applyStyleRanges(StyleRange[] ranges, boolean merge) {
int j= 0;
ArrayList<StyleRange> oldRanges= fRanges;
ArrayList<StyleRange> newRanges= new ArrayList<>(2*ranges.length + oldRanges.size());
for (int i= 0, n= ranges.length; i < n; i++) {
StyleRange range= ranges[i];
fRanges= oldRanges; // for getFirstIndexAfterWindow(...)
for (int m= getFirstIndexAfterWindow(new Region(range.start, range.length)); j < m; j++)
newRanges.add(oldRanges.get(j));
fRanges= newRanges; // for mergeStyleRange(...)
applyStyleRange(range, merge);
}
for (int m= oldRanges.size(); j < m; j++)
newRanges.add(oldRanges.get(j));
fRanges= newRanges;
}
/**
* Applies the template's style to the target.
*
* @param template the style range to be used as template
* @param target the style range to which to apply the template
* @param merge <code>true</code> if the style should be merged instead of replaced
* @since 3.0
*/
private void applyStyle(StyleRange template, StyleRange target, boolean merge) {
if (merge) {
if (template.font != null)
target.font= template.font;
target.fontStyle|= template.fontStyle;
if (template.metrics != null)
target.metrics= template.metrics;
if (template.foreground != null || template.underlineStyle == SWT.UNDERLINE_LINK)
target.foreground= template.foreground;
if (template.background != null)
target.background= template.background;
target.strikeout|= template.strikeout;
if (template.strikeoutColor != null)
target.strikeoutColor= template.strikeoutColor;
target.underline|= template.underline;
if (template.underlineStyle != SWT.NONE && target.underlineStyle != SWT.UNDERLINE_LINK)
target.underlineStyle= template.underlineStyle;
if (template.underlineColor != null)
target.underlineColor= template.underlineColor;
if (template.borderStyle != SWT.NONE)
target.borderStyle= template.borderStyle;
if (template.borderColor != null)
target.borderColor= template.borderColor;
} else {
target.font= template.font;
target.fontStyle= template.fontStyle;
target.metrics= template.metrics;
target.foreground= template.foreground;
target.background= template.background;
target.strikeout= template.strikeout;
target.strikeoutColor= template.strikeoutColor;
target.underline= template.underline;
target.underlineStyle= template.underlineStyle;
target.underlineColor= template.underlineColor;
target.borderStyle= template.borderStyle;
target.borderColor= template.borderColor;
}
}
/**
* Checks whether the given range is a subrange of the presentation's
* default style range.
*
* @param range the range to be checked
* @exception IllegalArgumentException if range is not a subrange of the presentation's default range
*/
private void checkConsistency(StyleRange range) {
if (range == null)
throw new IllegalArgumentException();
if (fDefaultRange != null) {
if (range.start < fDefaultRange.start)
range.start= fDefaultRange.start;
int defaultEnd= fDefaultRange.start + fDefaultRange.length;
int end= range.start + range.length;
if (end > defaultEnd)
range.length -= (end - defaultEnd);
}
}
/**
* Returns the index of the first range which overlaps with the
* specified window.
*
* @param window the window to be used for searching
* @return the index of the first range overlapping with the window
*/
private int getFirstIndexInWindow(IRegion window) {
if (window != null) {
int start= window.getOffset();
int i= -1, j= fRanges.size();
while (j - i > 1) {
int k= (i + j) >> 1;
StyleRange r= fRanges.get(k);
if (r.start + r.length > start)
j= k;
else
i= k;
}
return j;
}
return 0;
}
/**
* Returns the index of the first range which comes after the specified window and does
* not overlap with this window.
*
* @param window the window to be used for searching
* @return the index of the first range behind the window and not overlapping with the window
*/
private int getFirstIndexAfterWindow(IRegion window) {
if (window != null) {
int end= window.getOffset() + window.getLength();
int i= -1, j= fRanges.size();
while (j - i > 1) {
int k= (i + j) >> 1;
StyleRange r= fRanges.get(k);
if (r.start < end)
i= k;
else
j= k;
}
return j;
}
return fRanges.size();
}
/**
* Returns a style range which is relative to the specified window and
* appropriately clipped if necessary. The original style range is not
* modified.
*
* @param window the reference window
* @param range the absolute range
* @return the window relative range based on the absolute range
*/
private StyleRange createWindowRelativeRange(IRegion window, StyleRange range) {
if (window == null || range == null)
return range;
int start= range.start - window.getOffset();
if (start < 0)
start= 0;
int rangeEnd= range.start + range.length;
int windowEnd= window.getOffset() + window.getLength();
int end= (rangeEnd > windowEnd ? windowEnd : rangeEnd);
end -= window.getOffset();
StyleRange newRange= (StyleRange) range.clone();
newRange.start= start;
newRange.length= end - start;
return newRange;
}
/**
* Returns the region which is relative to the specified window and
* appropriately clipped if necessary.
*
* @param coverage the absolute coverage
* @return the window relative region based on the absolute coverage
* @since 3.0
*/
private IRegion createWindowRelativeRegion(IRegion coverage) {
if (fResultWindow == null || coverage == null)
return coverage;
int start= coverage.getOffset() - fResultWindow.getOffset();
if (start < 0)
start= 0;
int rangeEnd= coverage.getOffset() + coverage.getLength();
int windowEnd= fResultWindow.getOffset() + fResultWindow.getLength();
int end= (rangeEnd > windowEnd ? windowEnd : rangeEnd);
end -= fResultWindow.getOffset();
return new Region(start, end - start);
}
/**
* Returns an iterator which enumerates all style ranged which define a style
* different from the presentation's default style range. The default style range
* is not enumerated.
*
* @return a style range iterator
*/
public Iterator<StyleRange> getNonDefaultStyleRangeIterator() {
return new FilterIterator(fDefaultRange != null);
}
/**
* Returns an iterator which enumerates all style ranges of this presentation
* except the default style range. The returned <code>StyleRange</code>s
* are relative to the start of the presentation's result window.
*
* @return a style range iterator
*/
public Iterator<StyleRange> getAllStyleRangeIterator() {
return new FilterIterator(false);
}
/**
* Returns whether this collection contains any style range including
* the default style range.
*
* @return <code>true</code> if there is no style range in this presentation
*/
public boolean isEmpty() {
return (fDefaultRange == null && getDenumerableRanges() == 0);
}
/**
* Returns the number of style ranges in the presentation not counting the default
* style range.
*
* @return the number of style ranges in the presentation excluding the default style range
*/
public int getDenumerableRanges() {
int size= getFirstIndexAfterWindow(fResultWindow) - getFirstIndexInWindow(fResultWindow);
return (size < 0 ? 0 : size);
}
/**
* Returns the style range with the smallest offset ignoring the default style range or null
* if the presentation is empty.
*
* @return the style range with the smallest offset different from the default style range
*/
public StyleRange getFirstStyleRange() {
try {
StyleRange range= fRanges.get(getFirstIndexInWindow(fResultWindow));
return createWindowRelativeRange(fResultWindow, range);
} catch (NoSuchElementException x) {
} catch (IndexOutOfBoundsException x) {
}
return null;
}
/**
* Returns the style range with the highest offset ignoring the default style range.
*
* @return the style range with the highest offset different from the default style range
*/
public StyleRange getLastStyleRange() {
try {
StyleRange range= fRanges.get(getFirstIndexAfterWindow(fResultWindow) - 1);
return createWindowRelativeRange(fResultWindow, range);
} catch (NoSuchElementException x) {
return null;
} catch (IndexOutOfBoundsException x) {
return null;
}
}
/**
* Returns the coverage of this presentation as clipped by the presentation's
* result window.
*
* @return the coverage of this presentation
*/
public IRegion getCoverage() {
if (fDefaultRange != null) {
StyleRange range= getDefaultStyleRange();
return new Region(range.start, range.length);
}
StyleRange first= getFirstStyleRange();
StyleRange last= getLastStyleRange();
if (first == null || last == null)
return null;
return new Region(first.start, last.start - first. start + last.length);
}
/**
* Returns the extent of this presentation clipped by the
* presentation's result window.
*
* @return the clipped extent
* @since 3.0
*/
public IRegion getExtent() {
if (fExtent != null)
return createWindowRelativeRegion(fExtent);
return getCoverage();
}
/**
* Clears this presentation by resetting all applied changes.
* @since 2.0
*/
public void clear() {
fDefaultRange= null;
fResultWindow= null;
fRanges.clear();
}
}