Wednesday, October 08, 2008

Searching a JTree with Find Dialog

Code Snippet for searching a JTree (Find Dialog)



import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.text.Position;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

/**
* @author imran
* Reference -
* http://www.javareference.com/jrexamples/viewexample.jsp?id=99
* http://www.jroller.com/santhosh/entry/incremental_search_jtree
*/

public class TreeSearch {


/**
* @param args
*/
public static void main(String[] args) {
JFrame frame = new JFrame("Tree Search");
UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTree tree = new SearchableJTree();
JLabel statusLabel = new JLabel("Loading....");
frame.add(statusLabel);
wireTheTree(tree);
frame.setLocationRelativeTo(null);
frame.remove(statusLabel);
frame.setSize(new Dimension(150,200));
frame.add(new JScrollPane( tree));
frame.setVisible(true);
}//main

private static void wireTheTree(JTree tree) {
final String SEARCH = "SEARCH";
tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK), SEARCH );
tree.getActionMap().put(SEARCH, getFindAction(tree));
}//wireTheTree

private static Action getFindAction(final JTree tree) {
Action action = new AbstractAction() {

/**
*
*/
private static final long serialVersionUID = 2692932079021920706L;
JDialog dlg = null;
JTextField txtSearch = new JTextField();
private void initDlg() {
if (null == dlg) {
this.dlg = getDialog();
// DlgFind dlg_ = new DlgFind(null);
// dlg_.getBtnNext().addActionListener(getfindActionListener());
// this.txtSearch = dlg_.getTxtFind();
// this.dlg = dlg_;
}
}//initDlg()

private JDialog getDialog() {
JDialog dlg = new JDialog();
dlg.setLayout(new BorderLayout());
dlg.add(txtSearch , BorderLayout.NORTH);
JButton btnFind = new JButton("Find");
btnFind.addActionListener(getfindActionListener());
dlg.add(btnFind, BorderLayout.SOUTH);
dlg.setSize(new Dimension(150,75));
return dlg;
}//getDialog()

public void actionPerformed(ActionEvent e) {
initDlg();
dlg.setModal(true);
dlg.setLocationRelativeTo(null);
dlg.setVisible(true);
}//actionPerformed

//if is_built_in_used is false searchNode() is called (no need for collapsible tree)
boolean IS_BUILT_IN_USED = false;
private ActionListener getfindActionListener() {
ActionListener actionListener = new ActionListener() {
DefaultMutableTreeNode prevFoundNode = null;
int prevFoundRow = 0;
public void actionPerformed(ActionEvent evt) {
if (IS_BUILT_IN_USED) {
if (prevFoundRow == tree.getRowCount()-1) {
JOptionPane.showMessageDialog(null, "No match found");
}
System.out.println("Prev found row : " + prevFoundRow);
TreePath path1 = tree.getNextMatch(txtSearch.getText(), prevFoundRow, Position.Bias.Forward);
tree.scrollPathToVisible(path1);
tree.setSelectionPath(path1);
tree.expandPath(path1);
prevFoundRow = ((tree.getRowForPath(path1) + 1) < tree.getRowCount()) ?
(tree.getRowForPath(path1) + 1) : tree.getRowCount()-1;
System.out.println("Prev found row (updated): " + prevFoundRow);


}
else {
DefaultMutableTreeNode searchNode = searchNode(txtSearch.getText(), prevFoundNode);
prevFoundNode = searchNode;
if (null == searchNode) {
JOptionPane.showMessageDialog(null, "No match found");
} else {
//make the node visible by scroll to it
TreeNode[] nodes = ((DefaultTreeModel)tree.getModel()).getPathToRoot(searchNode);
TreePath path = new TreePath(nodes);
tree.scrollPathToVisible(path);
tree.setSelectionPath(path);
}
}//else BUILT_IN_USED
}//actionPerformed
};//ActionListener()

return actionListener;
}//getFindAction

/**
* This method takes the node string and traverses the tree till it finds the node
* matching the string. If the match is found the node is returned else null is returned
* @param nodeStr node string to search for
* @return tree node
*/
private DefaultMutableTreeNode searchNode(String nodeStr, DefaultMutableTreeNode prevFoundNode)
{
DefaultMutableTreeNode node = null;
//clear the current selection
tree.requestFocus(true);tree.clearSelection();
tree.repaint();
tree.updateUI();
tree.invalidate();

boolean isGonePastPrevFoundNode = (null == prevFoundNode) ? true : false;

System.out.println("Clearing selection...");
Object root = (DefaultMutableTreeNode)tree.getModel().getRoot();
Enumeration nodes = null;
if (null != root && root instanceof DefaultMutableTreeNode) {
//Get the enumeration
nodes = ((DefaultMutableTreeNode)root).breadthFirstEnumeration();
}
else {
return node;
}

//Pattern pattern = Pattern.compile(nodeStr);

//iterate through the enumeration
while(nodes.hasMoreElements())
{
//get the node
node = (DefaultMutableTreeNode)nodes.nextElement();

System.out.println("Searching ... " + node.getUserObject().toString());
//match the string with the user-object of the node
//Matcher matcher = pattern.matcher(node.getUserObject().toString());

if (Pattern.matches(nodeStr, node.getUserObject().toString())) {
System.out.println("Found match " + node.getUserObject());

if (isGonePastPrevFoundNode) {
return node;
}//if isGonePastPrevFoundNode

if (node == prevFoundNode) {
isGonePastPrevFoundNode = true;
}//if node == prevFoundNode
// return node;
}//if Pattern.matches

if (node.getUserObject().toString().toLowerCase().startsWith(nodeStr.toLowerCase())) {
System.out.println("Found match (starting with)" + node.getUserObject());

if (isGonePastPrevFoundNode) {
return node;
}//if isGonePastPrevFoundNode

if (node == prevFoundNode) {
isGonePastPrevFoundNode = true;
} // if (node == prevFoundNode)
}//if (node.getUserObject....
}//while(nodes.hasMoreElements)

//tree node with string node found return null
return null;
}//searchNode()
};//action()

return action;
}//getFindAction()
}//TreeSearch



class SearchableJTree extends JTree {

/**
*
*/
private static final long serialVersionUID = 7696185817045941689L;

@Override
public TreePath getNextMatch(String prefix, int startingRow,
Position.Bias bias) {

int max = getRowCount();
if (prefix == null) {
throw new IllegalArgumentException();
}
if (startingRow <>= max) {
throw new IllegalArgumentException();
}
prefix = prefix.toUpperCase();

// start search from the next/previous element froom the
// selected element
int increment = (bias == Position.Bias.Forward) ? 1 : -1;
int row = startingRow;
do {
TreePath path = getPathForRow(row);
String text = convertValueToText(path.getLastPathComponent(),
isRowSelected(row), isExpanded(row), true, row, false);

if (text.toUpperCase().startsWith(prefix)) {
return path;
}

if (this.isCollapsed(row)) {
TreePath collapsedPath = searchCollapsedNodes(this, path,
prefix);
if (collapsedPath != null) {
return collapsedPath;
}
}

row = (row + increment + max) % max;
} while (row != startingRow);
return null;
}//getNextMatch

private TreePath searchCollapsedNodes(JTree tree, TreePath parent,
String prefix) {
TreeNode node = (TreeNode) parent.getLastPathComponent();

int row = tree.getRowForPath(parent);
String text = tree
.convertValueToText(parent.getLastPathComponent(), tree
.isRowSelected(row), tree.isExpanded(row), true, row,
false);

// if (ignoreCase)
{
text = text.toUpperCase();
}
if (text.startsWith(prefix)) {
return parent;
}

if (node.getChildCount() >= 0) {
for (Enumeration e = node.children(); e.hasMoreElements();) {
TreeNode n = (TreeNode) e.nextElement();
TreePath path = parent.pathByAddingChild(n);
TreePath result = searchCollapsedNodes(tree, path, prefix);
// Found a match
if (result != null) {
return result;
}
}

}
return null;
}//searchCollapsedNodes
}//CollapsedTree

0 comments: