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

com.alee.extended.accordion.WebAccordionModel Maven / Gradle / Ivy

There is a newer version: 1.2.14
Show newest version
/*
 * 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.accordion;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.jdk.Objects;
import com.alee.utils.CollectionUtils;
import com.alee.utils.SwingUtils;

import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;

/**
 * Default {@link AccordionModel} implementation.
 *
 * @author Mikle Garin
 * @see How to use WebAccordion
 * @see WebAccordion
 * @see AccordionPane
 * @see AccordionModel
 * @see WebAccordionModel
 */
public class WebAccordionModel implements AccordionModel, PropertyChangeListener, ContainerListener
{
    /**
     * {@link WebAccordion} into which this {@link WebAccordionModel} is currently installed.
     */
    @Nullable
    protected WebAccordion accordion;

    /**
     * {@link Map} of all {@link AccordionPaneState}s available in this {@link WebAccordionModel}.
     * It can also contain {@link AccordionPaneState} for panes that do not yet exist in {@link WebAccordion}.
     */
    @NotNull
    protected Map states;

    /**
     * {@link Comparator} for {@link AccordionPane}s for descending sorting by state change times.
     */
    @NotNull
    protected Comparator newToOldPaneComparator;

    /**
     * Constructs new {@link WebAccordionModel}.
     */
    public WebAccordionModel ()
    {
        this.states = new HashMap ();
        this.newToOldPaneComparator = new Comparator ()
        {
            @Override
            public int compare ( final AccordionPane pane1, final AccordionPane pane2 )
            {
                return new Long ( getPaneState ( pane2.getId () ).getTime () )
                        .compareTo ( getPaneState ( pane1.getId () ).getTime () );
            }
        };
    }

    @Override
    public void install ( @NotNull final WebAccordion accordion )
    {
        if ( this.accordion == null )
        {
            this.accordion = accordion;
            this.accordion.addPropertyChangeListener ( this );
            this.accordion.addContainerListener ( this );

            // Validating states
            validateStates ();
        }
        else
        {
            throw new RuntimeException ( "WebAccordionModel is already installed" );
        }
    }

    @Override
    public void uninstall ( @NotNull final WebAccordion multiSplitPane )
    {
        if ( accordion != null )
        {
            this.accordion.removeContainerListener ( this );
            this.accordion.removePropertyChangeListener ( this );
            this.accordion = null;
        }
        else
        {
            throw new RuntimeException ( "WebAccordionModel is not yet installed" );
        }
    }

    /**
     * Returns non-{@code null} {@link WebAccordion}.
     *
     * @return non-{@code null} {@link WebAccordion}
     * @throws RuntimeException if this model is not yet installed and {@link WebAccordion} is unavailable
     */
    @NotNull
    protected WebAccordion getAccordion ()
    {
        if ( accordion != null )
        {
            return accordion;
        }
        else
        {
            throw new RuntimeException ( "WebAccordionModel is not yet installed" );
        }
    }

    @NotNull
    @Override
    public AccordionState getAccordionState ()
    {
        final Map states;
        if ( accordion != null )
        {
            // Make sure we filter out states for unavailable panes
            states = new HashMap ();
            for ( final Map.Entry state : this.states.entrySet () )
            {
                if ( accordion.containsPane ( state.getKey () ) )
                {
                    states.put ( state.getKey (), state.getValue () );
                }
            }
        }
        else
        {
            // Returning states "as is" since we can't filter them
            states = this.states;
        }
        return new AccordionState ( states );
    }

    @Override
    public void setAccordionState ( @NotNull final AccordionState accordionState )
    {
        // Updating states
        states = accordionState.states ();

        // Validating states
        validateStates ();
    }

    @Override
    public void propertyChange ( final PropertyChangeEvent e )
    {
        final String property = e.getPropertyName ();
        if ( Objects.equals ( property,
                WebAccordion.MINIMUM_EXPANDED_PANE_COUNT_PROPERTY,
                WebAccordion.MAXIMUM_EXPANDED_PANE_COUNT_PROPERTY ) )
        {
            // Validating states
            validateStates ();
        }
    }

    @Override
    public void componentAdded ( final ContainerEvent e )
    {
        // Adding state for newly added pane
        final AccordionPane pane = ( AccordionPane ) e.getChild ();
        if ( !states.containsKey ( pane.getId () ) )
        {
            // Only adding new state if it didn't exist since it might have been loaded
            states.put ( pane.getId (), new AccordionPaneState ( pane.getId (), false ) );
        }

        // Validating states
        validateStates ();

        // Inform layout about addition
        if ( accordion != null )
        {
            final AccordionLayout layout = accordion.getLayout ();
            if ( layout != null )
            {
                layout.paneAdded ( accordion, pane );
            }
        }
    }

    @Override
    public void componentRemoved ( final ContainerEvent e )
    {
        // Removing state for the removed pane
        final AccordionPane pane = ( AccordionPane ) e.getChild ();
        states.remove ( pane.getId () );

        // Validating states
        validateStates ();

        // Inform layout about removal
        if ( accordion != null )
        {
            final AccordionLayout layout = accordion.getLayout ();
            if ( layout != null )
            {
                layout.paneRemoved ( accordion, pane );
            }
        }
    }

    /**
     * Validating that our states comply with {@link WebAccordion} conditions.
     * If they don't - we will automatically adjust states to fix issues.
     */
    protected void validateStates ()
    {
        if ( accordion != null )
        {
            // Ensure no states are missing
            // This might be the case when states are loaded from settings
            // Or if some major changes have happened in accordion structure
            // Note that we do not remove states for non-existing panes as they might be added later
            for ( final AccordionPane pane : accordion.getPanes () )
            {
                if ( !states.containsKey ( pane.getId () ) )
                {
                    states.put ( pane.getId (), new AccordionPaneState ( pane.getId (), false ) );
                }
            }

            // Ensure we are meeting minimum/maximum expanded pane counts as much as possible
            final List expanded = getExpandedPanes ();
            final List collapsed = getCollapsedPanes ();
            if ( expanded.size () < accordion.getMinimumExpandedPaneCount () )
            {
                // Sorting from newest to oldest
                CollectionUtils.sort ( collapsed, newToOldPaneComparator );

                // Expanding collapsed panes
                final int toExpand = Math.min ( collapsed.size (), accordion.getMinimumExpandedPaneCount () - expanded.size () );
                for ( int i = 0; i < toExpand; i++ )
                {
                    expandPane ( collapsed.get ( i ).getId () );
                }
            }
            else if ( expanded.size () > accordion.getMaximumExpandedPaneCount () )
            {
                // Sorting from newest to oldest
                CollectionUtils.sort ( expanded, newToOldPaneComparator );

                // Collapsing excessive expanded panes
                final int toCollapse = expanded.size () - accordion.getMaximumExpandedPaneCount ();
                for ( int i = 0; i < toCollapse; i++ )
                {
                    collapsePane ( expanded.get ( i ).getId () );
                }
            }
        }
    }

    /**
     * Returns {@link AccordionPaneState} for the {@link AccordionPane} with the specified identifier.
     *
     * @param id {@link AccordionPane} identifier
     * @return {@link AccordionPaneState} for the {@link AccordionPane} with the specified identifier
     */
    @NotNull
    protected AccordionPaneState getPaneState ( @NotNull final String id )
    {
        if ( states.containsKey ( id ) )
        {
            return states.get ( id );
        }
        else
        {
            throw new RuntimeException ( "Cannot find AccordionPaneState for identifier: " + id );
        }
    }

    @Nullable
    @Override
    public AccordionPane getFirstExpandedPane ()
    {
        AccordionPane firstExpanded = null;
        final WebAccordion accordion = getAccordion ();
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneExpanded ( pane.getId () ) )
            {
                firstExpanded = pane;
                break;
            }
        }
        return firstExpanded;
    }

    @Nullable
    @Override
    public AccordionPane getLastExpandedPane ()
    {
        AccordionPane lastExpanded = null;
        final WebAccordion accordion = getAccordion ();
        for ( int i = accordion.getPaneCount () - 1; i >= 0; i-- )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneExpanded ( pane.getId () ) )
            {
                lastExpanded = pane;
                break;
            }
        }
        return lastExpanded;
    }

    @NotNull
    @Override
    public List getExpandedPanes ()
    {
        final WebAccordion accordion = getAccordion ();
        final List expandedPanes = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneExpanded ( pane.getId () ) )
            {
                expandedPanes.add ( pane );
            }
        }
        return expandedPanes;
    }

    @NotNull
    @Override
    public List getExpandedPaneIds ()
    {
        final WebAccordion accordion = getAccordion ();
        final List expandedIds = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneExpanded ( pane.getId () ) )
            {
                expandedIds.add ( pane.getId () );
            }
        }
        return expandedIds;
    }

    @Override
    public void setExpandedPaneIds ( @NotNull final List ids )
    {
        final WebAccordion accordion = getAccordion ();
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( ids.contains ( pane.getId () ) )
            {
                if ( isPaneCollapsed ( pane.getId () ) )
                {
                    expandUnconditionally ( pane.getId () );
                }
            }
            else
            {
                if ( isPaneExpanded ( pane.getId () ) )
                {
                    collapseUnconditionally ( pane.getId () );
                }
            }
        }
        validateStates ();
    }

    @NotNull
    @Override
    public int[] getExpandedPaneIndices ()
    {
        final WebAccordion accordion = getAccordion ();
        final List expandedIndices = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneExpanded ( pane.getId () ) )
            {
                expandedIndices.add ( i );
            }
        }
        return CollectionUtils.toIntArray ( expandedIndices );
    }

    @Override
    public void setExpandedPaneIndices ( @NotNull final int[] indices )
    {
        final WebAccordion accordion = getAccordion ();
        final List expanded = CollectionUtils.asList ( indices );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( expanded.contains ( i ) )
            {
                if ( isPaneCollapsed ( pane.getId () ) )
                {
                    expandUnconditionally ( pane.getId () );
                }
            }
            else
            {
                if ( isPaneExpanded ( pane.getId () ) )
                {
                    collapseUnconditionally ( pane.getId () );
                }
            }
        }
        validateStates ();
    }

    @Override
    public boolean isPaneExpanded ( @NotNull final String id )
    {
        return getPaneState ( id ).isExpanded ();
    }

    @Override
    public boolean expandPane ( @NotNull final String id )
    {
        final boolean wasExpanded;
        if ( isPaneCollapsed ( id ) )
        {
            /**
             * Whenever we expand above the maximum allowed expanded panes we will collapse the most recently expanded one.
             * Age is determined by pane expansion times which are saved within {@link AccordionPaneState} object.
             */
            final WebAccordion accordion = getAccordion ();
            final List expanded = getExpandedPanes ();
            if ( expanded.size () < accordion.getMaximumExpandedPaneCount () )
            {
                // Simply expanding pane
                expandUnconditionally ( id );
                wasExpanded = true;
            }
            else if ( expanded.size () > 0 )
            {
                // Sorting from newest to oldest
                CollectionUtils.sort ( expanded, newToOldPaneComparator );

                // Collapsing previously expanded pane
                collapseUnconditionally ( expanded.get ( 0 ).getId () );
                expandUnconditionally ( id );
                wasExpanded = true;
            }
            else
            {
                // We do not have enough panes to collapse to meet maximum expanded panes count
                wasExpanded = false;
            }
        }
        else
        {
            wasExpanded = false;
        }
        return wasExpanded;
    }

    /**
     * Expands {@link AccordionPane} with the specified identifier without any additional checks.
     * This method is intended for internal use only to avoid issues with overlapping minimum/maximum conditions.
     *
     * @param id {@link AccordionPane} identifier
     */
    protected void expandUnconditionally ( @NotNull final String id )
    {
        getPaneState ( id ).setExpanded ( true );

        final WebAccordion accordion = getAccordion ();
        final AccordionPane pane = accordion.getPane ( id );

        final AccordionLayout layout = accordion.getLayout ();
        if ( layout != null )
        {
            layout.expandPane ( accordion, id );
        }
        else
        {
            pane.fireExpanding ( accordion );
            accordion.fireExpanding ( pane );
            SwingUtils.update ( accordion );
            pane.fireExpanded ( accordion );
            accordion.fireExpanded ( pane );
        }
    }

    @NotNull
    @Override
    public List getCollapsedPanes ()
    {
        final WebAccordion accordion = getAccordion ();
        final List collapsedPanes = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneCollapsed ( pane.getId () ) )
            {
                collapsedPanes.add ( pane );
            }
        }
        return collapsedPanes;
    }

    @NotNull
    @Override
    public List getCollapsedPaneIds ()
    {
        final WebAccordion accordion = getAccordion ();
        final List collapsedIds = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneCollapsed ( pane.getId () ) )
            {
                collapsedIds.add ( pane.getId () );
            }
        }
        return collapsedIds;
    }

    @NotNull
    @Override
    public int[] getCollapsedPaneIndices ()
    {
        final WebAccordion accordion = getAccordion ();
        final List collapsedIndices = new ArrayList ( accordion.getPaneCount () );
        for ( int i = 0; i < accordion.getPaneCount (); i++ )
        {
            final AccordionPane pane = accordion.getPane ( i );
            if ( isPaneCollapsed ( pane.getId () ) )
            {
                collapsedIndices.add ( i );
            }
        }
        return CollectionUtils.toIntArray ( collapsedIndices );
    }

    @Override
    public boolean isPaneCollapsed ( @NotNull final String id )
    {
        return !isPaneExpanded ( id );
    }

    @Override
    public boolean collapsePane ( @NotNull final String id )
    {
        final boolean wasCollapsed;
        if ( isPaneExpanded ( id ) )
        {
            /**
             * Whenever we collapse below the minimum allowed expanded panes we will expand the most recently collapsed one.
             * Age is determined by pane expansion times which are saved within {@link AccordionPaneState} object.
             */
            final WebAccordion accordion = getAccordion ();
            final List expanded = getExpandedPanes ();
            final List collapsed = getCollapsedPanes ();
            if ( expanded.size () > accordion.getMinimumExpandedPaneCount () )
            {
                // Simply collapsing pane
                collapseUnconditionally ( id );
                wasCollapsed = true;
            }
            else if ( collapsed.size () > 0 )
            {
                // Sorting from newest to oldest
                CollectionUtils.sort ( collapsed, newToOldPaneComparator );

                // Expanding previously collapsed pane before we collapse new one
                expandUnconditionally ( collapsed.get ( 0 ).getId () );
                collapseUnconditionally ( id );
                wasCollapsed = true;
            }
            else
            {
                // We don't have enough panes to expand to meet minimum expanded panes count
                wasCollapsed = false;
            }
        }
        else
        {
            wasCollapsed = false;
        }
        return wasCollapsed;
    }

    /**
     * Collapses {@link AccordionPane} with the specified identifier without any additional checks.
     * This method is intended for internal use only to avoid issues with overlapping minimum/maximum conditions.
     *
     * @param id {@link AccordionPane} identifier
     */
    protected void collapseUnconditionally ( @NotNull final String id )
    {
        getPaneState ( id ).setExpanded ( false );

        final WebAccordion accordion = getAccordion ();
        final AccordionPane pane = accordion.getPane ( id );

        final AccordionLayout layout = accordion.getLayout ();
        if ( layout != null )
        {
            layout.collapsePane ( accordion, id );
        }
        else
        {
            pane.fireCollapsing ( accordion );
            accordion.fireCollapsing ( pane );
            SwingUtils.update ( accordion );
            pane.fireCollapsed ( accordion );
            accordion.fireCollapsed ( pane );
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy