/*
* 10/13/2013
*
* RTextAreaHighlighter.java - Highlighter for RTextAreas.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rtextarea;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
import javax.swing.plaf.TextUI;
import javax.swing.plaf.basic.BasicTextUI.BasicHighlighter;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.LayeredHighlighter;
import javax.swing.text.Position;
import javax.swing.text.View;
import org.fife.ui.rsyntaxtextarea.DocumentRange;
/**
* The highlighter implementation used by {@link RTextArea}s. It knows to
* always paint "mark all" highlights below selection highlights.<p>
*
* Most of this code is copied from javax.swing.text.DefaultHighlighter;
* unfortunately, we cannot re-use much of it since it is package private.
*
* @author Robert Futrell
* @version 1.0
*/
public class RTextAreaHighlighter extends BasicHighlighter {
/**
* The text component we are the highlighter for.
*/
protected RTextArea textArea;
/**
* The "mark all" highlights (to be painted separately from other
* highlights).
*/
private List<HighlightInfo> markAllHighlights;
/**
* Constructor.
*/
public RTextAreaHighlighter() {
markAllHighlights = new ArrayList<HighlightInfo>();
}
/**
* Adds a special "marked occurrence" highlight.
*
* @param start
* @param end
* @param p
* @return A tag to reference the highlight later.
* @throws BadLocationException
* @see #clearMarkAllHighlights()
*/
Object addMarkAllHighlight(int start, int end, HighlightPainter p)
throws BadLocationException {
Document doc = textArea.getDocument();
TextUI mapper = textArea.getUI();
// Always layered highlights for marked occurrences.
HighlightInfoImpl i = new LayeredHighlightInfoImpl();
i.setPainter(p);
i.p0 = doc.createPosition(start);
// HACK: Use "end-1" to prevent chars the user types at the "end" of
// the highlight to be absorbed into the highlight (default Highlight
// behavior).
i.p1 = doc.createPosition(end-1);
markAllHighlights.add(i);
mapper.damageRange(textArea, start, end);
return i;
}
/**
* Removes all "mark all" highlights from the view.
*
* @see #addMarkAllHighlight(int, int, javax.swing.text.Highlighter.HighlightPainter)
*/
void clearMarkAllHighlights() {
// Don't remove via an iterator; since our List is an ArrayList, this
// implies tons of System.arrayCopy()s
for (HighlightInfo info : markAllHighlights) {
repaintListHighlight(info);
}
markAllHighlights.clear();
}
/**
* {@inheritDoc}
*/
@Override
public void deinstall(JTextComponent c) {
this.textArea = null;
markAllHighlights.clear();
}
/**
* Returns the number of "mark all" highlights currently shown in the
* editor.
*
* @return The "mark all" highlight count.
*/
public int getMarkAllHighlightCount() {
return markAllHighlights.size();
}
/**
* Returns a list of "mark all" highlights in the text area. If there are
* no such highlights, this will be an empty list.
*
* @return The list of "mark all" highlight ranges.
*/
public List<DocumentRange> getMarkAllHighlightRanges() {
List<DocumentRange> list = new ArrayList<DocumentRange>(
markAllHighlights.size());
for (HighlightInfo info : markAllHighlights) {
int start = info.getStartOffset();
int end = info.getEndOffset() + 1; // HACK
DocumentRange range = new DocumentRange(start, end);
list.add(range);
}
return list;
}
/**
* {@inheritDoc}
*/
@Override
public void install(JTextComponent c) {
super.install(c);
this.textArea = (RTextArea)c;
}
/**
* When leaf Views (such as LabelView) are rendering they should
* call into this method. If a highlight is in the given region it will
* be drawn immediately.
*
* @param g Graphics used to draw
* @param lineStart starting offset of view
* @param lineEnd ending offset of view
* @param viewBounds Bounds of View
* @param editor JTextComponent
* @param view View instance being rendered
*/
@Override
public void paintLayeredHighlights(Graphics g, int lineStart, int lineEnd,
Shape viewBounds, JTextComponent editor, View view) {
paintListLayered(g, lineStart,lineEnd, viewBounds, editor, view,
markAllHighlights);
super.paintLayeredHighlights(g, lineStart, lineEnd, viewBounds, editor,
view);
}
protected void paintListLayered(Graphics g, int lineStart, int lineEnd,
Shape viewBounds, JTextComponent editor, View view,
List<? extends HighlightInfo> highlights) {
for (int i=highlights.size()-1; i>=0; i--) {
HighlightInfo tag = highlights.get(i);
if (tag instanceof LayeredHighlightInfo) {
LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
int highlightStart = lhi.getStartOffset();
int highlightEnd = lhi.getEndOffset() + 1; // "+1" workaround for Java highlight issues
if ((lineStart < highlightStart && lineEnd > highlightStart) ||
(lineStart >= highlightStart && lineStart < highlightEnd)) {
lhi.paintLayeredHighlights(g, lineStart, lineEnd, viewBounds,
editor, view);
}
}
}
}
protected void repaintListHighlight(HighlightInfo info) {
// Note: We're relying on implementation here, not interface. Yuck...
if (info instanceof LayeredHighlightInfoImpl) {
LayeredHighlightInfoImpl lhi = (LayeredHighlightInfoImpl)info;
if (lhi.width > 0 && lhi.height > 0) {
textArea.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
}
}
else {
TextUI ui = textArea.getUI();
ui.damageRange(textArea, info.getStartOffset(),info.getEndOffset());
//safeDamageRange(info.p0, info.p1);
}
}
/**
* Information about a highlight being painted by this highlighter.
*/
public static interface HighlightInfo extends Highlighter.Highlight {}
/**
* Information about a layered highlight being painted by this highlighter.
*/
public static interface LayeredHighlightInfo extends HighlightInfo {
/**
* Restricts the region based on the receivers offsets and messages
* the painter to paint the region.
*/
void paintLayeredHighlights(Graphics g, int p0, int p1,
Shape viewBounds, JTextComponent editor, View view);
}
/**
* A straightforward implementation of <code>HighlightInfo</code>.
*/
protected static class HighlightInfoImpl implements HighlightInfo {
private Position p0;
private Position p1;
private Highlighter.HighlightPainter painter;
/** To be extended by subclasses. */
public Color getColor() {
return null;
}
public int getStartOffset() {
return p0.getOffset();
}
public int getEndOffset() {
return p1.getOffset();
}
public Highlighter.HighlightPainter getPainter() {
return painter;
}
public void setStartOffset(Position startOffset) {
this.p0 = startOffset;
}
public void setEndOffset(Position endOffset) {
this.p1 = endOffset;
}
public void setPainter(Highlighter.HighlightPainter painter) {
this.painter = painter;
}
}
/**
* A straightforward implementation of <code>HighlightInfo</code> for
* painting layered highlights.
*/
/*
* NOTE: This implementation is a "hack" so typing at the "end" of the highlight
* does not extend it to include the newly-typed chars, which is the standard
* behavior of Swing Highlights. It assumes that the "p1" Position set is
* actually 1 char too short, and will render the selection as if that "extra"
* char should be highlighted.
*/
protected static class LayeredHighlightInfoImpl extends HighlightInfoImpl
implements LayeredHighlightInfo {
public int x;
public int y;
public int width;
public int height;
void union(Shape bounds) {
if (bounds == null) {
return;
}
Rectangle alloc = (bounds instanceof Rectangle) ?
(Rectangle)bounds : bounds.getBounds();
if (width == 0 || height == 0) {
x = alloc.x;
y = alloc.y;
width = alloc.width;
height = alloc.height;
}
else {
width = Math.max(x + width, alloc.x + alloc.width);
height = Math.max(y + height, alloc.y + alloc.height);
x = Math.min(x, alloc.x);
width -= x;
y = Math.min(y, alloc.y);
height -= y;
}
}
/**
* {@inheritDoc}
*/
public void paintLayeredHighlights(Graphics g, int p0, int p1,
Shape viewBounds, JTextComponent editor,
View view) {
int start = getStartOffset();
int end = getEndOffset();
end++; // Workaround for Java highlight issues
// Restrict the region to what we represent
p0 = Math.max(start, p0);
p1 = Math.min(end, p1);
if (getColor()!=null &&
(getPainter() instanceof ChangeableHighlightPainter)) {
((ChangeableHighlightPainter)getPainter()).setPaint(getColor());
}
// Paint the appropriate region using the painter and union
// the effected region with our bounds.
union(((LayeredHighlighter.LayerPainter)getPainter()).paintLayer
(g, p0, p1, viewBounds, editor, view));
}
}
}