001/* 002 * $Id: SearchPredicate.java 3927 2011-02-22 16:34:11Z kleopatra $ 003 * 004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx.decorator; 022 023import java.awt.Component; 024import java.util.regex.Pattern; 025 026/** 027 * Pattern based HighlightPredicate for searching. Highlights 028 * the current adapter cell if the value matches the pattern. 029 * The highlight scope can be limited to a certain column and 030 * row. <p> 031 * 032 * Note: this differs from PatternPredicate in that it is focused 033 * on the current cell (highlight coordinates == test coordinates) 034 * while the PatternPredicate can have separate test and highlight 035 * coordinates. <p> 036 * 037 * 038 * @author Jeanette Winzenburg 039 */ 040public class SearchPredicate implements HighlightPredicate { 041 public static final int ALL = -1; 042 public static final String MATCH_ALL = ".*"; 043 private int highlightColumn; 044 private int highlightRow; // in view coordinates? 045 private Pattern pattern; 046 047 /** 048 * Instantiates a Predicate with the given Pattern. 049 * All matching cells are highlighted. 050 * 051 * 052 * @param pattern the Pattern to test the cell value against 053 */ 054 public SearchPredicate(Pattern pattern) { 055 this(pattern, ALL, ALL); 056 } 057 058 /** 059 * Instantiates a Predicate with the given Pattern. Highlighting 060 * is limited to matching cells in the given column. 061 * 062 * @param pattern the Pattern to test the cell value against 063 * @param column the column to limit the highlight to 064 */ 065 public SearchPredicate(Pattern pattern, int column) { 066 this(pattern, ALL, column); 067 } 068 069 /** 070 * Instantiates a Predicate with the given Pattern. Highlighting 071 * is limited to matching cells in the given column and row. A 072 * value of -1 indicates all rows/columns. <p> 073 * 074 * Note: the coordinates are asymmetric - rows are in view- and 075 * column in model-coordinates - due to corresponding methods in 076 * ComponentAdapter. Hmm... no need to? This happens on the 077 * current adapter state which is view always, so could use view 078 * only? 079 * 080 * @param pattern the Pattern to test the cell value against 081 * @param row the row index in view coordinates to limit the 082 * highlight. 083 * @param column the column in model coordinates 084 * to limit the highlight to 085 */ 086 public SearchPredicate(Pattern pattern, int row, int column) { 087 this.pattern = pattern; 088 this.highlightColumn = column; 089 this.highlightRow = row; 090 } 091 092 /** 093 * Instantiates a Predicate with a Pattern compiled from the given 094 * regular expression. 095 * All matching cells are highlighted. 096 * 097 * @param regex the regular expression to test the cell value against 098 */ 099 public SearchPredicate(String regex) { 100 this(regex, ALL, ALL); 101 102 } 103 104 /** 105 * Instantiates a Predicate with a Pattern compiled from the given 106 * regular expression. Highlighting 107 * is applied to matching cells in all rows, but only in the given column. A 108 * value of ALL indicates all columns. <p> 109 * 110 * @param regex the regular expression to test the cell value against 111 * @param column the column index in model coordinates to limit the highlight to 112 */ 113 public SearchPredicate(String regex, int column) { 114 this(regex, ALL, column); 115 } 116 117 /** 118 * Instantiates a Predicate with a Pattern compiled from the given 119 * regular expression. Highlighting 120 * is limited to matching cells in the given column and row. A 121 * value of ALL indicates all rows/columns. <p> 122 * 123 * Note: the coordinates are asymmetric - rows are in view- and 124 * column in model-coordinates - due to corresponding methods in 125 * ComponentAdapter. Hmm... no need to? This happens on the 126 * current adapter state which is view always, so could use view 127 * only? 128 * 129 * @param regex the Pattern to test the cell value against 130 * @param row the row index in view coordinates to limit the 131 * highlight. 132 * @param column the column in model coordinates 133 * to limit the highlight to 134 */ 135 public SearchPredicate(String regex, int row, int column) { 136 // test against empty string 137 this((regex != null) && (regex.length() > 0) ? 138 Pattern.compile(regex) : null, row, column); 139 } 140 141 /** 142 * 143 * @return returns the column index to decorate (in model coordinates) 144 */ 145 public int getHighlightColumn() { 146 return highlightColumn; 147 } 148 149 /** 150 * 151 * @return returns the column index to decorate (in model coordinates) 152 */ 153 public int getHighlightRow() { 154 return highlightRow; 155 } 156 157 /** 158 * 159 * @return returns the Pattern to test the cell value against 160 */ 161 public Pattern getPattern() { 162 return pattern; 163 } 164 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { 171 if (isHighlightCandidate(renderer, adapter)) { 172 return test(renderer, adapter); 173 } 174 return false; 175 } 176 177 /** 178 * Test the value. This is called only if the 179 * pre-check returned true, because accessing the 180 * value might be potentially costly 181 * @param renderer 182 * @param adapter 183 * @return 184 */ 185 private boolean test(Component renderer, ComponentAdapter adapter) { 186 // PENDING JW: why convert here? we are focused on the adapter's cell 187 // looks like an oversight as of ol' days ;-) 188 int columnToTest = adapter.convertColumnIndexToModel(adapter.column); 189 String value = adapter.getString(columnToTest); 190 191 if ((value == null) || (value.length() == 0)) { 192 return false; 193 } 194 return pattern.matcher(value).find(); 195 } 196 197 /** 198 * A quick pre-check. 199 * 200 * @param renderer 201 * @param adapter 202 * @return 203 */ 204 private boolean isHighlightCandidate(Component renderer, ComponentAdapter adapter) { 205 if (!isEnabled()) return false; 206 if (highlightRow >= 0 && (adapter.row != highlightRow)) { 207 return false; 208 } 209 return 210 ((highlightColumn < 0) || 211 (highlightColumn == adapter.convertColumnIndexToModel(adapter.column))); 212 } 213 214 private boolean isEnabled() { 215 Pattern pattern = getPattern(); 216 if (pattern == null || MATCH_ALL.equals(pattern.pattern())) { 217 return false; 218 } 219 return true; 220 } 221 222 223}