Package edu.mit.blocks.workspace

Source Code of edu.mit.blocks.workspace.SearchBar

package edu.mit.blocks.workspace;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import edu.mit.blocks.codeblockutil.CQueryField;

/**
* Contributes a search bar component to the CodeBlocks GUI, which allows the user to find
* Searchables such as blocks in the drawers and workspace with a query by name.
*/
public class SearchBar {

    private final CQueryField searchPanel;
    private final JTextField searchBar;
    private final String defaultText;
    private Set<SearchableContainer> containerSet = new HashSet<SearchableContainer>();
    private Map<SearchableContainer, Set<SearchableElement>> searchResults = new HashMap<SearchableContainer, Set<SearchableElement>>();
    private Timer searchUpdater;
    private static final int SEARCH_UPDATER_DELAY = 5000;
    private Timer searchThrottle;
    private static final int SEARCH_THROTTLE_DELAY = 250;

    private enum SearchRange {

        CHECK_ALL, REMOVE_FROM_FOUND, ADD_FROM_NOT_FOUND
    }
    private SearchRange searchRange;

    /**
     * Contructs a new search bar.
     * @param defaultText the text to show when the user is not using the search bar,
     * such as "Search blocks"
     * @param tooltip the text to show as a tooltip for the search bar when the user hovers the mouse
     * over the search bar.
     * @param defaultComponent the component for which focus should be requested if the user
     * presses the Escape key while using the search bar.
     */
    public SearchBar(String defaultText, String tooltip, final Component defaultComponent) {
        this.defaultText = defaultText;
        this.searchPanel = new CQueryField();
        this.searchBar = this.searchPanel.getQueryField();
        searchBar.setToolTipText(tooltip);
        searchBar.setColumns(12);

        resetSearchBar();
        searchBar.addFocusListener(new FocusListener() {

            public void focusGained(FocusEvent e) {
                readySearchBar();
            }

            public void focusLost(FocusEvent e) {
                resetSearchBar();
            }
        });

        searchBar.addKeyListener(new KeyAdapter() {

            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    searchBar.setText("");
                    defaultComponent.requestFocusInWindow();
                }
            }
        });

        searchBar.getDocument().addDocumentListener(new DocumentListener() {

            public void changedUpdate(DocumentEvent e) {
                // This method intentionally left blank.
            }

            public void insertUpdate(DocumentEvent e) {
                //System.out.println("Called insertUpdate, offset = " + e.getOffset() + ", query length = " + searchBar.getText().length());
                if (searchBar.getText().equals(SearchBar.this.defaultText)) {
                    return;
                }
                // If the search term changed only at the beginning or end, then only
                // the blocks found already may change.  Remove unmatched blocks from
                // foundBlocks.
                if (e.getOffset() == 0 || e.getOffset() + e.getLength() == searchBar.getText().length()) {
                    performSearch(SearchRange.REMOVE_FROM_FOUND);
                } else {
                    // If the search term changed in the middle, then the blocks found and
                    // the blocks yet to be found may have changed.  Recheck all blocks.
                    performSearch(SearchRange.CHECK_ALL);
                }
            }

            public void removeUpdate(DocumentEvent e) {
                //System.out.println("Called removeUpdate, offset = " + e.getOffset() + ", query length = " + searchBar.getText().length());
                if (searchBar.getText().equals("")) {
                    performSearch(SearchRange.CHECK_ALL);
                } else if (e.getOffset() == 0 || e.getOffset() == searchBar.getText().length()) {
                    // If the search term changed only at the beginning or end, then
                    // the blocks found already do not change.  Check for additional blocks
                    // from the Worspace.
                    performSearch(SearchRange.ADD_FROM_NOT_FOUND);
                } else {
                    // If the search term changed in the middle, then the blocks found may have
                    // changed.  Recheck all blocks.
                    performSearch(SearchRange.CHECK_ALL);
                }
            }
        });

        // Repeat search periodically to refresh results in case elements change,
        // such as when a new block is dragged onto the Workspace and should be included in the results.
        searchUpdater = new Timer(SEARCH_UPDATER_DELAY, new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                // Skip the update if the throttle is about to perform a search anyway.
                if (searchThrottle != null && !searchThrottle.isRunning()) {
                    searchRange = SearchRange.CHECK_ALL;
                    performSearchTimerHandler();
                }
            }
        });
        searchUpdater.start();
    }

    /**
     * Returns the Swing component representation of the search bar.
     * @return the Swing the component representation of the search bar.
     */
    public JComponent getComponent() {
        return searchPanel;
    }

    /**
     * Returns a set of elements representing the search results for a particular
     * container.
     * @param container the returned search elements will be from this search container
     * @return search results for a particular container
     */
    public Iterable<SearchableElement> getSearchResults(SearchableContainer container) {
        Set<SearchableElement> results = searchResults.get(container);
        if (results == null) {
            results = Collections.emptySet();
            return results;
        }
        return Collections.unmodifiableSet(results);
    }

    /**
     * Adds a searchable to the set of searchables queried by this search bar.
     * If more than one search bar exists, the same searchable should not be added to more than search bar.
     * @param searchable the container to add
     */
    public void addSearchableContainer(SearchableContainer searchable) {
        synchronized (this) {
            containerSet.add(searchable);
        }
    }

    /**
     * Removes a searchable container from the set of searchables queried by this search bar.
     * @param searchable the container to remove
     */
    public void removeSearchableContainer(SearchableContainer searchable) {
        synchronized (this) {
            containerSet.remove(searchable);
        }
    }

    /**
     * Clears all the internal data of this.
     */
    public void reset() {
        synchronized (this) {
            searchResults.clear();
            containerSet.clear();
        }
    }

    /**
     * Whenever the search bar loses focus and has an empty document,
     * put "Search blocks" in gray italics.
     */
    private void resetSearchBar() {
        if (searchBar.getText().trim().equals("")) {
            Font font = searchBar.getFont();
            searchBar.setFont(new Font(font.getName(), Font.ITALIC, font.getSize()));
            searchBar.setForeground(Color.GRAY);
            searchBar.setText(defaultText);
        }
    }

    /**
     * Whenever the search bar gains focus, if the text is "Search Blocks",
     * then clear the contents and reset the font.  Otherwise, highlight
     * whatever is there.
     */
    private void readySearchBar() {
        if (defaultText.equals(searchBar.getText())) {
            searchBar.setText("");
            Font font = searchBar.getFont();
            searchBar.setFont(new Font(font.getName(), Font.PLAIN, font.getSize()));
            searchBar.setForeground(Color.BLACK);
        } else {
            searchBar.selectAll();
        }
    }

    /**
     * Clears all search results from a previous query.
     */
    private void clearSearchResults() {
        for (Set<SearchableElement> foundElements : searchResults.values()) {
            for (SearchableElement element : foundElements) {
                element.updateInSearchResults(false);
            }
            for (SearchableContainer container : searchResults.keySet()) {
                container.updateContainsSearchResults(false);
            }
        }
        searchResults.clear();
    }

    /**
     * Perform a new search for the specified range based on updates to the search bar.
     * @param range verifies the optimization for search depending on whether the search space
     * has become bigger or smaller since the last search.
     */
    private void performSearch(final SearchRange range) {
        // If new requests to search come in during the delay, reset the timer and update the range.
        // If the range changed from the previous request since starting the timer,
        // automatically do a CHECK_ALL.
        if (searchRange == null) {
            searchRange = range;
        }
        if (!searchRange.equals(range)) {
            searchRange = SearchRange.CHECK_ALL;
        }
        if (searchThrottle == null) {
            searchThrottle = new Timer(SEARCH_THROTTLE_DELAY, new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    performSearchTimerHandler();
                }
            });
            searchThrottle.setRepeats(false);
        }
        if (searchThrottle.isRunning()) {
            searchThrottle.restart();
        } else {
            searchThrottle.start();
        }
    }

    private void performSearchTimerHandler() {
        //System.out.println("performing search... range = " + searchRange);
        if (searchBar.getText().equals("")) {
            clearSearchResults();
            return;
        }
        // Called by a javax.swing.Timer to throttle search by about a quarter second.
        SearchRange range = searchRange;
        searchRange = null;
        Set<SearchableContainer> containers;
        synchronized (this) {
            // Safely grab a copy of the current set of containers to search.
            containers = new HashSet<SearchableContainer>(containerSet);
        }
        if (range == SearchRange.ADD_FROM_NOT_FOUND || range == SearchRange.CHECK_ALL) {
            for (SearchableContainer container : containers) {
                // Update the search results for each container for this query
                Set<SearchableElement> foundElements = searchResults.get(container);
                if (foundElements == null) {
                    foundElements = new HashSet<SearchableElement>();
                    searchResults.put(container, foundElements);
                }
                for (SearchableElement element : container.getSearchableElements()) {
                    if (!foundElements.contains(element) && element.getKeyword().toUpperCase().contains(searchBar.getText().toUpperCase())) {
                        foundElements.add(element);
                        element.updateInSearchResults(true);
                    }
                }
                if (!foundElements.isEmpty()) {
                    container.updateContainsSearchResults(true);
                }
            }
        }
        if (range == SearchRange.REMOVE_FROM_FOUND || range == SearchRange.CHECK_ALL) {
            for (SearchableContainer container : containers) {
                Set<SearchableElement> foundElements = searchResults.get(container);
                if (foundElements != null) {
                    Set<SearchableElement> elementsToRemove = new HashSet<SearchableElement>();
                    for (SearchableElement element : foundElements) {
                        if (!element.getKeyword().toUpperCase().contains(searchBar.getText().toUpperCase())) {
                            elementsToRemove.add(element);
                            element.updateInSearchResults(false);
                        }
                    }
                    foundElements.removeAll(elementsToRemove);
                    if (foundElements.isEmpty()) {
                        container.updateContainsSearchResults(false);
                    }
                }
            }
        }
    }
}
TOP

Related Classes of edu.mit.blocks.workspace.SearchBar

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.