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

com.alee.extended.dock.WebDockablePaneModel 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.data.CompassDirection;
import com.alee.api.data.Orientation;
import com.alee.api.jdk.Objects;
import com.alee.extended.dock.data.*;
import com.alee.extended.dock.drag.FrameDragData;
import com.alee.extended.dock.drag.FrameDropData;
import com.alee.extended.dock.drag.FrameTransferable;
import com.alee.laf.grouping.AbstractGroupingLayout;
import com.alee.laf.window.WebDialog;
import com.alee.utils.CollectionUtils;
import com.alee.utils.CoreSwingUtils;
import com.alee.utils.general.Pair;

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

import static com.alee.api.data.CompassDirection.*;
import static com.alee.api.data.Orientation.horizontal;
import static com.alee.api.data.Orientation.vertical;

/**
 * Basic {@link DockablePaneModel} implementation.
 * It handles elements location data and also provides appropriate layout for all elements on the dockable pane.
 * It also provides frames drag data and various methods to modify element locations.
 *
 * @author Mikle Garin
 * @see How to use WebDockablePane
 * @see WebDockablePane
 * @see DockablePaneModel
 */
public class WebDockablePaneModel extends AbstractGroupingLayout implements DockablePaneModel
{
    /**
     * Side width used to calculated side frame attachment area.
     */
    protected static final int dropSide = 40;

    /**
     * Root element on the {@link WebDockablePane}.
     */
    protected DockableContainer root;

    /**
     * Content element reference.
     */
    protected DockableContentElement content;

    /**
     * Sidebar widths cache.
     */
    protected final Map sidebarWidths = new HashMap ();

    /**
     * Resizable areas cache.
     * Used to optimize resize areas detection.
     */
    protected final List resizableAreas = new ArrayList ();

    /**
     * Preview frame bounds.
     * Saved to check intersection with resizable areas.
     */
    protected Rectangle previewBounds = null;

    /**
     * Constructs new {@link DockablePaneModel} implementation with only content in its structure.
     */
    public WebDockablePaneModel ()
    {
        this ( new DockableListContainer ( horizontal, new DockableContentElement () ) );
    }

    /**
     * Constructs new {@link DockablePaneModel} implementation with the specified structure of the elements.
     *
     * @param root root container on the {@link WebDockablePane}
     */
    public WebDockablePaneModel ( final DockableContainer root )
    {
        super ();
        setGroupButtons ( false );
        setRoot ( root );
    }

    @Override
    public DockableContainer getRoot ()
    {
        return root;
    }

    @Override
    public void setRoot ( final DockableContainer root )
    {
        this.root = root;
        this.content = root.get ( DockableContentElement.ID );
        this.root.added ( null );
    }

    @Override
    public  T getElement ( final String id )
    {
        return root.get ( id );
    }

    @Override
    public void updateFrame ( final WebDockablePane dockablePane, final WebDockableFrame frame )
    {
        // Check whether or not frame data is already available
        if ( root.contains ( frame.getId () ) )
        {
            // Restoring frame states from model
            final DockableFrameElement element = root.get ( frame.getId () );
            frame.setState ( element.getState () );
            frame.setRestoreState ( element.getRestoreState () );
        }
        else
        {
            // Model didn't store state for this frame, creating new one
            final DockableFrameElement element = new DockableFrameElement ( frame );
            addStructureElement ( content, element, frame.getPosition () );
        }

        // Ensuring frame position is correct
        final CompassDirection position = getFramePosition ( frame );
        frame.setPosition ( position );
    }

    @Override
    public void removeFrame ( final WebDockablePane dockablePane, final WebDockableFrame frame )
    {
        if ( root.contains ( frame.getId () ) )
        {
            // Removing frame state
            final DockableFrameElement element = root.get ( frame.getId () );
            removeStructureElement ( element );
        }
    }

    /**
     * Adds specified {@code newElement} relative to another {@code element} shifting to the specified {@code direction}.
     *
     * @param element    element relative to which {@code newElement} should be added
     * @param newElement element to add
     * @param direction  placement direction
     */
    protected void addStructureElement ( final DockableElement element, final DockableElement newElement, final CompassDirection direction )
    {
        final Orientation orientation = direction == north || direction == south ? vertical : horizontal;
        if ( element == root )
        {
            if ( orientation == root.getOrientation () || root.getElementCount () <= 1 )
            {
                if ( direction == north || direction == west )
                {
                    // Redirecting addition
                    addStructureElement ( root.get ( 0 ), newElement, direction );
                }
                else if ( direction == south || direction == east )
                {
                    // Redirecting addition
                    addStructureElement ( root.get ( root.getElementCount () - 1 ), newElement, direction );
                }
                else
                {
                    throw new RuntimeException ( "Unknown element position specified" );
                }
            }
            else
            {
                // Creating new root container
                root = new DockableListContainer ( orientation, element );

                // Redirecting addition
                addStructureElement ( element, newElement, direction );
            }
        }
        else
        {
            final DockableContainer parent = element.getParent ();
            final int index = parent.indexOf ( element );
            if ( orientation == parent.getOrientation () || parent.getElementCount () <= 1 )
            {
                // Ensure orientation is correct
                parent.setOrientation ( orientation );

                // Add element to either start or end of the container
                if ( orientation.isHorizontal () && direction == west || orientation.isVertical () && direction == north )
                {
                    parent.add ( index, newElement );
                }
                else if ( orientation.isHorizontal () && direction == east || orientation.isVertical () && direction == south )
                {
                    parent.add ( index + 1, newElement );
                }
                else
                {
                    throw new RuntimeException ( "Unknown element position specified" );
                }
            }
            else
            {
                // Saving fraction to keep it intact
                final Dimension size = element.getSize ();

                // Removing initial relation element
                parent.remove ( element );

                // Creating new list container
                final DockableListContainer list = new DockableListContainer ( orientation, element );
                list.setSize ( size );
                parent.add ( index, list );

                // Redirecting addition
                addStructureElement ( element, newElement, direction );
            }
        }
    }

    /**
     * Removes specified {@code element} from the model structure.
     *
     * @param element element to remove
     */
    protected void removeStructureElement ( final DockableElement element )
    {
        if ( element == root )
        {
            throw new RuntimeException ( "Structure element cannot be removed" );
        }
        else if ( element == content )
        {
            throw new RuntimeException ( "Content element cannot be removed" );
        }
        else
        {
            final DockableContainer container = element.getParent ();

            // Removing element from container
            container.remove ( element );

            // Removing redundant container
            if ( container != root && container.getElementCount () <= 1 )
            {
                // Moving single child up
                if ( container.getElementCount () == 1 )
                {
                    final DockableContainer containerParent = container.getParent ();
                    final int index = containerParent.indexOf ( container );
                    containerParent.add ( index, container.get ( 0 ) );
                }

                // Removing empty container
                removeStructureElement ( container );
            }
        }
    }

    @Override
    public FrameDropData dropData ( final WebDockablePane dockablePane, final TransferHandler.TransferSupport support )
    {
        if ( !support.getTransferable ().isDataFlavorSupported ( FrameTransferable.dataFlavor ) )
        {
            return null;
        }
        try
        {
            // Retrieving draged frame data
            final FrameDragData drag = ( FrameDragData ) support.getTransferable ().getTransferData ( FrameTransferable.dataFlavor );

            // Checking frame existence on this dockable pane
            // This is needed to avoid drag between different panes
            if ( dockablePane.getFrame ( drag.getId () ) == null )
            {
                return null;
            }

            // Basic drag information
            final String id = drag.getId ();
            final Point dropPoint = support.getDropLocation ().getDropPoint ();

            // Checking global side drop
            final Rectangle innerBounds = getInnerBounds ( dockablePane );
            final FrameDropData globalDrop = createDropData ( id, root, innerBounds, dropPoint, dropSide );
            if ( globalDrop != null )
            {
                return globalDrop;
            }

            // Checking content side drop
            // We cannot use "getComponentAt" method here as it will point at glass pane
            final JComponent paneContent = dockablePane.getContent ();
            if ( paneContent != null )
            {
                final Point location = paneContent.getLocation ();
                if ( paneContent.contains ( dropPoint.x - location.x, dropPoint.y - location.y ) )
                {
                    final Rectangle dropBounds = getDropBounds ( paneContent );
                    return createDropData ( id, content, dropBounds, dropPoint, dropSide * 2 );
                }
            }

            // Checking frame side drop
            // We cannot use "getComponentAt" method here as it will point at glass pane
            for ( final WebDockableFrame paneFrame : dockablePane.getFrames () )
            {
                // Ensure frame is showing and it is not the dragged frame
                if ( paneFrame.isDocked () && Objects.notEquals ( paneFrame.getId (), id ) )
                {
                    final Point location = paneFrame.getLocation ();
                    if ( paneFrame.contains ( dropPoint.x - location.x, dropPoint.y - location.y ) )
                    {
                        final DockableElement element = root.get ( paneFrame.getId () );
                        final Rectangle dropBounds = getDropBounds ( paneFrame );
                        return createDropData ( id, element, dropBounds, dropPoint, dropSide * 2 );
                    }
                }
            }

            // No information
            return null;
        }
        catch ( final Exception e )
        {
            throw new RuntimeException ( "Unknown frame drop exception occured", e );
        }
    }

    /**
     * Returns drop location bounds.
     *
     * @param component drop location component
     * @return drop location bounds
     */
    protected Rectangle getDropBounds ( final JComponent component )
    {
        final Rectangle bounds = component.getBounds ();
        final Insets i = component.getInsets ();
        bounds.x += i.left - 1;
        bounds.y += i.top - 1;
        bounds.width -= i.left + i.right - 1;
        bounds.height -= i.top + i.bottom - 1;
        return bounds;
    }

    /**
     * Returns component drop data.
     *
     * @param id        dragged frame ID
     * @param element   element currently placed at the drop location
     * @param bounds    drop location bounds
     * @param dropPoint drop point
     * @param dropSide  size of droppable side
     * @return component drop data
     */
    protected FrameDropData createDropData ( final String id, final DockableElement element, final Rectangle bounds, final Point dropPoint,
                                             final int dropSide )
    {
        // Calculating drop direction
        final boolean wSmall = dropSide > bounds.width / 2;
        final boolean hSmall = dropSide > bounds.height / 2;
        final int xSide = Math.min ( dropSide, bounds.width / 2 );
        final int ySide = Math.min ( dropSide, bounds.height / 2 );
        final boolean w = dropPoint.x <= bounds.x + xSide;
        final boolean e = dropPoint.x >= bounds.x + bounds.width - xSide;
        final boolean n = dropPoint.y <= bounds.y + ySide;
        final boolean s = dropPoint.y >= bounds.y + bounds.height - ySide;
        final CompassDirection direction;
        if ( wSmall || hSmall )
        {
            // Special behavior in case width or height are too small
            if ( wSmall )
            {
                // Prioritize north and south zones
                direction = n ? north : s ? south : w ? west : e ? east : null;
            }
            else
            {
                // Prioritize west and east zones
                direction = w ? west : e ? east : n ? north : s ? south : null;
            }
        }
        else if ( w && n )
        {
            // NW corner drop behavior
            direction = dropPoint.x - bounds.x > dropPoint.y - bounds.y ? north : west;
        }
        else if ( w && s )
        {
            // SW corner drop behavior
            direction = dropPoint.x - bounds.x > bounds.y + bounds.height - dropPoint.y ? south : west;
        }
        else if ( e && n )
        {
            // NE corner drop behavior
            direction = bounds.x + bounds.width - dropPoint.x > dropPoint.y - bounds.y ? north : east;
        }
        else if ( e && s )
        {
            // SE corner drop behavior
            direction = bounds.x + bounds.width - dropPoint.x > bounds.y + bounds.height - dropPoint.y ? south : east;
        }
        else
        {
            // General drop behavior
            direction = n ? north : s ? south : w ? west : e ? east : null;
        }

        // Creating drop data
        if ( direction != null )
        {
            switch ( direction )
            {
                case west:
                    bounds.width = xSide;
                    break;

                case east:
                    bounds.x = bounds.x + bounds.width - xSide;
                    bounds.width = xSide;
                    break;

                case north:
                    bounds.height = ySide;
                    break;

                case south:
                    bounds.y = bounds.y + bounds.height - ySide;
                    bounds.height = ySide;
                    break;
            }
            return new FrameDropData ( id, bounds, element, direction );
        }
        else
        {
            return null;
        }
    }

