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 }