2 Copyright (C) 2003 JabRef team
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
27 package net.sf.jabref;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.Collection;
33 import java.awt.event.*;
35 import javax.swing.event.*;
36 import javax.swing.event.ChangeListener;
37 import javax.swing.event.ChangeEvent;
39 import net.sf.jabref.search.*;
40 import net.sf.jabref.search.SearchExpression;
41 import ca.odell.glazedlists.matchers.Matcher;
42 import ca.odell.glazedlists.EventList;
44 class SearchManager2 extends SidePaneComponent
45 implements ActionListener, KeyListener, ItemListener, CaretListener, ErrorMessageDisplay {
47 private JabRefFrame frame;
49 GridBagLayout gbl = new GridBagLayout() ;
50 GridBagConstraints con = new GridBagConstraints() ;
52 IncrementalSearcher incSearcher;
54 //private JabRefFrame frame;
55 private JTextField searchField = new JTextField("", 12);
56 private JLabel lab = //new JLabel(Globals.lang("Search")+":");
57 new JLabel(GUIGlobals.getImage("search"));
58 private JPopupMenu settings = new JPopupMenu();
59 private JButton openset = new JButton(Globals.lang("Settings"));
60 private JButton escape = new JButton(Globals.lang("Clear"));
61 private JButton help = new JButton(GUIGlobals.getImage("help"));
62 /** This button's text will be set later. */
63 private JButton search = new JButton();
64 private JCheckBoxMenuItem searchReq, searchOpt, searchGen,
65 searchAll, caseSensitive, regExpSearch;
67 private JRadioButton increment, floatSearch, hideSearch;
68 private JCheckBoxMenuItem select;
69 private ButtonGroup types = new ButtonGroup();
70 private boolean incSearch = false, startedFloatSearch=false, startedFilterSearch=false;
72 private int incSearchPos = -1; // To keep track of where we are in
73 // an incremental search. -1 means
74 // that the search is inactive.
77 public SearchManager2(JabRefFrame frame, SidePaneManager manager) {
78 super(manager, GUIGlobals.getIconUrl("search"), Globals.lang("Search"));
81 incSearcher = new IncrementalSearcher(Globals.prefs);
85 //setBorder(BorderFactory.createMatteBorder(1,1,1,1,Color.magenta));
87 searchReq = new JCheckBoxMenuItem
88 (Globals.lang("Search required fields"),
89 Globals.prefs.getBoolean("searchReq"));
90 searchOpt = new JCheckBoxMenuItem
91 (Globals.lang("Search optional fields"),
92 Globals.prefs.getBoolean("searchOpt"));
93 searchGen = new JCheckBoxMenuItem
94 (Globals.lang("Search general fields"),
95 Globals.prefs.getBoolean("searchGen"));
96 searchAll = new JCheckBoxMenuItem
97 (Globals.lang("Search all fields"),
98 Globals.prefs.getBoolean("searchAll"));
99 regExpSearch = new JCheckBoxMenuItem
100 (Globals.lang("Use regular expressions"),
101 Globals.prefs.getBoolean("regExpSearch"));
104 increment = new JRadioButton(Globals.lang("Incremental"), false);
105 floatSearch = new JRadioButton(Globals.lang("Float"), true);
106 hideSearch = new JRadioButton(Globals.lang("Filter"), true);
107 types.add(increment);
108 types.add(floatSearch);
109 types.add(hideSearch);
111 select = new JCheckBoxMenuItem(Globals.lang("Select matches"), false);
112 increment.setToolTipText(Globals.lang("Incremental search"));
113 floatSearch.setToolTipText(Globals.lang("Gray out non-matching entries"));
114 hideSearch.setToolTipText(Globals.lang("Hide non-matching entries"));
116 // Add an item listener that makes sure we only listen for key events
117 // when incremental search is turned on.
118 increment.addItemListener(this);
119 floatSearch.addItemListener(this);
120 hideSearch.addItemListener(this);
122 // Add the global focus listener, so a menu item can see if this field was focused when
123 // an action was called.
124 searchField.addFocusListener(Globals.focusListener);
127 if (searchAll.isSelected()) {
128 searchReq.setEnabled(false);
129 searchOpt.setEnabled(false);
130 searchGen.setEnabled(false);
132 searchAll.addChangeListener(new ChangeListener() {
133 public void stateChanged(ChangeEvent event) {
134 boolean state = !searchAll.isSelected();
135 searchReq.setEnabled(state);
136 searchOpt.setEnabled(state);
137 searchGen.setEnabled(state);
141 caseSensitive = new JCheckBoxMenuItem(Globals.lang("Case sensitive"),
142 Globals.prefs.getBoolean("caseSensitiveSearch"));
143 settings.add(select);
145 // 2005.03.29, trying to remove field category searches, to simplify
147 //settings.addSeparator();
148 //settings.add(searchReq);
149 //settings.add(searchOpt);
150 //settings.add(searchGen);
151 //settings.addSeparator();
152 //settings.add(searchAll);
153 // ---------------------------------------------------------------
154 settings.addSeparator();
155 settings.add(caseSensitive);
156 settings.add(regExpSearch);
157 //settings.addSeparator();
160 searchField.addActionListener(this);
161 searchField.addCaretListener(this);
162 search.addActionListener(this);
163 searchField.addFocusListener(new FocusAdapter() {
164 public void focusGained(FocusEvent e) {
165 if (increment.isSelected())
166 searchField.setText("");
168 public void focusLost(FocusEvent e) {
170 incSearchPos = -1; // Reset incremental
171 // search. This makes the
172 // incremental search reset
173 // once the user moves focus to
175 if (increment.isSelected()) {
176 //searchField.setText("");
177 //System.out.println("focuslistener");
181 escape.addActionListener(this);
182 escape.setEnabled(false); // enabled after searching
184 openset.addActionListener(new ActionListener() {
185 public void actionPerformed(ActionEvent e) {
186 if (settings.isVisible()) {
187 //System.out.println("oee");
188 //settings.setVisible(false);
191 JButton src = (JButton) e.getSource();
192 settings.show(src, 0, openset.getHeight());
197 Insets margin = new Insets(0, 2, 0, 2);
198 //search.setMargin(margin);
199 escape.setMargin(margin);
200 openset.setMargin(margin);
201 int butSize = help.getIcon().getIconHeight() + 5;
202 Dimension butDim = new Dimension(butSize, butSize);
203 help.setPreferredSize(butDim);
204 help.setMinimumSize(butDim);
205 help.setMargin(margin);
206 help.addActionListener(new HelpAction(Globals.helpDiag, GUIGlobals.searchHelp, "Help"));
208 if (Globals.prefs.getBoolean("incrementS"))
209 increment.setSelected(true);
211 JPanel main = new JPanel();
213 //SidePaneHeader header = new SidePaneHeader("Search", GUIGlobals.searchIconFile, this);
214 con.gridwidth = GridBagConstraints.REMAINDER;
215 con.fill = GridBagConstraints.BOTH;
217 //con.insets = new Insets(0, 0, 2, 0);
218 //gbl.setConstraints(header, con);
220 //con.insets = new Insets(0, 0, 0, 0);
221 gbl.setConstraints(searchField,con);
222 main.add(searchField) ;
224 gbl.setConstraints(search,con);
226 con.gridwidth = GridBagConstraints.REMAINDER;
227 gbl.setConstraints(escape,con);
229 con.insets = new Insets(0, 2, 0, 0);
230 gbl.setConstraints(increment, con);
232 gbl.setConstraints(floatSearch, con);
233 main.add(floatSearch);
234 gbl.setConstraints(hideSearch, con);
235 main.add(hideSearch);
236 con.insets = new Insets(0, 0, 0, 0);
237 JPanel pan = new JPanel();
238 GridBagLayout gb = new GridBagLayout();
239 gbl.setConstraints(pan, con);
243 gb.setConstraints(openset, con);
246 gb.setConstraints(help, con);
249 main.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
250 add(main, BorderLayout.CENTER);
252 searchField.getInputMap().put(Globals.prefs.getKey("Repeat incremental search"),
255 searchField.getActionMap().put("repeat", new AbstractAction() {
256 public void actionPerformed(ActionEvent e) {
257 if (increment.isSelected())
261 searchField.getInputMap().put(Globals.prefs.getKey("Clear search"), "escape");
262 searchField.getActionMap().put("escape", new AbstractAction() {
263 public void actionPerformed(ActionEvent e) {
265 //SearchManager2.this.actionPerformed(new ActionEvent(escape, 0, ""));
268 setSearchButtonSizes();
269 updateSearchButtonText();
272 /** force the search button to be large enough for
273 * the longer of the two texts */
274 private void setSearchButtonSizes() {
275 search.setText(Globals.lang("Search Specified Field(s)"));
276 Dimension size1 = search.getPreferredSize();
277 search.setText(Globals.lang("Search All Fields"));
278 Dimension size2 = search.getPreferredSize();
279 size2.width = Math.max(size1.width,size2.width);
280 search.setMinimumSize(size2);
281 search.setPreferredSize(size2);
284 public void updatePrefs() {
285 Globals.prefs.putBoolean("searchReq", searchReq.isSelected());
286 Globals.prefs.putBoolean("searchOpt", searchOpt.isSelected());
287 Globals.prefs.putBoolean("searchGen", searchGen.isSelected());
288 Globals.prefs.putBoolean("searchAll", searchAll.isSelected());
289 Globals.prefs.putBoolean("incrementS", increment.isSelected());
290 Globals.prefs.putBoolean("selectS", select.isSelected());
291 Globals.prefs.putBoolean("grayOutNonHits", floatSearch.isSelected());
292 Globals.prefs.putBoolean("caseSensitiveSearch",
293 caseSensitive.isSelected());
294 Globals.prefs.putBoolean("regExpSearch", regExpSearch.isSelected());
298 public void startIncrementalSearch() {
299 increment.setSelected(true);
300 searchField.setText("");
301 //System.out.println("startIncrementalSearch");
302 searchField.requestFocus();
306 * Clears and focuses the search field if it is not
307 * focused. Otherwise, cycles to the next search type.
309 public void startSearch() {
310 if (increment.isSelected() && incSearch) {
314 if (!searchField.hasFocus()) {
315 //searchField.setText("");
316 searchField.selectAll();
317 searchField.requestFocus();
319 if (increment.isSelected())
320 floatSearch.setSelected(true);
321 else if (floatSearch.isSelected())
322 hideSearch.setSelected(true);
324 increment.setSelected(true);
326 increment.revalidate();
329 searchField.requestFocus();
334 public void actionPerformed(ActionEvent e) {
335 if (e.getSource() == escape) {
338 Thread t = new Thread() {
343 // do this after the button action is over
344 SwingUtilities.invokeLater(t);
347 else if (((e.getSource() == searchField) || (e.getSource() == search))
348 && !increment.isSelected()
349 && (panel != null)) {
350 updatePrefs(); // Make sure the user's choices are recorded.
351 if (searchField.getText().equals("")) {
352 // An empty search field should cause the search to be cleared.
353 panel.stopShowingSearchResults();
356 // Setup search parameters common to both normal and float.
357 Hashtable searchOptions = new Hashtable();
358 searchOptions.put("option",searchField.getText()) ;
359 SearchRuleSet searchRules = new SearchRuleSet() ;
362 rule1 = new BasicSearch(Globals.prefs.getBoolean("caseSensitiveSearch"),
363 Globals.prefs.getBoolean("regExpSearch"));
366 if (Globals.prefs.getBoolean("regExpSearch"))
367 rule1 = new RegExpRule(
368 Globals.prefs.getBoolean("caseSensitiveSearch"));
370 rule1 = new SimpleSearchRule(
371 Globals.prefs.getBoolean("caseSensitiveSearch"));
376 // this searches specified fields if specified,
377 // and all fields otherwise
378 rule1 = new SearchExpression(Globals.prefs,searchOptions);
379 } catch (Exception ex) {
380 // we'll do a search in all fields
383 searchRules.addRule(rule1) ;
384 SearchWorker worker = new SearchWorker(searchRules, searchOptions);
385 worker.getWorker().run();
386 worker.getCallBack().update();
387 escape.setEnabled(true);
391 class SearchWorker extends AbstractWorker {
392 private SearchRuleSet rules;
393 Hashtable searchTerm;
395 public SearchWorker(SearchRuleSet rules, Hashtable searchTerm) {
397 this.searchTerm = searchTerm;
401 Collection entries = panel.getDatabase().getEntries();
402 for (Iterator i=entries.iterator(); i.hasNext();) {
403 BibtexEntry entry = (BibtexEntry)i.next();
404 boolean hit = rules.applyRule(searchTerm, entry) > 0;
405 entry.setSearchHit(hit);
410 public void update() {
411 panel.output(Globals.lang("Searched database. Number of hits")
414 // Show the result in the chosen way:
415 if (hideSearch.isSelected()) {
416 // Filtering search - removes non-hits from the table:
417 if (startedFloatSearch) {
418 panel.mainTable.stopShowingFloatSearch();
419 startedFloatSearch = false;
421 startedFilterSearch = true;
422 panel.setSearchMatcher(SearchMatcher.INSTANCE);
425 // Float search - floats hits to the top of the table:
426 if (startedFilterSearch) {
427 panel.stopShowingSearchResults();
428 startedFilterSearch = false;
430 startedFloatSearch = true;
431 panel.mainTable.showFloatSearch(SearchMatcher.INSTANCE);
435 // Afterwards, select all text in the search field.
436 searchField.select(0, searchField.getText().length());
441 public void clearSearch() {
442 if (startedFloatSearch) {
443 startedFloatSearch = false;
444 panel.mainTable.stopShowingFloatSearch();
445 } else if (startedFilterSearch) {
446 startedFilterSearch = false;
447 panel.stopShowingSearchResults();
449 // disable "Cancel" button to signal this to the user
450 escape.setEnabled(false);
452 public void itemStateChanged(ItemEvent e) {
453 if (e.getSource() == increment) {
454 if (startedFilterSearch || startedFloatSearch) {
457 updateSearchButtonText();
458 if (increment.isSelected())
459 searchField.addKeyListener(this);
461 searchField.removeKeyListener(this);
462 } else /*if (e.getSource() == normal)*/ {
463 updateSearchButtonText();
465 // If this search type is disabled, remove reordering from
467 /*if ((panel != null) && increment.isSelected()) {
473 private void repeatIncremental() {
480 * Used for incremental search. Only activated when incremental
483 * The variable incSearchPos keeps track of which entry was last
486 public void keyTyped(KeyEvent e) {
487 if (e.isControlDown()) {
494 private void goIncremental() {
496 escape.setEnabled(true);
497 SwingUtilities.invokeLater(new Thread() {
499 String text = searchField.getText();
502 if (incSearchPos >= panel.getDatabase().getEntryCount()) {
503 panel.output("'"+text+"' : "+Globals.lang
505 ("Incremental search failed. Repeat to search from top.")+".");
510 if (searchField.getText().equals("")) return;
511 if (incSearchPos < 0)
513 BibtexEntry be = panel.mainTable.getEntryAt(incSearchPos);
514 while (!incSearcher.search(text, be)) {
516 if (incSearchPos < panel.getDatabase().getEntryCount())
517 be = panel.mainTable.getEntryAt(incSearchPos);
519 panel.output("'"+text+"' : "+Globals.lang
520 ("Incremental search failed. Repeat to search from top."));
525 if (incSearchPos >= 0) {
527 panel.selectSingleEntry(incSearchPos);
528 panel.output("'"+text+"' "+Globals.lang
537 public void componentClosing() {
538 frame.searchToggle.setSelected(false);
540 if (startedFilterSearch || startedFloatSearch)
546 public void keyPressed(KeyEvent e) {}
547 public void keyReleased(KeyEvent e) {}
549 public void caretUpdate(CaretEvent e) {
550 if (e.getSource() == searchField) {
551 updateSearchButtonText();
555 /** Updates the text on the search button to reflect
556 * the type of search that will happen on click. */
557 private void updateSearchButtonText() {
558 search.setText(!increment.isSelected()
559 && SearchExpressionParser.checkSyntax(
560 searchField.getText(),
561 caseSensitive.isSelected(),
562 regExpSearch.isSelected()) != null
563 ? Globals.lang("Search Specified Field(s)")
564 : Globals.lang("Search All Fields"));
568 * This method is required by the ErrorMessageDisplay interface, and lets this class
569 * serve as a callback for regular expression exceptions happening in DatabaseSearch.
570 * @param errorMessage
572 public void reportError(String errorMessage) {
573 JOptionPane.showMessageDialog(panel, errorMessage, Globals.lang("Search error"),
574 JOptionPane.ERROR_MESSAGE);
578 * This method is required by the ErrorMessageDisplay interface, and lets this class
579 * serve as a callback for regular expression exceptions happening in DatabaseSearch.
580 * @param errorMessage
582 public void reportError(String errorMessage, Exception exception) {
583 reportError(errorMessage);