/* Copyright (C) 2003 JabRef team All programs in this directory and subdirectories are published under the GNU General Public License as described below. This program 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 2 of the License, or (at your option) any later version. This program 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Further information about the GNU GPL is available at: http://www.gnu.org/copyleft/gpl.ja.html */ package net.sf.jabref; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.*; import java.util.Hashtable; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import net.sf.jabref.search.BasicSearch; import net.sf.jabref.search.SearchExpression; import net.sf.jabref.search.SearchExpressionParser; import net.sf.jabref.search.SearchMatcher; import net.sf.jabref.gui.SearchResultsDialog; public class SearchManager2 extends SidePaneComponent implements ActionListener, KeyListener, ItemListener, CaretListener, ErrorMessageDisplay { private JabRefFrame frame; GridBagLayout gbl = new GridBagLayout() ; GridBagConstraints con = new GridBagConstraints() ; IncrementalSearcher incSearcher; SearchResultsDialog searchDialog = null; //private JabRefFrame frame; private JTextField searchField = new JTextField("", 12); private JPopupMenu settings = new JPopupMenu(); private JButton openset = new JButton(Globals.lang("Settings")); private JButton escape = new JButton(Globals.lang("Clear")); private JButton help = new JButton(GUIGlobals.getImage("help")); /** This button's text will be set later. */ private JButton search = new JButton(); private JCheckBoxMenuItem searchReq, searchOpt, searchGen, searchAll, caseSensitive, regExpSearch; private JRadioButton increment, floatSearch, hideSearch, showResultsInDialog, searchAllBases; private JCheckBoxMenuItem select; private ButtonGroup types = new ButtonGroup(); private boolean incSearch = false, startedFloatSearch=false, startedFilterSearch=false; private int incSearchPos = -1; // To keep track of where we are in // an incremental search. -1 means // that the search is inactive. public SearchManager2(JabRefFrame frame, SidePaneManager manager) { super(manager, GUIGlobals.getIconUrl("search"), Globals.lang("Search")); this.frame = frame; incSearcher = new IncrementalSearcher(Globals.prefs); //setBorder(BorderFactory.createMatteBorder(1,1,1,1,Color.magenta)); searchReq = new JCheckBoxMenuItem (Globals.lang("Search required fields"), Globals.prefs.getBoolean("searchReq")); searchOpt = new JCheckBoxMenuItem (Globals.lang("Search optional fields"), Globals.prefs.getBoolean("searchOpt")); searchGen = new JCheckBoxMenuItem (Globals.lang("Search general fields"), Globals.prefs.getBoolean("searchGen")); searchAll = new JCheckBoxMenuItem (Globals.lang("Search all fields"), Globals.prefs.getBoolean("searchAll")); regExpSearch = new JCheckBoxMenuItem (Globals.lang("Use regular expressions"), Globals.prefs.getBoolean("regExpSearch")); increment = new JRadioButton(Globals.lang("Incremental"), false); floatSearch = new JRadioButton(Globals.lang("Float"), true); hideSearch = new JRadioButton(Globals.lang("Filter"), true); showResultsInDialog = new JRadioButton(Globals.lang("Show results in dialog"), true); searchAllBases = new JRadioButton(Globals.lang("Global search"), Globals.prefs.getBoolean("searchAllBases")); types.add(increment); types.add(floatSearch); types.add(hideSearch); types.add(showResultsInDialog); types.add(searchAllBases); select = new JCheckBoxMenuItem(Globals.lang("Select matches"), false); increment.setToolTipText(Globals.lang("Incremental search")); floatSearch.setToolTipText(Globals.lang("Gray out non-matching entries")); hideSearch.setToolTipText(Globals.lang("Hide non-matching entries")); showResultsInDialog.setToolTipText(Globals.lang("Show search results in a window")); // Add an item listener that makes sure we only listen for key events // when incremental search is turned on. increment.addItemListener(this); floatSearch.addItemListener(this); hideSearch.addItemListener(this); showResultsInDialog.addItemListener(this); // Add the global focus listener, so a menu item can see if this field was focused when // an action was called. searchField.addFocusListener(Globals.focusListener); if (searchAll.isSelected()) { searchReq.setEnabled(false); searchOpt.setEnabled(false); searchGen.setEnabled(false); } searchAll.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { boolean state = !searchAll.isSelected(); searchReq.setEnabled(state); searchOpt.setEnabled(state); searchGen.setEnabled(state); } }); caseSensitive = new JCheckBoxMenuItem(Globals.lang("Case sensitive"), Globals.prefs.getBoolean("caseSensitiveSearch")); settings.add(select); // 2005.03.29, trying to remove field category searches, to simplify // search usability. //settings.addSeparator(); //settings.add(searchReq); //settings.add(searchOpt); //settings.add(searchGen); //settings.addSeparator(); //settings.add(searchAll); // --------------------------------------------------------------- settings.addSeparator(); settings.add(caseSensitive); settings.add(regExpSearch); //settings.addSeparator(); searchField.addActionListener(this); searchField.addCaretListener(this); search.addActionListener(this); searchField.addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent e) { if (increment.isSelected()) searchField.setText(""); } public void focusLost(FocusEvent e) { incSearch = false; incSearchPos = -1; // Reset incremental // search. This makes the // incremental search reset // once the user moves focus to // somewhere else. if (increment.isSelected()) { //searchField.setText(""); //System.out.println("focuslistener"); } } }); escape.addActionListener(this); escape.setEnabled(false); // enabled after searching openset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (settings.isVisible()) { //System.out.println("oee"); //settings.setVisible(false); } else { JButton src = (JButton) e.getSource(); settings.show(src, 0, openset.getHeight()); } } }); Insets margin = new Insets(0, 2, 0, 2); //search.setMargin(margin); escape.setMargin(margin); openset.setMargin(margin); int butSize = help.getIcon().getIconHeight() + 5; Dimension butDim = new Dimension(butSize, butSize); help.setPreferredSize(butDim); help.setMinimumSize(butDim); help.setMargin(margin); help.addActionListener(new HelpAction(Globals.helpDiag, GUIGlobals.searchHelp, "Help")); // Select the last used mode of search: if (Globals.prefs.getBoolean("incrementS")) increment.setSelected(true); else if (Globals.prefs.getBoolean("floatSearch")) floatSearch.setSelected(true); else if (Globals.prefs.getBoolean("showSearchInDialog")) showResultsInDialog.setSelected(true); else if (Globals.prefs.getBoolean("searchAllBases")) searchAllBases.setSelected(true); else hideSearch.setSelected(true); JPanel main = new JPanel(); main.setLayout(gbl); //SidePaneHeader header = new SidePaneHeader("Search", GUIGlobals.searchIconFile, this); con.gridwidth = GridBagConstraints.REMAINDER; con.fill = GridBagConstraints.BOTH; con.weightx = 1; //con.insets = new Insets(0, 0, 2, 0); //gbl.setConstraints(header, con); //add(header); //con.insets = new Insets(0, 0, 0, 0); gbl.setConstraints(searchField,con); main.add(searchField) ; //con.gridwidth = 1; gbl.setConstraints(search,con); main.add(search) ; con.gridwidth = GridBagConstraints.REMAINDER; gbl.setConstraints(escape,con); main.add(escape) ; con.insets = new Insets(0, 2, 0, 0); gbl.setConstraints(increment, con); main.add(increment); gbl.setConstraints(floatSearch, con); main.add(floatSearch); gbl.setConstraints(hideSearch, con); main.add(hideSearch); gbl.setConstraints(showResultsInDialog, con); main.add(showResultsInDialog); gbl.setConstraints(searchAllBases, con); main.add(searchAllBases); con.insets = new Insets(0, 0, 0, 0); JPanel pan = new JPanel(); GridBagLayout gb = new GridBagLayout(); gbl.setConstraints(pan, con); pan.setLayout(gb); con.weightx = 1; con.gridwidth = 1; gb.setConstraints(openset, con); pan.add(openset); con.weightx = 0; gb.setConstraints(help, con); pan.add(help); main.add(pan); main.setBorder(BorderFactory.createEmptyBorder(1,1,1,1)); setContent(main); searchField.getInputMap().put(Globals.prefs.getKey("Repeat incremental search"), "repeat"); searchField.getActionMap().put("repeat", new AbstractAction() { public void actionPerformed(ActionEvent e) { if (increment.isSelected()) repeatIncremental(); } }); searchField.getInputMap().put(Globals.prefs.getKey("Clear search"), "escape"); searchField.getActionMap().put("escape", new AbstractAction() { public void actionPerformed(ActionEvent e) { hideAway(); //SearchManager2.this.actionPerformed(new ActionEvent(escape, 0, "")); } }); setSearchButtonSizes(); updateSearchButtonText(); } /** force the search button to be large enough for * the longer of the two texts */ private void setSearchButtonSizes() { search.setText(Globals.lang("Search Specified Field(s)")); Dimension size1 = search.getPreferredSize(); search.setText(Globals.lang("Search All Fields")); Dimension size2 = search.getPreferredSize(); size2.width = Math.max(size1.width,size2.width); search.setMinimumSize(size2); search.setPreferredSize(size2); } /** * Instantiate the search dialog, unless it has already been instantiated: */ protected void instantiateSearchDialog() { if (searchDialog == null) searchDialog = new SearchResultsDialog(frame, Globals.lang("Search results")); } public void updatePrefs() { Globals.prefs.putBoolean("searchReq", searchReq.isSelected()); Globals.prefs.putBoolean("searchOpt", searchOpt.isSelected()); Globals.prefs.putBoolean("searchGen", searchGen.isSelected()); Globals.prefs.putBoolean("searchAll", searchAll.isSelected()); Globals.prefs.putBoolean("incrementS", increment.isSelected()); Globals.prefs.putBoolean("selectS", select.isSelected()); Globals.prefs.putBoolean("floatSearch", floatSearch.isSelected()); Globals.prefs.putBoolean("caseSensitiveSearch", caseSensitive.isSelected()); Globals.prefs.putBoolean("regExpSearch", regExpSearch.isSelected()); Globals.prefs.putBoolean("showSearchInDialog", showResultsInDialog.isSelected()); Globals.prefs.putBoolean("searchAllBases", searchAllBases.isSelected()); } public void startIncrementalSearch() { increment.setSelected(true); searchField.setText(""); //System.out.println("startIncrementalSearch"); searchField.requestFocus(); } /** * Clears and focuses the search field if it is not * focused. Otherwise, cycles to the next search type. */ public void startSearch() { if (increment.isSelected() && incSearch) { repeatIncremental(); return; } if (!searchField.hasFocus()) { //searchField.setText(""); searchField.selectAll(); searchField.requestFocus(); } else { if (increment.isSelected()) floatSearch.setSelected(true); else if (floatSearch.isSelected()) hideSearch.setSelected(true); else if (hideSearch.isSelected()) showResultsInDialog.setSelected(true); else if (showResultsInDialog.isSelected()) searchAllBases.setSelected(true); else { increment.setSelected(true); } increment.revalidate(); increment.repaint(); searchField.requestFocus(); } } public void actionPerformed(ActionEvent e) { if (e.getSource() == escape) { incSearch = false; if (panel != null) { Thread t = new Thread() { public void run() { clearSearch(); } }; // do this after the button action is over SwingUtilities.invokeLater(t); } } else if (((e.getSource() == searchField) || (e.getSource() == search)) && !increment.isSelected() && (panel != null)) { updatePrefs(); // Make sure the user's choices are recorded. if (searchField.getText().equals("")) { // An empty search field should cause the search to be cleared. panel.stopShowingSearchResults(); return; } // Setup search parameters common to both normal and float. Hashtable<String, String> searchOptions = new Hashtable<String, String>(); searchOptions.put("option",searchField.getText()) ; SearchRuleSet searchRules = new SearchRuleSet() ; SearchRule rule1; rule1 = new BasicSearch(Globals.prefs.getBoolean("caseSensitiveSearch"), Globals.prefs.getBoolean("regExpSearch")); try { // this searches specified fields if specified, // and all fields otherwise rule1 = new SearchExpression(Globals.prefs,searchOptions); } catch (Exception ex) { // we'll do a search in all fields } searchRules.addRule(rule1) ; if (!searchRules.validateSearchStrings(searchOptions)) { panel.output(Globals.lang("Search failed: illegal search expression")); panel.stopShowingSearchResults(); return; } SearchWorker worker = new SearchWorker(searchRules, searchOptions); worker.getWorker().run(); worker.getCallBack().update(); escape.setEnabled(true); } } class SearchWorker extends AbstractWorker { private SearchRuleSet rules; Hashtable<String, String> searchTerm; int hits = 0; public SearchWorker(SearchRuleSet rules, Hashtable<String, String> searchTerm) { this.rules = rules; this.searchTerm = searchTerm; } public void run() { if (!searchAllBases.isSelected()) { // Search only the current database: for (BibtexEntry entry : panel.getDatabase().getEntries()){ boolean hit = rules.applyRule(searchTerm, entry) > 0; entry.setSearchHit(hit); if (hit) hits++; } } else { // Search all databases: for (int i=0; i<frame.getTabbedPane().getTabCount(); i++) { BasePanel p = frame.baseAt(i); for (BibtexEntry entry : p.getDatabase().getEntries()){ boolean hit = rules.applyRule(searchTerm, entry) > 0; entry.setSearchHit(hit); if (hit) hits++; } } } } public void update() { panel.output(Globals.lang("Searched database. Number of hits") + ": " + hits); // Show the result in the chosen way: if (searchAllBases.isSelected()) { // Search all databases. This means we need to use the search results dialog. // Turn off other search mode, if activated: if (startedFloatSearch) { panel.mainTable.stopShowingFloatSearch(); startedFloatSearch = false; } if (startedFilterSearch) { panel.stopShowingSearchResults(); startedFilterSearch = false; } // Make sure the search dialog is instantiated and cleared: instantiateSearchDialog(); searchDialog.clear(); for (int i=0; i<frame.getTabbedPane().getTabCount(); i++) { BasePanel p = frame.baseAt(i); for (BibtexEntry entry : p.getDatabase().getEntries()){ if (entry.isSearchHit()) searchDialog.addEntry(entry, p); } } searchDialog.selectFirstEntry(); searchDialog.setVisible(true); } else if (showResultsInDialog.isSelected()) { // Turn off other search mode, if activated: if (startedFloatSearch) { panel.mainTable.stopShowingFloatSearch(); startedFloatSearch = false; } if (startedFilterSearch) { panel.stopShowingSearchResults(); startedFilterSearch = false; } // Make sure the search dialog is instantiated and cleared: instantiateSearchDialog(); searchDialog.clear(); for (BibtexEntry entry : panel.getDatabase().getEntries()) { if (entry.isSearchHit()) searchDialog.addEntry(entry, panel); } searchDialog.selectFirstEntry(); searchDialog.setVisible(true); } else if (hideSearch.isSelected()) { // Filtering search - removes non-hits from the table: if (startedFloatSearch) { panel.mainTable.stopShowingFloatSearch(); startedFloatSearch = false; } startedFilterSearch = true; panel.setSearchMatcher(SearchMatcher.INSTANCE); } else { // Float search - floats hits to the top of the table: if (startedFilterSearch) { panel.stopShowingSearchResults(); startedFilterSearch = false; } startedFloatSearch = true; panel.mainTable.showFloatSearch(SearchMatcher.INSTANCE); } // Afterwards, select all text in the search field. searchField.select(0, searchField.getText().length()); } } public void clearSearch() { if (panel.isShowingFloatSearch()) { startedFloatSearch = false; panel.mainTable.stopShowingFloatSearch(); } else if (panel.isShowingFilterSearch()) { startedFilterSearch = false; panel.stopShowingSearchResults(); } // disable "Cancel" button to signal this to the user escape.setEnabled(false); } public void itemStateChanged(ItemEvent e) { if (e.getSource() == increment) { if (startedFilterSearch || startedFloatSearch) { clearSearch(); } updateSearchButtonText(); if (increment.isSelected()) searchField.addKeyListener(this); else searchField.removeKeyListener(this); } else /*if (e.getSource() == normal)*/ { updateSearchButtonText(); // If this search type is disabled, remove reordering from // all databases. /*if ((panel != null) && increment.isSelected()) { clearSearch(); } */ } } private void repeatIncremental() { incSearchPos++; if (panel != null) goIncremental(); } /** * Used for incremental search. Only activated when incremental * is selected. * * The variable incSearchPos keeps track of which entry was last * checked. */ public void keyTyped(KeyEvent e) { if (e.isControlDown()) { return; } if (panel != null) goIncremental(); } private void goIncremental() { incSearch = true; escape.setEnabled(true); SwingUtilities.invokeLater(new Thread() { public void run() { String text = searchField.getText(); if (incSearchPos >= panel.getDatabase().getEntryCount()) { panel.output("'"+text+"' : "+Globals.lang ("Incremental search failed. Repeat to search from top.")+"."); incSearchPos = -1; return; } if (searchField.getText().equals("")) return; if (incSearchPos < 0) incSearchPos = 0; BibtexEntry be = panel.mainTable.getEntryAt(incSearchPos); while (!incSearcher.search(text, be)) { incSearchPos++; if (incSearchPos < panel.getDatabase().getEntryCount()) be = panel.mainTable.getEntryAt(incSearchPos); else { panel.output("'"+text+"' : "+Globals.lang ("Incremental search failed. Repeat to search from top.")); incSearchPos = -1; return; } } if (incSearchPos >= 0) { panel.selectSingleEntry(incSearchPos); panel.output("'"+text+"' "+Globals.lang ("found")+"."); } } }); } public void componentClosing() { frame.searchToggle.setSelected(false); if (panel != null) { if (startedFilterSearch || startedFloatSearch) clearSearch(); } } public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) {} public void caretUpdate(CaretEvent e) { if (e.getSource() == searchField) { updateSearchButtonText(); } } /** Updates the text on the search button to reflect * the type of search that will happen on click. */ private void updateSearchButtonText() { search.setText(!increment.isSelected() && SearchExpressionParser.checkSyntax( searchField.getText(), caseSensitive.isSelected(), regExpSearch.isSelected()) != null ? Globals.lang("Search Specified Field(s)") : Globals.lang("Search All Fields")); } /** * This method is required by the ErrorMessageDisplay interface, and lets this class * serve as a callback for regular expression exceptions happening in DatabaseSearch. * @param errorMessage */ public void reportError(String errorMessage) { JOptionPane.showMessageDialog(panel, errorMessage, Globals.lang("Search error"), JOptionPane.ERROR_MESSAGE); } /** * This method is required by the ErrorMessageDisplay interface, and lets this class * serve as a callback for regular expression exceptions happening in DatabaseSearch. * @param errorMessage */ public void reportError(String errorMessage, Exception exception) { reportError(errorMessage); } public void setActiveBasePanel(BasePanel panel) { super.setActiveBasePanel(panel); if (panel != null) escape.setEnabled(panel.isShowingFloatSearch() || panel.isShowingFilterSearch()); else escape.setEnabled(false); } }