package org.openswing.swing.util.client;
import java.util.regex.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: Search window used in combo control or list control or grid control.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
*
* <p> This file is part of OpenSwing Framework.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the (LGPL) Lesser General Public
* Licen1se as published by the Free Software Foundation;
*
* GNU LESSER GENERAL PUBLIC LICENSE
* Version 2.1, February 1999
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* The author may be contacted at:
* maurocarniel@tin.it</p>
*
* @author Mauro Carniel
* @version 1.0
*/
public class SearchWindowManager {
/** input control that will use this search window */
private SearchControl inputControl;
/** component included in the input control that will use this search window */
private JComponent comp;
/** text in the search window */
private String textToSearch = "";
/** search window window */
private SearchWindow searchWindow;
/** last element found*/
private int lastElementFound = -1;
public SearchWindowManager(SearchControl inputControl) {
this.inputControl = inputControl;
inputControl.getComponent().addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) {
processKey(e);
}
public void keyPressed(KeyEvent e) {
// for ESCAPE/BACKSPACE events...
processKey(e);
}
});
inputControl.getComponent().addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
hideSearchWindow();
textToSearch = "";
}
});
// add a component listener to synchronize input control component events with search window events...
Container c = inputControl.getComponent().getParent();
while(c!=null && !(c instanceof JScrollPane) )
c = c.getParent();
if (c instanceof JScrollPane)
this.comp = (JScrollPane)c;
else
this.comp = inputControl.getComponent();
this.comp.addComponentListener(new ComponentAdapter() {
public void componentHidden(ComponentEvent e) {
super.componentHidden(e);
hideSearchWindow();
}
public void componentResized(ComponentEvent e) {
super.componentResized(e);
Point location = getSearchWindowLocation();
if (location!=null && searchWindow!=null)
searchWindow.setLocation(location.x, location.y);
}
public void componentMoved(ComponentEvent e) {
super.componentMoved(e);
Point location = getSearchWindowLocation();
if (location!=null && searchWindow!=null)
searchWindow.setLocation(location.x, location.y);
}
});
}
/**
* Process key pressed/typed events.
*/
private void processKey(KeyEvent e) {
if (!inputControl.isReadOnly() || inputControl.disableListener()) {
hideSearchWindow();
return;
}
char c = e.getKeyChar();
boolean ok = e.getID() == KeyEvent.KEY_TYPED && (Character.isLetterOrDigit(c) || c==' ' || c == '*' || c == '?' || c == '\b' || c == '/' || c =='.' || c==',' || c=='-');
if (ok) {
if (e.getID() == KeyEvent.KEY_TYPED) {
if (((e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0)) {
return;
}
if (e.isAltDown()) {
return;
}
if (c == '\b')
// backspace pressed...
textToSearch = textToSearch.length()>0 ? textToSearch.substring(0,textToSearch.length()-1) : "";
else
textToSearch += String.valueOf(e.getKeyChar());
}
// show search window...
if (searchWindow!=null)
hideSearchWindow();
// recreate search window...
searchWindow = new SearchWindow(textToSearch);
// retrieve window location...
Point location = getSearchWindowLocation();
if (location != null)
searchWindow.setLocation(location.x, location.y);
searchWindow.setVisible(true);
if (e.getKeyCode() != KeyEvent.VK_ENTER && e.getKeyCode() != e.VK_ESCAPE) {
e.consume();
}
}
else if (e.getKeyCode()==e.VK_ESCAPE) {
if (searchWindow!=null) {
hideSearchWindow();
textToSearch = "";
e.consume();
}
}
}
/**
* @return <code>true</code> if search window is currently visible, <code>false</code> otherwise
*/
public final boolean isSearchWindowVisible() {
return searchWindow!=null;
}
/**
* @return location of the search window
*/
private Point getSearchWindowLocation() {
Point searchWindowLocation;
try {
searchWindowLocation = comp.getLocationOnScreen();
}
catch (IllegalComponentStateException e) {
return comp.getLocation();
}
if(searchWindowLocation==null)
return null;
if (searchWindow==null)
return null;
searchWindowLocation.y = searchWindowLocation.y - searchWindow.getPreferredSize().height;
if ((searchWindowLocation.y < 0))
searchWindowLocation.y = 0;
if ((searchWindowLocation.x < 0))
searchWindowLocation.x = 0;
packSearchWindow();
return searchWindowLocation;
}
/**
* Method used to pack the search window.
*/
private void packSearchWindow() {
if (searchWindow == null)
return;
searchWindow.pack();
}
/**
* Hide the search window.
*/
public void hideSearchWindow() {
if (searchWindow != null) {
searchWindow.setVisible(false);
searchWindow.dispose();
searchWindow = null;
//textToSearch = "";
}
}
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: Inner class that defines the real search window.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
* <p> </p>
* @author Mauro Carniel
* @version 1.0
*/
private class SearchWindow extends JWindow {
/** "search" label */
private JLabel searchLabel = new JLabel(ClientSettings.getInstance().getResources().getResource("search")+": ");
/** "pattern not found" label */
private JLabel notFoundLabel = new JLabel();
/** search text field */
private SearchField searchField;
private Color defaultForeground;
public SearchWindow(String text) {
searchLabel.setVerticalAlignment(JLabel.BOTTOM);
searchLabel.setFont(new Font(searchLabel.getFont().getName(),Font.BOLD,searchLabel.getFont().getSize()));
notFoundLabel.setVerticalAlignment(JLabel.BOTTOM);
notFoundLabel.setForeground(Color.red);
//setup search field
searchField = new SearchField();
searchField.setCursor(getCursor());
defaultForeground = searchField.getForeground();
// add listener to search field...
searchField.getDocument().addDocumentListener(new DocumentListener() {
private Timer timer = new Timer(200, new ActionListener() {
public void actionPerformed(ActionEvent e) {
applyText();
}
});
public void insertUpdate(DocumentEvent e) {
startTimer();
}
public void removeUpdate(DocumentEvent e) {
startTimer();
}
public void changedUpdate(DocumentEvent e) {
startTimer();
}
protected void applyText() {
String text = searchField.getText().trim();
if (text.length() != 0) {
int found = searchFromCurrentIndex(text);
if (found == -1) {
found = inputControl.search(text);
if (found==-1)
searchField.setForeground(Color.red);
else
searchField.setForeground(defaultForeground);
}
else
searchField.setForeground(defaultForeground);
select(found, null, text);
}
else
hideSearchWindow();
}
void startTimer() {
applyText();
}
});
searchField.setText(text);
try {
( (JPanel) getContentPane()).setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.lightGray, 1),
BorderFactory.createEmptyBorder(2, 6, 2, 8))
);
getContentPane().setBackground(new Color(250,250,200));
}
catch (Exception ex) {
}
// set dimensions...
Dimension size = searchLabel.getPreferredSize();
size.height = searchField.getPreferredSize().height;
searchLabel.setPreferredSize(size);
// add labels and search field to window...
getContentPane().setLayout(new GridBagLayout());
getContentPane().add(searchLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,0,0,0), 0, 0));
getContentPane().add(searchField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0
,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,5,0,0), 0, 0));
getContentPane().add(notFoundLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0
,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,5,0,0), 0, 0));
}
/**
* Search the next matching index from the current index.
* If it reaches the end, it will restart from the beginning.
* @param text pattern to search
* @return the next index that the element matches the searching pattern
*/
private int searchFromCurrentIndex(String textToSearch) {
// search is always case insensitive...
textToSearch = textToSearch.toUpperCase();
// retrieve starting search index...
int selectedIndex =
lastElementFound != -1 ?
lastElementFound :
inputControl.getSelectedIndex();
if (selectedIndex < 0)
selectedIndex = 0;
if (inputControl.getRowCount() == 0)
return -1; // no items to search...
// search from starting index...
String element = null;
for(int i = selectedIndex; i < inputControl.getRowCount(); i++) {
element = inputControl.getValueAt(i);
if (compare(element, textToSearch))
return i;
}
// if not found, start again from the beginning
for (int i = 0; i < selectedIndex; i++) {
element = inputControl.getValueAt(i);
if (compare(element, textToSearch))
return i;
}
// no item matches the pattern...
return -1;
}
/**
* @param item current item to compare
* @param textToSearch text to search
* @return <code>true</code> if matches; <code>false</code> otherwise
*/
private boolean compare(String item, String textToSearch) {
if (item == null)
return false;
if (textToSearch == null ||
textToSearch.trim().length() == 0)
return true;
if (textToSearch.indexOf('*')==-1 && textToSearch.indexOf('?')==-1) {
// no jolly symbols found...
return item.toUpperCase().startsWith(textToSearch);
}
try {
if (!textToSearch.startsWith("*"))
return Pattern.compile(".*"+textToSearch.toUpperCase(),Pattern.CASE_INSENSITIVE).matcher(item.toUpperCase()).find();
else
return Pattern.compile("."+textToSearch.toUpperCase(),Pattern.CASE_INSENSITIVE).matcher(item.toUpperCase()).find();
}
catch (PatternSyntaxException e) {
return false;
}
}
/**
* Listen window events and forward them to search field.
*/
public void processKeyEvent(KeyEvent e) {
searchField.processKeyEvent(e);
if (e.isConsumed()) {
if (searchField.getText()==null ||
searchField.getText().trim().length() == 0) {
return;
}
}
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
e.consume();
}
}
/**
* Method invoked when an item does (not) match the search pattern.
* @param index index to select
* @param e key event fired
* @param textToSearch text just searched
*/
private void select(int index, KeyEvent e, String textToSearch) {
if (index != -1) {
// item found...
inputControl.setSelectedIndex(index);
lastElementFound = index;
searchField.setForeground(defaultForeground);
notFoundLabel.setText("");
}
else {
// item not found...
searchField.setForeground(Color.red);
notFoundLabel.setText(ClientSettings.getInstance().getResources().getResource("not found"));
}
}
} // end inner class
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: Inner class used to define a search input field.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
* <p> </p>
* @author Mauro Carniel
* @version 1.0
*/
class SearchField extends JTextField {
SearchField() {
setBorder(BorderFactory.createEmptyBorder());
setFocusable(false);
setOpaque(false);
}
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
size.width = getFontMetrics(getFont()).stringWidth(getText()) + 4;
return size;
}
public void processKeyEvent(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_BACK_SPACE && getDocument().getLength() == 0) {
e.consume();
return;
}
if (isDeactivateKey(e)) {
hideSearchWindow();
if (keyCode == KeyEvent.VK_ESCAPE)
e.consume();
return;
}
super.processKeyEvent(e);
if (keyCode == KeyEvent.VK_BACK_SPACE)
e.consume();
}
private boolean isDeactivateKey(KeyEvent e) {
int keyCode = e.getKeyCode();
return keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE
|| keyCode == KeyEvent.VK_PAGE_UP || keyCode == KeyEvent.VK_PAGE_DOWN
|| keyCode == KeyEvent.VK_HOME || keyCode == KeyEvent.VK_END
|| keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT
|| keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN;
}
} // end inner class
}