    @Override
    public boolean drop ( final WebDockablePane dockablePane, final TransferHandler.TransferSupport support )
    {
        final FrameDropData dropData = dropData ( dockablePane, support );
        if ( dropData != null )
        {
            // Removing frame from initial location first
            final DockableElement element = root.get ( dropData.getId () );
            removeStructureElement ( element );

            // Adding frame to target location
            addStructureElement ( dropData.getElement (), element, dropData.getDirection () );

            // Updating frame position
            final WebDockableFrame frame = dockablePane.getFrame ( element.getId () );
            final CompassDirection position = getFramePosition ( frame );
            frame.setPosition ( position );

            // Updating dockable
            dockablePane.revalidate ();
            dockablePane.repaint ();

            // Informing frame listeners
            frame.fireFrameMoved ( position );
        }
        return false;
    }

    @Override
    public void layoutContainer ( @NotNull final Container container )
    {
        // Base settings
        final WebDockablePane dockablePane = ( WebDockablePane ) container;
        final int w = dockablePane.getWidth ();
        final int h = dockablePane.getHeight ();

        // Outer bounds, sidebars are placed within these bounds
        final Rectangle outer = getOuterBounds ( dockablePane );

        // Positioning sidebar elements
        final List locations = CollectionUtils.asList ( north, west, south, east );
        final Map> allButtons = new HashMap> ();
        for ( final CompassDirection location : locations )
        {
            final List barButtons = getVisibleButtons ( dockablePane, location );
            allButtons.put ( location, barButtons );
            sidebarWidths.put ( location, calculateBarWidth ( location, barButtons ) );
        }
        for ( final CompassDirection location : locations )
        {
            // Retrieving bar cache
            final List buttons = allButtons.get ( location );
            if ( buttons.size () > 0 )
            {
                // Calculating bar bounds
                final int barWidth = sidebarWidths.get ( location );
                final Rectangle bounds;
                final int fw;
                final int lw;
                switch ( location )
                {
                    case north:
                        fw = sidebarWidths.get ( west );
                        lw = sidebarWidths.get ( east );
                        bounds = new Rectangle ( outer.x + fw, outer.y, outer.width - fw - lw, barWidth );
                        break;

                    case west:
                        fw = sidebarWidths.get ( north );
                        lw = sidebarWidths.get ( south );
                        bounds = new Rectangle ( outer.x, outer.y + fw, barWidth, outer.height - fw - lw );
                        break;

                    case south:
                        fw = sidebarWidths.get ( west );
                        lw = sidebarWidths.get ( east );
                        bounds = new Rectangle ( outer.x + fw, outer.y + outer.height - barWidth, outer.width - fw - lw, barWidth );
                        break;

                    case east:
                        fw = sidebarWidths.get ( north );
                        lw = sidebarWidths.get ( south );
                        bounds = new Rectangle ( outer.x + outer.width - barWidth, outer.y + fw, barWidth, outer.height - fw - lw );
                        break;

                    default:
                        throw new RuntimeException ( "Unknown location specified: " + location );
                }

                // Placing buttons
                int x = bounds.x;
                int y = bounds.y;
                for ( final JComponent button : buttons )
                {
                    final Dimension bps = button.getPreferredSize ();
                    if ( location == north || location == south )
                    {
                        // Horizontal placement
                        button.setBounds ( x, y, bps.width, barWidth );
                        x += button.getWidth ();
                    }
                    else
                    {
                        // Vertical placement
                        button.setBounds ( x, y, barWidth, bps.height );
                        y += button.getHeight ();
                    }
                }
            }
        }

        // Inner bounds, frames and content are placed within these bounds
        final Rectangle inner = getInnerBounds ( dockablePane );

        // Looking for special frames
        // There could be only single preview and maximized frame at a time
        WebDockableFrame preview = null;
        WebDockableFrame maximized = null;
        if ( dockablePane.frames != null )
        {
            for ( final WebDockableFrame frame : dockablePane.frames )
            {
                if ( frame.isPreview () )
                {
                    preview = frame;
                }
                if ( frame.isDocked () && frame.isMaximized () )
                {
                    maximized = frame;
                }
            }
        }

        // Positioning frames and content
        resizableAreas.clear ();
        if ( maximized == null )
        {
            // Placing frames normally
            root.setSize ( inner.getSize () );
            root.layout ( dockablePane, inner, resizableAreas );
        }
        else
        {
            // Simply hide all frames behind visible area
            // This is required to avoid issues with opaque components on other frames overlapping with maximized one
            // Plus this also brings some level of rendering optimization so it is a convenient thing to do
            if ( dockablePane.frames != null )
            {
                for ( final WebDockableFrame frame : dockablePane.frames )
                {
                    if ( frame != preview && frame != maximized && frame.isDocked () )
                    {
                        frame.setBounds ( 0, 0, 0, 0 );
                    }
                }
            }
        }

        // Positioning preview frame
        if ( preview != null )
        {
            // Retrieving preview bounds
            previewBounds = getPreviewBounds ( dockablePane, preview );

            // Updating frame bounds
            preview.setBounds ( previewBounds );
            root.get ( preview.getId () ).setBounds ( previewBounds );

            // Moving frame to the topmost possible Z-index after glass pane
            dockablePane.setComponentZOrder ( preview, 1 );
        }
        else
        {
            // Resetting preview bounds
            previewBounds = null;
        }

        // Positioning maximized frame
        if ( maximized != null )
        {
            // Updating frame bounds
            maximized.setBounds ( inner );
            root.get ( maximized.getId () ).setBounds ( inner );

            // Moving frame to the topmost possible Z-index after glass pane and preview frame
            dockablePane.setComponentZOrder ( maximized, preview != null ? 2 : 1 );
        }

        // Positioning glass layer
        // It is placed over whole dockable pane for calculations convenience
        final JComponent glassLayer = dockablePane.getGlassLayer ();
        if ( glassLayer != null )
        {
            glassLayer.setBounds ( 0, 0, w, h );
        }
    }

