/* * Copyright (c) 2008-2012, Matthias Mann * * 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 Matthias Mann 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. */ package de.matthiasmann.twl; import de.matthiasmann.twl.model.TableModel; import de.matthiasmann.twl.model.TableSelectionModel; /** * Provides search as you type functionality to a Table. * * @author Matthias Mann */ public class TableSearchWindow extends InfoWindow implements TableBase.KeyboardSearchHandler { private final TableSelectionModel selectionModel; private final EditField searchTextField; private final StringBuilder searchTextBuffer; private String searchText; private String searchTextLowercase; private Timer timer; private TableModel model; private int column; private int currentRow; private boolean searchStartOnly; public TableSearchWindow(Table table, TableSelectionModel selectionModel) { super(table); this.selectionModel = selectionModel; this.searchTextField = new EditField(); this.searchTextBuffer = new StringBuilder(); this.searchText = ""; Label label = new Label("Search"); label.setLabelFor(searchTextField); searchTextField.setReadOnly(true); DialogLayout l = new DialogLayout(); l.setHorizontalGroup(l.createSequentialGroup() .addWidget(label) .addWidget(searchTextField)); l.setVerticalGroup(l.createParallelGroup() .addWidget(label) .addWidget(searchTextField)); add(l); } public Table getTable() { return (Table)getOwner(); } public TableModel getModel() { return model; } public void setModel(TableModel model, int column) { if(column < 0) { throw new IllegalArgumentException("column"); } if(model != null && column >= model.getNumColumns()) { throw new IllegalArgumentException("column"); } this.model = model; this.column = column; cancelSearch(); } public boolean isActive() { return isOpen(); } public void updateInfoWindowPosition() { adjustSize(); setPosition(getOwner().getX(), getOwner().getBottom()); } public boolean handleKeyEvent(Event evt) { if(model == null) { return false; } if(evt.isKeyPressedEvent()) { switch (evt.getKeyCode()) { case Event.KEY_ESCAPE: if(isOpen()) { cancelSearch(); return true; } break; case Event.KEY_RETURN: return false; case Event.KEY_BACK: { if(isOpen()) { int length = searchTextBuffer.length(); if(length > 0) { searchTextBuffer.setLength(length - 1); updateText(); } restartTimer(); return true; } break; } case Event.KEY_UP: if(isOpen()) { searchDir(-1); restartTimer(); return true; } break; case Event.KEY_DOWN: if(isOpen()) { searchDir(+1); restartTimer(); return true; } break; default: if(evt.hasKeyCharNoModifiers() && !Character.isISOControl(evt.getKeyChar())) { if(searchTextBuffer.length() == 0) { currentRow = Math.max(0, getTable().getSelectionManager().getLeadRow()); searchStartOnly = true; } searchTextBuffer.append(evt.getKeyChar()); updateText(); restartTimer(); return true; } break; } } return false; } public void cancelSearch() { searchTextBuffer.setLength(0); updateText(); closeInfo(); if(timer != null) { timer.stop(); } } @Override protected void afterAddToGUI(GUI gui) { super.afterAddToGUI(gui); timer = gui.createTimer(); timer.setDelay(3000); timer.setCallback(new Runnable() { public void run() { cancelSearch(); } }); } @Override protected void beforeRemoveFromGUI(GUI gui) { timer.stop(); timer = null; super.beforeRemoveFromGUI(gui); } private void updateText() { searchText = searchTextBuffer.toString(); searchTextLowercase = null; searchTextField.setText(searchText); if(searchText.length() >= 0 && model != null) { if(!isOpen() && openInfo()) { updateInfoWindowPosition(); } updateSearch(); } } private void restartTimer() { timer.stop(); timer.start(); } private void updateSearch() { int numRows = model.getNumRows(); if(numRows == 0) { return; } for(int row=currentRow ; row<numRows ; row++) { if(checkRow(row)) { setRow(row); return; } } if(searchStartOnly) { searchStartOnly = false; } else { numRows = currentRow; } for(int row=0 ; row<numRows ; row++) { if(checkRow(row)) { setRow(row); return; } } searchTextField.setErrorMessage("'" + searchText + "' not found"); } private void searchDir(int dir) { int numRows = model.getNumRows(); if(numRows == 0) { return; } int startRow = wrap(currentRow, numRows); int row = startRow; for(;;) { do { row = wrap(row + dir, numRows); if(checkRow(row)) { setRow(row); return; } } while(row != startRow); if(!searchStartOnly) { break; } searchStartOnly = false; } } private void setRow(int row) { if(currentRow != row) { currentRow = row; getTable().scrollToRow(row); if(selectionModel != null) { selectionModel.setSelection(row, row); } } searchTextField.setErrorMessage(null); } private boolean checkRow(int row) { Object data = model.getCell(row, column); if(data == null) { return false; } String str = data.toString(); if(searchStartOnly) { return str.regionMatches(true, 0, searchText, 0, searchText.length()); } str = str.toLowerCase(); if(searchTextLowercase == null) { searchTextLowercase = searchText.toLowerCase(); } return str.contains(searchTextLowercase); } private static int wrap(int row, int numRows) { if(row < 0) { return numRows - 1; } if(row >= numRows) { return 0; } return row; } }