de.codesourcery.eve.skills.ui.model.FilteringTreeModel.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.eve.skills.ui.model.FilteringTreeModel.java

Source

/**
 * 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()));
    }
}