    /**
     * Returns outer pane bounds.
     * These bounds include sidebar and content elements.
     *
     * @param dockablePane {@link WebDockablePane}
     * @return outer pane bounds
     */
    protected Rectangle getOuterBounds ( final WebDockablePane dockablePane )
    {
        final int w = dockablePane.getWidth ();
        final int h = dockablePane.getHeight ();
        final Insets bi = dockablePane.getInsets ();
        return new Rectangle ( bi.left, bi.top, w - bi.left - bi.right, h - bi.top - bi.bottom );
    }

    /**
     * Returns inner pane bounds.
     * These bounds include only content elements.
     *
     * @param dockablePane {@link WebDockablePane}
     * @return inner pane bounds
     */
    protected Rectangle getInnerBounds ( final WebDockablePane dockablePane )
    {
        final Rectangle bounds = getOuterBounds ( dockablePane );
        if ( sidebarWidths.size () > 0 )
        {
            bounds.x += sidebarWidths.get ( west );
            bounds.width -= sidebarWidths.get ( west ) + sidebarWidths.get ( east );
            bounds.y += sidebarWidths.get ( north );
            bounds.height -= sidebarWidths.get ( north ) + sidebarWidths.get ( south );
        }
        return bounds;
    }

    /**
     * Returns frame bounds for {@link DockableFrameState#preview} state.
     *
     * @param dockablePane {@link WebDockablePane}
     * @param frame        {@link WebDockableFrame}
     * @return frame bounds for {@link DockableFrameState#preview} state
     */
    protected Rectangle getPreviewBounds ( final WebDockablePane dockablePane, final WebDockableFrame frame )
    {
        final Rectangle pb;
        final DockableElement element = root.get ( frame.getId () );
        final Rectangle inner = getInnerBounds ( dockablePane );
        switch ( frame.getPosition () )
        {
            case north:
            {
                final int height = Math.min ( element.getSize ().height, inner.height );
                pb = new Rectangle ( inner.x, inner.y, inner.width, height );
                break;
            }
            case west:
            {
                final int width = Math.min ( element.getSize ().width, inner.width );
                pb = new Rectangle ( inner.x, inner.y, width, inner.height );
                break;
            }
            case south:
            {
                final int height = Math.min ( element.getSize ().height, inner.height );
                pb = new Rectangle ( inner.x, inner.y + inner.height - height, inner.width, height );
                break;
            }
            case east:
            {
                final int width = Math.min ( element.getSize ().width, inner.width );
                pb = new Rectangle ( inner.x + inner.width - width, inner.y, width, inner.height );
                break;
            }
            default:
            {
                throw new RuntimeException ( "Unknown frame position: " + frame.getPosition () );
            }
        }
        return pb;
    }

