package prefuse.util.ui; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; import prefuse.Visualization; import prefuse.data.Tuple; import prefuse.data.event.TupleSetListener; import prefuse.data.search.PrefixSearchTupleSet; import prefuse.data.search.SearchTupleSet; import prefuse.data.tuple.TupleSet; import prefuse.util.ColorLib; /** * Swing component that enables keyword search over prefuse data tuples. * * @author jeffrey heer * @see prefuse.data.query.SearchQueryBinding */ public class JSearchPanel extends JPanel implements DocumentListener, ActionListener { private Object m_lock; private SearchTupleSet m_searcher; private JTextField m_queryF = new JTextField(15); private JLabel m_resultL = new JLabel(" "); private JLabel m_searchL = new JLabel("search >> "); private Box m_sbox = new Box(BoxLayout.X_AXIS); private String[] m_fields; private boolean m_includeHitCount = false; private boolean m_monitorKeys = false; private boolean m_autoIndex = true; private boolean m_showBorder = true; private boolean m_showCancel = true; // ------------------------------------------------------------------------ // Free form constructors /** * Create a new JSearchPanel. * @param search the search tuple set conducting the searches * @param field the data field being searched */ public JSearchPanel(SearchTupleSet search, String field) { this(search, field, false); } /** * Create a new JSearchPanel. * @param search the search tuple set conducting the searches * @param field the data field being searched * @param monitorKeystrokes indicates if each keystroke event should result * in a new search being issued (true) or if searches should only be * initiated by hitting the enter key (false) */ public JSearchPanel(SearchTupleSet search, String field, boolean monitorKeystrokes) { this(null, search, new String[] {field}, false, monitorKeystrokes); } /** * Create a new JSearchPanel. * @param source the source set of tuples that should be searched over * @param search the search tuple set conducting the searches * @param fields the data fields being searched * @param monitorKeystrokes indicates if each keystroke event should result * in a new search being issued (true) or if searches should only be * initiated by hitting the enter key (false) */ public JSearchPanel(TupleSet source, SearchTupleSet search, String[] fields, boolean autoIndex, boolean monitorKeystrokes) { m_lock = new Object(); m_fields = fields; m_autoIndex = autoIndex; m_monitorKeys = monitorKeystrokes; m_searcher = ( search != null ? search : new PrefixSearchTupleSet() ); init(source); } // ------------------------------------------------------------------------ // Visualization-based constructors /** * Create a new JSearchPanel. The default search tuple set for the * visualization will be used. * @param vis the Visualization to search over * @param field the data field being searched */ public JSearchPanel(Visualization vis, String field) { this(vis, Visualization.ALL_ITEMS, field, true); } /** * Create a new JSearchPanel. The default search tuple set for the * visualization will be used. * @param vis the Visualization to search over * @param group the particular data group to search over * @param field the data field being searched */ public JSearchPanel(Visualization vis, String group, String field) { this(vis, group, field, true); } /** * Create a new JSearchPanel. The default search tuple set for the * visualization will be used. * @param vis the Visualization to search over * @param group the particular data group to search over * @param field the data field being searched * @param autoIndex indicates if items should be automatically * indexed and unindexed as their membership in the source group * changes. */ public JSearchPanel(Visualization vis, String group, String field, boolean autoIndex) { this(vis, group, Visualization.SEARCH_ITEMS, new String[] {field}, autoIndex, false); } /** * Create a new JSearchPanel. The default search tuple set for the * visualization will be used. * @param vis the Visualization to search over * @param group the particular data group to search over * @param field the data field being searched * @param autoIndex indicates if items should be automatically * indexed and unindexed as their membership in the source group * changes. * @param monitorKeystrokes indicates if each keystroke event should result * in a new search being issued (true) or if searches should only be * initiated by hitting the enter key (false) */ public JSearchPanel(Visualization vis, String group, String field, boolean autoIndex, boolean monitorKeystrokes) { this(vis, group, Visualization.SEARCH_ITEMS, new String[] {field}, autoIndex, true); } /** * Create a new JSearchPanel. * @param vis the Visualization to search over * @param group the particular data group to search over * @param searchGroup the group name that resolves to the SearchTupleSet * to use * @param field the data field being searched * @param autoIndex indicates if items should be automatically * indexed and unindexed as their membership in the source group * changes. * @param monitorKeystrokes indicates if each keystroke event should result * in a new search being issued (true) or if searches should only be * initiated by hitting the enter key (false) */ public JSearchPanel(Visualization vis, String group, String searchGroup, String field, boolean autoIndex, boolean monitorKeystrokes) { this(vis, group, searchGroup, new String[] {field}, autoIndex, monitorKeystrokes); } /** * Create a new JSearchPanel. * @param vis the Visualization to search over * @param group the particular data group to search over * @param searchGroup the group name that resolves to the SearchTupleSet * to use * @param fields the data fields being searched * @param autoIndex indicates if items should be automatically * indexed and unindexed as their membership in the source group * changes. * @param monitorKeystrokes indicates if each keystroke event should result * in a new search being issued (true) or if searches should only be * initiated by hitting the enter key (false) */ public JSearchPanel(Visualization vis, String group, String searchGroup, String[] fields, boolean autoIndex, boolean monitorKeystrokes) { m_lock = vis; m_fields = fields; m_autoIndex = autoIndex; m_monitorKeys = monitorKeystrokes; TupleSet search = vis.getGroup(searchGroup); if ( search != null ) { if ( search instanceof SearchTupleSet ) { m_searcher = (SearchTupleSet)search; } else { throw new IllegalStateException( "Search focus set not instance of SearchTupleSet!"); } } else { m_searcher = new PrefixSearchTupleSet(); vis.addFocusGroup(searchGroup, m_searcher); } init(vis.getGroup(group)); } // ------------------------------------------------------------------------ // Initialization private void init(TupleSet source) { if ( m_autoIndex && source != null ) { // index everything already there for ( int i=0; i < m_fields.length; i++ ) m_searcher.index(source.tuples(), m_fields[i]); // add a listener to dynamically build search index source.addTupleSetListener(new TupleSetListener() { public void tupleSetChanged(TupleSet tset, Tuple[] add, Tuple[] rem) { if ( add != null ) { for ( int i=0; i