0N/A/*
3261N/A * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text;
0N/A
0N/Aimport java.util.Vector;
0N/Aimport java.awt.*;
0N/Aimport javax.swing.plaf.*;
0N/Aimport javax.swing.*;
0N/A
0N/A/**
0N/A * Implements the Highlighter interfaces. Implements a simple highlight
0N/A * painter that renders in a solid color.
0N/A *
0N/A * @author Timothy Prinzing
0N/A * @see Highlighter
0N/A */
0N/Apublic class DefaultHighlighter extends LayeredHighlighter {
0N/A
0N/A /**
0N/A * Creates a new DefaultHighlighther object.
0N/A */
0N/A public DefaultHighlighter() {
0N/A drawsLayeredHighlights = true;
0N/A }
0N/A
0N/A // ---- Highlighter methods ----------------------------------------------
0N/A
0N/A /**
0N/A * Renders the highlights.
0N/A *
0N/A * @param g the graphics context
0N/A */
0N/A public void paint(Graphics g) {
0N/A // PENDING(prinz) - should cull ranges not visible
0N/A int len = highlights.size();
0N/A for (int i = 0; i < len; i++) {
611N/A HighlightInfo info = highlights.elementAt(i);
0N/A if (!(info instanceof LayeredHighlightInfo)) {
0N/A // Avoid allocing unless we need it.
0N/A Rectangle a = component.getBounds();
0N/A Insets insets = component.getInsets();
0N/A a.x = insets.left;
0N/A a.y = insets.top;
0N/A a.width -= insets.left + insets.right;
0N/A a.height -= insets.top + insets.bottom;
0N/A for (; i < len; i++) {
611N/A info = highlights.elementAt(i);
0N/A if (!(info instanceof LayeredHighlightInfo)) {
0N/A Highlighter.HighlightPainter p = info.getPainter();
0N/A p.paint(g, info.getStartOffset(), info.getEndOffset(),
0N/A a, component);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Called when the UI is being installed into the
0N/A * interface of a JTextComponent. Installs the editor, and
0N/A * removes any existing highlights.
0N/A *
0N/A * @param c the editor component
0N/A * @see Highlighter#install
0N/A */
0N/A public void install(JTextComponent c) {
0N/A component = c;
0N/A removeAllHighlights();
0N/A }
0N/A
0N/A /**
0N/A * Called when the UI is being removed from the interface of
0N/A * a JTextComponent.
0N/A *
0N/A * @param c the component
0N/A * @see Highlighter#deinstall
0N/A */
0N/A public void deinstall(JTextComponent c) {
0N/A component = null;
0N/A }
0N/A
0N/A /**
0N/A * Adds a highlight to the view. Returns a tag that can be used
0N/A * to refer to the highlight.
0N/A *
0N/A * @param p0 the start offset of the range to highlight >= 0
0N/A * @param p1 the end offset of the range to highlight >= p0
0N/A * @param p the painter to use to actually render the highlight
0N/A * @return an object that can be used as a tag
0N/A * to refer to the highlight
0N/A * @exception BadLocationException if the specified location is invalid
0N/A */
0N/A public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException {
3032N/A if (p0 < 0) {
3032N/A throw new BadLocationException("Invalid start offset", p0);
3032N/A }
3032N/A
3032N/A if (p1 < p0) {
3032N/A throw new BadLocationException("Invalid end offset", p1);
3032N/A }
3032N/A
0N/A Document doc = component.getDocument();
0N/A HighlightInfo i = (getDrawsLayeredHighlights() &&
0N/A (p instanceof LayeredHighlighter.LayerPainter)) ?
0N/A new LayeredHighlightInfo() : new HighlightInfo();
0N/A i.painter = p;
0N/A i.p0 = doc.createPosition(p0);
0N/A i.p1 = doc.createPosition(p1);
0N/A highlights.addElement(i);
0N/A safeDamageRange(p0, p1);
0N/A return i;
0N/A }
0N/A
0N/A /**
0N/A * Removes a highlight from the view.
0N/A *
0N/A * @param tag the reference to the highlight
0N/A */
0N/A public void removeHighlight(Object tag) {
0N/A if (tag instanceof LayeredHighlightInfo) {
0N/A LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
0N/A if (lhi.width > 0 && lhi.height > 0) {
0N/A component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
0N/A }
0N/A }
0N/A else {
0N/A HighlightInfo info = (HighlightInfo) tag;
0N/A safeDamageRange(info.p0, info.p1);
0N/A }
0N/A highlights.removeElement(tag);
0N/A }
0N/A
0N/A /**
0N/A * Removes all highlights.
0N/A */
0N/A public void removeAllHighlights() {
0N/A TextUI mapper = component.getUI();
0N/A if (getDrawsLayeredHighlights()) {
0N/A int len = highlights.size();
0N/A if (len != 0) {
0N/A int minX = 0;
0N/A int minY = 0;
0N/A int maxX = 0;
0N/A int maxY = 0;
0N/A int p0 = -1;
0N/A int p1 = -1;
0N/A for (int i = 0; i < len; i++) {
611N/A HighlightInfo hi = highlights.elementAt(i);
0N/A if (hi instanceof LayeredHighlightInfo) {
0N/A LayeredHighlightInfo info = (LayeredHighlightInfo)hi;
0N/A minX = Math.min(minX, info.x);
0N/A minY = Math.min(minY, info.y);
0N/A maxX = Math.max(maxX, info.x + info.width);
0N/A maxY = Math.max(maxY, info.y + info.height);
0N/A }
0N/A else {
0N/A if (p0 == -1) {
0N/A p0 = hi.p0.getOffset();
0N/A p1 = hi.p1.getOffset();
0N/A }
0N/A else {
0N/A p0 = Math.min(p0, hi.p0.getOffset());
0N/A p1 = Math.max(p1, hi.p1.getOffset());
0N/A }
0N/A }
0N/A }
0N/A if (minX != maxX && minY != maxY) {
0N/A component.repaint(minX, minY, maxX - minX, maxY - minY);
0N/A }
0N/A if (p0 != -1) {
0N/A try {
0N/A safeDamageRange(p0, p1);
0N/A } catch (BadLocationException e) {}
0N/A }
0N/A highlights.removeAllElements();
0N/A }
0N/A }
0N/A else if (mapper != null) {
0N/A int len = highlights.size();
0N/A if (len != 0) {
0N/A int p0 = Integer.MAX_VALUE;
0N/A int p1 = 0;
0N/A for (int i = 0; i < len; i++) {
611N/A HighlightInfo info = highlights.elementAt(i);
0N/A p0 = Math.min(p0, info.p0.getOffset());
0N/A p1 = Math.max(p1, info.p1.getOffset());
0N/A }
0N/A try {
0N/A safeDamageRange(p0, p1);
0N/A } catch (BadLocationException e) {}
0N/A
0N/A highlights.removeAllElements();
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Changes a highlight.
0N/A *
0N/A * @param tag the highlight tag
0N/A * @param p0 the beginning of the range >= 0
0N/A * @param p1 the end of the range >= p0
0N/A * @exception BadLocationException if the specified location is invalid
0N/A */
0N/A public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException {
3032N/A if (p0 < 0) {
3032N/A throw new BadLocationException("Invalid beginning of the range", p0);
3032N/A }
3032N/A
3032N/A if (p1 < p0) {
3032N/A throw new BadLocationException("Invalid end of the range", p1);
3032N/A }
3032N/A
0N/A Document doc = component.getDocument();
0N/A if (tag instanceof LayeredHighlightInfo) {
0N/A LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
0N/A if (lhi.width > 0 && lhi.height > 0) {
0N/A component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
0N/A }
0N/A // Mark the highlights region as invalid, it will reset itself
0N/A // next time asked to paint.
0N/A lhi.width = lhi.height = 0;
0N/A lhi.p0 = doc.createPosition(p0);
0N/A lhi.p1 = doc.createPosition(p1);
0N/A safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
0N/A }
0N/A else {
0N/A HighlightInfo info = (HighlightInfo) tag;
0N/A int oldP0 = info.p0.getOffset();
0N/A int oldP1 = info.p1.getOffset();
0N/A if (p0 == oldP0) {
0N/A safeDamageRange(Math.min(oldP1, p1),
0N/A Math.max(oldP1, p1));
0N/A } else if (p1 == oldP1) {
0N/A safeDamageRange(Math.min(p0, oldP0),
0N/A Math.max(p0, oldP0));
0N/A } else {
0N/A safeDamageRange(oldP0, oldP1);
0N/A safeDamageRange(p0, p1);
0N/A }
0N/A info.p0 = doc.createPosition(p0);
0N/A info.p1 = doc.createPosition(p1);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Makes a copy of the highlights. Does not actually clone each highlight,
0N/A * but only makes references to them.
0N/A *
0N/A * @return the copy
0N/A * @see Highlighter#getHighlights
0N/A */
0N/A public Highlighter.Highlight[] getHighlights() {
0N/A int size = highlights.size();
0N/A if (size == 0) {
0N/A return noHighlights;
0N/A }
0N/A Highlighter.Highlight[] h = new Highlighter.Highlight[size];
0N/A highlights.copyInto(h);
0N/A return h;
0N/A }
0N/A
0N/A /**
0N/A * When leaf Views (such as LabelView) are rendering they should
0N/A * call into this method. If a highlight is in the given region it will
0N/A * be drawn immediately.
0N/A *
0N/A * @param g Graphics used to draw
0N/A * @param p0 starting offset of view
0N/A * @param p1 ending offset of view
0N/A * @param viewBounds Bounds of View
0N/A * @param editor JTextComponent
0N/A * @param view View instance being rendered
0N/A */
0N/A public void paintLayeredHighlights(Graphics g, int p0, int p1,
0N/A Shape viewBounds,
0N/A JTextComponent editor, View view) {
0N/A for (int counter = highlights.size() - 1; counter >= 0; counter--) {
611N/A HighlightInfo tag = highlights.elementAt(counter);
0N/A if (tag instanceof LayeredHighlightInfo) {
0N/A LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
0N/A int start = lhi.getStartOffset();
0N/A int end = lhi.getEndOffset();
0N/A if ((p0 < start && p1 > start) ||
0N/A (p0 >= start && p0 < end)) {
0N/A lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
0N/A editor, view);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Queues damageRange() call into event dispatch thread
0N/A * to be sure that views are in consistent state.
0N/A */
0N/A private void safeDamageRange(final Position p0, final Position p1) {
0N/A safeDamager.damageRange(p0, p1);
0N/A }
0N/A
0N/A /**
0N/A * Queues damageRange() call into event dispatch thread
0N/A * to be sure that views are in consistent state.
0N/A */
0N/A private void safeDamageRange(int a0, int a1) throws BadLocationException {
0N/A Document doc = component.getDocument();
0N/A safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
0N/A }
0N/A
0N/A /**
0N/A * If true, highlights are drawn as the Views draw the text. That is
0N/A * the Views will call into <code>paintLayeredHighlight</code> which
0N/A * will result in a rectangle being drawn before the text is drawn
0N/A * (if the offsets are in a highlighted region that is). For this to
0N/A * work the painter supplied must be an instance of
0N/A * LayeredHighlightPainter.
0N/A */
0N/A public void setDrawsLayeredHighlights(boolean newValue) {
0N/A drawsLayeredHighlights = newValue;
0N/A }
0N/A
0N/A public boolean getDrawsLayeredHighlights() {
0N/A return drawsLayeredHighlights;
0N/A }
0N/A
0N/A // ---- member variables --------------------------------------------
0N/A
0N/A private final static Highlighter.Highlight[] noHighlights =
0N/A new Highlighter.Highlight[0];
611N/A private Vector<HighlightInfo> highlights = new Vector<HighlightInfo>();
0N/A private JTextComponent component;
0N/A private boolean drawsLayeredHighlights;
0N/A private SafeDamager safeDamager = new SafeDamager();
0N/A
0N/A
0N/A /**
0N/A * Default implementation of LayeredHighlighter.LayerPainter that can
0N/A * be used for painting highlights.
0N/A * <p>
0N/A * As of 1.4 this field is final.
0N/A */
0N/A public static final LayeredHighlighter.LayerPainter DefaultPainter = new DefaultHighlightPainter(null);
0N/A
0N/A
0N/A /**
0N/A * Simple highlight painter that fills a highlighted area with
0N/A * a solid color.
0N/A */
0N/A public static class DefaultHighlightPainter extends LayeredHighlighter.LayerPainter {
0N/A
0N/A /**
0N/A * Constructs a new highlight painter. If <code>c</code> is null,
0N/A * the JTextComponent will be queried for its selection color.
0N/A *
0N/A * @param c the color for the highlight
0N/A */
0N/A public DefaultHighlightPainter(Color c) {
0N/A color = c;
0N/A }
0N/A
0N/A /**
0N/A * Returns the color of the highlight.
0N/A *
0N/A * @return the color
0N/A */
0N/A public Color getColor() {
0N/A return color;
0N/A }
0N/A
0N/A // --- HighlightPainter methods ---------------------------------------
0N/A
0N/A /**
0N/A * Paints a highlight.
0N/A *
0N/A * @param g the graphics context
0N/A * @param offs0 the starting model offset >= 0
0N/A * @param offs1 the ending model offset >= offs1
0N/A * @param bounds the bounding box for the highlight
0N/A * @param c the editor
0N/A */
0N/A public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
0N/A Rectangle alloc = bounds.getBounds();
0N/A try {
0N/A // --- determine locations ---
0N/A TextUI mapper = c.getUI();
0N/A Rectangle p0 = mapper.modelToView(c, offs0);
0N/A Rectangle p1 = mapper.modelToView(c, offs1);
0N/A
0N/A // --- render ---
0N/A Color color = getColor();
0N/A
0N/A if (color == null) {
0N/A g.setColor(c.getSelectionColor());
0N/A }
0N/A else {
0N/A g.setColor(color);
0N/A }
0N/A if (p0.y == p1.y) {
0N/A // same line, render a rectangle
0N/A Rectangle r = p0.union(p1);
0N/A g.fillRect(r.x, r.y, r.width, r.height);
0N/A } else {
0N/A // different lines
0N/A int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
0N/A g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
0N/A if ((p0.y + p0.height) != p1.y) {
0N/A g.fillRect(alloc.x, p0.y + p0.height, alloc.width,
0N/A p1.y - (p0.y + p0.height));
0N/A }
0N/A g.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height);
0N/A }
0N/A } catch (BadLocationException e) {
0N/A // can't render
0N/A }
0N/A }
0N/A
0N/A // --- LayerPainter methods ----------------------------
0N/A /**
0N/A * Paints a portion of a highlight.
0N/A *
0N/A * @param g the graphics context
0N/A * @param offs0 the starting model offset >= 0
0N/A * @param offs1 the ending model offset >= offs1
0N/A * @param bounds the bounding box of the view, which is not
0N/A * necessarily the region to paint.
0N/A * @param c the editor
0N/A * @param view View painting for
0N/A * @return region drawing occured in
0N/A */
0N/A public Shape paintLayer(Graphics g, int offs0, int offs1,
0N/A Shape bounds, JTextComponent c, View view) {
0N/A Color color = getColor();
0N/A
0N/A if (color == null) {
0N/A g.setColor(c.getSelectionColor());
0N/A }
0N/A else {
0N/A g.setColor(color);
0N/A }
0N/A
0N/A Rectangle r;
0N/A
0N/A if (offs0 == view.getStartOffset() &&
0N/A offs1 == view.getEndOffset()) {
0N/A // Contained in view, can just use bounds.
0N/A if (bounds instanceof Rectangle) {
0N/A r = (Rectangle) bounds;
0N/A }
0N/A else {
0N/A r = bounds.getBounds();
0N/A }
0N/A }
0N/A else {
0N/A // Should only render part of View.
0N/A try {
0N/A // --- determine locations ---
0N/A Shape shape = view.modelToView(offs0, Position.Bias.Forward,
0N/A offs1,Position.Bias.Backward,
0N/A bounds);
0N/A r = (shape instanceof Rectangle) ?
0N/A (Rectangle)shape : shape.getBounds();
0N/A } catch (BadLocationException e) {
0N/A // can't render
0N/A r = null;
0N/A }
0N/A }
0N/A
0N/A if (r != null) {
0N/A // If we are asked to highlight, we should draw something even
0N/A // if the model-to-view projection is of zero width (6340106).
0N/A r.width = Math.max(r.width, 1);
0N/A
0N/A g.fillRect(r.x, r.y, r.width, r.height);
0N/A }
0N/A
0N/A return r;
0N/A }
0N/A
0N/A private Color color;
0N/A
0N/A }
0N/A
0N/A
0N/A class HighlightInfo implements Highlighter.Highlight {
0N/A
0N/A public int getStartOffset() {
0N/A return p0.getOffset();
0N/A }
0N/A
0N/A public int getEndOffset() {
0N/A return p1.getOffset();
0N/A }
0N/A
0N/A public Highlighter.HighlightPainter getPainter() {
0N/A return painter;
0N/A }
0N/A
0N/A Position p0;
0N/A Position p1;
0N/A Highlighter.HighlightPainter painter;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * LayeredHighlightPainter is used when a drawsLayeredHighlights is
0N/A * true. It maintains a rectangle of the region to paint.
0N/A */
0N/A class LayeredHighlightInfo extends HighlightInfo {
0N/A
0N/A void union(Shape bounds) {
0N/A if (bounds == null)
0N/A return;
0N/A
0N/A Rectangle alloc;
0N/A if (bounds instanceof Rectangle) {
0N/A alloc = (Rectangle)bounds;
0N/A }
0N/A else {
0N/A alloc = bounds.getBounds();
0N/A }
0N/A if (width == 0 || height == 0) {
0N/A x = alloc.x;
0N/A y = alloc.y;
0N/A width = alloc.width;
0N/A height = alloc.height;
0N/A }
0N/A else {
0N/A width = Math.max(x + width, alloc.x + alloc.width);
0N/A height = Math.max(y + height, alloc.y + alloc.height);
0N/A x = Math.min(x, alloc.x);
0N/A width -= x;
0N/A y = Math.min(y, alloc.y);
0N/A height -= y;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Restricts the region based on the receivers offsets and messages
0N/A * the painter to paint the region.
0N/A */
0N/A void paintLayeredHighlights(Graphics g, int p0, int p1,
0N/A Shape viewBounds, JTextComponent editor,
0N/A View view) {
0N/A int start = getStartOffset();
0N/A int end = getEndOffset();
0N/A // Restrict the region to what we represent
0N/A p0 = Math.max(start, p0);
0N/A p1 = Math.min(end, p1);
0N/A // Paint the appropriate region using the painter and union
0N/A // the effected region with our bounds.
0N/A union(((LayeredHighlighter.LayerPainter)painter).paintLayer
0N/A (g, p0, p1, viewBounds, editor, view));
0N/A }
0N/A
0N/A int x;
0N/A int y;
0N/A int width;
0N/A int height;
0N/A }
0N/A
0N/A /**
0N/A * This class invokes <code>mapper.damageRange</code> in
0N/A * EventDispatchThread. The only one instance per Highlighter
0N/A * is cretaed. When a number of ranges should be damaged
0N/A * it collects them into queue and damages
0N/A * them in consecutive order in <code>run</code>
0N/A * call.
0N/A */
0N/A class SafeDamager implements Runnable {
611N/A private Vector<Position> p0 = new Vector<Position>(10);
611N/A private Vector<Position> p1 = new Vector<Position>(10);
0N/A private Document lastDoc = null;
0N/A
0N/A /**
0N/A * Executes range(s) damage and cleans range queue.
0N/A */
0N/A public synchronized void run() {
0N/A if (component != null) {
0N/A TextUI mapper = component.getUI();
0N/A if (mapper != null && lastDoc == component.getDocument()) {
0N/A // the Document should be the same to properly
0N/A // display highlights
0N/A int len = p0.size();
0N/A for (int i = 0; i < len; i++){
0N/A mapper.damageRange(component,
611N/A p0.get(i).getOffset(),
611N/A p1.get(i).getOffset());
0N/A }
0N/A }
0N/A }
0N/A p0.clear();
0N/A p1.clear();
0N/A
0N/A // release reference
0N/A lastDoc = null;
0N/A }
0N/A
0N/A /**
0N/A * Adds the range to be damaged into the range queue. If the
0N/A * range queue is empty (the first call or run() was already
0N/A * invoked) then adds this class instance into EventDispatch
0N/A * queue.
0N/A *
0N/A * The method also tracks if the current document changed or
0N/A * component is null. In this case it removes all ranges added
0N/A * before from range queue.
0N/A */
0N/A public synchronized void damageRange(Position pos0, Position pos1) {
0N/A if (component == null) {
0N/A p0.clear();
0N/A lastDoc = null;
0N/A return;
0N/A }
0N/A
0N/A boolean addToQueue = p0.isEmpty();
0N/A Document curDoc = component.getDocument();
0N/A if (curDoc != lastDoc) {
0N/A if (!p0.isEmpty()) {
0N/A p0.clear();
0N/A p1.clear();
0N/A }
0N/A lastDoc = curDoc;
0N/A }
0N/A p0.add(pos0);
0N/A p1.add(pos1);
0N/A
0N/A if (addToQueue) {
0N/A SwingUtilities.invokeLater(this);
0N/A }
0N/A }
0N/A }
0N/A}