    /**
     * Returns current frame position relative to content.
     *
     * @param frame dockable frame
     * @return current frame position relative to content
     */
    protected CompassDirection getFramePosition ( final WebDockableFrame frame )
    {
        // Looking for frame relative to content position
        DockableContainer parent = content.getParent ();
        int divider = parent.indexOf ( content );
        while ( parent != null )
        {
            // Elements before content
            for ( int i = 0; i < divider; i++ )
            {
                final DockableElement element = parent.get ( i );
                if ( Objects.equals ( element.getId (), frame.getId () ) ||
                        element instanceof DockableContainer && ( ( DockableContainer ) element ).contains ( frame.getId () ) )
                {
                    return parent.getOrientation ().isHorizontal () ? west : north;
                }
            }

            // Elements after content
            for ( int i = divider + 1; i < parent.getElementCount (); i++ )
            {
                final DockableElement element = parent.get ( i );
                if ( Objects.equals ( element.getId (), frame.getId () ) ||
                        element instanceof DockableContainer && ( ( DockableContainer ) element ).contains ( frame.getId () ) )
                {
                    return parent.getOrientation ().isHorizontal () ? east : south;
                }
            }

            // Going higher in the structure
            final DockableElement old = parent;
            parent = parent.getParent ();
            if ( parent != null )
            {
                divider = parent.indexOf ( old );
            }
        }
        throw new RuntimeException ( "Specified frame cannot be found in model: " + frame.getId () );
    }

    /**
     * Returns visible sidebar buttons list.
     *
     * @param dockablePane {@link WebDockablePane}
     * @param side         buttons side
     * @return visible sidebar buttons list
     */
    protected List getVisibleButtons ( final WebDockablePane dockablePane, final CompassDirection side )
    {
        final List buttons = new ArrayList ( 3 );
        DockableContainer parent = content.getParent ();
        int divider = parent.indexOf ( content );
        while ( parent != null )
        {
            if ( parent.getOrientation ().isHorizontal () ? side == west : side == north )
            {
                // Elements before content
                for ( int i = 0; i < divider; i++ )
                {
                    collectVisibleButtons ( dockablePane, parent.get ( i ), buttons );
                }
            }
            else if ( parent.getOrientation ().isHorizontal () ? side == east : side == south )
            {
                // Elements after content
                for ( int i = divider + 1; i < parent.getElementCount (); i++ )
                {
                    collectVisibleButtons ( dockablePane, parent.get ( i ), buttons );
                }
            }

            // Going higher in the structure
            final DockableElement old = parent;
            parent = parent.getParent ();
            if ( parent != null )
            {
                divider = parent.indexOf ( old );
            }
        }
        return buttons;
    }

