Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.alee.extended.layout.AbstractLineLayout Maven / Gradle / Ivy
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.extended.layout;
import com.alee.api.jdk.Objects;
import com.alee.utils.CollectionUtils;
import com.alee.utils.TextUtils;
import com.alee.utils.swing.ZOrderComparator;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Custom {@link LayoutManager}
*
* @author Mikle Garin
*/
public abstract class AbstractLineLayout extends AbstractLayoutManager implements SwingConstants
{
/**
* Positions component at the leading side of the container.
*/
public static final String START = "START";
/**
* Positions component in the middle between leading and trailing sides.
*/
public static final String MIDDLE = "MIDDLE";
/**
* Forces component to fill all the space left between leading and trailing sides.
*/
public static final String FILL = "FILL";
/**
* Positions component at the trailing side of the container.
*/
public static final String END = "END";
/**
* Special position for an element displayed at the end of the container whenever its size is less than preferred size.
*/
public static final String TRIM = "TRIM";
/**
* Lazy instance of {@link ZOrderComparator}.
*/
protected static final ZOrderComparator COMPONENTS_COMPARATOR = new ZOrderComparator ();
/**
* Bounds used to hide components.
*/
protected static final Rectangle HIDE_BOUNDS = new Rectangle ( -1, -1, 0, 0 );
/**
* Spacing between layout elements.
*/
protected int spacing;
/**
* Spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts.
*/
protected int partsSpacing;
/**
* Saved layout constraints.
*/
protected transient Map constraints;
/**
* Mapped components.
*/
protected transient Map> components;
/**
* Constructs new {@link AbstractLineLayout}.
*/
public AbstractLineLayout ()
{
this ( 2 );
}
/**
* Constructs new {@link AbstractLineLayout}.
*
* @param spacing spacing between layout elements
*/
public AbstractLineLayout ( final int spacing )
{
this ( spacing, 20 );
}
/**
* Constructs new {@link AbstractLineLayout}.
*
* @param spacing spacing between layout elements
* @param partsSpacing spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts
*/
public AbstractLineLayout ( final int spacing, final int partsSpacing )
{
super ();
this.spacing = spacing;
this.partsSpacing = partsSpacing;
this.constraints = new HashMap ( 10 );
this.components = new HashMap> ( 4 );
}
/**
* Returns spacing between layout elements.
*
* @return spacing between layout elements
*/
public int getSpacing ()
{
return spacing;
}
/**
* Sets spacing between layout elements
*
* @param spacing spacing between layout elements
*/
public void setSpacing ( final int spacing )
{
this.spacing = spacing;
}
/**
* Returns spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts.
*
* @return spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts
*/
public int getPartsSpacing ()
{
return partsSpacing;
}
/**
* Sets spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts.
*
* @param partsSpacing spacing between {@link #START}, {@link #MIDDLE} and {@link #END} layout parts
*/
public void setPartsSpacing ( final int partsSpacing )
{
this.partsSpacing = partsSpacing;
}
@Override
public void addComponent ( final Component component, final Object constraints )
{
final String value = ( String ) constraints;
if ( !TextUtils.isBlank ( value ) && Objects.notEquals ( value, START, MIDDLE, FILL, END, TRIM ) )
{
final String msg = "Unsupported layout constraints: %s";
throw new IllegalArgumentException ( String.format ( msg, value ) );
}
/**
* Resolving proper constraints.
*/
final String actualConstraints = value == null || value.trim ().equals ( "" ) ? START : value;
/**
* Checking intersection to ensure layout doesn't get messy.
*/
if ( Objects.equals ( actualConstraints, MIDDLE ) &&
this.components.containsKey ( FILL ) )
{
throw new RuntimeException ( "Layout already contains element under `FILL` constraints" );
}
else if ( Objects.equals ( actualConstraints, FILL ) &&
( this.components.containsKey ( MIDDLE ) || this.components.containsKey ( FILL ) ) )
{
throw new RuntimeException ( "Layout already contains element under `MIDDLE` or `FILL` constraints" );
}
else if ( Objects.equals ( actualConstraints, TRIM ) &&
this.components.containsKey ( TRIM ) )
{
throw new RuntimeException ( "Layout already contains element under `TRIM` constraints" );
}
/**
* Saving constraints per component.
*/
this.constraints.put ( component, actualConstraints );
/**
* Saving components per constraints.
*/
List components = this.components.get ( actualConstraints );
if ( components == null )
{
components = new ArrayList ( 1 );
}
components.add ( component );
this.components.put ( actualConstraints, components );
}
@Override
public void removeComponent ( final Component component )
{
final String constraints = this.constraints.get ( component );
/**
* Removing constraints for component.
*/
this.constraints.remove ( component );
/**
* Removing component for constraints.
*/
final List components = this.components.get ( constraints );
components.remove ( component );
if ( components.isEmpty () )
{
this.components.remove ( constraints );
}
}
@Override
public void layoutContainer ( final Container container )
{
final int orientation = getOrientation ( container );
final boolean horizontal = orientation == HORIZONTAL;
final Insets insets = container.getInsets ();
final Dimension size = container.getSize ();
final boolean ltr = container.getComponentOrientation ().isLeftToRight ();
final int lineWidth = horizontal ? size.height - insets.top - insets.bottom : size.width - insets.left - insets.right;
/**
* Calculating preferred size of the whole layout.
* We will need this to decide how components should be placed.
* We also cache all component preferred sizes to avoid overhead calculations.
*/
final Map cache = new HashMap ();
final Dimension preferredSize = preferredLayoutSize ( container, cache );
/**
* Calculating {@link #TRIM} size separately.
* We will need this later for various calculations.
*/
final Dimension trimSize = preferredPartSize ( TRIM, orientation, ltr, cache );
/**
* Bounds available within the container.
*/
final Rectangle available = new Rectangle (
insets.left,
insets.top,
size.width - insets.left - insets.right,
size.height - insets.top - insets.bottom
);
/**
* There are two different placement cases.
* One is when we have enough space to place all elements in their preferred sizes.
* The other one is when we don't have enough space and need to make sure components don't overlap.
*/
final boolean fit = horizontal ? preferredSize.width <= size.width : preferredSize.height <= size.height;
if ( fit )
{
/**
* There is enough space to place all components.
* We can simplify calculations and assume positions.
*/
/**
* Placing START components.
*/
final Dimension startSize = preferredPartSize ( START, orientation, ltr, cache );
if ( startSize.width > 0 )
{
final Point startPoint = new Point ( insets.left, insets.top );
placeComponents ( START, startPoint, orientation, ltr, lineWidth, available, cache );
}
/**
* Placing END components.
*/
final Dimension endSize = preferredPartSize ( END, orientation, ltr, cache );
if ( endSize.width > 0 )
{
final Point endPoint = new Point (
horizontal ? size.width - insets.right - endSize.width : insets.left,
horizontal ? insets.top : size.height - insets.bottom - endSize.height
);
placeComponents ( END, endPoint, orientation, ltr, lineWidth, available, cache );
}
/**
* Placing components at the middle.
*/
final Dimension middleSize = preferredPartSize ( MIDDLE, orientation, ltr, cache );
final Dimension fillSize = preferredPartSize ( FILL, orientation, ltr, cache );
if ( middleSize.width > 0 || fillSize.width > 0 )
{
final int xStartLength = horizontal ? startSize.width + ( startSize.width > 0 ? partsSpacing : 0 ) : 0;
final int yStartLength = horizontal ? 0 : startSize.height + ( startSize.height > 0 ? partsSpacing : 0 );
final int xEndLength = horizontal ? endSize.width + ( endSize.width > 0 ? partsSpacing : 0 ) : 0;
final int yEndLength = horizontal ? 0 : endSize.height + ( endSize.height > 0 ? partsSpacing : 0 );
final Rectangle fillBounds = new Rectangle (
insets.left + xStartLength,
insets.top + yStartLength,
size.width - insets.left - xStartLength - xEndLength - insets.right,
size.height - insets.top - yStartLength - yEndLength - insets.bottom
);
if ( middleSize.width > 0 )
{
/**
* Placing MIDDLE components.
*/
final Point middlePoint = new Point (
horizontal ? fillBounds.x + fillBounds.width / 2 - middleSize.width / 2 : fillBounds.x,
horizontal ? fillBounds.y : fillBounds.y + fillBounds.height / 2 - middleSize.height / 2
);
placeComponents ( MIDDLE, middlePoint, orientation, ltr, lineWidth, available, cache );
}
else
{
/**
* Placing FILL component.
*/
placeFillComponent ( fillBounds, available );
}
}
}
else
{
/**
* There is not enough space to place all components.
* We have to carefully position them one by one from left to right or from right to left (depending on component orientation).
*/
/**
* Bounds available for all container elements except {@link #TRIM}.
* It is necessary to hide all components that are staying outside of the bounds.
*/
final Rectangle trimmed;
if ( trimSize.width > 0 )
{
/**
* Only calculating trimmed size when {@link #TRIM} component is available.
* This will slightly shrink available bounds by the {@link #TRIM} component size plus {@link #spacing}.
*/
trimmed = new Rectangle (
available.x + ( horizontal && !ltr ? trimSize.width + spacing : 0 ),
available.y,
available.width - ( horizontal ? trimSize.width + spacing : 0 ),
available.height - ( !horizontal ? trimSize.height + spacing : 0 )
);
}
else
{
trimmed = available;
}
/**
* Starting point for all components.
*/
final Point point = new Point (
insets.left + ( horizontal && !ltr ? size.width - preferredSize.width : 0 ),
insets.top
);
/**
* Placing START components.
*/
final Dimension startSize = preferredPartSize ( START, orientation, ltr, cache );
if ( startSize.width > 0 )
{
placeComponents ( START, point, orientation, ltr, lineWidth, trimmed, cache );
point.x += horizontal ? partsSpacing : 0;
point.y += horizontal ? 0 : partsSpacing;
}
/**
* Placing MIDDLE components.
*/
final Dimension middleSize = preferredPartSize ( MIDDLE, orientation, ltr, cache );
if ( middleSize.width > 0 )
{
placeComponents ( MIDDLE, point, orientation, ltr, lineWidth, trimmed, cache );
point.x += horizontal ? partsSpacing : 0;
point.y += horizontal ? 0 : partsSpacing;
}
/**
* Placing FILL components.
*/
final Dimension fillSize = preferredPartSize ( FILL, orientation, ltr, cache );
if ( fillSize.width > 0 )
{
placeComponents ( FILL, point, orientation, ltr, lineWidth, trimmed, cache );
point.x += horizontal ? partsSpacing : 0;
point.y += horizontal ? 0 : partsSpacing;
}
/**
* Placing END components.
*/
final Dimension endSize = preferredPartSize ( END, orientation, ltr, cache );
if ( endSize.width > 0 )
{
placeComponents ( END, point, orientation, ltr, lineWidth, trimmed, cache );
}
}
/**
* Placing TRIM component.
*/
if ( trimSize.width > 0 )
{
placeTrimComponent ( trimSize, orientation, ltr, available, fit );
}
}
/**
* Places {@link Component}s under specified constraints in a line starting at the specified {@link Point}.
*
* @param c layout constraints
* @param point starting {@link Point}
* @param orientation layout orientation
* @param ltr layout component orientation
* @param lineWidth layout line width
* @param available available space bounds
* @param cache {@link Map} containing {@link Component} size caches
*/
protected void placeComponents ( final String c, final Point point,
final int orientation, final boolean ltr, final int lineWidth,
final Rectangle available, final Map cache )
{
final String constraints = constraints ( c, orientation, ltr );
final boolean horizontal = orientation == HORIZONTAL;
/**
* Retrieving components for constraints.
* We need to sort them by Z-order to place them correctly.
*/
final List components = this.components.get ( constraints );
CollectionUtils.sort ( components, COMPONENTS_COMPARATOR );
/**
* Placing components.
*/
if ( ltr || !horizontal )
{
/**
* Direct components placement order.
*/
for ( final Component component : components )
{
final Dimension cps = preferredSize ( component, cache );
final Rectangle bounds = new Rectangle (
point.x,
point.y,
horizontal ? cps.width : lineWidth,
horizontal ? lineWidth : cps.height
);
if ( available.contains ( bounds ) )
{
component.setBounds ( bounds );
}
else
{
component.setBounds ( HIDE_BOUNDS );
}
point.x += horizontal ? bounds.width + spacing : 0;
point.y += horizontal ? 0 : bounds.height + spacing;
}
}
else
{
/**
* Reversed (RTL) components placement order.
*/
for ( int i = components.size () - 1; i >= 0; i-- )
{
final Component component = components.get ( i );
final Dimension cps = preferredSize ( component, cache );
final Rectangle bounds = new Rectangle (
point.x,
point.y,
horizontal ? cps.width : lineWidth,
horizontal ? lineWidth : cps.height
);
if ( available.contains ( bounds ) )
{
component.setBounds ( bounds );
}
else
{
component.setBounds ( HIDE_BOUNDS );
}
point.x += horizontal ? bounds.width + spacing : 0;
point.y += horizontal ? 0 : bounds.height + spacing;
}
}
point.x -= horizontal ? spacing : 0;
point.y -= horizontal ? 0 : spacing;
}
/**
* Places {@link #FILL} component at the specified bounds.
*
* @param bounds {@link #FILL} bounds
* @param available available space bounds
*/
protected void placeFillComponent ( final Rectangle bounds, final Rectangle available )
{
/**
* Placing single {@link #FILL} component.
* It will always be only one component and we will never call this method if there is none.
*/
if ( available.contains ( bounds ) )
{
components.get ( FILL ).get ( 0 ).setBounds ( bounds );
}
else
{
components.get ( FILL ).get ( 0 ).setBounds ( HIDE_BOUNDS );
}
}
/**
* Places {@link #TRIM} component.
*
* @param trimSize size of the {@link #TRIM} component
* @param orientation layout orientation
* @param ltr layout component orientation
* @param available available space bounds
* @param fit whether or not layout size is larger or equal to its preferred
*/
protected void placeTrimComponent ( final Dimension trimSize, final int orientation, final boolean ltr,
final Rectangle available, final boolean fit )
{
if ( !fit )
{
final boolean horizontal = orientation == HORIZONTAL;
final Rectangle trimBounds = new Rectangle (
horizontal && ltr ? available.x + available.width - trimSize.width : available.x,
horizontal ? available.y : available.y + available.height - trimSize.height,
horizontal ? trimSize.width : available.width,
horizontal ? available.height : trimSize.height
);
components.get ( TRIM ).get ( 0 ).setBounds ( trimBounds );
}
else
{
components.get ( TRIM ).get ( 0 ).setBounds ( HIDE_BOUNDS );
}
}
@Override
public Dimension preferredLayoutSize ( final Container container )
{
return preferredLayoutSize ( container, new HashMap () );
}
/**
* Returns preferred layout size.
*
* @param container layout container
* @param cache {@link Map} containing {@link Component} size caches
* @return preferred layout size
*/
protected Dimension preferredLayoutSize ( final Container container, final Map cache )
{
final Dimension ps = new Dimension ( 0, 0 );
/**
* Calculating content size.
* We don't need to differentiate {@link #MIDDLE} and {@link #FILL} constraints here.
* Whatever doesn't exist will simply be ignored by having zero sizes.
* Also {@link #TRIM} part is not included into preferred size at all as it only appears when size is not enough.
*/
final int orientation = getOrientation ( container );
final boolean ltr = container.getComponentOrientation ().isLeftToRight ();
expandSizeFromPart ( ps, START, partsSpacing, orientation, ltr, cache );
expandSizeFromPart ( ps, MIDDLE, partsSpacing, orientation, ltr, cache );
expandSizeFromPart ( ps, FILL, partsSpacing, orientation, ltr, cache );
expandSizeFromPart ( ps, END, partsSpacing, orientation, ltr, cache );
/**
* Adding insets afterwards.
*/
final Insets insets = container.getInsets ();
ps.width += insets.left + insets.right;
ps.height += insets.top + insets.bottom;
return ps;
}
/**
* Expands provided {@link Dimension} by preferred size of {@link Component}s under specified constraints.
*
* @param size {@link Dimension} to expand
* @param constraints layout constraints
* @param spacing extra spacing between this and previous parts
* @param orientation layout orientation
* @param ltr layout component orientation
* @param cache {@link Map} containing {@link Component} size caches
*/
protected void expandSizeFromPart ( final Dimension size, final String constraints, final int spacing,
final int orientation, final boolean ltr,
final Map cache )
{
final Dimension partSize = preferredPartSize ( constraints, orientation, ltr, cache );
if ( partSize.width > 0 )
{
final boolean horizontal = orientation == HORIZONTAL;
size.width = horizontal ? size.width + ( size.width > 0 ? spacing : 0 ) + partSize.width :
Math.max ( size.width, partSize.width );
size.height = horizontal ? Math.max ( size.height, partSize.height ) :
size.height + ( size.height > 0 ? spacing : 0 ) + partSize.height;
}
}
/**
* Returns preferred size of {@link Component}s under the specified constraints.
*
* @param c layout constraints
* @param orientation layout orientation
* @param ltr layout component orientation
* @param cache {@link Map} containing {@link Component} size caches
* @return preferred size of {@link Component}s under the specified constraints
*/
protected Dimension preferredPartSize ( final String c, final int orientation, final boolean ltr,
final Map cache )
{
final Dimension ps = new Dimension ( 0, 0 );
final String constraints = constraints ( c, orientation, ltr );
/**
* Retrieving components for constraints.
* We do not need to sort them for preferred size calculations.
*/
final List components = this.components.get ( constraints );
/**
* Collecting component preferred sizes.
*/
if ( CollectionUtils.notEmpty ( components ) )
{
final boolean horizontal = orientation == HORIZONTAL;
for ( final Component component : components )
{
final Dimension cps = preferredSize ( component, cache );
ps.width = horizontal ? ps.width + cps.width + spacing : Math.max ( ps.width, cps.width );
ps.height = horizontal ? Math.max ( ps.height, cps.height ) : ps.height + cps.height + spacing;
}
ps.width -= horizontal ? spacing : 0;
ps.height -= horizontal ? 0 : spacing;
}
return ps;
}
/**
* Returns cached {@link Component} preferred size.
*
* @param component {@link Component}
* @param cache {@link Map} containing {@link Component} size caches
* @return cached {@link Component} preferred size
*/
protected Dimension preferredSize ( final Component component, final Map cache )
{
Dimension ps = cache.get ( component );
if ( ps == null )
{
ps = component.getPreferredSize ();
cache.put ( component, ps );
}
return ps;
}
/**
* Returns actual constraints based on the layout orientation and component orientation.
*
* @param constraints layout constraints
* @param orientation layout orientation
* @param ltr layout component orientation
* @return actual constraints based on the layout orientation and component orientation
*/
protected String constraints ( final String constraints, final int orientation, final boolean ltr )
{
final String c;
if ( Objects.equals ( constraints, START ) )
{
c = ltr || orientation != HORIZONTAL ? START : END;
}
else if ( Objects.equals ( constraints, END ) )
{
c = ltr || orientation != HORIZONTAL ? END : START;
}
else
{
c = constraints;
}
return c;
}
/**
* Returns either {@link #HORIZONTAL} or {@link #VERTICAL} orientation for {@link Container}.
*
* @param container {@link Container} to retrieve orientation for
* @return either {@link #HORIZONTAL} or {@link #VERTICAL} orientation for {@link Container}
*/
public abstract int getOrientation ( Container container );
}