/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.text;
import java.util.
Vector;
import java.awt.*;
import javax.swing.plaf.*;
import javax.swing.*;
/**
* Implements the Highlighter interfaces. Implements a simple highlight
* painter that renders in a solid color.
*
* @author Timothy Prinzing
* @see Highlighter
*/
public class
DefaultHighlighter extends
LayeredHighlighter {
/**
* Creates a new DefaultHighlighther object.
*/
public
DefaultHighlighter() {
drawsLayeredHighlights = true;
}
// ---- Highlighter methods ----------------------------------------------
/**
* Renders the highlights.
*
* @param g the graphics context
*/
public void
paint(
Graphics g) {
// PENDING(prinz) - should cull ranges not visible
int
len =
highlights.
size();
for (int
i = 0;
i <
len;
i++) {
HighlightInfo info =
highlights.
elementAt(
i);
if (!(
info instanceof
LayeredHighlightInfo)) {
// Avoid allocing unless we need it.
Rectangle a =
component.
getBounds();
Insets insets =
component.
getInsets();
a.
x =
insets.
left;
a.
y =
insets.
top;
a.
width -=
insets.
left +
insets.
right;
a.
height -=
insets.
top +
insets.
bottom;
for (;
i <
len;
i++) {
info =
highlights.
elementAt(
i);
if (!(
info instanceof
LayeredHighlightInfo)) {
Highlighter.
HighlightPainter p =
info.
getPainter();
p.
paint(
g,
info.
getStartOffset(),
info.
getEndOffset(),
a,
component);
}
}
}
}
}
/**
* Called when the UI is being installed into the
* interface of a JTextComponent. Installs the editor, and
* removes any existing highlights.
*
* @param c the editor component
* @see Highlighter#install
*/
public void
install(
JTextComponent c) {
component =
c;
removeAllHighlights();
}
/**
* Called when the UI is being removed from the interface of
* a JTextComponent.
*
* @param c the component
* @see Highlighter#deinstall
*/
public void
deinstall(
JTextComponent c) {
component = null;
}
/**
* Adds a highlight to the view. Returns a tag that can be used
* to refer to the highlight.
*
* @param p0 the start offset of the range to highlight >= 0
* @param p1 the end offset of the range to highlight >= p0
* @param p the painter to use to actually render the highlight
* @return an object that can be used as a tag
* to refer to the highlight
* @exception BadLocationException if the specified location is invalid
*/
public
Object addHighlight(int
p0, int
p1,
Highlighter.
HighlightPainter p) throws
BadLocationException {
if (
p0 < 0) {
throw new
BadLocationException("Invalid start offset",
p0);
}
if (
p1 <
p0) {
throw new
BadLocationException("Invalid end offset",
p1);
}
Document doc =
component.
getDocument();
HighlightInfo i = (
getDrawsLayeredHighlights() &&
(
p instanceof
LayeredHighlighter.
LayerPainter)) ?
new
LayeredHighlightInfo() : new
HighlightInfo();
i.
painter =
p;
i.
p0 =
doc.
createPosition(
p0);
i.
p1 =
doc.
createPosition(
p1);
highlights.
addElement(
i);
safeDamageRange(
p0,
p1);
return
i;
}
/**
* Removes a highlight from the view.
*
* @param tag the reference to the highlight
*/
public void
removeHighlight(
Object tag) {
if (
tag instanceof
LayeredHighlightInfo) {
LayeredHighlightInfo lhi = (
LayeredHighlightInfo)
tag;
if (
lhi.
width > 0 &&
lhi.
height > 0) {
component.
repaint(
lhi.
x,
lhi.
y,
lhi.
width,
lhi.
height);
}
}
else {
HighlightInfo info = (
HighlightInfo)
tag;
safeDamageRange(
info.
p0,
info.
p1);
}
highlights.
removeElement(
tag);
}
/**
* Removes all highlights.
*/
public void
removeAllHighlights() {
TextUI mapper =
component.
getUI();
if (
getDrawsLayeredHighlights()) {
int
len =
highlights.
size();
if (
len != 0) {
int
minX = 0;
int
minY = 0;
int
maxX = 0;
int
maxY = 0;
int
p0 = -1;
int
p1 = -1;
for (int
i = 0;
i <
len;
i++) {
HighlightInfo hi =
highlights.
elementAt(
i);
if (
hi instanceof
LayeredHighlightInfo) {
LayeredHighlightInfo info = (
LayeredHighlightInfo)
hi;
minX =
Math.
min(
minX,
info.
x);
minY =
Math.
min(
minY,
info.
y);
maxX =
Math.
max(
maxX,
info.
x +
info.
width);
maxY =
Math.
max(
maxY,
info.
y +
info.
height);
}
else {
if (
p0 == -1) {
p0 =
hi.
p0.
getOffset();
p1 =
hi.
p1.
getOffset();
}
else {
p0 =
Math.
min(
p0,
hi.
p0.
getOffset());
p1 =
Math.
max(
p1,
hi.
p1.
getOffset());
}
}
}
if (
minX !=
maxX &&
minY !=
maxY) {
component.
repaint(
minX,
minY,
maxX -
minX,
maxY -
minY);
}
if (
p0 != -1) {
try {
safeDamageRange(
p0,
p1);
} catch (
BadLocationException e) {}
}
highlights.
removeAllElements();
}
}
else if (
mapper != null) {
int
len =
highlights.
size();
if (
len != 0) {
int
p0 =
Integer.
MAX_VALUE;
int
p1 = 0;
for (int
i = 0;
i <
len;
i++) {
HighlightInfo info =
highlights.
elementAt(
i);
p0 =
Math.
min(
p0,
info.
p0.
getOffset());
p1 =
Math.
max(
p1,
info.
p1.
getOffset());
}
try {
safeDamageRange(
p0,
p1);
} catch (
BadLocationException e) {}
highlights.
removeAllElements();
}
}
}
/**
* Changes a highlight.
*
* @param tag the highlight tag
* @param p0 the beginning of the range >= 0
* @param p1 the end of the range >= p0
* @exception BadLocationException if the specified location is invalid
*/
public void
changeHighlight(
Object tag, int
p0, int
p1) throws
BadLocationException {
if (
p0 < 0) {
throw new
BadLocationException("Invalid beginning of the range",
p0);
}
if (
p1 <
p0) {
throw new
BadLocationException("Invalid end of the range",
p1);
}
Document doc =
component.
getDocument();
if (
tag instanceof
LayeredHighlightInfo) {
LayeredHighlightInfo lhi = (
LayeredHighlightInfo)
tag;
if (
lhi.
width > 0 &&
lhi.
height > 0) {
component.
repaint(
lhi.
x,
lhi.
y,
lhi.
width,
lhi.
height);
}
// Mark the highlights region as invalid, it will reset itself
// next time asked to paint.
lhi.
width =
lhi.
height = 0;
lhi.
p0 =
doc.
createPosition(
p0);
lhi.
p1 =
doc.
createPosition(
p1);
safeDamageRange(
Math.
min(
p0,
p1),
Math.
max(
p0,
p1));
}
else {
HighlightInfo info = (
HighlightInfo)
tag;
int
oldP0 =
info.
p0.
getOffset();
int
oldP1 =
info.
p1.
getOffset();
if (
p0 ==
oldP0) {
safeDamageRange(
Math.
min(
oldP1,
p1),
Math.
max(
oldP1,
p1));
} else if (
p1 ==
oldP1) {
safeDamageRange(
Math.
min(
p0,
oldP0),
Math.
max(
p0,
oldP0));
} else {
safeDamageRange(
oldP0,
oldP1);
safeDamageRange(
p0,
p1);
}
info.
p0 =
doc.
createPosition(
p0);
info.
p1 =
doc.
createPosition(
p1);
}
}
/**
* Makes a copy of the highlights. Does not actually clone each highlight,
* but only makes references to them.
*
* @return the copy
* @see Highlighter#getHighlights
*/
public
Highlighter.
Highlight[]
getHighlights() {
int
size =
highlights.
size();
if (
size == 0) {
return
noHighlights;
}
Highlighter.
Highlight[]
h = new
Highlighter.
Highlight[
size];
highlights.
copyInto(
h);
return
h;
}
/**
* 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 p0 starting offset of view
* @param p1 ending offset of view
* @param viewBounds Bounds of View
* @param editor JTextComponent
* @param view View instance being rendered
*/
public void
paintLayeredHighlights(
Graphics g, int
p0, int
p1,
Shape viewBounds,
JTextComponent editor,
View view) {
for (int
counter =
highlights.
size() - 1;
counter >= 0;
counter--) {
HighlightInfo tag =
highlights.
elementAt(
counter);
if (
tag instanceof
LayeredHighlightInfo) {
LayeredHighlightInfo lhi = (
LayeredHighlightInfo)
tag;
int
start =
lhi.
getStartOffset();
int
end =
lhi.
getEndOffset();
if ((
p0 <
start &&
p1 >
start) ||
(
p0 >=
start &&
p0 <
end)) {
lhi.
paintLayeredHighlights(
g,
p0,
p1,
viewBounds,
editor,
view);
}
}
}
}
/**
* Queues damageRange() call into event dispatch thread
* to be sure that views are in consistent state.
*/
private void
safeDamageRange(final
Position p0, final
Position p1) {
safeDamager.
damageRange(
p0,
p1);
}
/**
* Queues damageRange() call into event dispatch thread
* to be sure that views are in consistent state.
*/
private void
safeDamageRange(int
a0, int
a1) throws
BadLocationException {
Document doc =
component.
getDocument();
safeDamageRange(
doc.
createPosition(
a0),
doc.
createPosition(
a1));
}
/**
* If true, highlights are drawn as the Views draw the text. That is
* the Views will call into <code>paintLayeredHighlight</code> which
* will result in a rectangle being drawn before the text is drawn
* (if the offsets are in a highlighted region that is). For this to
* work the painter supplied must be an instance of
* LayeredHighlightPainter.
*/
public void
setDrawsLayeredHighlights(boolean
newValue) {
drawsLayeredHighlights =
newValue;
}
public boolean
getDrawsLayeredHighlights() {
return
drawsLayeredHighlights;
}
// ---- member variables --------------------------------------------
private final static
Highlighter.
Highlight[]
noHighlights =
new
Highlighter.
Highlight[0];
private
Vector<
HighlightInfo>
highlights = new
Vector<
HighlightInfo>();
private
JTextComponent component;
private boolean
drawsLayeredHighlights;
private
SafeDamager safeDamager = new
SafeDamager();
/**
* Default implementation of LayeredHighlighter.LayerPainter that can
* be used for painting highlights.
* <p>
* As of 1.4 this field is final.
*/
public static final
LayeredHighlighter.
LayerPainter DefaultPainter = new
DefaultHighlightPainter(null);
/**
* Simple highlight painter that fills a highlighted area with
* a solid color.
*/
public static class
DefaultHighlightPainter extends
LayeredHighlighter.
LayerPainter {
/**
* Constructs a new highlight painter. If <code>c</code> is null,
* the JTextComponent will be queried for its selection color.
*
* @param c the color for the highlight
*/
public
DefaultHighlightPainter(
Color c) {
color =
c;
}
/**
* Returns the color of the highlight.
*
* @return the color
*/
public
Color getColor() {
return
color;
}
// --- HighlightPainter methods ---------------------------------------
/**
* Paints a highlight.
*
* @param g the graphics context
* @param offs0 the starting model offset >= 0
* @param offs1 the ending model offset >= offs1
* @param bounds the bounding box for the highlight
* @param c the editor
*/
public void
paint(
Graphics g, int
offs0, int
offs1,
Shape bounds,
JTextComponent c) {
Rectangle alloc =
bounds.
getBounds();
try {
// --- determine locations ---
TextUI mapper =
c.
getUI();
Rectangle p0 =
mapper.
modelToView(
c,
offs0);
Rectangle p1 =
mapper.
modelToView(
c,
offs1);
// --- render ---
Color color =
getColor();
if (
color == null) {
g.
setColor(
c.
getSelectionColor());
}
else {
g.
setColor(
color);
}
if (
p0.
y ==
p1.
y) {
// same line, render a rectangle
Rectangle r =
p0.
union(
p1);
g.
fillRect(
r.
x,
r.
y,
r.
width,
r.
height);
} else {
// different lines
int
p0ToMarginWidth =
alloc.
x +
alloc.
width -
p0.
x;
g.
fillRect(
p0.
x,
p0.
y,
p0ToMarginWidth,
p0.
height);
if ((
p0.
y +
p0.
height) !=
p1.
y) {
g.
fillRect(
alloc.
x,
p0.
y +
p0.
height,
alloc.
width,
p1.
y - (
p0.
y +
p0.
height));
}
g.
fillRect(
alloc.
x,
p1.
y, (
p1.
x -
alloc.
x),
p1.
height);
}
} catch (
BadLocationException e) {
// can't render
}
}
// --- LayerPainter methods ----------------------------
/**
* Paints a portion of a highlight.
*
* @param g the graphics context
* @param offs0 the starting model offset >= 0
* @param offs1 the ending model offset >= offs1
* @param bounds the bounding box of the view, which is not
* necessarily the region to paint.
* @param c the editor
* @param view View painting for
* @return region drawing occurred in
*/
public
Shape paintLayer(
Graphics g, int
offs0, int
offs1,
Shape bounds,
JTextComponent c,
View view) {
Color color =
getColor();
if (
color == null) {
g.
setColor(
c.
getSelectionColor());
}
else {
g.
setColor(
color);
}
Rectangle r;
if (
offs0 ==
view.
getStartOffset() &&
offs1 ==
view.
getEndOffset()) {
// Contained in view, can just use bounds.
if (
bounds instanceof
Rectangle) {
r = (
Rectangle)
bounds;
}
else {
r =
bounds.
getBounds();
}
}
else {
// Should only render part of View.
try {
// --- determine locations ---
Shape shape =
view.
modelToView(
offs0,
Position.
Bias.
Forward,
offs1,
Position.
Bias.
Backward,
bounds);
r = (
shape instanceof
Rectangle) ?
(
Rectangle)
shape :
shape.
getBounds();
} catch (
BadLocationException e) {
// can't render
r = null;
}
}
if (
r != null) {
// If we are asked to highlight, we should draw something even
// if the model-to-view projection is of zero width (6340106).
r.
width =
Math.
max(
r.
width, 1);
g.
fillRect(
r.
x,
r.
y,
r.
width,
r.
height);
}
return
r;
}
private
Color color;
}
class
HighlightInfo implements
Highlighter.
Highlight {
public int
getStartOffset() {
return
p0.
getOffset();
}
public int
getEndOffset() {
return
p1.
getOffset();
}
public
Highlighter.
HighlightPainter getPainter() {
return
painter;
}
Position p0;
Position p1;
Highlighter.
HighlightPainter painter;
}
/**
* LayeredHighlightPainter is used when a drawsLayeredHighlights is
* true. It maintains a rectangle of the region to paint.
*/
class
LayeredHighlightInfo extends
HighlightInfo {
void
union(
Shape bounds) {
if (
bounds == null)
return;
Rectangle alloc;
if (
bounds instanceof
Rectangle) {
alloc = (
Rectangle)
bounds;
}
else {
alloc =
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;
}
}
/**
* 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) {
int
start =
getStartOffset();
int
end =
getEndOffset();
// Restrict the region to what we represent
p0 =
Math.
max(
start,
p0);
p1 =
Math.
min(
end,
p1);
// Paint the appropriate region using the painter and union
// the effected region with our bounds.
union(((
LayeredHighlighter.
LayerPainter)
painter).
paintLayer
(
g,
p0,
p1,
viewBounds,
editor,
view));
}
int
x;
int
y;
int
width;
int
height;
}
/**
* This class invokes <code>mapper.damageRange</code> in
* EventDispatchThread. The only one instance per Highlighter
* is cretaed. When a number of ranges should be damaged
* it collects them into queue and damages
* them in consecutive order in <code>run</code>
* call.
*/
class
SafeDamager implements
Runnable {
private
Vector<
Position>
p0 = new
Vector<
Position>(10);
private
Vector<
Position>
p1 = new
Vector<
Position>(10);
private
Document lastDoc = null;
/**
* Executes range(s) damage and cleans range queue.
*/
public synchronized void
run() {
if (
component != null) {
TextUI mapper =
component.
getUI();
if (
mapper != null &&
lastDoc ==
component.
getDocument()) {
// the Document should be the same to properly
// display highlights
int
len =
p0.
size();
for (int
i = 0;
i <
len;
i++){
mapper.
damageRange(
component,
p0.
get(
i).
getOffset(),
p1.
get(
i).
getOffset());
}
}
}
p0.
clear();
p1.
clear();
// release reference
lastDoc = null;
}
/**
* Adds the range to be damaged into the range queue. If the
* range queue is empty (the first call or run() was already
* invoked) then adds this class instance into EventDispatch
* queue.
*
* The method also tracks if the current document changed or
* component is null. In this case it removes all ranges added
* before from range queue.
*/
public synchronized void
damageRange(
Position pos0,
Position pos1) {
if (
component == null) {
p0.
clear();
lastDoc = null;
return;
}
boolean
addToQueue =
p0.
isEmpty();
Document curDoc =
component.
getDocument();
if (
curDoc !=
lastDoc) {
if (!
p0.
isEmpty()) {
p0.
clear();
p1.
clear();
}
lastDoc =
curDoc;
}
p0.
add(
pos0);
p1.
add(
pos1);
if (
addToQueue) {
SwingUtilities.
invokeLater(this);
}
}
}
}