All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.swingset3.codeview.SnippetHighlighter Maven / Gradle / Ivy

There is a newer version: 2.6.5
Show newest version
/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.sun.swingset3.codeview;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.*;
import javax.swing.SwingUtilities;
import javax.swing.plaf.TextUI;
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;

/**
 * Implements the Highlighter interfaces.  Implements a simple highlight
 * painter that renders in a solid color.
 * 
 * @see     Highlighter
 */
public class SnippetHighlighter extends LayeredHighlighter {

    /**
     * Creates a new DefaultHighlighther object.
     */
    public SnippetHighlighter() {
	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 lge 0
     * @param p1   the end offset of the range to highlight lge 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 {
	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);
        // For snippets, we want to make sure selection is layered ON TOP
        // since we add transparency to the selection color;  so rather
        // than append the highlight, we insert it in the front.
	highlights.insertElementAt(i, 0);
        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 lge 0
     * @param p1 the end of the range lge p0
     * @exception BadLocationException if the specified location is invalid
     */
    public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException {
	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--) {
	    Object 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 paintLayeredHighlight 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 final Vector highlights = new Vector();
    private JTextComponent component;
    private boolean drawsLayeredHighlights;
    private final SafeDamager safeDamager = new SafeDamager();


    /**
     * Default implementation of LayeredHighlighter.LayerPainter that can
     * be used for painting highlights.
     * 

* As of 1.4 this field is final. */ public static LayeredHighlighter.LayerPainter SnippetPainter = new SnippetHighlightPainter(null); /** * Simple highlight painter that fills a highlighted area with * a solid color. */ public static class SnippetHighlightPainter extends LayeredHighlighter.LayerPainter { /** * Constructs a new highlight painter. If c is null, * the JTextComponent will be queried for its selection color. * * @param c the color for the highlight */ public SnippetHighlightPainter(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 lge 0 * @param offs1 the ending model offset lge 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 lge 0 * @param offs1 the ending model offset lge 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 occured in */ public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) { Color base = getColor(); // use transparency so selection isn't clobbered Color color = base != null? new Color(base.getRed(), base.getGreen(), base.getBlue(), 255) : null; if (color == null) { g.setColor(c.getSelectionColor()); } else { g.setColor(color); } if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) { // Contained in view, can just use bounds. Rectangle alloc; if (bounds instanceof Rectangle) { alloc = (Rectangle)bounds; } else { alloc = bounds.getBounds(); } // For CodeViewer Snippet painting, this is the paint block that is called. // We want to paint the highlight on the full width of the text pane, // so we only need to do this when the region is the beginning of a new // line. //g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height); g.fillRect(alloc.x, alloc.y, c.getWidth() - alloc.x, alloc.height); } else { // Should only render part of View. try { // --- determine locations --- Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1,Position.Bias.Backward, bounds); Rectangle r = (shape instanceof Rectangle) ? (Rectangle)shape : shape.getBounds(); g.fillRect(0, r.y, c.getWidth(), r.height); return r; } catch (BadLocationException e) { // can't render } } // Only if exception return null; } private Color color; } private static 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. */ private static 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 mapper.damageRange 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 run * call. */ class SafeDamager implements Runnable { private final List p0 = new ArrayList(); private final List p1 = new ArrayList(); 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. */ private 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); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy