All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.extended.tree.DefaultTreeCheckingModel Maven / Gradle / Ivy

/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.extended.tree;

import com.alee.laf.checkbox.CheckState;
import com.alee.laf.tree.TreeUtils;
import com.alee.utils.CollectionUtils;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.*;

/**
 * Default checking model for WebCheckBoxTree.
 *
 * @author Mikle Garin
 */

public class DefaultTreeCheckingModel implements TreeCheckingModel
{
    /**
     * Checkbox tree which uses this checking model.
     */
    protected WebCheckBoxTree checkBoxTree;

    /**
     * Node check states cache.
     */
    protected Map nodeCheckStates = new WeakHashMap ();

    /**
     * Checkbox tree check state change listeners.
     */
    protected List> checkStateChangeListeners = new ArrayList> ( 1 );

    /**
     * @param checkBoxTree checkbox tree which uses this checking model.
     */
    public DefaultTreeCheckingModel ( final WebCheckBoxTree checkBoxTree )
    {
        super ();
        this.checkBoxTree = checkBoxTree;
    }

    /**
     * Returns checkbox tree which uses this checking model.
     *
     * @return checkbox tree which uses this checking model
     */
    public WebCheckBoxTree getCheckBoxTree ()
    {
        return checkBoxTree;
    }

    /**
     * Sets checkbox tree which uses this checking model.
     * This method call also forces cache to clear all stored values.
     *
     * @param checkBoxTree checkbox tree which uses this checking model.
     */
    public void setCheckBoxTree ( final WebCheckBoxTree checkBoxTree )
    {
        nodeCheckStates.clear ();
        this.checkBoxTree = checkBoxTree;
    }

    @Override
    public List getCheckedNodes ( final boolean optimize )
    {
        return getAllNodesForState ( CheckState.checked, optimize );
    }

    @Override
    public List getMixedNodes ()
    {
        return getAllNodesForState ( CheckState.mixed, false );
    }

    /**
     * Returns list of nodes for the specified state.
     * For a reasonable cause this will not work for unchecked state.
     *
     * @param state    check state
     * @param optimize whether should optimize selected nodes or not
     * @return list of nodes for the specified state
     */
    protected List getAllNodesForState ( final CheckState state, final boolean optimize )
    {
        final List checkedNodes = new ArrayList ( nodeCheckStates.size () );
        for ( final Map.Entry entry : nodeCheckStates.entrySet () )
        {
            if ( entry.getValue () == state )
            {
                checkedNodes.add ( entry.getKey () );
            }
        }
        if ( optimize )
        {
            TreeUtils.optimizeNodes ( checkedNodes );
        }
        return checkedNodes;
    }

    @Override
    public void setChecked ( final Collection nodes, final boolean checked )
    {
        // Collecting state changes
        final boolean collectChanges = checkStateChangeListeners.size () > 0;
        List> changes = null;
        if ( collectChanges )
        {
            changes = new ArrayList> ( nodes.size () );
        }

        // Updating states
        final List toUpdate = new ArrayList ();
        for ( final E node : nodes )
        {
            setCheckedImpl ( node, checked, toUpdate, changes );
        }
        repaintTreeNodes ( toUpdate );

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    @Override
    public CheckState getCheckState ( final E node )
    {
        final CheckState checkState = nodeCheckStates.get ( node );
        return checkState != null ? checkState : CheckState.unchecked;
    }

    @Override
    public void setChecked ( final E node, final boolean checked )
    {
        // Collecting state changes
        final boolean collectChanges = checkStateChangeListeners.size () > 0;
        List> changes = null;
        if ( collectChanges )
        {
            changes = new ArrayList> ( 1 );
        }

        // Updating states
        final List toUpdate = new ArrayList ();
        setCheckedImpl ( node, checked, toUpdate, changes );
        repaintTreeNodes ( toUpdate );

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    /**
     * Sets whether the specified tree node is checked or not.
     *
     * @param node     tree node to process
     * @param checked  whether the specified tree node is checked or not
     * @param toUpdate list of nodes for later update
     * @param changes  list to collect state changes into
     */
    protected void setCheckedImpl ( final E node, final boolean checked, final List toUpdate, final List> changes )
    {
        // Remembering old and new states
        final CheckState oldState = getCheckState ( node );
        final CheckState newState = checked ? CheckState.checked : CheckState.unchecked;

        // Updating node if check state has actually changed
        if ( oldState != newState )
        {
            // Changing node check state
            updateNodeState ( node, newState, toUpdate );

            // Saving changes
            if ( changes != null )
            {
                changes.add ( new CheckStateChange ( node, oldState, newState ) );
            }

            // Updating parent and child node states
            if ( checkBoxTree.isRecursiveCheckingEnabled () )
            {
                updateChildNodesState ( node, newState, toUpdate, changes );
                updateParentStates ( node, toUpdate, changes );
            }
        }
    }

    /**
     * Updates parent nodes check states.
     *
     * @param node     node to start checking parents from
     * @param toUpdate list of nodes for later update
     * @param changes  list to collect state changes into
     */
    protected void updateParentStates ( final E node, final List toUpdate, final List> changes )
    {
        // Updating all parent node states
        E parent = ( E ) node.getParent ();
        while ( parent != null )
        {
            // Calculating parent state
            CheckState state = CheckState.unchecked;
            boolean hasChecked = false;
            boolean hasUnchecked = false;
            for ( int i = 0; i < parent.getChildCount (); i++ )
            {
                final CheckState checkState = getCheckState ( ( E ) parent.getChildAt ( i ) );
                if ( checkState == CheckState.mixed )
                {
                    state = CheckState.mixed;
                    break;
                }
                else if ( checkState == CheckState.checked )
                {
                    hasChecked = true;
                    if ( hasUnchecked )
                    {
                        state = CheckState.mixed;
                        break;
                    }
                    else
                    {
                        state = CheckState.checked;
                    }
                }
                else if ( checkState == CheckState.unchecked )
                {
                    hasUnchecked = true;
                    if ( hasChecked )
                    {
                        state = CheckState.mixed;
                        break;
                    }
                    else
                    {
                        state = CheckState.unchecked;
                    }
                }
            }

            final CheckState oldState = getCheckState ( parent );
            if ( oldState != state )
            {
                // Saving changes
                if ( changes != null )
                {
                    changes.add ( new CheckStateChange ( parent, oldState, state ) );
                }

                // Updating state
                updateNodeState ( parent, state, toUpdate );
            }

            // Moving upstairs
            parent = ( E ) parent.getParent ();
        }
    }

    /**
     * Updates child nodes check state.
     *
     * @param node     parent node
     * @param newState new check state
     * @param toUpdate list of nodes for later update
     * @param changes  list to collect state changes into
     */
    protected void updateChildNodesState ( final E node, final CheckState newState, final List toUpdate,
                                           final List> changes )
    {
        for ( int i = 0; i < node.getChildCount (); i++ )
        {
            final E childNode = ( E ) node.getChildAt ( i );

            // Saving changes
            if ( changes != null )
            {
                changes.add ( new CheckStateChange ( childNode, getCheckState ( childNode ), newState ) );
            }

            // Updating state
            updateNodeState ( childNode, newState, toUpdate );

            // Updating child nodes state
            updateChildNodesState ( childNode, newState, toUpdate, changes );
        }
    }

    /**
     * Updates single node check state.
     *
     * @param node     node to update
     * @param newState new check state
     * @param toUpdate list of nodes for later update
     */
    protected void updateNodeState ( final E node, final CheckState newState, final List toUpdate )
    {
        if ( newState != CheckState.unchecked )
        {
            nodeCheckStates.put ( node, newState );
        }
        else
        {
            nodeCheckStates.remove ( node );
        }
        toUpdate.add ( node );
    }

    @Override
    public void invertCheck ( final E node )
    {
        // Collecting state changes
        final boolean collectChanges = checkStateChangeListeners.size () > 0;
        List> changes = null;
        if ( collectChanges )
        {
            changes = new ArrayList> ( 1 );
        }

        // Updating states
        final List toUpdate = new ArrayList ();
        setCheckedImpl ( node, getNextState ( getCheckState ( node ) ) == CheckState.checked, toUpdate, changes );
        repaintTreeNodes ( toUpdate );

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    @Override
    public void invertCheck ( final Collection nodes )
    {
        // Collecting state changes
        final boolean collectChanges = checkStateChangeListeners.size () > 0;
        List> changes = null;
        if ( collectChanges )
        {
            changes = new ArrayList> ( nodes.size () );
        }

        // Updating states
        final List toUpdate = new ArrayList ();
        boolean check = false;
        for ( final E node : nodes )
        {
            if ( getCheckState ( node ) != CheckState.checked )
            {
                check = true;
                break;
            }
        }
        for ( final E node : nodes )
        {
            setCheckedImpl ( node, check, toUpdate, changes );
        }
        repaintTreeNodes ( toUpdate );

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    @Override
    public void uncheckAll ()
    {
        // Collecting state changes
        List> changes = null;
        if ( checkStateChangeListeners.size () > 0 )
        {
            changes = new ArrayList> ( nodeCheckStates.size () );
            for ( final Map.Entry entry : nodeCheckStates.entrySet () )
            {
                final CheckState state = entry.getValue ();
                if ( state == CheckState.mixed || state == CheckState.checked )
                {
                    changes.add ( new CheckStateChange ( entry.getKey (), state, CheckState.unchecked ) );
                }
            }
        }

        // Updating states
        nodeCheckStates.clear ();
        repaintVisibleTreeRect ();

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    @Override
    public void checkAll ()
    {
        final List allNodes = checkBoxTree.getAllNodes ();

        // Collecting state changes
        List> changes = null;
        if ( checkStateChangeListeners.size () > 0 )
        {
            changes = new ArrayList> ( allNodes.size () );
            for ( final E node : allNodes )
            {
                final CheckState state = getCheckState ( node );
                if ( state != CheckState.checked )
                {
                    changes.add ( new CheckStateChange ( node, state, CheckState.checked ) );
                }
            }
        }

        // Updating states
        for ( final E node : allNodes )
        {
            nodeCheckStates.put ( node, CheckState.checked );
        }
        repaintVisibleTreeRect ();

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    /**
     * Returns next check state for check invertion action.
     *
     * @param checkState current check state
     * @return next check state for check invertion action
     */
    protected CheckState getNextState ( final CheckState checkState )
    {
        switch ( checkState )
        {
            case unchecked:
                return CheckState.checked;
            case checked:
                return CheckState.unchecked;
            case mixed:
                return checkBoxTree.isCheckMixedOnToggle () ? CheckState.checked : CheckState.unchecked;
            default:
                return CheckState.unchecked;
        }
    }

    @Override
    public void checkingModeChanged ( final boolean recursive )
    {
        // Collecting state changes
        final boolean collectChanges = checkStateChangeListeners.size () > 0;
        List> changes = null;
        if ( collectChanges )
        {
            changes = new ArrayList> ();
        }

        // Updating states
        final List toUpdate = new ArrayList ();
        if ( recursive )
        {
            // Retrieving all checked nodes
            final List checked = new ArrayList ( nodeCheckStates.size () );
            for ( final Map.Entry entry : nodeCheckStates.entrySet () )
            {
                if ( entry.getValue () == CheckState.checked )
                {
                    checked.add ( entry.getKey () );
                }
            }

            // Filtering out child nodes under other checked nodes
            // We don't need to update them because they will get checked in the process
            filterOutChildNodes ( checked );

            // Updating node states
            for ( final E node : checked )
            {
                updateParentStates ( node, toUpdate, changes );
                updateChildNodesState ( node, CheckState.checked, toUpdate, changes );
            }
        }
        else
        {
            // Removing existing mixed states
            final Iterator> iterator = nodeCheckStates.entrySet ().iterator ();
            while ( iterator.hasNext () )
            {
                final Map.Entry entry = iterator.next ();
                if ( entry.getValue () == CheckState.mixed )
                {
                    // Updating node state
                    final E node = entry.getKey ();
                    toUpdate.add ( node );
                    iterator.remove ();

                    // Saving changes
                    if ( changes != null )
                    {
                        changes.add ( new CheckStateChange ( node, CheckState.mixed, CheckState.unchecked ) );
                    }
                }
            }
        }
        repaintTreeNodes ( toUpdate );

        // Informing about state changes
        fireCheckStateChanged ( changes );
    }

    /**
     * Filters out all nodes which are children of other nodes presented in the list.
     *
     * @param nodes list of nodes to filter
     */
    protected void filterOutChildNodes ( final List nodes )
    {
        final Iterator checkedIterator = nodes.iterator ();
        while ( checkedIterator.hasNext () )
        {
            final E node = checkedIterator.next ();
            for ( final E otherNode : nodes )
            {
                if ( isChildNode ( node, otherNode ) )
                {
                    checkedIterator.remove ();
                    break;
                }
            }
        }
    }

    /**
     * Returns whether the specified node is a child of another node or some of its child nodes or not.
     *
     * @param node    node to process
     * @param childOf node to compare parent nodes with
     * @return true if the specified node is a child of another node or some of its child nodes, false otherwise
     */
    protected boolean isChildNode ( final E node, final E childOf )
    {
        if ( node == childOf )
        {
            return false;
        }
        else if ( childOf == null )
        {
            return true;
        }
        else
        {
            TreeNode parent = node.getParent ();
            while ( parent != null )
            {
                if ( parent == childOf )
                {
                    return true;
                }
                parent = parent.getParent ();
            }
            return false;
        }
    }

    /**
     * Repaints visible tree rect.
     */
    protected void repaintVisibleTreeRect ()
    {
        checkBoxTree.repaint ( checkBoxTree.getVisibleRect () );
    }

    /**
     * Repaints specified tree nodes.
     *
     * @param nodes tree nodes to repaint
     */
    protected void repaintTreeNodes ( final List nodes )
    {
        checkBoxTree.repaint ( nodes );
    }

    @Override
    public void addCheckStateChangeListener ( final CheckStateChangeListener listener )
    {
        checkStateChangeListeners.add ( listener );
    }

    @Override
    public void removeCheckStateChangeListener ( final CheckStateChangeListener listener )
    {
        checkStateChangeListeners.remove ( listener );
    }

    /**
     * Informs about single or multiply check state changes.
     *
     * @param stateChanges check state changes list
     */
    public void fireCheckStateChanged ( final List> stateChanges )
    {
        if ( stateChanges != null )
        {
            for ( final CheckStateChangeListener listener : CollectionUtils.copy ( checkStateChangeListeners ) )
            {
                listener.checkStateChanged ( stateChanges );
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy