001 /*
002 * $Id: TableSearchable.java 3194 2009-01-21 11:39:19Z kleopatra $
003 *
004 * Copyright 2007 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 */
021 package org.jdesktop.swingx.search;
022
023 import java.awt.Rectangle;
024 import java.util.regex.Matcher;
025 import java.util.regex.Pattern;
026
027 import org.jdesktop.swingx.JXTable;
028 import org.jdesktop.swingx.decorator.AbstractHighlighter;
029 import org.jdesktop.swingx.decorator.Highlighter;
030
031 /**
032 * An Searchable implementation for use in JXTable.
033 *
034 * @author Jeanette Winzenburg
035 */
036 public class TableSearchable extends AbstractSearchable {
037
038 /** The target JXTable. */
039 protected JXTable table;
040
041 /**
042 * Instantiates a TableSearchable with the given table as target.
043 *
044 * @param table the JXTable to search.
045 */
046 public TableSearchable(JXTable table) {
047 this.table = table;
048 }
049
050 /**
051 * {@inheritDoc}
052 * <p>
053 *
054 * This implementation loops through the cells in a row to find a match.
055 */
056 @Override
057 protected void findMatchAndUpdateState(Pattern pattern, int startRow,
058 boolean backwards) {
059 SearchResult matchRow = null;
060 if (backwards) {
061 // CHECK: off-one end still needed?
062 // Probably not - the findXX don't have side-effects any longer
063 // hmmm... still needed: even without side-effects we need to
064 // guarantee calling the notfound update at the very end of the
065 // loop.
066 for (int r = startRow; r >= -1 && matchRow == null; r--) {
067 matchRow = findMatchBackwardsInRow(pattern, r);
068 updateState(matchRow);
069 }
070 } else {
071 for (int r = startRow; r <= getSize() && matchRow == null; r++) {
072 matchRow = findMatchForwardInRow(pattern, r);
073 updateState(matchRow);
074 }
075 }
076 // KEEP - JW: Needed to update if loop wasn't entered!
077 // the alternative is to go one off in the loop. Hmm - which is
078 // preferable?
079 // updateState(matchRow);
080
081 }
082
083 /**
084 * {@inheritDoc}
085 * <p>
086 *
087 * Implemented to search for an extension in the cell given by row and
088 * foundColumn.
089 */
090 @Override
091 protected SearchResult findExtendedMatch(Pattern pattern, int row) {
092 return findMatchAt(pattern, row, lastSearchResult.foundColumn);
093 }
094
095 /**
096 * Searches forward through columns of the given row. Starts at
097 * lastFoundColumn or first column if lastFoundColumn < 0. returns an
098 * appropriate SearchResult if a matching cell is found in this row or null
099 * if no match is found. A row index out off range results in a no-match.
100 *
101 * @param pattern <code>Pattern</code> that we will try to locate
102 * @param row the row to search
103 * @return an appropriate <code>SearchResult</code> if a matching cell is
104 * found in this row or null if no match is found
105 */
106 private SearchResult findMatchForwardInRow(Pattern pattern, int row) {
107 int startColumn = (lastSearchResult.foundColumn < 0) ? 0
108 : lastSearchResult.foundColumn;
109 if (isValidIndex(row)) {
110 for (int column = startColumn; column < table.getColumnCount(); column++) {
111 SearchResult result = findMatchAt(pattern, row, column);
112 if (result != null)
113 return result;
114 }
115 }
116 return null;
117 }
118
119 /**
120 * Searches forward through columns of the given row. Starts at
121 * lastFoundColumn or first column if lastFoundColumn < 0. returns an
122 * appropriate SearchResult if a matching cell is found in this row or null
123 * if no match is found. A row index out off range results in a no-match.
124 *
125 * @param pattern <code>Pattern</code> that we will try to locate
126 * @param row the row to search
127 * @return an appropriate <code>SearchResult</code> if a matching cell is
128 * found in this row or null if no match is found
129 */
130 private SearchResult findMatchBackwardsInRow(Pattern pattern, int row) {
131 int startColumn = (lastSearchResult.foundColumn < 0) ? table
132 .getColumnCount() - 1 : lastSearchResult.foundColumn;
133 if (isValidIndex(row)) {
134 for (int column = startColumn; column >= 0; column--) {
135 SearchResult result = findMatchAt(pattern, row, column);
136 if (result != null)
137 return result;
138 }
139 }
140 return null;
141 }
142
143 /**
144 * Matches the cell content at row/col against the given Pattern. Returns an
145 * appropriate SearchResult if matching or null if no matching
146 *
147 * @param pattern <code>Pattern</code> that we will try to locate
148 * @param row a valid row index in view coordinates
149 * @param column a valid column index in view coordinates
150 * @return an appropriate <code>SearchResult</code> if matching or null
151 */
152 protected SearchResult findMatchAt(Pattern pattern, int row, int column) {
153 String text = table.getStringAt(row, column);
154 if ((text != null) && (text.length() > 0)) {
155 Matcher matcher = pattern.matcher(text);
156 if (matcher.find()) {
157 return createSearchResult(matcher, row, column);
158 }
159 }
160 return null;
161 }
162
163 /**
164 *
165 * {@inheritDoc}
166 * <p>
167 *
168 * Overridden to adjust the column index to -1.
169 */
170 @Override
171 protected int adjustStartPosition(int startIndex, boolean backwards) {
172 lastSearchResult.foundColumn = -1;
173 return super.adjustStartPosition(startIndex, backwards);
174 }
175
176 /**
177 * {@inheritDoc}
178 * <p>
179 *
180 * Overridden to loop through all columns in a row.
181 */
182 @Override
183 protected int moveStartPosition(int startRow, boolean backwards) {
184 if (backwards) {
185 lastSearchResult.foundColumn--;
186 if (lastSearchResult.foundColumn < 0) {
187 startRow--;
188 }
189 } else {
190 lastSearchResult.foundColumn++;
191 if (lastSearchResult.foundColumn >= table.getColumnCount()) {
192 lastSearchResult.foundColumn = -1;
193 startRow++;
194 }
195 }
196 return startRow;
197 }
198
199 /**
200 * {@inheritDoc}
201 * <p>
202 *
203 * Overridden to check the column index of last find.
204 */
205 @Override
206 protected boolean isEqualStartIndex(final int startIndex) {
207 return super.isEqualStartIndex(startIndex)
208 && isValidColumn(lastSearchResult.foundColumn);
209 }
210
211 /**
212 * Checks if row is in range: 0 <= row < getRowCount().
213 *
214 * @param column the column index to check in view coordinates.
215 * @return true if the column is in range, false otherwise
216 */
217 private boolean isValidColumn(int column) {
218 return column >= 0 && column < table.getColumnCount();
219 }
220
221 /**
222 * {@inheritDoc}
223 */
224 @Override
225 protected int getSize() {
226 return table.getRowCount();
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 @Override
233 public JXTable getTarget() {
234 return table;
235 }
236
237 /**
238 * Configures the match highlighter to the current match. Ensures that the
239 * matched cell is visible, if there is a match.
240 *
241 * PRE: markByHighlighter
242 *
243 */
244 protected void moveMatchByHighlighter() {
245 AbstractHighlighter searchHL = getConfiguredMatchHighlighter();
246 // no match
247 if (!hasMatch()) {
248 return;
249 } else {
250 ensureInsertedSearchHighlighters(searchHL);
251 table.scrollCellToVisible(lastSearchResult.foundRow,
252 lastSearchResult.foundColumn);
253 }
254 }
255
256 /**
257 * {@inheritDoc}
258 * <p>
259 *
260 * Overridden to convert the column index in the table's view coordinate
261 * system to model coordinate.
262 * <p>
263 *
264 * PENDING JW: this is only necessary because the SearchPredicate wants its
265 * highlight column in model coordinates. But code comments in the
266 * SearchPredicate seem to indicate that we probably want to revise that
267 * (legacy?).
268 */
269 @Override
270 protected int convertColumnIndexToModel(int viewColumn) {
271 return getTarget().convertColumnIndexToModel(viewColumn);
272 }
273
274 /**
275 * Moves the row selection to the matching cell and ensures its visibility,
276 * if any. Does nothing if there is no match.
277 *
278 */
279 protected void moveMatchBySelection() {
280 if (!hasMatch()) {
281 return;
282 }
283 int row = lastSearchResult.foundRow;
284 int column = lastSearchResult.foundColumn;
285 table.changeSelection(row, column, false, false);
286 if (!table.getAutoscrolls()) {
287 // scrolling not handled by moving selection
288 Rectangle cellRect = table.getCellRect(row, column, true);
289 if (cellRect != null) {
290 table.scrollRectToVisible(cellRect);
291 }
292 }
293 }
294
295 /**
296 * {@inheritDoc}
297 * <p>
298 */
299 @Override
300 protected void moveMatchMarker() {
301 if (markByHighlighter()) {
302 moveMatchByHighlighter();
303 } else { // use selection
304 moveMatchBySelection();
305 }
306 }
307
308 /**
309 * {@inheritDoc}
310 * <p>
311 */
312 @Override
313 protected void removeHighlighter(Highlighter searchHighlighter) {
314 table.removeHighlighter(searchHighlighter);
315 }
316
317 /**
318 * {@inheritDoc}
319 * <p>
320 */
321 @Override
322 protected Highlighter[] getHighlighters() {
323 return table.getHighlighters();
324 }
325
326 /**
327 * {@inheritDoc}
328 * <p>
329 */
330 @Override
331 protected void addHighlighter(Highlighter highlighter) {
332 table.addHighlighter(highlighter);
333 }
334
335 }