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

org.jdesktop.swingx.demos.search.MatchingTextHighlighter Maven / Gradle / Ivy

/*
 * Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 */
package org.jdesktop.swingx.demos.search;

import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;

import javax.swing.JLabel;
import javax.swing.Painter;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.jdesktop.swingx.decorator.SearchPredicate;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.renderer.PainterAware;

/**
 * 

* Highlighter implementation that changes the background behind * characters that match a regular expression. The highlighting style can be * configured with a {@link Painter}. *

*

* This highlighter is designed to work with a {@link SearchPredicate}. All * other predicate types will be ignored and no highlighting will be performed. *

* *

* NOTE: This highlighter is designed to work with renderers that both * extend {@link JLabel} and implements {@link PainterAware}. Other renderers * will be left undecorated. *

* * @author gregtan * @author Jeanette Winzenburg * @author Thorsten Klimpel */ public class MatchingTextHighlighter extends AbstractHighlighter { /** * Comparator that orders rectangles by their x coordinate. */ private static final Comparator X_AXIS_RECTANGLE_COMPARATOR = new Comparator() { @Override public int compare(Rectangle o1, Rectangle o2) { return o1.x - o2.x; } }; /** * Painter that delegates character highlighting to {@link #painter}. */ private final DelegatingPainter delegatingPainter = new DelegatingPainter(); /** * The painter used for highlighting characters. */ private Painter painter; // Rectangles and insets fields to minimize object instantiation, // used in findHighlightAreas method private Rectangle viewR = new Rectangle(); private Rectangle iconR = new Rectangle(); private Rectangle textR = new Rectangle(); private Insets insets = new Insets(0, 0, 0, 0); private PropertyChangeListener painterListener; /** * Instantiates a MatchingTextHighlighter with no highlight * predicate or painter. */ public MatchingTextHighlighter() { this(null, null); } /** * Instantiates a MatchingTextHighlighter with no highlight * predicate that paints with the specified painter. * * @param painter the painter used to render matching text */ public MatchingTextHighlighter(Painter painter) { this(null, painter); } /** *

* Instantiates a MatchingTextHighlighter with the given * predicate that matches text with the specified pattern with the specified * highlight color. *

* * @param predicate the HighlightPredicate to use * @param painter the painter used to render matching text */ public MatchingTextHighlighter(HighlightPredicate predicate, Painter painter) { super(predicate); setPainter(painter); } /** * {@inheritDoc} */ // MatchingTextHighlighter // Check if Painter applicable @Override protected boolean canHighlight(Component component, ComponentAdapter adapter) { return component instanceof JLabel && component instanceof PainterAware && painter != null && getHighlightPredicate() instanceof SearchPredicate; } // /** * {@inheritDoc} */ @Override protected Component doHighlight(Component component, ComponentAdapter adapter) { ((PainterAware) component).setPainter(delegatingPainter); return component; } /** * Returns the painter used for highlighting matching characters. * * @return a Painter */ public Painter getPainter() { return painter; } /** * Sets the painter used for highlighting matching characters. * * @param painter a Painter */ public void setPainter(Painter painter) { if (areEqual(painter, this.painter)) { return; } uninstallPainterListener(); this.painter = painter; installPainterListener(); fireStateChanged(); } /** * Installs a listener to the painter if appropriate. This implementation * registers its painterListener if the Painter is of type AbstractPainter. */ protected void installPainterListener() { if (getPainter() instanceof AbstractPainter) { ((AbstractPainter) getPainter()).addPropertyChangeListener(getPainterListener()); } } /** * Uninstalls a listener from the painter if appropriate. This * implementation removes its painterListener if the Painter is of type * AbstractPainter. */ protected void uninstallPainterListener() { if (getPainter() instanceof AbstractPainter) { ((AbstractPainter) getPainter()).removePropertyChangeListener(painterListener); } } /** * Lazily creates and returns the property change listener used to listen to * changes of the painter. * * @return the property change listener used to listen to changes of the * painter. */ protected final PropertyChangeListener getPainterListener() { if (painterListener == null) { painterListener = createPainterListener(); } return painterListener; } /** * Creates and returns the property change listener used to listen to * changes of the painter. *

* * This implementation fires a stateChanged on receiving any propertyChange, * if the isAdjusting flag is false. Otherwise does nothing. * * @return the property change listener used to listen to changes of the * painter. */ protected PropertyChangeListener createPainterListener() { PropertyChangeListener l = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { // TODO why is this commented out? // if (isAdjusting) return; fireStateChanged(); } }; return l; } /** * Finds the rectangles that contain rendered characters that match the * pattern. * * @param object an optional configuration parameter. This may be null. * @param width width of the area to paint. * @param height height of the area to paint. * @return a List of Rectangles marking characters * to highlight */ protected List findHighlightAreas(JLabel object, int width, int height) { insets = object.getInsets(insets); viewR.x = 0 + insets.left; viewR.y = 0 + insets.bottom; viewR.width = width - insets.right; viewR.height = height - insets.top; // Reset the text and view rectangle x any y coordinates. // These are not set to 0 in SwingUtilities.layoutCompoundLabel iconR.x = iconR.y = 0; textR.x = textR.y = 0; FontMetrics fm = object.getFontMetrics(object.getFont()); // TODO Bug: Try to get always the current textR-Size. // The method SwingUtilities.layoutCompoundLabel sets the parameter // textR to an old value. While resizing a centered or right aligned // tableColumn, the calculated width of the text-rectangle seems to be // "one event behind". Perhaps Anti-Aliasing draws different? String clippedText = SwingUtilities.layoutCompoundLabel(object, fm, object.getText(), object.getIcon(), object.getVerticalAlignment(), object.getHorizontalAlignment(), object.getVerticalTextPosition(), object.getHorizontalTextPosition(), viewR, iconR, textR, object.getIconTextGap()); int xOffset = calculateXOffset(object, viewR, textR, iconR, object.getIconTextGap()); int yOffset = textR.y - 1;// magic -1 for a nicer look int highlightHeight = textR.height + 1;// magic +1 for a nicer look String clippedTextToSearch = clippedText; // Check to see if the text will be clipped if (!object.getText().equals(clippedText)) { // TODO There has to be a better way that assuming ellipsis are the // last characters of the text clippedTextToSearch = clippedText.substring(0, clippedText.length() - 3); } return createHighlightAreas(object.getText(), clippedTextToSearch, fm, xOffset, yOffset, highlightHeight); } /** * Creates the rectangles that contain matched characters in the given text. *

* TODO: Improve partial clipped matches: If one of the matched characters * is clipped, the remaining characters lose their highlight; just the * ellipsis is highlighted. * * @param fullText useful for highlighting if matches exist in clipped text * and in the ellipsis * @param clippedText the clipped text to search (could be the same as * fullText) * @param fm the font metrics of the rendered font * @param xOffset the x offset at which text rendering starts * @param yOffset the y offset at which text rendering starts (e.g. * different rowHeights) * @param height the height of painted highlights * @return a List of highlight areas to paint */ protected List createHighlightAreas(String fullText, String clippedText, FontMetrics fm, int xOffset, int yOffset, int height) { SearchPredicate predicate = (SearchPredicate) getHighlightPredicate(); Matcher matcher = predicate.getPattern().matcher(clippedText); List highlightAreas = null; int startFrom = 0; while (startFrom < clippedText.length() && matcher.find(startFrom)) { if (highlightAreas == null) { highlightAreas = new ArrayList(); } int start = matcher.start(); int end = matcher.end(); if (start == end) { // empty matcher will cause infinite loop break; } startFrom = end; int highlightx; int highlightWidth; if (start == 0) { // start highlight from the start of the field highlightx = xOffset; } else { // Calculate the width of the unhighlighted text to get the // start of the highlighted region. String strToStart = clippedText.substring(0, start); highlightx = fm.stringWidth(strToStart) + xOffset; } // Get the width of the highlighted region String highlightText = clippedText.substring(start, end); highlightWidth = fm.stringWidth(highlightText); highlightAreas.add(new Rectangle(highlightx, yOffset, highlightWidth, height)); }// while ( startFrom < text.length() && matcher.find( startFrom ) ) if (highlightAreas == null) { highlightAreas = Collections.emptyList(); } else { coalesceHighlightAreas(highlightAreas); } return highlightAreas; } /** * Joins highlight rectangles that mark adjacent horizontal areas into * single rectangles. This is useful to renderers that vary horizontally, * such a horizontal gradient - the gradient will not restart when there are * two adjacent highlight areas. * * @param highlightAreas a List of Rectangles. */ protected void coalesceHighlightAreas(List highlightAreas) { Collections.sort(highlightAreas, X_AXIS_RECTANGLE_COMPARATOR); int i = 0; while (i < highlightAreas.size() - 1) { Rectangle r1 = highlightAreas.get(i); Rectangle r2 = highlightAreas.get(i + 1); if (r1.x + r1.width == r2.x) { r1.width += r2.width; highlightAreas.remove(i + 1); } else { i++; } } } /** * Calculates the x offset of highlights based on component orientation and text direction. * * @param component the renderer component * @param viewR the view rectangle of the renderer component * @param textR the text rectangle of the renderer component * @param iconR icon Rectangle * @param iconTextGap gap between the icon and the text * @return the number of pixels to offset the highlight from the left edge of the component */ protected int calculateXOffset(JLabel component, Rectangle viewR, Rectangle textR, Rectangle iconR, int iconTextGap) { int horizAlignment = component.getHorizontalAlignment(); boolean leftToRight = component.getComponentOrientation().isLeftToRight(); if (horizAlignment == SwingConstants.LEFT || (horizAlignment == SwingConstants.LEADING && leftToRight) || (horizAlignment == SwingConstants.TRAILING && !leftToRight)) { return textR.x;// respect the icon and start the highlight at the // beginning of the text not at 0 } else if (horizAlignment == SwingConstants.RIGHT || (horizAlignment == SwingConstants.TRAILING && leftToRight) || (horizAlignment == SwingConstants.LEADING && !leftToRight)) { int offsetWhenRight; if (leftToRight) offsetWhenRight = viewR.width - textR.width; else { int currentIconTextGap = component.getIcon() != null ? iconTextGap : 0;// The gap between the icon and the text of the // JLabel has to be included, if an icon is set offsetWhenRight = viewR.width - textR.width - iconR.width - currentIconTextGap; } return offsetWhenRight; } else if (horizAlignment == SwingConstants.CENTER) { int currentIconTextGap = component.getIcon() != null ? iconTextGap : 0;// The gap between the icon and the text of the JLabel // has to be included, if an icon is set/ visible int offsetWhenCentered; if (leftToRight) offsetWhenCentered = Math.round((viewR.width - textR.width + iconR.width + currentIconTextGap) / 2f);// round a // float to prevent a jumping (see ColumnHeader for example) // Highlighter (most of the time...(because of Anti-Aliased-Text? // Even or uneven width of text to paint?)) else // if the orientation is RightToLeft the icon and the gap is at // the right side of the text: offsetWhenCentered = Math.round((viewR.width - textR.width - iconR.width - currentIconTextGap) / 2f);// round a // float to prevent a jumping (see ColumnHeader for example) // Highlighter (most of the time...( because of Anti-Aliased-Text? // Even or uneven Width of text to paint?)) return offsetWhenCentered; } throw new AssertionError("Unknown horizonal alignment " + horizAlignment); } /** * Painter that draws highlight rectangles at matching character positions. */ private class DelegatingPainter implements Painter { /** * {@inheritDoc} */ // MatchingTextHighlighter // delegate to painter to paint the matches @Override public void paint(Graphics2D g, JLabel object, int width, int height) { List highlightAreas = findHighlightAreas(object, width, height); for (Rectangle r : highlightAreas) { Graphics2D scratchGraphics = (Graphics2D) g.create(r.x, r.y, r.width, r.height); ((Painter)painter).paint(scratchGraphics, object, r.width, r.height); scratchGraphics.dispose(); } } // } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy