Package org.openquark.util.ui

Source Code of org.openquark.util.ui.SearchableTree

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* SearchableTree.java
* Created: Sept 15, 2004
* By: Richard Webster
*/
package org.openquark.util.ui;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.openquark.util.Messages;


/**
* A tree which can show search results.
* @author Richard Webster
*/
public class SearchableTree implements SearchableComponent, TreeModelListener {

    /** Use this message bundle to get string resources. */
    private static Messages messages = UIMessages.instance;

    /** The tree to make searchable. */
    private final UtilTree tree;

    /** The parent node for all search results. */
    private DefaultMutableTreeNode searchNode;

    /** Keep track of the last search performed. */
    private String lastSearchText;

    /** Keep track of whether a search is in progress. */
    private boolean searching = false;

// TODO: rerunning the search each time a change is made to the tree could be problematic if many changes are done at once...
//       If this turns out to be a problem, then it might be better to use a timer to rerun the search after a small delay.
//    /** The timer used for updating the search results after changes are made to the tree. */
//    private Timer searchUpdateTimer;

    /**
     * SearchableTree constructor.
     * @param tree  the tree to make searchable
     */
    public SearchableTree(final UtilTree tree) {
        super();
        this.tree = tree;

        tree.getModel().addTreeModelListener(SearchableTree.this);

        tree.addPropertyChangeListener(JTree.TREE_MODEL_PROPERTY, new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    rerunLastSearch();
                    tree.getModel().addTreeModelListener(SearchableTree.this);
                }
            });

        installRenderer(tree);
       
//        // A timer that runs once changes are made to the tree.
//        searchUpdateTimer = new Timer(250, new ActionListener() {
//                public void actionPerformed(ActionEvent e) {
//                    if (e.getSource() == searchUpdateTimer) {
//                        rerunLastSearch();
//                    }
//                }
//            });
//        searchUpdateTimer.setRepeats(false);
    }

    /**
     * Reruns the last search on the tree.
     * This can be called after updating the tree to apply the search criteria to the new tree contents.
     */
    public void rerunLastSearch() {
        if (lastSearchText != null && lastSearchText.length() > 0) {
            showSearchResults(lastSearchText, false);
        }
    }

    /**
     * Returns the tree model.
     * @return the tree model
     */
    private DefaultTreeModel getTreeModel() {
        return (DefaultTreeModel) tree.getModel();
    }

    /**
     * Returns the root node of the tree.
     * @return the root node of the tree
     */
    private DefaultMutableTreeNode getRootNode() {
        return (DefaultMutableTreeNode) getTreeModel().getRoot();
    }

    /**
     * Installs appropriate renderer to the tree to highlight search results.
     * @param tree
     */
    private void installRenderer(final UtilTree tree) {
        // Wrap the existing cell renderer with a search tree cell renderer
        TreeCellRenderer renderer = tree.getCellRenderer();
        if (!(renderer instanceof SearchableTreeCellRenderer)) {
            tree.setCellRenderer(SearchableTreeCellRenderer.create(renderer));
        }
       
        // Install a listener that listens for cell renderer changes
        tree.addPropertyChangeListener(
                JTree.CELL_RENDERER_PROPERTY,
                new PropertyChangeListener() {
                    public void propertyChange(PropertyChangeEvent evt) {
                        TreeCellRenderer renderer = (TreeCellRenderer) evt.getNewValue();
                        tree.setCellRenderer(SearchableTreeCellRenderer.create(renderer));
                    }
                }
        );
    }

    /**
     * Sets up the tree for showing search results.
     */
    private void initSearch() {
        final DefaultMutableTreeNode rootNode = getRootNode();

        // If the search node already exists, then make sure it is in the correct spot in the tree.
        if (searchNode != null) {
            int searchIndex = rootNode.getIndex(searchNode);

            // If the search node is already the last child of the root, then do nothing.
            if (rootNode.getChildCount() != 0 && searchIndex == rootNode.getChildCount() - 1) {
                return;
            }         

            // If the search node is in the wrong spot, then remove it.
            if (searchIndex > 0) {
                rootNode.remove(searchNode);
            }
        }

        // Create the search node, if necessary.
        if (searchNode == null) {
            searchNode = new DefaultMutableTreeNode(new DisplayOnlyTreeItem(messages.getString("Search_Results_TreeNode_Caption"), UIUtilities.LoadImageIcon("/Resources/search_results.gif")))//$NON-NLS-1$//$NON-NLS-2$
        }
       
        // Add the search node as the last child of the root.
        rootNode.add(searchNode);
        getTreeModel().nodeStructureChanged(rootNode);
    }
    /**
     * Updates the tree to show search results for any items matching the specified substring.
     * If an empty string is provided, then the search results will be cleared.
     * @param searchSubstring  the substring to be searched for, or an empty string to clear the search results
     */
    public void showSearchResults(String searchSubstring) {
        showSearchResults(searchSubstring, true);
    }

    /**
     * Updates the tree to show search results for any items matching the specified substring.
     * If an empty string is provided, then the search results will be cleared.
     * @param searchSubstring  the substring to be searched for, or an empty string to clear the search results
     * @param scrollIntoView   if True then the search results will be scrolled into view
     */
    public void showSearchResults(String searchSubstring, boolean scrollIntoView) {
        // Don't start another search when already searching.
        if (searching) {
            return;
        }
        searching = true;

        // Trim any leading and trailing spaces from the search substring
        searchSubstring = searchSubstring.trim();

        // Set the search string in the renderer so that the renderer knows what to highlight
        TreeCellRenderer renderer = tree.getCellRenderer();
        if (renderer instanceof SearchableTreeCellRenderer) {
            ((SearchableTreeCellRenderer) renderer).setSearchString(searchSubstring);
        }
       
        try {
            final DefaultMutableTreeNode rootNode = getRootNode();

            tree.saveState();

            // Make sure that the search node has been added to the tree.
            initSearch();

            // Clear the current search, if any.
            searchNode.removeAllChildren();

            if (searchSubstring == null || searchSubstring.length() == 0) {
                // Remove the search node if the search text is cleared.
                rootNode.remove(searchNode);
                getTreeModel().nodeStructureChanged(rootNode);

                tree.restoreSavedState();

                // If we didn't search for anything scroll to the all first tree node.
                if (rootNode.getChildCount() > 0) {
                    DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) rootNode.getFirstChild();
                    final TreePath firstNodePath = new TreePath(firstNode.getPath());
                    tree.setSelectionPath(firstNodePath);
                    tree.scrollPathToTop(firstNodePath);
                }

                return;
            }

            // Search for the substring without matching case.
            searchSubstring = searchSubstring.toLowerCase();
            List<MutableTreeNode> resultNodes = getSearchResults(searchSubstring);

            for (final MutableTreeNode mutableTreeNode : resultNodes) {
                searchNode.add(mutableTreeNode);
            }

            // Change the search node text to include the number of search results.
            String searchNodeText = messages.getString("Search_Results_TreeNode_Caption") + " (" + resultNodes.size() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            searchNode.setUserObject(new DisplayOnlyTreeItem(searchNodeText, UIUtilities.LoadImageIcon("/Resources/search_results.gif"))); //$NON-NLS-1$

            getTreeModel().nodeStructureChanged(searchNode);

            tree.restoreSavedState();

            // Expand the search node, select it, and scroll it into view.
            final TreePath searchNodePath = new TreePath(searchNode.getPath());
            tree.expandPath(searchNodePath);
            tree.setSelectionPath(searchNodePath);

            if (scrollIntoView) {
                // Invoke this later once the tree has synced up with the model.
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        tree.scrollPathToTop(searchNodePath);
                    }
                });
            }
        }
        finally {
            // Keep track of the last search text.
            this.lastSearchText = searchSubstring;

            searching = false;
        }
    }

    /**
     * Returns a list of nodes for the values matching the search string.
     * @param searchString  the string to search for
     * @return a list of nodes for the values matching the search string
     */
    private List<MutableTreeNode> getSearchResults(String searchString) {
        final DefaultMutableTreeNode rootNode = getRootNode();

        List<MutableTreeNode> resultNodes = new ArrayList<MutableTreeNode>();
        if (rootNode != null) {
            addNodeSearchResults(searchString, resultNodes, rootNode);
        }

        // Sort the resulting nodes by their display names (not case sensitive).
        Collections.sort(resultNodes, new Comparator<MutableTreeNode>() {
            public int compare(MutableTreeNode node1, MutableTreeNode node2) {
                return node1.toString().compareToIgnoreCase(node2.toString());
            }});

        return resultNodes;
    }

    /**
     * A recursive helper function for searching all nodes in a tree.
     * @param searchString  the search text
     * @param resultNodes   the current list of result nodes
     * @param node          the node to be searched
     */
    private void addNodeSearchResults(String searchString, List<MutableTreeNode> resultNodes, TreeNode node) {
        // Check whether the current node is searchable.
        if (node instanceof SearchableTreeNode) {
            SearchableTreeNode searchableNode = (SearchableTreeNode) node;
            MutableTreeNode searchResult = searchableNode.searchNode(searchString);

            if (searchResult != null) {
                resultNodes.add(searchResult);
            }
        }

        // Check any child nodes as well.
        for (int childN = 0, nChildren = node.getChildCount(); childN < nChildren; ++childN) {
            addNodeSearchResults(searchString, resultNodes, node.getChildAt(childN));
        }
    }

    /**
     * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesChanged(TreeModelEvent e) {
//      if (!searching) {
//          searchUpdateTimer.restart();
//      }
        rerunLastSearch();
    }

    /**
     * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesInserted(TreeModelEvent e) {
//      if (!searching) {
//          searchUpdateTimer.restart();
//      }
        rerunLastSearch();
    }

    /**
     * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesRemoved(TreeModelEvent e) {
//      if (!searching) {
//          searchUpdateTimer.restart();
//      }
        rerunLastSearch();
    }

    /**
     * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)
     */
    public void treeStructureChanged(TreeModelEvent e) {
//      if (!searching) {
//          searchUpdateTimer.restart();
//      }
        rerunLastSearch();
    }
}
TOP

Related Classes of org.openquark.util.ui.SearchableTree

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.