Java tutorial
/** * Copyright 2004-2009 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.eve.skills.ui.model; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.event.TreeModelEvent; import javax.swing.tree.TreePath; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import de.codesourcery.eve.skills.ui.utils.GridLayoutBuilder; /** * Wrapping tree model that applies a {@link IViewFilter} to * another tree model. * * For things to work correctly, this wrapper disables * tree listener notifications on the wrapped model. * * @author tobias.gierke@code-sourcery.de */ public class FilteringTreeModel extends AbstractTreeModel { private final ITreeModel delegate; private volatile IViewFilter<ITreeNode> viewFilter; public FilteringTreeModel(ITreeModel delegate) { if (delegate == null) { throw new IllegalArgumentException("delegate cannot be NULL"); } this.delegate = delegate; /* * Disable event generation for all * add/remove etc. operations because * the TreePaths generated by the delegate * model are NOT the actual paths as seen * by the JTree. */ delegate.setListenerNotificationEnabled(false); } public static void main(String[] args) { final JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final FilteringTreeModel model = new FilteringTreeModel(createTreeModel()); // setup text field final JTextField filterTextField = new JTextField(30); filterTextField.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { model.viewFilterChanged(); } }); // setup tree final JTree tree = new JTree(); final IViewFilter<ITreeNode> filter = new AbstractViewFilter<ITreeNode>() { @Override public boolean isHiddenUnfiltered(ITreeNode node) { final String nodeValue = node.toString(); final String expected = filterTextField.getText(); final boolean isHidden = !StringUtils.isBlank(nodeValue) && !StringUtils.isBlank(expected) && nodeValue.startsWith("child") && !nodeValue.toLowerCase().contains(expected.toLowerCase()); System.out.println(nodeValue + " does not match " + expected + " => " + isHidden); return isHidden; } }; model.setViewFilter(filter); tree.setModel(model); // set final JPanel panel = new JPanel(); new GridLayoutBuilder() .add(new GridLayoutBuilder.HorizontalGroup(new GridLayoutBuilder.Cell(new JScrollPane(tree)), new GridLayoutBuilder.FixedCell(filterTextField))) .addTo(panel); frame.getContentPane().add(panel); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } // TESTING ONLY private static ITreeModel createTreeModel() { final DefaultTreeModel result = new DefaultTreeModel(); ITreeNode root = new DefaultTreeNode("root"); root.addChild(createNodeWithChildren("cat 1", "child 1 1", "child 1 2", "child 1 3 ")); root.addChild(createNodeWithChildren("cat 2", "child 2 1", "child 2 2", "child 2 3 ")); root.addChild(createNodeWithChildren("cat 3", "child 3 1", "child 3 2", "child 3 3 ")); result.setRoot(root); return result; } private static ITreeNode createNodeWithChildren(String parent, String... children) { ITreeNode result = new DefaultTreeNode(parent); if (children != null) { for (String s : children) { result.addChild(new DefaultTreeNode(s)); } } return result; } protected boolean isHidden(ITreeNode node) { return viewFilter != null && viewFilter.isHidden(node); } public void setViewFilter(IViewFilter<ITreeNode> viewFilter) { IViewFilter<ITreeNode> oldFilter = this.viewFilter; this.viewFilter = viewFilter; if (oldFilter != viewFilter) { viewFilterChanged(); } } @Override public int addChild(ITreeNode parent, ITreeNode n) { delegate.addChild(parent, n); final int index = getIndexOfChild(parent, n); if (index != -1) { fireEvent(EventType.ADDED, new TreeModelEvent(this, parent.getPathToRoot(), new int[] { index }, new Object[] { n })); } return index; } @Override public void addChild(ITreeNode parent, ITreeNode n, int index) { delegate.addChild(parent, n); final int realIndex = getIndexOfChild(parent, n); if (realIndex != -1) { fireEvent(EventType.ADDED, new TreeModelEvent(this, parent.getPathToRoot(), new int[] { realIndex }, new Object[] { n })); } } /** * Determines whether the TreePath starting * at the tree root down to (and including) a given * node is visible. * * @param child * @return <code>true</code> if the node itself and all it's parent nodes * are visible (not hidden). * @see #isHidden(ITreeNode) */ protected boolean isPathToRootVisible(ITreeNode child) { ITreeNode current = child; do { if (isHidden(current)) { return false; } current = (ITreeNode) current.getParent(); } while (current != null); return true; } @Override public void addChildren(ITreeNode parent, Collection<ITreeNode> nodes) { delegate.addChildren(parent, nodes); if (!isPathToRootVisible(parent)) { // children cannot be visible if their parent is hidden return; } final List<Integer> childIndices = new ArrayList<Integer>(); final List<ITreeNode> children = new ArrayList<ITreeNode>(); for (ITreeNode child : nodes) { if (isHidden(child)) { continue; } childIndices.add(getIndexOfChild(parent, child)); children.add(child); } if (!childIndices.isEmpty()) { final int[] indices = ArrayUtils.toPrimitive(childIndices.toArray(new Integer[childIndices.size()])); final Object[] childs = children.toArray(new ITreeNode[children.size()]); fireEvent(EventType.ADDED, new TreeModelEvent(this, parent.getPathToRoot(), indices, childs)); } } @Override public void dispose() { delegate.dispose(); } @Override public List<ITreeNode> getChildren(ITreeNode parent) { if (!isPathToRootVisible(parent)) { return Collections.emptyList(); } final List<ITreeNode> filtered = new ArrayList<ITreeNode>(); for (ITreeNode n : delegate.getChildren(parent)) { if (!isHidden(n)) { filtered.add(n); } } return filtered; } @Override public void modelChanged() { delegate.modelChanged(); fireEvent(EventType.STRUCTURE_CHANGED, new TreeModelEvent(this, ((ITreeNode) getRoot()).getPathToRoot())); } @Override public int removeChild(ITreeNode parent, ITreeNode child) { final int index = getIndexOfChild(parent, child); delegate.removeChild(parent, child); if (index != -1) { fireEvent(EventType.REMOVED, new TreeModelEvent(this, parent.getPathToRoot(), new int[] { index }, new Object[] { child })); } return index; } @Override public int replaceChild(ITreeNode parent, ITreeNode oldChild, ITreeNode newChild) { int oldIndex = getIndexOfChild(parent, oldChild); delegate.replaceChild(parent, oldChild, newChild); final int newIndex = getIndexOfChild(parent, newChild); if (oldIndex != -1) { // old child was visible if (newIndex != -1) { // new child is visible as well fireEvent(EventType.STRUCTURE_CHANGED, new TreeModelEvent(this, parent.getPathToRoot())); } else { // new child is hidden => REMOVE EVENT fireEvent(EventType.REMOVED, new TreeModelEvent(this, parent.getPathToRoot(), new int[] { oldIndex }, new Object[] { oldChild })); } } else if (newIndex != -1) { // old (replaced) node was hidden if (newIndex != -1) { // new node is visible => ADD event fireEvent(EventType.ADDED, new TreeModelEvent(this, parent.getPathToRoot(), new int[] { newIndex }, new Object[] { newChild })); } } return oldIndex; } @Override public void setRoot(ITreeNode rootNode) { delegate.setRoot(rootNode); } @Override public void sortChildren(ITreeNode parent, Comparator<ITreeNode> comp, boolean recursive) { delegate.sortChildren(parent, comp, recursive); } @Override public Object getChild(Object parent, int index) { return getChildren((ITreeNode) parent).get(index); } @Override public int getChildCount(Object parent) { return getChildren((ITreeNode) parent).size(); } @Override public int getIndexOfChild(Object parent, Object child) { if (!isPathToRootVisible((ITreeNode) child)) { return -1; } int index = 0; for (ITreeNode n : getChildren((ITreeNode) parent)) { if (n == child) { return index; } index++; } return -1; } @Override public Object getRoot() { return delegate.getRoot(); } @Override public boolean isLeaf(Object node) { if (delegate.isLeaf(node)) { return true; } if (node instanceof LazyTreeNode) { // do not trigger call to getChildren() for lazy nodes return false; } return getChildCount(node) == 0; } @Override public void valueForPathChanged(TreePath path, Object newValue) { delegate.valueForPathChanged(path, newValue); final ITreeNode changedNode = (ITreeNode) path.getLastPathComponent(); if (isPathToRootVisible(changedNode)) { fireEvent(EventType.CHANGED, new TreeModelEvent(this, changedNode.getPathToRoot())); } } @Override public void nodeValueChanged(ITreeNode jobNode) { delegate.nodeValueChanged(jobNode); if (isPathToRootVisible(jobNode)) { fireEvent(EventType.CHANGED, new TreeModelEvent(this, jobNode.getPathToRoot())); } } public void viewFilterChanged() { System.out.println(">> View filter changed <<"); delegate.modelChanged(); fireEvent(EventType.STRUCTURE_CHANGED, new TreeModelEvent(this, ((ITreeNode) getRoot()).getPathToRoot())); } }