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

com.alee.extended.dock.WebDockablePane 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.dock;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.data.CompassDirection;
import com.alee.api.jdk.Objects;
import com.alee.extended.WebContainer;
import com.alee.extended.dock.data.DockableContainer;
import com.alee.managers.style.StyleId;
import com.alee.managers.style.StyleManager;
import com.alee.utils.CollectionUtils;

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

/**
 * Implementation of a pane containing {@link WebDockableFrame}s.
 * {@link WebDockableFrame}s can be added to the pane, repositioned within it, minimized, previewed, made floating or closed.
 * Note that closing the frame doesn't remove it from the pane, but only changes it's state to {@link DockableFrameState#closed}.
 * Positioning of the frames is handled by the currently installed {@link DockablePaneModel} implementation.
 * Resize of the {@link WebDockableFrame}s and their drop position while dragged is handled by the {@link DockablePaneGlassLayer}.
 *
 * 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 WebDockablePane
 * @see WebDockablePaneModel
 * @see DockablePaneModel
 * @see DockablePaneGlassLayer
 * @see DockablePaneDescriptor
 * @see WDockablePaneUI
 * @see WebDockablePaneUI
 * @see IDockablePanePainter
 * @see DockablePanePainter
 * @see WebContainer
 */
public class WebDockablePane extends WebContainer
{
    /**
     * Component properties.
     */
    public static final String SIDEBAR_BUTTON_VISIBILITY_PROPERTY = "sidebarButtonVisibility";
    public static final String SIDEBAR_BUTTON_ACTION_PROPERTY = "sidebarButtonAction";
    public static final String GROUP_ELEMENTS_PROPERTY = "groupElements";
    public static final String SIDEBAR_SPACING_PROPERTY = "sidebarSpacing";
    public static final String CONTENT_SPACING_PROPERTY = "contentSpacing";
    public static final String RESIZE_GRIPPER_WIDTH_PROPERTY = "resizeGripperWidth";
    public static final String MINIMUM_ELEMENT_SIZE_PROPERTY = "minimumElementSize";
    public static final String OCCUPY_MINIMUM_SIZE_FOR_CHILDREN_PROPERTY = "occupyMinimumSizeForChildren";
    public static final String MODEL_PROPERTY = "model";
    public static final String MODEL_STATE_PROPERTY = "modelState";
    public static final String GLASS_LAYER_PROPERTY = "glassLayer";
    public static final String FRAMES_PROPERTY = "frames";
    public static final String FRAME_PROPERTY = "frame";
    public static final String CONTENT_PROPERTY = "content";

    /**
     * Sidebar buttons visibility type.
     *
     * @see SidebarButtonVisibility
     */
    @NotNull
    protected SidebarButtonVisibility sidebarButtonVisibility;

    /**
     * Sidebar button action.
     */
    @NotNull
    protected SidebarButtonAction sidebarButtonAction;

    /**
     * Whether or not dockable pane elements should be visually grouped.
     * This will only work with zero {@link #contentSpacing} as there is no point to visually group elements otherwise.
     * Also note that {@link #sidebarSpacing} value will have no effect on this setting as it is used to provide correct padding.
     */
    protected boolean groupElements;

    /**
     * Spacing between sidebars elements and other elements.
     * todo Remove once sidebars are properly implemented as separate containers, it will be covered by container's padding
     */
    protected int sidebarSpacing;

    /**
     * Spacing between content elements.
     * todo Move into DockablePaneLayout once it's separated from the model
     */
    protected int contentSpacing;

    /**
     * Content elements resize gripper width.
     * This setting represents actual draggable area width and not visual representation width.
     * It can be larger than {@link #contentSpacing} and will overlap elements in that case blocking their mouse events.
     */
    protected int resizeGripperWidth;

    /**
     * Minimum size of {@link WebDockableFrame} or content component added onto the dockable pane.
     */
    @NotNull
    protected Dimension minimumElementSize;

    /**
     * Whether containers minimum size should include children minimum sizes or simply be equal to {@link #minimumElementSize}.
     */
    protected boolean occupyMinimumSizeForChildren;

    /**
     * Model containing information about dockable pane elements.
     * It stores all information on frames and content location specifics.
     */
    protected DockablePaneModel model;

    /**
     * Glass layer component.
     * It usually handles frames resize and displays frame placement locations while any frame is being dragged.
     */
    protected JComponent glassLayer;

    /**
     * List of frames added to this {@link WebDockablePane}.
     * This list only contains frames opened frames.
     * Upon closing frame it gets removed from the pane and this list.
     */
    protected final List frames;

    /**
     * Content component.
     * Content takes all free space left after frames positioning.
     */
    protected JComponent content;

    /**
     * Constructs new dockable pane.
     */
    public WebDockablePane ()
    {
        this ( StyleId.auto );
    }

    /**
     * Constructs new dockable pane.
     *
     * @param id {@link StyleId}
     */
    public WebDockablePane ( @NotNull final StyleId id )
    {
        this.sidebarButtonVisibility = SidebarButtonVisibility.minimized;
        this.sidebarButtonAction = SidebarButtonAction.restore;
        this.groupElements = false;
        this.sidebarSpacing = 0;
        this.contentSpacing = 0;
        this.resizeGripperWidth = 10;
        this.minimumElementSize = new Dimension ( 40, 40 );
        this.occupyMinimumSizeForChildren = true;
        this.frames = new ArrayList ( 3 );
        setModel ( createDefaultModel () );
        updateUI ();
        setStyleId ( id );
    }

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

    /**
     * Returns {@link SidebarButton}s visibility condition.
     *
     * @return {@link SidebarButton}s visibility condition
     */
    @NotNull
    public SidebarButtonVisibility getSidebarButtonVisibility ()
    {
        return sidebarButtonVisibility;
    }

    /**
     * Sets {@link SidebarButton}s visibility condition.
     *
     * @param condition {@link SidebarButton}s visibility condition
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setSidebarButtonVisibility ( @NotNull final SidebarButtonVisibility condition )
    {
        if ( this.sidebarButtonVisibility != condition )
        {
            final SidebarButtonVisibility old = this.sidebarButtonVisibility;
            this.sidebarButtonVisibility = condition;
            firePropertyChange ( SIDEBAR_BUTTON_VISIBILITY_PROPERTY, old, condition );
        }
        return this;
    }

    /**
     * Returns sidebar button action.
     *
     * @return sidebar button action
     */
    @NotNull
    public SidebarButtonAction getSidebarButtonAction ()
    {
        return sidebarButtonAction;
    }

    /**
     * Sets sidebar button action.
     *
     * @param action sidebar button action
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setSidebarButtonAction ( @NotNull final SidebarButtonAction action )
    {
        if ( this.sidebarButtonAction != action )
        {
            final SidebarButtonAction old = this.sidebarButtonAction;
            this.sidebarButtonAction = action;
            firePropertyChange ( SIDEBAR_BUTTON_ACTION_PROPERTY, old, action );
        }
        return this;
    }

    /**
     * Returns whether or not dockable pane elements should be visually grouped.
     *
     * @return {@code true} if dockable pane elements should be visually grouped, {@code false} otherwise
     */
    public boolean isGroupElements ()
    {
        return groupElements;
    }

    /**
     * Sets whether or not dockable pane elements should be visually grouped.
     * Note that this will only work with zero {@link #contentSpacing} as there is no point to visually group elements otherwise.
     * Also note that {@link #sidebarSpacing} value will have no effect on this setting as it is used to provide correct padding.
     *
     * @param group whether or not dockable pane elements should be visually grouped
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setGroupElements ( final boolean group )
    {
        if ( this.groupElements != group )
        {
            final boolean old = this.groupElements;
            this.groupElements = group;
            firePropertyChange ( GROUP_ELEMENTS_PROPERTY, old, group );
        }
        return this;
    }

    /**
     * Returns spacing between sidebars elements and other elements.
     *
     * @return spacing between sidebars elements and other elements
     */
    public int getSidebarSpacing ()
    {
        return sidebarSpacing;
    }

    /**
     * Sets spacing between sidebars elements and other elements.
     *
     * @param spacing spacing between sidebars elements and other elements
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setSidebarSpacing ( final int spacing )
    {
        if ( this.sidebarSpacing != spacing )
        {
            final int old = this.sidebarSpacing;
            this.sidebarSpacing = spacing;
            firePropertyChange ( SIDEBAR_SPACING_PROPERTY, old, spacing );
        }
        return this;
    }

    /**
     * Returns spacing between content elements.
     *
     * @return spacing between content elements
     */
    public int getContentSpacing ()
    {
        return contentSpacing;
    }

    /**
     * Sets spacing between content elements.
     *
     * @param spacing spacing between content elements
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setContentSpacing ( final int spacing )
    {
        if ( this.contentSpacing != spacing )
        {
            final int old = this.contentSpacing;
            this.contentSpacing = spacing;
            firePropertyChange ( CONTENT_SPACING_PROPERTY, old, spacing );
        }
        return this;
    }

    /**
     * Returns content elements resize gripper width.
     *
     * @return content elements resize gripper width
     */
    public int getResizeGripperWidth ()
    {
        return resizeGripperWidth;
    }

    /**
     * Sets content elements resize gripper width.
     *
     * @param resizeGripperWidth content elements resize gripper width
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setResizeGripperWidth ( final int resizeGripperWidth )
    {
        if ( this.resizeGripperWidth != resizeGripperWidth )
        {
            final int old = this.resizeGripperWidth;
            this.resizeGripperWidth = resizeGripperWidth;
            firePropertyChange ( RESIZE_GRIPPER_WIDTH_PROPERTY, old, resizeGripperWidth );
        }
        return this;
    }

    /**
     * Returns minimum size of {@link WebDockableFrame} or content component added onto the dockable pane.
     *
     * @return minimum size of {@link WebDockableFrame} or content component added onto the dockable pane
     */
    @NotNull
    public Dimension getMinimumElementSize ()
    {
        return minimumElementSize;
    }

    /**
     * Sets minimum size of {@link WebDockableFrame} or content component added onto the dockable pane.
     *
     * @param size minimum size of {@link WebDockableFrame} or content component added onto the dockable pane
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setMinimumElementSize ( @NotNull final Dimension size )
    {
        if ( this.minimumElementSize != size )
        {
            final Dimension old = this.minimumElementSize;
            this.minimumElementSize = size;
            firePropertyChange ( MINIMUM_ELEMENT_SIZE_PROPERTY, old, size );
        }
        return this;
    }

    /**
     * Returns whether containers minimum size should include children sizes or simply be equal to {@link #minimumElementSize}.
     *
     * @return true if containers minimum size should include children sizes, false if it should be equal to {@link #minimumElementSize}
     */
    public boolean isOccupyMinimumSizeForChildren ()
    {
        return occupyMinimumSizeForChildren;
    }

    /**
     * Sets whether containers minimum size should include children sizes or simply be equal to {@link #minimumElementSize}.
     *
     * @param occupy whether containers minimum size should include children sizes or simply be equal to {@link #minimumElementSize}
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setOccupyMinimumSizeForChildren ( final boolean occupy )
    {
        if ( this.occupyMinimumSizeForChildren != occupy )
        {
            final boolean old = this.occupyMinimumSizeForChildren;
            this.occupyMinimumSizeForChildren = occupy;
            firePropertyChange ( OCCUPY_MINIMUM_SIZE_FOR_CHILDREN_PROPERTY, old, occupy );
        }
        return this;
    }

    /**
     * Returns model containing information about dockable pane elements.
     *
     * @return model containing information about dockable pane elements
     */
    @NotNull
    public DockablePaneModel getModel ()
    {
        return model;
    }

    /**
     * Sets model containing information about dockable pane elements.
     *
     * @param model model containing information about dockable pane elements
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setModel ( @NotNull final DockablePaneModel model )
    {
        if ( this.model != model )
        {
            final DockablePaneModel old = this.model;
            this.model = model;
            setLayout ( model );
            firePropertyChange ( MODEL_PROPERTY, old, model );
        }
        return this;
    }

    /**
     * Returns default {@link DockablePaneModel} implementation to be used.
     *
     * @return default {@link DockablePaneModel} implementation to be used
     */
    @NotNull
    protected DockablePaneModel createDefaultModel ()
    {
        return new WebDockablePaneModel ();
    }

    /**
     * Returns dockable pane element states data.
     * It contains data which can be used to restore dockable element states.
     *
     * @return dockable pane element states data
     * @see #setState(DockableContainer)
     */
    @NotNull
    public DockableContainer getState ()
    {
        return getModel ().getRoot ();
    }

    /**
     * Sets dockable pane element states data.
     * This data can be retrieved from dockable pane at any time in runtime.
     *
     * @param state dockable pane element states data
     * @return this {@link WebDockablePane}
     * @see #getState()
     */
    @NotNull
    public WebDockablePane setState ( @NotNull final DockableContainer state )
    {
        // Changing root element
        if ( getModel ().getRoot () != state )
        {
            final DockableContainer old = getModel ().getRoot ();
            getModel ().setRoot ( state );
            firePropertyChange ( MODEL_STATE_PROPERTY, old, model );
        }
        return this;
    }

    /**
     * Returns glass layer component.
     *
     * @return glass layer component
     */
    @Nullable
    public JComponent getGlassLayer ()
    {
        return glassLayer;
    }

    /**
     * Sets glass layer component.
     *
     * @param glassLayer glass layer component
     * @return this {@link WebDockablePane}
     */
    @NotNull
    public WebDockablePane setGlassLayer ( @Nullable final JComponent glassLayer )
    {
        if ( this.glassLayer != glassLayer )
        {
            final JComponent old = this.glassLayer;
            this.glassLayer = glassLayer;
            firePropertyChange ( GLASS_LAYER_PROPERTY, old, glassLayer );
        }
        return this;
    }

    /**
     * Returns list of dockable frames added to this {@link WebDockablePane}.
     *
     * @return list of dockable frames added to this {@link WebDockablePane}
     */
    @NotNull
    public List getFrames ()
    {
        return CollectionUtils.copy ( frames );
    }

    /**
     * Returns {@link List} of {@link WebDockableFrame}s added to this {@link WebDockablePane} and positioned on the specified side.
     *
     * @param position frames position relative to content area
     * @return {@link List} of {@link WebDockableFrame}s added to this {@link WebDockablePane} and positioned on the specified side
     * @see WebDockableFrame#getPosition()
     */
    @NotNull
    public List getFrames ( @NotNull final CompassDirection position )
    {
        final List positioned = new ArrayList ( frames.size () );
        for ( final WebDockableFrame frame : frames )
        {
            if ( frame.getPosition () == position )
            {
                positioned.add ( frame );
            }
        }
        return positioned;
    }

    /**
     * Returns {@link WebDockableFrame} with the specified identifier.
     *
     * @param id {@link WebDockableFrame} identifier
     * @return {@link WebDockableFrame} with the specified identifier
     */
    @NotNull
    public WebDockableFrame getFrame ( @NotNull final String id )
    {
        final WebDockableFrame frameById = findFrame ( id );
        if ( frameById == null )
        {
            throw new RuntimeException ( "Unable to find frame with identifier: " + id );
        }
        return frameById;
    }

    /**
     * Returns {@link WebDockableFrame} with the specified identifier or {@code null} if it doesn't exist within {@link WebDockablePane}.
     *
     * @param id {@link WebDockableFrame} identifier
     * @return {@link WebDockableFrame} with the specified identifier or {@code null} if it doesn't exist within {@link WebDockablePane}
     */
    @Nullable
    public WebDockableFrame findFrame ( @NotNull final String id )
    {
        WebDockableFrame frameById = null;
        for ( final WebDockableFrame frame : frames )
        {
            if ( Objects.equals ( id, frame.getId () ) )
            {
                frameById = frame;
                break;
            }
        }
        return frameById;
    }

    /**
     * Returns currently maximized {@link WebDockableFrame} or {@code null} if none maximized.
     *
     * @return currently maximized {@link WebDockableFrame} or {@code null} if none maximized
     */
    @Nullable
    public WebDockableFrame getMaximizedFrame ()
    {
        WebDockableFrame maximizedFrame = null;
        for ( final WebDockableFrame frame : frames )
        {
            if ( frame.isMaximized () )
            {
                maximizedFrame = frame;
                break;
            }
        }
        return maximizedFrame;
    }

    /**
     * Adds specified {@link WebDockableFrame} into this {@link WebDockablePane}.
     * Position of {@link WebDockableFrame} will get restored by its identifier if it's state was saved at least once before.
     * Note that {@link WebDockableFrame} might be in closed state, in that case it will still not be visible.
     *
     * @param frame {@link WebDockableFrame} to add
     * @return added {@link WebDockableFrame}
     */
    @NotNull
    public WebDockableFrame addFrame ( @NotNull final WebDockableFrame frame )
    {
        if ( !frames.contains ( frame ) )
        {
            final List old = CollectionUtils.copy ( frames );
            frames.add ( frame );
            firePropertyChange ( FRAMES_PROPERTY, old, frames );
            firePropertyChange ( FRAME_PROPERTY, null, frame );
        }
        return frame;
    }

    /**
     * Removes specified {@link WebDockableFrame} from this {@link WebDockablePane}.
     * This will completely remove {@link WebDockableFrame} and its data from {@link WebDockablePaneModel}.
     *
     * @param frame {@link WebDockableFrame} to remove
     * @return removed {@link WebDockableFrame}
     */
    @NotNull
    public WebDockableFrame removeFrame ( @NotNull final WebDockableFrame frame )
    {
        if ( frames.contains ( frame ) )
        {
            final List old = CollectionUtils.copy ( frames );
            frames.remove ( frame );
            firePropertyChange ( FRAMES_PROPERTY, old, frames );
            firePropertyChange ( FRAME_PROPERTY, frame, null );
        }
        return frame;
    }

    /**
     * Returns current content {@link JComponent}.
     *
     * @return current content {@link JComponent}
     */
    @Nullable
    public JComponent getContent ()
    {
        return content;
    }

    /**
     * Sets content {@link JComponent}.
     *
     * @param content content {@link JComponent}
     * @return previous content {@link JComponent}
     */
    @Nullable
    public JComponent setContent ( @Nullable final JComponent content )
    {
        final JComponent old = this.content;
        if ( this.content != content )
        {
            this.content = content;
            firePropertyChange ( CONTENT_PROPERTY, old, content );
        }
        return old;
    }

    /**
     * Adds new {@link DockablePaneListener}.
     *
     * @param listener {@link DockablePaneListener} to add
     */
    public void addDockablePaneListener ( @NotNull final DockablePaneListener listener )
    {
        listenerList.add ( DockablePaneListener.class, listener );
    }

    /**
     * Removes specified {@link DockablePaneListener}.
     *
     * @param listener {@link DockablePaneListener} to remove
     */
    public void removeDockablePaneListener ( @NotNull final DockablePaneListener listener )
    {
        listenerList.remove ( DockablePaneListener.class, listener );
    }

    /**
     * Informs listeners about {@link WebDockableFrame} being added.
     *
     * @param frame        {@link WebDockableFrame} which was added
     * @param dockablePane {@link WebDockablePane} where frame was added
     */
    public void fireFrameAdded ( @NotNull final WebDockableFrame frame, @NotNull final WebDockablePane dockablePane )
    {
        for ( final DockablePaneListener listener : listenerList.getListeners ( DockablePaneListener.class ) )
        {
            listener.frameAdded ( frame, dockablePane );
        }
    }

    /**
     * Informs listeners about {@link WebDockableFrame} state change.
     *
     * @param frame    {@link WebDockableFrame}
     * @param oldState previous frame state
     * @param newState current frame state
     */
    public void fireFrameStateChanged ( @NotNull final WebDockableFrame frame, @NotNull final DockableFrameState oldState,
                                        @NotNull final DockableFrameState newState )
    {
        for ( final DockablePaneListener listener : listenerList.getListeners ( DockablePaneListener.class ) )
        {
            listener.frameStateChanged ( frame, oldState, newState );
        }
    }

    /**
     * Informs listeners about {@link WebDockableFrame} being moved.
     *
     * @param frame    {@link WebDockableFrame}
     * @param position current frame position relative to content
     */
    public void fireFrameMoved ( @NotNull final WebDockableFrame frame, @NotNull final CompassDirection position )
    {
        for ( final DockablePaneListener listener : listenerList.getListeners ( DockablePaneListener.class ) )
        {
            listener.frameMoved ( frame, position );
        }
    }

    /**
     * Informs listeners about {@link WebDockableFrame} being removed.
     *
     * @param frame        {@link WebDockableFrame} which was removed
     * @param dockablePane {@link WebDockablePane} where frame was removed from
     */
    public void fireFrameRemoved ( @NotNull final WebDockableFrame frame, @NotNull final WebDockablePane dockablePane )
    {
        for ( final DockablePaneListener listener : listenerList.getListeners ( DockablePaneListener.class ) )
        {
            listener.frameRemoved ( frame, dockablePane );
        }
    }

    @Override
    public void applyComponentOrientation ( @NotNull final ComponentOrientation orientation )
    {
        setComponentOrientation ( orientation );
        synchronized ( getTreeLock () )
        {
            for ( final WebDockableFrame frame : frames )
            {
                frame.applyComponentOrientation ( orientation );
            }
            final JComponent content = getContent ();
            if ( content != null )
            {
                content.applyComponentOrientation ( orientation );
            }
        }
    }

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

    /**
     * Sets the LaF object that renders this component.
     *
     * @param ui {@link WDockablePaneUI}
     */
    public void setUI ( final WDockablePaneUI 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