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

com.alee.extended.split.WebMultiSplitPane 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.split;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.data.Orientation;
import com.alee.extended.WebContainer;
import com.alee.managers.style.StyleId;
import com.alee.managers.style.StyleManager;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Split pane component that unlike {@link javax.swing.JSplitPane} can contain more than two views.
 * It is also more successful at adjusting sizes for existing views and handling weights and resize operations.
 *
 * This component should never be used with a non-Web UIs as it might cause an unexpected behavior.
 * You could still use that component even if WebLaF is not your application LaF as this component will use Web-UI in any case.
 *
 * @author Mikle Garin
 * @see How to use WebMultiSplitPane
 * @see MultiSplitPaneModel
 * @see MultiSplitPaneDescriptor
 * @see WMultiSplitPaneUI
 * @see WebMultiSplitPaneUI
 * @see IMultiSplitPanePainter
 * @see MultiSplitPanePainter
 * @see WebContainer
 */
public class WebMultiSplitPane extends WebContainer
{
    /**
     * Component properties.
     */
    public static final String ORIENTATION_PROPERTY = "orientation";
    public static final String CONTINUOUS_LAYOUT_PROPERTY = "continuousLayout";
    public static final String DIVIDER_SIZE_PROPERTY = "dividerSize";
    public static final String ONE_TOUCH_EXPANDABLE_PROPERTY = "oneTouchExpandable";
    public static final String MODEL_PROPERTY = "model";

    /**
     * Split {@link Orientation}.
     * For {@link Orientation#horizontal} all components are placed in one horizontal row.
     * For {@link Orientation#vertical} all components are placed in one vertical column.
     */
    protected Orientation orientation;

    /**
     * Whether or not split pane layout should be continuously updated upon dividers drag.
     * Changing it to {@code false} might improve overall performance and visual feedback in complex UIs.
     */
    protected boolean continuousLayout;

    /**
     * Divider size in pixels.
     * This size forces {@link IMultiSplitPaneDividerPainter} implementation to paint within set bounds.
     */
    protected int dividerSize;

    /**
     * Whether or not split pane should display one-touch-expand buttons on dividers.
     */
    protected boolean oneTouchExpandable;

    /**
     * {@link MultiSplitPaneModel} implementation.
     */
    @Nullable
    protected MultiSplitPaneModel model;

    /**
     * Constructs new {@link WebMultiSplitPane}.
     */
    public WebMultiSplitPane ()
    {
        this ( StyleId.auto, Orientation.horizontal );
    }

    /**
     * Constructs new {@link WebMultiSplitPane}.
     *
     * @param orientation split {@link Orientation}
     */
    public WebMultiSplitPane ( @Nullable final Orientation orientation )
    {
        this ( StyleId.auto, orientation );
    }

    /**
     * Constructs new {@link WebMultiSplitPane}.
     *
     * @param id {@link StyleId}
     */
    public WebMultiSplitPane ( @NotNull final StyleId id )
    {
        this ( id, Orientation.horizontal );
    }

    /**
     * Constructs new {@link WebMultiSplitPane}.
     *
     * @param id          {@link StyleId}
     * @param orientation split {@link Orientation}
     */
    public WebMultiSplitPane ( @NotNull final StyleId id, @Nullable final Orientation orientation )
    {
        super ();
        setOrientation ( orientation );
        setContinuousLayout ( true );
        setDividerSize ( 11 );
        setOneTouchExpandable ( false );
        setModel ( createModel () );
        updateUI ();
        setStyleId ( id );
    }

    @NotNull
    @Override
    public StyleId getDefaultStyleId ()
    {
        return StyleId.multisplitpane;
    }

    /**
     * Returns split {@link Orientation}.
     *
     * @return split {@link Orientation}
     */
    @NotNull
    public Orientation getOrientation ()
    {
        return orientation != null ? orientation : Orientation.horizontal;
    }

    /**
     * Sets split {@link Orientation}.
     *
     * @param orientation new split {@link Orientation}
     * @return this {@link WebMultiSplitPane}
     */
    @NotNull
    public WebMultiSplitPane setOrientation ( @Nullable final Orientation orientation )
    {
        if ( this.orientation != orientation )
        {
            final Orientation old = this.orientation;
            this.orientation = orientation;
            firePropertyChange ( ORIENTATION_PROPERTY, old, orientation );
        }
        return this;
    }

    /**
     * Returns whether or not split pane layout should be continuously updated on resize operations.
     *
     * @return {@code true} if split pane layout should be continuously updated on resize operations, {@code false} otherwise
     */
    public boolean isContinuousLayout ()
    {
        return continuousLayout;
    }

    /**
     * Sets whether or not split pane layout should be continuously updated on resize operations.
     *
     * @param continuousLayout whether or not split pane layout should be continuously updated on resize operations
     * @return this {@link WebMultiSplitPane}
     */
    @NotNull
    public WebMultiSplitPane setContinuousLayout ( final boolean continuousLayout )
    {
        if ( this.continuousLayout != continuousLayout )
        {
            final boolean old = this.continuousLayout;
            this.continuousLayout = continuousLayout;
            firePropertyChange ( CONTINUOUS_LAYOUT_PROPERTY, old, continuousLayout );
        }
        return this;
    }

    /**
     * Returns divider size in pixels.
     *
     * @return divider size in pixels
     */
    public int getDividerSize ()
    {
        return dividerSize;
    }

    /**
     * Sets divider size in pixels.
     *
     * @param size divider size in pixels
     * @return this {@link WebMultiSplitPane}
     */
    @NotNull
    public WebMultiSplitPane setDividerSize ( final int size )
    {
        if ( this.dividerSize != size )
        {
            final int old = this.dividerSize;
            this.dividerSize = size;
            firePropertyChange ( DIVIDER_SIZE_PROPERTY, old, size );
        }
        return this;
    }

    /**
     * Returns whether or not split pane should display one-touch-expand buttons on dividers.
     *
     * @return whether or not split pane should display one-touch-expand buttons on dividers
     */
    public boolean isOneTouchExpandable ()
    {
        return oneTouchExpandable;
    }

    /**
     * Sets whether or not split pane should display one-touch-expand buttons on dividers.
     *
     * @param oneTouchExpandable whether or not split pane should display one-touch-expand buttons on dividers
     * @return this {@link WebMultiSplitPane}
     */
    @NotNull
    public WebMultiSplitPane setOneTouchExpandable ( final boolean oneTouchExpandable )
    {
        if ( this.oneTouchExpandable != oneTouchExpandable )
        {
            final boolean old = this.oneTouchExpandable;
            this.oneTouchExpandable = oneTouchExpandable;
            firePropertyChange ( ONE_TOUCH_EXPANDABLE_PROPERTY, old, oneTouchExpandable );
        }
        return this;
    }

    /**
     * Returns {@link MultiSplitPaneModel} implementation.
     *
     * @return {@link MultiSplitPaneModel} implementation
     */
    @Nullable
    public MultiSplitPaneModel getModel ()
    {
        return model;
    }

    /**
     * Returns default {@link MultiSplitPaneModel} implementation to be used.
     *
     * @return default {@link MultiSplitPaneModel} implementation to be used
     */
    @NotNull
    protected MultiSplitPaneModel createModel ()
    {
        return new WebMultiSplitPaneModel ();
    }

    /**
     * Sets {@link MultiSplitPaneModel} implementation.
     *
     * @param model new {@link MultiSplitPaneModel} implementation
     * @return this {@link WebMultiSplitPane}
     */
    @NotNull
    public WebMultiSplitPane setModel ( @Nullable final MultiSplitPaneModel model )
    {
        if ( this.model != model )
        {
            final MultiSplitPaneModel old = this.model;

            // Copying old model data over to new one
            final List views = old != null ? old.getViews () : null;
            final List dividers = old != null ? old.getDividers () : null;

            // Uninstalling previous model
            if ( this.model != null )
            {
                this.model.uninstall ( this );
            }

            // Updating layout manager
            this.model = model;
            setLayout ( model );

            // Installing new model
            if ( model != null )
            {
                model.install ( this, views, dividers );
            }

            firePropertyChange ( MODEL_PROPERTY, old, model );
        }
        return this;
    }

    @Override
    public void setLayout ( @Nullable final LayoutManager layout )
    {
        if ( layout == null || layout instanceof MultiSplitPaneModel )
        {
            super.setLayout ( layout );
        }
        else
        {
            throw new IllegalArgumentException ( "Only MultiSplitPaneModel instances are supported" );
        }
    }

    @Nullable
    @Override
    public MultiSplitPaneModel getLayout ()
    {
        return ( MultiSplitPaneModel ) super.getLayout ();
    }

    /**
     * Returns amount of view {@link Component}s.
     *
     * @return amount of view {@link Component}s
     */
    public int getViewCount ()
    {
        return getModel () != null ? getModel ().getViewCount () : 0;
    }

    /**
     * Returns copy of the {@link List} of all {@link MultiSplitView}s contained in {@link MultiSplitPaneModel}.
     *
     * @return copy of the {@link List} of all {@link MultiSplitView}s contained in {@link MultiSplitPaneModel}
     */
    @Nullable
    public List getViews ()
    {
        return getModel () != null ? getModel ().getViews () : null;
    }

    /**
     * Returns index of the {@link MultiSplitView} that contains specified {@link Component}.
     *
     * @param component {@link Component} contained in {@link MultiSplitView}
     * @return index of the {@link MultiSplitView} that contains specified {@link Component}
     */
    public int getViewIndex ( @NotNull final Component component )
    {
        return getModel () != null ? getModel ().getViewIndex ( component ) : -1;
    }

    /**
     * Returns {@link Component} from {@link MultiSplitView} at the specified index.
     *
     * @param index {@link MultiSplitView} index
     * @return {@link Component} from {@link MultiSplitView} at the specified index
     */
    @Nullable
    public Component getViewComponent ( final int index )
    {
        return getModel () != null ? getModel ().getViewComponent ( index ) : null;
    }

    /**
     * Returns {@link List} of view {@link Component}s.
     *
     * @return {@link List} of view {@link Component}s
     */
    @NotNull
    public List getViewComponents ()
    {
        return getModel () != null ? getModel ().getViewComponents () : new ArrayList ();
    }

    /**
     * Returns copy of the {@link List} of all {@link WebMultiSplitPaneDivider}s used by {@link WebMultiSplitPane}.
     *
     * @return copy of the {@link List} of all {@link WebMultiSplitPaneDivider}s used by {@link WebMultiSplitPane}
     */
    @NotNull
    public List getDividers ()
    {
        return getModel () != null ? getModel ().getDividers () : new ArrayList ();
    }

    /**
     * Returns default {@link WebMultiSplitPaneDivider} to be used.
     *
     * @return default {@link WebMultiSplitPaneDivider} to be used
     */
    @NotNull
    protected WebMultiSplitPaneDivider createDivider ()
    {
        final StyleId styleId = StyleId.multisplitpaneContinuousDivider.at ( this );
        return new WebMultiSplitPaneDivider ( styleId, this );
    }

    /**
     * Returns {@link MultiSplitState} describing current {@link WebMultiSplitPane} state.
     *
     * @return {@link MultiSplitState} describing current {@link WebMultiSplitPane} state
     */
    @Nullable
    public MultiSplitState getMultiSplitState ()
    {
        return getModel () != null ? getModel ().getMultiSplitState () : null;
    }

    /**
     * Updates {@link WebMultiSplitPane} using specified {@link MultiSplitState}.
     *
     * @param state {@link MultiSplitState} to update {@link WebMultiSplitPane} with
     */
    public void setMultiSplitState ( @NotNull final MultiSplitState state )
    {
        if ( getModel () != null )
        {
            getModel ().setMultiSplitState ( state );
        }
    }

    /**
     * Resets {@link MultiSplitView} sizes to ones specified by {@link MultiSplitConstraints}.
     * Such reset always takes place at layout initialization to assign initial sizes but normally isn't required later on.
     * It can be used to reset all {@link MultiSplitView} sizes to initial ones if they were added after layout initialization.
     */
    public void resetViewSizes ()
    {
        if ( getModel () != null )
        {
            getModel ().resetViewStates ();
        }
    }

    /**
     * Returns whether or not any view is currently expanded in {@link WebMultiSplitPane}.
     *
     * @return {@code true} if any view is currently expanded in {@link WebMultiSplitPane}, {@code false} otherwise
     */
    public boolean isAnyViewExpanded ()
    {
        return getModel () != null && getModel ().isAnyViewExpanded ();
    }

    /**
     * Returns index of currently expanded view in {@link WebMultiSplitPane}.
     * Might return {@code -1} if there are currently no expanded view.
     *
     * @return index of currently expanded view in {@link WebMultiSplitPane}
     */
    public int getExpandedViewIndex ()
    {
        return getModel () != null ? getModel ().getExpandedViewIndex () : -1;
    }

    /**
     * Expands view positioned in {@link WebMultiSplitPane} at the specified index.
     *
     * @param index view index
     */
    public void expandView ( final int index )
    {
        if ( getModel () != null )
        {
            getModel ().expandView ( index );
        }
    }

    /**
     * Collapses expanded view if any view is currently expanded in {@link WebMultiSplitPane}.
     */
    public void collapseExpandedView ()
    {
        if ( getModel () != null )
        {
            getModel ().collapseExpandedView ();
        }
    }

    /**
     * Toggles expansion state for the view positioned in {@link WebMultiSplitPane} at the specified index.
     *
     * @param index view index
     */
    public void toggleViewExpansion ( final int index )
    {
        if ( getModel () != null )
        {
            getModel ().toggleViewExpansion ( index );
        }
    }

    @Override
    public Component add ( @NotNull final Component component )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        addImpl ( component, null, getViewCount () );
        return component;
    }

    @Override
    public Component add ( @Nullable final String constraints, @NotNull final Component component )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        addImpl ( component, constraints, getViewCount () );
        return component;
    }

    @Override
    public Component add ( @NotNull final Component component, final int index )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        if ( index > getViewCount () )
        {
            throw new IndexOutOfBoundsException ( "Illegal view component index" );
        }
        addImpl ( component, null, index );
        return component;
    }

    @Override
    public void add ( @NotNull final Component component, @Nullable final Object constraints )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        addImpl ( component, constraints, getViewCount () );
    }

    @Override
    public void add ( @NotNull final Component component, @Nullable final Object constraints, final int index )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        if ( index > getViewCount () )
        {
            throw new IndexOutOfBoundsException ( "Illegal view component index" );
        }
        addImpl ( component, constraints, index );
    }

    /**
     * Overriden for accessibility.
     */
    @Override
    protected void addImpl ( @NotNull final Component component, @Nullable final Object constraints, final int index )
    {
        super.addImpl ( component, constraints, index );
    }

    @Override
    public void setComponentZOrder ( @NotNull final Component component, final int index )
    {
        if ( component instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers order cannot be modified explicitely" );
        }
        if ( index > getViewCount () )
        {
            throw new IndexOutOfBoundsException ( "Illegal view component index" );
        }
        setComponentZOrderImpl ( component, index );
        if ( getModel () != null )
        {
            getModel ().moveComponent ( component, index );
        }
    }

    /**
     * Special method for internal usage within {@link MultiSplitPaneModel}.
     * Unlike {@link #setComponentZOrder(Component, int)} method it allows modifying {@link WebMultiSplitPaneDivider} z-order.
     *
     * @param component {@link Component} to change z-order for
     * @param index     z-order index
     */
    protected void setComponentZOrderImpl ( @NotNull final Component component, final int index )
    {
        super.setComponentZOrder ( component, index );
    }

    @Override
    public void remove ( final int index )
    {
        if ( index < 0 || getViewCount () <= index )
        {
            throw new IndexOutOfBoundsException ( "There is no view component at specified index" );
        }
        if ( getComponent ( index ) instanceof WebMultiSplitPaneDivider )
        {
            throw new IllegalArgumentException ( "Dividers cannot be added explicitely" );
        }
        removeImpl ( index );
    }

    /**
     * Special method for internal usage within {@link MultiSplitPaneModel}.
     * Unlike {@link #remove(int)} method it allows removing {@link WebMultiSplitPaneDivider}s.
     *
     * @param index index of child {@link Component} to remove
     */
    protected void removeImpl ( final int index )
    {
        super.remove ( index );
    }

    @Override
    public void removeAll ()
    {
        // Removing view components one by one from the end
        // This will ensure that everything goes smooth and clean
        for ( int index = getViewCount () - 1; index >= 0; index-- )
        {
            remove ( index );
        }
    }

    /**
     * Adds specified {@link MultiSplitExpansionListener} to this {@link WebMultiSplitPane}.
     *
     * @param listener {@link MultiSplitExpansionListener} to add
     */
    public void addResizeListener ( @NotNull final MultiSplitResizeListener listener )
    {
        listenerList.add ( MultiSplitResizeListener.class, listener );
    }

    /**
     * Removes specified {@link MultiSplitExpansionListener} from this {@link WebMultiSplitPane}.
     *
     * @param listener {@link MultiSplitExpansionListener} to remove
     */
    public void removeResizeListener ( @NotNull final MultiSplitResizeListener listener )
    {
        listenerList.remove ( MultiSplitResizeListener.class, listener );
    }

    /**
     * Informs about view resize being started due to the specified {@link WebMultiSplitPaneDivider} being pressed.
     *
     * @param divider {@link WebMultiSplitPaneDivider} that is being pressed
     */
    public void fireViewResizeStarted ( @NotNull final WebMultiSplitPaneDivider divider )
    {
        for ( final MultiSplitResizeListener listener : listenerList.getListeners ( MultiSplitResizeListener.class ) )
        {
            listener.viewResizeStarted ( this, divider );
        }
    }

    /**
     * Informs about occurred view resize due to the specified {@link WebMultiSplitPaneDivider} being dragged.
     *
     * @param divider {@link WebMultiSplitPaneDivider} that is being dragged
     */
    public void fireViewResized ( @NotNull final WebMultiSplitPaneDivider divider )
    {
        for ( final MultiSplitResizeListener listener : listenerList.getListeners ( MultiSplitResizeListener.class ) )
        {
            listener.viewResized ( this, divider );
        }
    }

    /**
     * Informs about view resize being finished due to the specified {@link WebMultiSplitPaneDivider} being released.
     *
     * @param divider {@link WebMultiSplitPaneDivider} that is being released
     */
    public void fireViewResizeEnded ( @NotNull final WebMultiSplitPaneDivider divider )
    {
        for ( final MultiSplitResizeListener listener : listenerList.getListeners ( MultiSplitResizeListener.class ) )
        {
            listener.viewResizeEnded ( this, divider );
        }
    }

    /**
     * Informs about view size adjustments occurred due to {@link WebMultiSplitPane} size or settings changes.
     */
    public void fireViewSizeAdjusted ()
    {
        for ( final MultiSplitResizeListener listener : listenerList.getListeners ( MultiSplitResizeListener.class ) )
        {
            listener.viewSizeAdjusted ( this );
        }
    }

    /**
     * Adds specified {@link MultiSplitExpansionListener} to this {@link WebMultiSplitPane}.
     *
     * @param listener {@link MultiSplitExpansionListener} to add
     */
    public void addExpansionListener ( @NotNull final MultiSplitExpansionListener listener )
    {
        listenerList.add ( MultiSplitExpansionListener.class, listener );
    }

    /**
     * Removes specified {@link MultiSplitExpansionListener} from this {@link WebMultiSplitPane}.
     *
     * @param listener {@link MultiSplitExpansionListener} to remove
     */
    public void removeExpansionListener ( @NotNull final MultiSplitExpansionListener listener )
    {
        listenerList.remove ( MultiSplitExpansionListener.class, listener );
    }

    /**
     * Informs all listeners about {@link Component} becoming an expanded view within this {@link WebMultiSplitPane}.
     *
     * @param view expanded view {@link Component}
     */
    public void fireViewExpanded ( @NotNull final Component view )
    {
        for ( final MultiSplitExpansionListener listener : listenerList.getListeners ( MultiSplitExpansionListener.class ) )
        {
            listener.viewExpanded ( this, view );
        }
    }

    /**
     * Informs all listeners about previously expanded {@link Component} view becoming collapsed within this {@link WebMultiSplitPane}.
     *
     * @param view collapsed view {@link Component}
     */
    public void fireViewCollapsed ( @NotNull final Component view )
    {
        for ( final MultiSplitExpansionListener listener : listenerList.getListeners ( MultiSplitExpansionListener.class ) )
        {
            listener.viewCollapsed ( this, view );
        }
    }

    /**
     * Returns the look and feel (LaF) object that renders this component.
     *
     * @return the {@link WMultiSplitPaneUI} object that renders this component
     */
    @Nullable
    public WMultiSplitPaneUI getUI ()
    {
        return ( WMultiSplitPaneUI ) ui;
    }

    /**
     * Sets the LaF object that renders this component.
     *
     * @param ui {@link WMultiSplitPaneUI}
     */
    public void setUI ( @Nullable final WMultiSplitPaneUI ui )
    {
        super.setUI ( ui );
    }

    @Override
    public void updateUI ()
    {
        StyleManager.getDescriptor ( this ).updateUI ( this );
    }

    @NotNull
    @Override
    public String getUIClassID ()
    {
        return StyleManager.getDescriptor ( this ).getUIClassId ();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy