/* JMeld is a visual diff and merge tool. Copyright (C) 2007 Kees Kuip This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jmeld.ui.swing; import javax.swing.text.*; import java.awt.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A highlighter that paints in layers. */ public class JMHighlighter implements Highlighter { public static final Integer LAYER0 = 1; public static final Integer LAYER1 = 2; public static final Integer LAYER2 = 3; public static final Integer LAYER3 = 4; public static final Integer UPPER_LAYER; private static ArrayList<Integer> layers; static { layers = new ArrayList<Integer>(); layers.add(LAYER0); layers.add(LAYER1); layers.add(LAYER2); layers.add(LAYER3); UPPER_LAYER = layers.get(layers.size() - 1); } private Map<Integer, List<Highlighter.Highlight>> highlights; private JTextComponent component; private boolean doNotRepaint; public JMHighlighter() { highlights = new HashMap<Integer, List<Highlighter.Highlight>>(); } public void setDoNotRepaint(boolean doNotRepaint) { this.doNotRepaint = doNotRepaint; } /** * Renders the highlights. * * @param g the graphics context */ public void paint(Graphics g) { int upperLayer; List<Highlighter.Highlight> list; Rectangle a; Insets insets; Rectangle clip; int startOffset; int endOffset; int lineHeight; LineNumberBorder lineNumberBorder; clip = g.getClipBounds(); lineHeight = component.getFontMetrics(component.getFont()).getHeight(); startOffset = component.viewToModel(new Point(clip.x - lineHeight, clip.y)); endOffset = component.viewToModel(new Point(clip.x, clip.y + clip.height + lineHeight)); // Just some hacks to allow linenumbers painted in the emptyborder. lineNumberBorder = null; if (component.getBorder() instanceof LineNumberBorder) { lineNumberBorder = (LineNumberBorder) component.getBorder(); } if (lineNumberBorder != null) { lineNumberBorder.paintBefore(g); } a = null; for (Integer layer : layers) { list = highlights.get(layer); if (list == null) { continue; } if (list.size() == 0) { continue; } if (a == null) { a = component.getBounds(); insets = component.getInsets(); a.x = insets.left; a.y = insets.top; a.width -= insets.left + insets.right; a.height -= insets.top + insets.bottom; } for (Highlighter.Highlight hli : list) { // Don't paint highlighters that are not in sight! if (hli.getStartOffset() > endOffset || hli.getEndOffset() < startOffset) { continue; } hli.getPainter().paint(g, hli.getStartOffset(), hli.getEndOffset(), a, component); } } if (lineNumberBorder != null) { lineNumberBorder.paintAfter(g, startOffset, endOffset); } } public void install(JTextComponent c) { component = c; removeAllHighlights(); } public void deinstall(JTextComponent c) { component = null; } public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter painter) throws BadLocationException { return addHighlight(UPPER_LAYER, p0, p1, painter); } public Object addHighlight(Integer layer, int p0, int p1, Highlighter.HighlightPainter painter) throws BadLocationException { Document doc; HighlightInfo hli; doc = component.getDocument(); hli = new HighlightInfo(); hli.p0 = doc.createPosition(p0); hli.p1 = doc.createPosition(p1); hli.painter = painter; getLayer(layer).add(hli); repaint(); return hli; } public void removeHighlight(Object object) { removeHighlight(UPPER_LAYER, object); } public void removeHighlight(Integer layer, Object object) { getLayer(layer).remove(object); repaint(); } public void removeHighlights(Integer layer) { getLayer(layer).clear(); repaint(); } /** * Removes all highlights. */ public void removeAllHighlights() { for (Integer layer : layers) { getLayer(layer).clear(); } repaint(); } public void changeHighlight(Object object, int p0, int p1) throws BadLocationException { changeHighlight(UPPER_LAYER, object, p0, p1); } public void changeHighlight(Integer layer, Object object, int p0, int p1) throws BadLocationException { Document doc; HighlightInfo hli; doc = component.getDocument(); hli = (HighlightInfo) object; hli.p0 = doc.createPosition(p0); hli.p1 = doc.createPosition(p1); repaint(); } /** * 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; Highlighter.Highlight[] result; int index; size = 0; for (Integer layer : layers) { size += getLayer(layer).size(); } result = new Highlighter.Highlight[size]; index = 0; for (Integer layer : layers) { for (Highlighter.Highlight hli : getLayer(layer)) { result[index] = hli; index++; } } return result; } private List<Highlighter.Highlight> getLayer(Integer layer) { List<Highlighter.Highlight> result; result = highlights.get(layer); if (result == null) { result = new ArrayList<Highlighter.Highlight>(); highlights.put(layer, result); } return result; } public void repaint() { if (doNotRepaint) { return; } component.repaint(); } class HighlightInfo implements Highlighter.Highlight { Position p0; Position p1; Highlighter.HighlightPainter painter; public int getStartOffset() { return p0.getOffset(); } public int getEndOffset() { return p1.getOffset(); } public Highlighter.HighlightPainter getPainter() { return painter; } } }