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 }