/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* 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 Business Objects 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.
*/
/*
* SearchableTreeCellRenderer.java
* Created: Nov 4, 2004
* By: Kevin Sit
*/
package org.openquark.util.ui;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
/**
* A <code>TreeCellRenderer</code> implementation that is used in conjunction
* with a <code>SearchableTree</code>.
*/
public class SearchableTreeCellRenderer implements TreeCellRenderer {
/**
* Each renderer can implements its own delegate to customize its behaviour
* for displaying a search result. By using a delegate, we can avoid
* having a lot of instanceof checks during runtime, consider the fact
* that a renderer is used so frequently.
*/
private abstract class Delegate {
/**
* Configure the renderer for display.
* @param renderer
* @return Component
*/
public abstract Component configureRenderer(Component renderer);
}
/**
* A delegate implementation that is used for any TreeCellRenderer.
*/
private class GenericTreeCellRendererDelegate extends Delegate {
/** {@inheritDoc} */
@Override
public Component configureRenderer(Component renderer) {
renderer.setForeground(HIGHLIGHT_COLOR);
return renderer;
}
}
/**
* A delegate implementation that is used for DefaultTreeCellRenderer.
*/
private class DefaultTreeCellRendererDelegate extends Delegate {
/** {@inheritDoc} */
@Override
public Component configureRenderer(Component renderer) {
DefaultTreeCellRenderer comp = (DefaultTreeCellRenderer) renderer;
String text = comp.getText();
if (text != null && lowerCaseSearchString != null) {
int startIndex = text.toLowerCase().indexOf(lowerCaseSearchString);
if (startIndex >= 0) {
int endIndex = startIndex + searchStringLength; // index is exclusive
StringBuilder sb = new StringBuilder();
sb.append("<html>"); //$NON-NLS-1$
if (startIndex > 0) {
sb.append(text.substring(0, startIndex));
}
if (searchStringLength > 0) {
sb.append("<font color=\"#"); //$NON-NLS-1$
sb.append(HIGHLIGHT_COLOR_HEX);
sb.append(")\">"); //$NON-NLS-1$
sb.append(text.substring(startIndex, endIndex));
sb.append("</font>"); //$NON-NLS-1$
}
if (endIndex < text.length()) {
sb.append(text.substring(endIndex, text.length()));
}
sb.append("</html>"); //$NON-NLS-1$
comp.setText(sb.toString());
}
}
return comp;
}
}
/** The default highlight color */
private static final Color HIGHLIGHT_COLOR = Color.MAGENTA;
/** The default highlight color as hex string */
private static final String HIGHLIGHT_COLOR_HEX = "FF00FF"; //$NON-NLS-1$
/** The wrapped renderer */
private final TreeCellRenderer renderer;
/** The delegate */
private final Delegate delegate;
/** The actual search string */
private String searchString;
/** Lower case search string, avoid recomputing this again and again */
private String lowerCaseSearchString;
/** Search string length, avoid recomputing this again and again */
private int searchStringLength;
private SearchableTreeCellRenderer(TreeCellRenderer renderer) {
this.renderer = renderer;
if (renderer instanceof DefaultTreeCellRenderer) {
this.delegate = new DefaultTreeCellRendererDelegate();
} else {
this.delegate = new GenericTreeCellRendererDelegate();
}
}
/**
* Returns the search string.
* @return String
*/
public String getSearchString() {
return searchString;
}
/**
* Sets the search string.
* @param searchString
*/
public void setSearchString(String searchString) {
this.searchString = searchString;
this.lowerCaseSearchString = searchString == null ? null : searchString.toLowerCase();
this.searchStringLength = searchString == null ? 0 : searchString.length();
}
/* (non-Javadoc)
* @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean)
*/
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component c = renderer.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof SearchHighlightTreeNode) {
return delegate.configureRenderer(c);
} else {
return c;
}
}
/**
* Creates a new instance of <code>SearchableTreeCellRenderer</code> and use it
* to wrap the given tree cell renderer.
* @param renderer
* @return TreeCellRenderer
*/
public static TreeCellRenderer create(TreeCellRenderer renderer) {
// The given renderer can never be null
if (renderer == null) {
renderer = new DefaultTreeCellRenderer();
}
// Only wrap the renderer if it is not a searchable tree cell renderer
if (renderer instanceof SearchableTreeCellRenderer) {
return renderer;
} else {
return new SearchableTreeCellRenderer(renderer);
}
}
}