org.fife.ui.rtextarea.ChangeableHighlightPainter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
/*
* 11/10/2004
*
* ChangeableHighlightPainter.java - A highlight painter whose color you can
* change.
*
* This library is distributed under a modified BSD license. See the included
* LICENSE file for details.
*/
package org.fife.ui.rtextarea;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.SystemColor;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.swing.plaf.TextUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.LayeredHighlighter;
import javax.swing.text.Position;
import javax.swing.text.View;
/**
* An extension of LayerPainter
that allows the user to
* change several of its properties:
*
*
* - Its color/fill style (can use a
GradientPaint
, for
* example).
* - Whether the edges of a painted highlight are rounded.
* - Whether painted highlights have translucency.
*
*
* @author Robert Futrell
* @version 0.6
*/
public class ChangeableHighlightPainter
extends LayeredHighlighter.LayerPainter implements Serializable {
/**
* The Paint
/Color
of this highlight.
*/
private Paint paint;
/**
* Whether selections have rounded edges.
*/
private boolean roundedEdges;
/**
* The alpha composite used to render with translucency.
*/
private transient AlphaComposite alphaComposite;
/**
* The alpha value used in computing translucency. This should stay in the
* range 0.0f
(completely invisible) to 1.0f
* (completely opaque).
*/
private float alpha;
private static final int ARCWIDTH = 8;
private static final int ARCHEIGHT = 8;
/**
* Creates a new ChangeableHighlightPainter
that paints
* highlights with the text area's selection color (i.e., behaves exactly
* like
* javax.swing.text.DefaultHighlighter.DefaultHighlightPainter
*
).
*/
public ChangeableHighlightPainter() {
this(null);
}
/**
* Creates a new highlight painter using the specified Paint
* without rounded edges.
*
* @param paint The Paint
(usually a
* java.awt.Color
) with which to paint the
* highlights.
*/
public ChangeableHighlightPainter(Paint paint) {
this(paint, false);
}
/**
* Creates a new highlight painter.
*
* @param paint The Paint
(usually a
* java.awt.Color
) with which to paint the
* highlights.
* @param rounded Whether to use rounded edges on the highlights.
*/
public ChangeableHighlightPainter(Paint paint, boolean rounded) {
this(paint, rounded, 1.0f);
}
/**
* Creates a new highlight painter.
*
* @param paint The Paint
(usually a
* java.awt.Color
) with which to paint the
* highlights.
* @param rounded Whether to use rounded edges on the highlights.
* @param alpha The alpha value to use when painting highlights. This
* value should be in the range 0.0f
(completely
* transparent) through 1.0f
(opaque).
*/
public ChangeableHighlightPainter(Paint paint, boolean rounded,
float alpha) {
setPaint(paint);
setRoundedEdges(rounded);
setAlpha(alpha);
}
/**
* Returns the alpha value used in computing the translucency of these
* highlights. A value of 1.0f
(the default) means that no
* translucency is used; there is no performance hit for this value. For
* all other values ([0.0f..1.0f)
), there will be a
* performance hit.
*
* @return The alpha value.
* @see #setAlpha
*/
public float getAlpha() {
return alpha;
}
/**
* Returns the alpha composite to use when rendering highlights with this
* painter.
*
* @return The alpha composite.
*/
private AlphaComposite getAlphaComposite() {
if (alphaComposite==null) {
alphaComposite = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
}
return alphaComposite;
}
/**
* Returns the Paint
(usually a java.awt.Color
)
* being used to paint highlights.
*
* @return The Paint
.
* @see #setPaint
*/
public Paint getPaint() {
return paint;
}
/**
* Returns whether rounded edges are used when painting selections with
* this highlight painter.
*
* @return Whether rounded edges are used.
* @see #setRoundedEdges
*/
public boolean getRoundedEdges() {
return roundedEdges;
}
/**
* 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
*/
@Override
public void paint(Graphics g, int offs0, int offs1, Shape bounds,
JTextComponent c) {
Rectangle alloc = bounds.getBounds();
// Set up translucency if necessary.
Graphics2D g2d = (Graphics2D)g;
Composite originalComposite = null;
if (getAlpha()<1.0f) {
originalComposite = g2d.getComposite();
g2d.setComposite(getAlphaComposite());
}
try {
// Determine locations.
TextUI mapper = c.getUI();
Rectangle p0 = mapper.modelToView(c, offs0);
Rectangle p1 = mapper.modelToView(c, offs1);
Paint paint = getPaint();
if (paint==null) {
g2d.setColor(c.getSelectionColor());
}
else {
g2d.setPaint(paint);
}
// Entire highlight is on one line.
if (p0.y == p1.y) {
// Standard Swing views return 0 width for chars, but ours
// returns the char's width. Set this to 0 here since p1 is
// technically an exclusive boundary.
p1.width = 0;
Rectangle r = p0.union(p1);
g2d.fillRect(r.x, r.y, r.width, r.height);
}
// Highlight spans lines.
else {
int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
g2d.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
if ((p0.y + p0.height) != p1.y) {
g2d.fillRect(alloc.x, p0.y + p0.height, alloc.width,
p1.y - (p0.y + p0.height));
}
g2d.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height);
}
} catch (BadLocationException e) {
// Never happens.
e.printStackTrace();
} finally {
// Restore state from before translucency if necessary.
if (getAlpha()<1.0f) {
g2d.setComposite(originalComposite);
}
}
}
/**
* 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
*/
@Override
public Shape paintLayer(Graphics g, int offs0, int offs1,
Shape bounds, JTextComponent c, View view) {
// Set up translucency if necessary.
Graphics2D g2d = (Graphics2D)g;
Composite originalComposite = null;
if (getAlpha()<1.0f) {
originalComposite = g2d.getComposite();
g2d.setComposite(getAlphaComposite());
}
// Set the color (our own if defined, otherwise text area's).
Paint paint = getPaint();
if (paint==null) {
g2d.setColor(c.getSelectionColor());
}
else {
g2d.setPaint(paint);
}
// This special case isn't needed for most standard Swing Views (which
// always return a width of 1 for modelToView() calls), but it is
// needed for RSTA views, which actually return the width of chars for
// modelToView calls. But this should be faster anyway, as we
// short-circuit and do only one modelToView() for one offset.
if (offs0==offs1) {
try {
Shape s = view.modelToView(offs0, bounds,
Position.Bias.Forward);
Rectangle r = s.getBounds();
g.drawLine(r.x, r.y, r.x, r.y+r.height);
return r;
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
return null;
}
}
// Contained in view, can just use bounds.
if (offs0==view.getStartOffset() && offs1==view.getEndOffset()) {
Rectangle alloc;
if (bounds instanceof Rectangle) {
alloc = (Rectangle)bounds;
}
else {
alloc = bounds.getBounds();
}
g2d.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
// Restore state from before translucency if necessary.
if (getAlpha()<1.0f) {
g2d.setComposite(originalComposite);
}
return alloc;
}
// Should only render part of View.
else {
try {
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
offs1,Position.Bias.Backward,
bounds);
Rectangle r = (shape instanceof Rectangle) ?
(Rectangle)shape : shape.getBounds();
if (roundedEdges) {
g2d.fillRoundRect(r.x,r.y, r.width,r.height, ARCWIDTH,
ARCHEIGHT);
}
else {
g2d.fillRect(r.x, r.y, r.width, r.height);
}
// Restore state from before translucency if necessary.
if (getAlpha()<1.0f) {
g2d.setComposite(originalComposite);
}
return r;
} catch (BadLocationException ble) {
ble.printStackTrace();
} finally {
// Restore state from before translucency if necessary.
if (getAlpha()<1.0f) {
g2d.setComposite(originalComposite);
}
}
}
// Only if exception
return null;
}
/**
* Deserializes a painter.
*
* @param s The stream to read from.
* @throws ClassNotFoundException If something bad happens.
* @throws IOException If an IO error occurs.
*/
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException {
s.defaultReadObject();
// We cheat and always serialize the Paint as a Color. "-1" means
// no Paint (i.e. use system selection color when painting).
int rgb = s.readInt();
paint = rgb==-1 ? null : new Color(rgb);
alphaComposite = null; // Keep FindBugs happy. This will get set later
}
/**
* Sets the alpha value used in rendering highlights. If this value is
* 1.0f
(the default), the highlights are rendered completely
* opaque. This behavior matches that of
* DefaultHighlightPainter
and imposes no performance hit. If
* this value is below 1.0f
, it represents how opaque the
* highlight will be. There will be a small performance hit for values
* less than 1.0f
.
*
* @param alpha The new alpha value to use for transparency.
* @see #getAlpha
*/
public void setAlpha(float alpha) {
this.alpha = Math.max(alpha, 0.0f);
this.alpha = Math.min(1.0f, this.alpha);
alphaComposite = null; // So it is recreated with new alpha.
}
/**
* Sets the Paint
(usually a java.awt.Color
)
* used to paint this highlight.
*
* @param paint The new Paint
.
* @see #getPaint
*/
public void setPaint(Paint paint) {
this.paint = paint;
}
/**
* Sets whether rounded edges are used when painting this highlight.
*
* @param rounded Whether rounded edges should be used.
* @see #getRoundedEdges
*/
public void setRoundedEdges(boolean rounded) {
roundedEdges = rounded;
}
/**
* Serializes this painter.
*
* @param s The stream to write to.
* @throws IOException If an IO error occurs.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
int rgb = -1; // No Paint -> Use JTextComponent's selection color
if (paint!=null) {
// NOTE: We cheat and always serialize the Paint as a Color.
// This is (practically) always the case anyway.
Color c = (paint instanceof Color) ? ((Color)paint) :
SystemColor.textHighlight;
rgb = c.getRGB();
}
s.writeInt(rgb);
}
}