    /**
     * Collects visible sidebar buttons at the specified side inside the specified {@link DockableElement}.
     *
     * @param dockablePane {@link WebDockablePane}
     * @param element      element to collect sidebar buttons from
     * @param buttons      buttons list to fill in
     */
    protected void collectVisibleButtons ( final WebDockablePane dockablePane, final DockableElement element,
                                           final List buttons )
    {
        if ( element instanceof DockableFrameElement )
        {
            // Frame might not yet be added to the pane so we have to be careful here
            final WebDockableFrame frame = dockablePane.getFrame ( element.getId () );
            if ( frame != null )
            {
                if ( frame.isSidebarButtonVisible () )
                {
                    buttons.add ( frame.getSidebarButton () );
                }
            }
        }
        else if ( element instanceof DockableContainer )
        {
            final DockableContainer container = ( DockableContainer ) element;
            for ( int i = 0; i < container.getElementCount (); i++ )
            {
                collectVisibleButtons ( dockablePane, container.get ( i ), buttons );
            }
        }
    }

    /**
     * Returns sidebar width.
     *
     * @param side       buttons side
     * @param barButtons sidebar buttons
     * @return sidebar width
     */
    protected int calculateBarWidth ( final CompassDirection side, final List barButtons )
    {
        int width = 0;
        for ( final JComponent component : barButtons )
        {
            final Dimension ps = component.getPreferredSize ();
            width = Math.max ( width, side == north || side == south ? ps.height : ps.width );
        }
        return width;
    }

    @Override
    public Dimension preferredLayoutSize ( @NotNull final Container container )
    {
        // todo Use structure to recursively go through sizes
        return new Dimension ( 0, 0 );
    }

    @NotNull
    @Override
    public Pair getDescriptors ( @NotNull final Container container, @NotNull final Component component, final int index )
    {
        // todo Group buttons for grouped frames (tabs) when grouping is available
        // todo Group frames in case it is enabled here (simply group up the whole center area)
        return new Pair ();
    }

    @Override
    public ResizeData getResizeData ( final int x, final int y )
    {
        if ( previewBounds != null && previewBounds.contains ( x, y ) )
        {
            return null;
        }
        for ( final ResizeData resizeData : resizableAreas )
        {
            if ( resizeData.bounds ().contains ( x, y ) )
            {
                return resizeData;
            }
        }
        return null;
    }

    @Override
    public Rectangle getFloatingBounds ( final WebDockablePane dockablePane, final WebDockableFrame frame, final WebDialog dialog )
    {
        final DockableFrameElement element = root.get ( frame.getId () );
        final Rectangle previewBounds;
        if ( element.getFloatingBounds () == null )
        {
            // Frame haven't been in floating state before
            // We will either use provided last bounds within dockable pane or simply use preview bounds
            final Rectangle lastBounds = element.getBounds ();
            previewBounds = lastBounds != null ? lastBounds : getPreviewBounds ( dockablePane, frame );

            // Adjusting bounds to location on screen
            final Point los = CoreSwingUtils.locationOnScreen ( dockablePane );
            previewBounds.x += los.x;
            previewBounds.y += los.y;
        }
        else
        {
            // Restoring previously saved floating bounds
            previewBounds = new Rectangle ( element.getFloatingBounds () );
        }

        // Adjusting location for dialog decorations
        final Insets rpi = dialog.getRootPane ().getInsets ();
        final Insets ci = dialog.getContentPane ().getInsets ();
        previewBounds.x -= rpi.left + ci.left;
        previewBounds.y -= rpi.top + ci.top;
        previewBounds.width += rpi.left + ci.left + ci.right + rpi.right;
        previewBounds.height += rpi.top + ci.top + ci.bottom + rpi.bottom;

        return previewBounds;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy