net.sf.jabref.gui.search.SearchBar.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.gui.search.SearchBar.java

Source

/*  Copyright (C) 2003-2011 JabRef contributors.
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package net.sf.jabref.gui.search;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;

import net.sf.jabref.Globals;
import net.sf.jabref.gui.BasePanel;
import net.sf.jabref.gui.IconTheme;
import net.sf.jabref.gui.OSXCompatibleToolbar;
import net.sf.jabref.gui.WrapLayout;
import net.sf.jabref.gui.autocompleter.AutoCompleteSupport;
import net.sf.jabref.gui.help.HelpAction;
import net.sf.jabref.gui.help.HelpFile;
import net.sf.jabref.gui.maintable.MainTableDataModel;
import net.sf.jabref.gui.util.component.JTextFieldWithUnfocusedText;
import net.sf.jabref.gui.worker.AbstractWorker;
import net.sf.jabref.logic.autocompleter.AutoCompleter;
import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.logic.search.SearchQuery;
import net.sf.jabref.logic.search.SearchQueryHighlightObservable;
import net.sf.jabref.logic.util.OS;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.preferences.JabRefPreferences;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The search bar at the top of the screen allowing the user to search his database.
 */
public class SearchBar extends JPanel {

    private static final Log LOGGER = LogFactory.getLog(SearchBar.class);

    private static final Color NO_RESULTS_COLOR = new Color(232, 202, 202);
    private static final Color RESULTS_FOUND_COLOR = new Color(217, 232, 202);
    private static final Color ADVANCED_SEARCH_COLOR = new Color(102, 255, 255);

    private final JButton openCurrentResultsInDialog;
    private final JButton globalSearch;
    private final JButton searchModeButton;

    private final BasePanel basePanel;

    private final SearchQueryHighlightObservable searchQueryHighlightObservable;
    private final JTextFieldWithUnfocusedText searchField = new JTextFieldWithUnfocusedText(
            Localization.lang("Search") + "...");

    private SearchMode searchMode = getSearchModeFromSettings();

    private final JToggleButton caseSensitive;
    private final JToggleButton regularExp;

    private final JLabel currentResults = new JLabel("");

    private AutoCompleteSupport<String> autoCompleteSupport;
    private final JLabel searchIcon;
    private SearchWorker searchWorker;

    /**
     * Initializes the search bar.
     *
     * @param basePanel the base panel
     */
    public SearchBar(BasePanel basePanel) {
        super();

        this.basePanel = Objects.requireNonNull(basePanel);
        this.searchQueryHighlightObservable = new SearchQueryHighlightObservable();

        currentResults.setFont(currentResults.getFont().deriveFont(Font.BOLD));

        caseSensitive = new JToggleButton(IconTheme.JabRefIcon.CASE_SENSITIVE.getSmallIcon(),
                Globals.prefs.getBoolean(JabRefPreferences.SEARCH_CASE_SENSITIVE));
        caseSensitive.setToolTipText(Localization.lang("Case sensitive"));
        caseSensitive.addActionListener(e -> {
            performSearch();
            updatePreferences();
        });

        regularExp = new JToggleButton(IconTheme.JabRefIcon.REG_EX.getSmallIcon(),
                Globals.prefs.getBoolean(JabRefPreferences.SEARCH_REG_EXP));
        regularExp.setToolTipText(Localization.lang("regular expression"));
        regularExp.addActionListener(e -> {
            performSearch();
            updatePreferences();
        });

        openCurrentResultsInDialog = new JButton(IconTheme.JabRefIcon.OPEN_IN_NEW_WINDOW.getSmallIcon());
        openCurrentResultsInDialog.setToolTipText(Localization.lang("Show search results in a window"));
        openCurrentResultsInDialog.addActionListener(ae -> {
            SearchResultsDialog searchDialog = new SearchResultsDialog(basePanel.frame(),
                    Localization.lang("Search results in database %0 for %1",
                            basePanel.getBibDatabaseContext().getDatabaseFile().getName(),
                            this.getSearchQuery().localize()));
            List<BibEntry> entries = basePanel.getDatabase().getEntries().stream().filter(BibEntry::isSearchHit)
                    .collect(Collectors.toList());
            searchDialog.addEntries(entries, basePanel);
            searchDialog.selectFirstEntry();
            searchDialog.setVisible(true);
        });
        openCurrentResultsInDialog.setEnabled(false);

        // Init controls
        setLayout(new WrapLayout(FlowLayout.LEFT));

        searchIcon = new JLabel(IconTheme.JabRefIcon.SEARCH.getSmallIcon());
        this.add(searchIcon);
        initSearchField();
        if (OS.OS_X) {
            searchField.putClientProperty("JTextField.variant", "search");
        }
        this.add(searchField);

        JButton clearSearchButton = new JButton(IconTheme.JabRefIcon.CLOSE.getSmallIcon());
        clearSearchButton.setToolTipText(Localization.lang("Clear"));
        clearSearchButton.addActionListener(l -> endSearch());

        this.add(clearSearchButton);

        searchModeButton = new JButton();
        updateSearchModeButtonText();
        searchModeButton.addActionListener(l -> toggleSearchModeAndSearch());

        JToolBar toolBar = new OSXCompatibleToolbar();
        toolBar.setFloatable(false);
        toolBar.add(clearSearchButton);
        toolBar.addSeparator();
        toolBar.add(regularExp);
        toolBar.add(caseSensitive);
        toolBar.addSeparator();
        toolBar.add(searchModeButton);
        toolBar.addSeparator();
        toolBar.add(openCurrentResultsInDialog);
        globalSearch = new JButton(Localization.lang("Search globally"));
        globalSearch.setToolTipText(Localization.lang("Search in all open databases"));
        globalSearch.addActionListener(l -> {
            AbstractWorker worker = new GlobalSearchWorker(basePanel.frame(), getSearchQuery());
            worker.run();
            worker.update();
        });
        globalSearch.setEnabled(false);
        toolBar.add(globalSearch);
        toolBar.addSeparator();
        toolBar.add(new HelpAction(HelpFile.SEARCH));

        this.add(toolBar);
        this.add(currentResults);

        paintBackgroundWhite(this);
    }

    private void paintBackgroundWhite(Container container) {
        container.setBackground(Color.WHITE);
        for (Component component : container.getComponents()) {
            component.setBackground(Color.WHITE);

            if (component instanceof Container) {
                paintBackgroundWhite((Container) component);
            }
        }
    }

    private static SearchMode getSearchModeFromSettings() {
        if (Globals.prefs.getBoolean(JabRefPreferences.SEARCH_MODE_FILTER)) {
            return SearchMode.FILTER;
        } else if (Globals.prefs.getBoolean(JabRefPreferences.SEARCH_MODE_FLOAT)) {
            return SearchMode.FLOAT;
        } else {
            return SearchMode.FILTER;
        }
    }

    private void toggleSearchModeAndSearch() {
        this.searchMode = searchMode == SearchMode.FILTER ? SearchMode.FLOAT : SearchMode.FILTER;
        updatePreferences();
        updateSearchModeButtonText();
        performSearch();
    }

    private void updateSearchModeButtonText() {
        searchModeButton.setText(searchMode.getDisplayName());
        searchModeButton.setToolTipText(searchMode.getToolTipText());
    }

    /**
     * Initializes the search text field
     */
    private void initSearchField() {
        searchField.setColumns(30);
        searchField.addKeyListener(new KeyAdapter() {

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getExtendedKeyCode() == KeyEvent.VK_ESCAPE) {
                    endSearch();
                }
            }
        });

        // Add autocompleter
        autoCompleteSupport = new AutoCompleteSupport<>(searchField);
        autoCompleteSupport.install();

        // Add the global focus listener, so a menu item can see if this field was focused when an action was called.
        searchField.addFocusListener(Globals.getFocusListener());

        // Search if user press enter
        searchField.addActionListener(e -> performSearch());

        // Subscribe to changes to the text in the search field in order to "live search"
        JTextFieldChangeListenerUtil.addChangeListener(searchField, e -> performSearch());

    }

    private void endSearch() {
        // first focus request is necessary so that the UI stays nice
        basePanel.getMainTable().requestFocus();
        clearSearch();
        basePanel.getMainTable().requestFocus();
    }

    /**
     * Save current settings.
     */
    private void updatePreferences() {
        Globals.prefs.putBoolean(JabRefPreferences.SEARCH_MODE_FLOAT, searchMode == SearchMode.FLOAT);
        Globals.prefs.putBoolean(JabRefPreferences.SEARCH_MODE_FILTER, searchMode == SearchMode.FILTER);

        Globals.prefs.putBoolean(JabRefPreferences.SEARCH_CASE_SENSITIVE, caseSensitive.isSelected());
        Globals.prefs.putBoolean(JabRefPreferences.SEARCH_REG_EXP, regularExp.isSelected());
    }

    /**
     * Focuses the search field if it is not focused.
     */
    public void focus() {
        if (!searchField.hasFocus()) {
            searchField.requestFocus();
        }
    }

    /**
     * Clears the current search. This includes resetting the search text.
     */
    private void clearSearch() {
        searchField.setText("");
        searchField.setBackground(Color.WHITE);

        searchQueryHighlightObservable.reset();

        this.currentResults.setText("");

        basePanel.getMainTable().getTableModel().updateSearchState(MainTableDataModel.DisplayOption.DISABLED);

        globalSearch.setEnabled(false);
        openCurrentResultsInDialog.setEnabled(false);

        searchIcon.setIcon(IconTheme.JabRefIcon.SEARCH.getSmallIcon());
    }

    /**
     * Performs a new search based on the current search query.
     */
    private void performSearch() {
        if (searchWorker != null) {
            searchWorker.cancel(true);
        }

        // An empty search field should cause the search to be cleared.
        if (searchField.getText().isEmpty()) {
            clearSearch();
            return;
        }

        SearchQuery searchQuery = getSearchQuery();
        LOGGER.debug("Searching " + searchQuery + " in " + basePanel.getTabTitle());

        if (!searchQuery.isValid()) {
            informUserAboutInvalidSearchQuery();

            return;
        }

        searchWorker = new SearchWorker(basePanel, searchQuery, searchMode);
        searchWorker.execute();
    }

    private void informUserAboutInvalidSearchQuery() {
        searchField.setBackground(NO_RESULTS_COLOR);

        searchQueryHighlightObservable.reset();

        globalSearch.setEnabled(false);
        openCurrentResultsInDialog.setEnabled(false);

        basePanel.getMainTable().getTableModel().updateSearchState(MainTableDataModel.DisplayOption.DISABLED);

        searchIcon.setIcon(IconTheme.JabRefIcon.SEARCH.getSmallIcon().createWithNewColor(NO_RESULTS_COLOR));
        searchIcon.setToolTipText(Localization.lang("Search failed: illegal search expression"));

        currentResults.setText(Localization.lang("Search failed: illegal search expression"));
    }

    /**
     * Sets the autocompleter used in the search field.
     *
     * @param searchCompleter the autocompleter
     */
    public void setAutoCompleter(AutoCompleter<String> searchCompleter) {
        this.autoCompleteSupport.setAutoCompleter(searchCompleter);
    }

    public SearchQueryHighlightObservable getSearchQueryHighlightObservable() {
        return searchQueryHighlightObservable;
    }

    boolean isStillValidQuery(SearchQuery query) {
        return query.getQuery().equals(this.searchField.getText())
                && (query.isRegularExpression() == regularExp.isSelected())
                && (query.isCaseSensitive() == caseSensitive.isSelected());
    }

    private SearchQuery getSearchQuery() {
        return new SearchQuery(this.searchField.getText(), this.caseSensitive.isSelected(),
                this.regularExp.isSelected());
    }

    void updateResults(int matched, String description, boolean grammarBasedSearch) {
        if (matched == 0) {
            // nothing found
            this.currentResults.setText(Localization.lang("No results found."));
            this.searchField.setBackground(NO_RESULTS_COLOR);
        } else {
            // specific set found, could be all
            this.currentResults.setText(Localization.lang("Found %0 results.", String.valueOf(matched)));
            this.searchField.setBackground(RESULTS_FOUND_COLOR);
        }
        this.searchField.setToolTipText("<html>" + description + "</html>");

        if (grammarBasedSearch) {
            searchIcon
                    .setIcon(IconTheme.JabRefIcon.SEARCH.getSmallIcon().createWithNewColor(ADVANCED_SEARCH_COLOR));
            searchIcon.setToolTipText(Localization.lang("Advanced search active."));
        } else {
            searchIcon.setIcon(IconTheme.JabRefIcon.SEARCH.getSmallIcon());
            searchIcon.setToolTipText(Localization.lang("Normal search active."));
        }

        globalSearch.setEnabled(true);
        openCurrentResultsInDialog.setEnabled(true);
    }
}