/*
* Copyright (c) 2009, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.swingui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* This text field is specifically for searching. It has a search
* icon on it that can be clicked to change the way the searching
* is done.
*/
public class SearchTextField {
private static final ImageIcon SEARCH_ICON = new ImageIcon(SearchTextField.class.getClassLoader().getResource("ca/sqlpower/swingui/search.png"));
private static final ImageIcon REG_EX_ICON = new ImageIcon(SearchTextField.class.getClassLoader().getResource("ca/sqlpower/swingui/searchField_rx.png"));
private static final ImageIcon EXACT_MATCH_ICON = new ImageIcon(SearchTextField.class.getClassLoader().getResource("ca/sqlpower/swingui/searchField_xm.png"));
private static final ImageIcon SUBSTRING_ICON = new ImageIcon(SearchTextField.class.getClassLoader().getResource("ca/sqlpower/swingui/searchField_ss.png"));
private static final ImageIcon DOWN_ARROW_ICON = new ImageIcon(SearchTextField.class.getClassLoader().getResource("ca/sqlpower/swingui/downarrow.png"));
private enum SearchType {
SUBSTRING,
REG_EX,
EXACT_MATCH;
}
private final JPanel panel;
/**
* This is the text field that users enter the actual text to search on.
*/
private final JTextField searchText;
/**
* This pop-up lets the user select the type of search they want to use. For
* example they can search with plain text or regular expressions.
*/
private final JPopupMenu searchTypePopup;
private SearchType searchType = SearchType.SUBSTRING;
/**
* The search object that doSearch will be called on every time the search text
* changes.
*/
private final Search search;
/**
* This JLabel can be clicked on to display a pop-up to change how the search is
* interpreted. The label also contains the icon corresponding to the current method
* of search.
*/
private JLabel searchTypeIcon;
public SearchTextField(Search s, int columns) {
this.search = s;
searchTypePopup = new JPopupMenu();
searchTypePopup.add(new JMenuItem(new AbstractAction("Substring") {
public void actionPerformed(ActionEvent e) {
searchType = SearchType.SUBSTRING;
searchTypeIcon.setIcon(composeWithArrow(SUBSTRING_ICON));
doSearch();
}
}));
searchTypePopup.add(new JMenuItem(new AbstractAction("Regular Exp") {
public void actionPerformed(ActionEvent e) {
searchType = SearchType.REG_EX;
searchTypeIcon.setIcon(composeWithArrow(REG_EX_ICON));
doSearch();
}
}));
searchTypePopup.add(new JMenuItem(new AbstractAction("Exact Match") {
public void actionPerformed(ActionEvent e) {
searchType = SearchType.EXACT_MATCH;
searchTypeIcon.setIcon(composeWithArrow(EXACT_MATCH_ICON));
doSearch();
}
}));
panel = new JPanel();
searchText = new JTextField(columns);
searchText.setPreferredSize(new Dimension(searchText.getPreferredSize().width, 20));
panel.setLayout(new BorderLayout());
final JLabel searchIconLabel = new JLabel(SEARCH_ICON);
searchIconLabel.setBackground(searchText.getBackground());
searchTypeIcon = new JLabel(composeWithArrow(SUBSTRING_ICON));
searchTypeIcon.setBackground(searchText.getBackground());
searchTypeIcon.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
searchTypePopup.show(panel, searchTypeIcon.getX(), searchTypeIcon.getY() + searchTypeIcon.getHeight());
}
});
panel.setBorder(searchText.getBorder());
panel.setBackground(searchText.getBackground());
searchText.setBorder(null);
panel.add(searchIconLabel, BorderLayout.WEST);
panel.add(searchText);
panel.add(searchTypeIcon, BorderLayout.EAST);
searchText.getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
doSearch();
}
public void insertUpdate(DocumentEvent e) {
doSearch();
}
public void changedUpdate(DocumentEvent e) {
doSearch();
}
});
}
public void doSearch() {
Pattern p;
boolean matchExactly;
try {
switch (searchType) {
case REG_EX:
p = Pattern.compile(getText(), Pattern.CASE_INSENSITIVE);
matchExactly = true;
break;
case SUBSTRING:
p = Pattern.compile(getText(), Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
matchExactly = false;
break;
case EXACT_MATCH:
p = Pattern.compile(getText(), Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
matchExactly = true;
break;
default :
throw new RuntimeException("Searching on the type " + searchType + " has not been implemented.");
}
} catch (PatternSyntaxException e) {
//If the pattern is malformed then search across the empty string.
p = Pattern.compile("");
matchExactly = false;
}
search.doSearch(p, matchExactly);
}
public JPanel getPanel() {
return panel;
}
public JTextField getTextField() {
return searchText;
}
public String getText() {
return searchText.getText();
}
public void setText(String text) {
searchText.setText(text);
}
public void clear() {
searchText.setText("");
}
/**
* This will compose a new icon from the base icon and the
* down arrow icon for an icon that will be used to gain access
* to a pop-up menu.
*/
private Icon composeWithArrow(Icon baseIcon) {
return new ComposedIcon(Arrays.asList(new Icon[]{baseIcon, DOWN_ARROW_ICON}));
}
}