/* * 11/10/2004 * * ChangableHighlightPainter.java - A highlight painter whose color you can * change. * * 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.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 <code>LayerPainter</code> that allows the user to * change several of its properties: * * <ul> * <li>Its color/fill style (can use a <code>GradientPaint</code>, for * example).</li> * <li>Whether the edges of a painted highlight are rounded.</li> * <li>Whether painted highlights have translucency.</li> * </ul> * * @author Robert Futrell * @version 0.6 */ public class ChangeableHighlightPainter extends LayeredHighlighter.LayerPainter implements Serializable { /** * The <code>Paint</code>/<code>Color</code> 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 <code>0.0f</code> (completely invisible) to <code>1.0f</code> * (completely opaque). */ private float alpha; private static final int ARCWIDTH = 8; private static final int ARCHEIGHT = 8; /** * Creates a new <code>ChangableHighlightPainter</code> that paints * highlights with the text area's selection color (i.e., behaves exactly * like * <code>javax.swing.text.DefaultHighlighter.DefaultHighlightPainter * </code>). */ public ChangeableHighlightPainter() { this(null); } /** * Creates a new highlight painter using the specified <code>Paint</code> * without rounded edges. * * @param paint The <code>Paint</code> (usually a * <code>java.awt.Color</code>) with which to paint the * highlights. */ public ChangeableHighlightPainter(Paint paint) { this(paint, false); } /** * Creates a new highlight painter. * * @param paint The <code>Paint</code> (usually a * <code>java.awt.Color</code>) 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 <code>Paint</code> (usually a * <code>java.awt.Color</code>) 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 <code>0.0f</code> (completely * transparent) through <code>1.0f</code> (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 <code>1.0f</code> (the default) means that no * translucency is used; there is no performance hit for this value. For * all other values (<code>[0.0f..1.0f)</code>), 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 <code>Paint</code> (usually a <code>java.awt.Color</code>) * being used to paint highlights. * * @return The <code>Paint</code>. * @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 */ 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 * @throws IOException */ 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 * <code>1.0f</code> (the default), the highlights are rendered completely * opaque. This behavior matches that of * <code>DefaultHighlightPainter</code> and imposes no performance hit. If * this value is below <code>1.0f</code>, it represents how opaque the * highlight will be. There will be a small performance hit for values * less than <code>1.0f</code>. * * @param alpha The new alpha value to use for transparency. * @see #getAlpha */ public void setAlpha(float alpha) { this.alpha = alpha; this.alpha = Math.max(alpha, 0.0f); this.alpha = Math.min(1.0f, alpha); alphaComposite = null; // So it is recreated with new alpha. } /** * Sets the <code>Paint</code> (usually a <code>java.awt.Color</code>) * used to paint this highlight. * * @param paint The new <code>Paint</code>. * @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); } }