/*
* ExtendedGridLayout.java - a grid layout manager with variable cell sizes
* that supports colspans and rowspans
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Originally written by Björn Kautler for the jEdit project. This work has been
* placed into the public domain. You may use this work in any way and for any
* purpose you wish.
*
* THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
* IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
* _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
* OR REDISTRIBUTION OF THIS SOFTWARE.
*/
package org.gjt.sp.jedit.gui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import static java.awt.Component.CENTER_ALIGNMENT;
import static org.gjt.sp.jedit.gui.ExtendedGridLayoutConstraints.REMAINDER;
/** <p>A layout manager that places components in a rectangular grid
* with variable cell sizes that supports colspans and rowspans. </p>
*
* <p> The container is divided into rectangles, and each component is placed
* in a rectangular space defined by its colspan and rowspan.
* Each row is as large as the largest component in
* that row, and each column is as wide as the widest component in
* that column. </p>
* <p>
* This behavior is similar to
* <a href="http://download.oracle.com/javase/6/docs/api/java/awt/GridLayout.html">{@code java.awt.GridLayout}</a>
* but it supports different row heights and
* column widths for each row/column. </p>
* <p>
* For example, the following is a Dialog that lays out ten buttons
* exactly the same as in the example of the JavaDoc of
* <a href="http://download.oracle.com/javase/6/docs/api/java/awt/GridBagLayout.html">{@code java.awt.GridBagLayout}</a>
* with the difference of vertical and horizontal gaps that can be configured:
* <hr>
* <blockquote><pre><font color="#000000">
* <font color="#000000"> 1:</font><font color="#009966"><strong>import</strong></font> java.awt.Button;
* <font color="#000000"> 2:</font><font color="#009966"><strong>import</strong></font> java.awt.Dimension;
* <font color="#000000"> 3:</font>
* <font color="#000000"> 4:</font><font color="#009966"><strong>import</strong></font> javax.swing.JDialog;
* <font color="#990066"> 5:</font>
* <font color="#000000"> 6:</font><font color="#009966"><strong>import</strong></font> org.gjt.sp.jedit.gui.ExtendedGridLayout;
* <font color="#000000"> 7:</font><font color="#009966"><strong>import</strong></font> org.gjt.sp.jedit.gui.ExtendedGridLayoutConstraints;
* <font color="#000000"> 8:</font>
* <font color="#000000"> 9:</font><font color="#009966"><strong>import</strong></font> <font color="#006699"><strong>static</strong></font> org.gjt.sp.jedit.gui.ExtendedGridLayoutConstraints.REMAINDER;
* <font color="#990066"> 10:</font>
* <font color="#000000"> 11:</font><font color="#006699"><strong>public</strong></font> <font color="#0099ff"><strong>class</strong></font> ExampleDialog <font color="#006699"><strong>extends</strong></font> JDialog <font color="#000000"><strong>{</strong></font>
* <font color="#000000"> 12:</font> <font color="#006699"><strong>public</strong></font> <font color="#9966ff">ExampleDialog</font>() <font color="#000000"><strong>{</strong></font>
* <font color="#000000"> 13:</font> <font color="#cc00cc">super</font>(<font color="#cc00cc">null</font>,<font color="#ff00cc">"</font><font color="#ff00cc">Example</font><font color="#ff00cc"> </font><font color="#ff00cc">Dialog</font><font color="#ff00cc">"</font>,<font color="#cc00cc">true</font>);
* <font color="#000000"> 14:</font> <font color="#9966ff">setLayout</font>(<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayout</font>(<font color="#ff0000">5</font>,<font color="#ff0000">5</font>,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">Insets</font>(<font color="#ff0000">5</font>,<font color="#ff0000">5</font>,<font color="#ff0000">5</font>,<font color="#ff0000">5</font>)));
* <font color="#990066"> 15:</font>
* <font color="#000000"> 16:</font> <font color="#9966ff">add</font>(<font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button1</font><font color="#ff00cc">"</font>));
* <font color="#000000"> 17:</font> <font color="#9966ff">add</font>(<font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button2</font><font color="#ff00cc">"</font>));
* <font color="#000000"> 18:</font> <font color="#9966ff">add</font>(<font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button3</font><font color="#ff00cc">"</font>));
* <font color="#000000"> 19:</font> <font color="#9966ff">add</font>(<font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button4</font><font color="#ff00cc">"</font>));
* <font color="#990066"> 20:</font> Button button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button5</font><font color="#ff00cc">"</font>);
* <font color="#000000"> 21:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">1</font>,REMAINDER,<font color="#ff0000">1</font>,button));
* <font color="#000000"> 22:</font> button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button6</font><font color="#ff00cc">"</font>);
* <font color="#000000"> 23:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">2</font>,<font color="#ff0000">3</font>,<font color="#ff0000">1</font>,button));
* <font color="#000000"> 24:</font> button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button7</font><font color="#ff00cc">"</font>);
* <font color="#990066"> 25:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">2</font>,button));
* <font color="#000000"> 26:</font> button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button8</font><font color="#ff00cc">"</font>);
* <font color="#000000"> 27:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">3</font>,<font color="#ff0000">1</font>,<font color="#ff0000">2</font>,button));
* <font color="#000000"> 28:</font> button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button9</font><font color="#ff00cc">"</font>);
* <font color="#000000"> 29:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">3</font>,<font color="#ff0000">3</font>,<font color="#ff0000">1</font>,button));
* <font color="#990066"> 30:</font> button <font color="#000000"><strong>=</strong></font> <font color="#9966ff">makeButton</font>(<font color="#ff00cc">"</font><font color="#ff00cc">Button10</font><font color="#ff00cc">"</font>);
* <font color="#000000"> 31:</font> <font color="#9966ff">add</font>(button,<font color="#006699"><strong>new</strong></font> <font color="#9966ff">ExtendedGridLayoutConstraints</font>(<font color="#ff0000">4</font>,REMAINDER,<font color="#ff0000">1</font>,button));
* <font color="#000000"> 32:</font>
* <font color="#000000"> 33:</font> <font color="#9966ff">pack</font>();
* <font color="#000000"> 34:</font> <font color="#9966ff">setLocationRelativeTo</font>(<font color="#cc00cc">null</font>);
* <font color="#990066"> 35:</font> <font color="#9966ff">setVisible</font>(<font color="#cc00cc">true</font>);
* <font color="#000000"> 36:</font> <font color="#000000"><strong>}</strong></font>
* <font color="#000000"> 37:</font>
* <font color="#000000"> 38:</font> <font color="#006699"><strong>private</strong></font> Button <font color="#9966ff">makeButton</font>(String name) <font color="#000000"><strong>{</strong></font>
* <font color="#000000"> 39:</font> Button button <font color="#000000"><strong>=</strong></font> <font color="#006699"><strong>new</strong></font> <font color="#9966ff">Button</font>(name);
* <font color="#990066"> 40:</font> button.<font color="#9966ff">setMaximumSize</font>(<font color="#006699"><strong>new</strong></font> <font color="#9966ff">Dimension</font>(Integer.MAX_VALUE,Integer.MAX_VALUE));
* <font color="#000000"> 41:</font> <font color="#006699"><strong>return</strong></font> button;
* <font color="#000000"> 42:</font> <font color="#000000"><strong>}</strong></font>
* <font color="#000000"> 43:</font><font color="#000000"><strong>}</strong></font>
* </font></pre></blockquote>
* <hr>
* If you use {@code REMAINDER} as colspan or rowspan then a component takes
* up the remaining space in that column or row. Any additional components in
* a row are ignored and not displayed. Additional components in a column are
* moved rightside. If a rowspan hits a colspan, the colspan ends and the
* rowspan takes precedence.
* <p>
* Components for which {@code isVisible() == false} are ignored. Because
* of this, components can be replaced "in-place" by adding two components next to
* each other, with different {@code isVisible()} values, and toggling the
* {@code setVisible()} values of both when we wish to swap the currently
* visible component with the one that is hidden. </p>
*
* <p>
* If you want to reserve free space in a row inbetween components,
* add a <a href="http://download.oracle.com/javase/6/docs/api/javax/swing/Box.Filler.html">{@code javax.swing.Box.Filler}</a>
* to the layout if the free space is in the middle of a row,
* or just don't add components if the free space
* should be at the end of a row.</p>
* <p>
* If a row is taller, or a column is wider than the {@code maximumSize} of a component,
* the component is resized to its maximum size and aligned according to its
* {@code alignmentX} and {@code alignmentY} values. </p>
* <p>
* One instance of this class can be used to layout multiple
* containers at the same time. </p>
*
* @author Björn "Vampire" Kautler
* @version 1.0
* @see ExtendedGridLayoutConstraints
* @see <a href="http://download.oracle.com/javase/6/docs/api/java/awt/Component.html"><code>java.awt.Component</code></a>
* @see <a href="http://download.oracle.com/javase/6/docs/api/javax/swing/Box.Filler.html"><code>javax.swing.Box.Filler</code></a>
*/
public class ExtendedGridLayout implements LayoutManager2
{
/**
* This hashtable maintains the association between
* a component and its ExtendedGridLayoutConstraints.
* The Keys in {@code comptable} are the components and the
* values are the instances of {@code ExtendedGridLayoutConstraints}.
*
* @see ExtendedGridLayoutConstraints
*/
private final Hashtable<Component,ExtendedGridLayoutConstraints> comptable;
/**
* Specifies the horizontal space between two columns.
* The default value is 0.
*
* @see #distanceToBorders
* @see #vgap
*/
private final int hgap;
/**
* Specifies the vertical space between two rows.
* The default value is 0.
*
* @see #distanceToBorders
* @see #hgap
*/
private final int vgap;
/**
* Specifies the gap between the grid and the borders of the parent container.
* The default value is 0 for all four borders.
*
* @see #hgap
* @see #vgap
*/
private final Insets distanceToBorders;
/**
* An enum to tell the {@code getSize()} method which size is requested.
*
* @see #getSize()
*/
private static enum LayoutSize { MINIMUM, PREFERRED, MAXIMUM }
/**
* Creates an extended grid layout manager with the specified horizontal
* and vertical gap, and the specified distance to the borders
* of the parent container.
*
* @param hgap The horizontal space between two columns ({@literal >=0})
* @param vgap The vertical space between two rows ({@literal >=0})
* @param distanceToBorders The distances to the borders of the parent container
* @throws IllegalArgumentException if hgap {@literal < 0}
* @throws IllegalArgumentException if vgap {@literal < 0}
*/
public ExtendedGridLayout(int hgap, int vgap, Insets distanceToBorders)
{
if (hgap < 0)
{
throw new IllegalArgumentException("hgap must be non-negative (" + hgap + ')');
}
if (vgap < 0)
{
throw new IllegalArgumentException("vgap must be non-negative (" + vgap + ')');
}
this.hgap = hgap;
this.vgap = vgap;
this.distanceToBorders = (Insets)distanceToBorders.clone();
comptable = new Hashtable<Component,ExtendedGridLayoutConstraints>();
}
/**
* Creates an extended grid layout manager with zero horizontal
* and vertical gap, and zero distance to the borders
* of the parent container.
*/
public ExtendedGridLayout()
{
this(0,0,new Insets(0,0,0,0));
}
/**
* If the layout manager uses a per-component string,
* adds the component <code>component</code> to the layout,
* associating it with the string specified by <code>name</code>.
*
* @param name The string to be associated with the component.
* Has to be {@code null}, so that default constraints are used.
* @param component The component to be added
* @throws IllegalArgumentException if {@code name} is not {@code null}
* @see #addLayoutComponent(java.awt.Component, java.lang.Object)
*/
public void addLayoutComponent(String name, Component component)
{
addLayoutComponent(component,name);
}
/**
* Adds the specified component to the layout, using the specified
* constraints object.
*
* @param component The component to be added
* @param constraints Where/how the component is added to the layout.
* @throws IllegalArgumentException if {@code constraints} is not an ExtendedGridLayoutConstraints object
* @throws IllegalArgumentException if {@code constraints} is a placeholder
* @throws IllegalArgumentException if {@code constraints} is not the right one for the component
* @see ExtendedGridLayoutConstraints
*/
public void addLayoutComponent(Component component, Object constraints)
{
if (null == constraints)
{
constraints = new ExtendedGridLayoutConstraints(component);
}
if (constraints instanceof ExtendedGridLayoutConstraints)
{
ExtendedGridLayoutConstraints eglConstraints = (ExtendedGridLayoutConstraints)constraints;
if (eglConstraints.isPlaceholder())
{
throw new IllegalArgumentException("constraints must not be a placeholder");
}
else if (component != eglConstraints.getComponent())
{
throw new IllegalArgumentException("constraints is not the right one for this component");
}
comptable.put(component,eglConstraints);
}
else
{
throw new IllegalArgumentException("constraints must not be an ExtendedGridLayoutConstraints object");
}
}
/**
* Retrieves the constraints for the specified {@code component}.
* If {@code component} is not in the {@code ExtendedGridLayout},
* a set of default {@code ExtendedGridLayoutConstraints} are returned.
*
* @param component the {@code component} to be queried
* @return the contraints for the specified {@code component}
* @throws NullPointerException if {@code component} is {@code null}
* @see ExtendedGridLayoutConstraints
*/
private ExtendedGridLayoutConstraints lookupConstraints(Component component)
{
if (null == component)
{
throw new NullPointerException("component must not be null");
}
ExtendedGridLayoutConstraints constraints = comptable.get(component);
if (null == constraints)
{
constraints = new ExtendedGridLayoutConstraints(component);
comptable.put(component,constraints);
}
return constraints;
}
/**
* Removes the specified component from the layout.
*
* @param component The component to be removed
*/
public void removeLayoutComponent(Component component)
{
comptable.remove(component);
}
/**
* Returns the alignment along the X axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
*
* @param container The container for which the alignment should be returned
* @return {@code java.awt.Component.CENTER_ALIGNMENT}
*/
public float getLayoutAlignmentX(Container container)
{
return CENTER_ALIGNMENT;
}
/**
* Returns the alignment along the Y axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
*
* @param container The container for which the alignment should be returned
* @return {@code java.awt.Component.CENTER_ALIGNMENT}
*/
public float getLayoutAlignmentY(Container container)
{
return CENTER_ALIGNMENT;
}
/**
* Calculates the minimum size dimensions for the specified
* container, given the components it contains.
*
* @param parent The component to be laid out
* @return The minimum size for the container
* @see #maximumLayoutSize
* @see #preferredLayoutSize
*/
public Dimension minimumLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
List<List<ExtendedGridLayoutConstraints>> gridRows = new ArrayList<List<ExtendedGridLayoutConstraints>>();
Set<ExtendedGridLayoutConstraints> colspans = new HashSet<ExtendedGridLayoutConstraints>();
Set<ExtendedGridLayoutConstraints> rowspans = new HashSet<ExtendedGridLayoutConstraints>();
Dimension gridSize = buildGrid(parent,gridRows,colspans,rowspans);
return getSize(parent,LayoutSize.MINIMUM,false,gridSize,gridRows,colspans,rowspans,new int[0][0]);
}
}
/**
* Calculates the preferred size dimensions for the specified
* container, given the components it contains.
*
* @param parent The container to be laid out
* @return The preferred size for the container
* @see #maximumLayoutSize
* @see #minimumLayoutSize
*/
public Dimension preferredLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
List<List<ExtendedGridLayoutConstraints>> gridRows = new ArrayList<List<ExtendedGridLayoutConstraints>>();
Set<ExtendedGridLayoutConstraints> colspans = new HashSet<ExtendedGridLayoutConstraints>();
Set<ExtendedGridLayoutConstraints> rowspans = new HashSet<ExtendedGridLayoutConstraints>();
Dimension gridSize = buildGrid(parent,gridRows,colspans,rowspans);
return getSize(parent,LayoutSize.PREFERRED,false,gridSize,gridRows,colspans,rowspans,new int[0][0]);
}
}
/**
* Calculates the maximum size dimensions for the specified
* container, given the components it contains.
*
* @param parent The container to be laid out
* @return The maximum size for the container
* @see #minimumLayoutSize
* @see #preferredLayoutSize
*/
public Dimension maximumLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
List<List<ExtendedGridLayoutConstraints>> gridRows = new ArrayList<List<ExtendedGridLayoutConstraints>>();
Set<ExtendedGridLayoutConstraints> colspans = new HashSet<ExtendedGridLayoutConstraints>();
Set<ExtendedGridLayoutConstraints> rowspans = new HashSet<ExtendedGridLayoutConstraints>();
Dimension gridSize = buildGrid(parent,gridRows,colspans,rowspans);
return getSize(parent,LayoutSize.MAXIMUM,false,gridSize,gridRows,colspans,rowspans,new int[0][0]);
}
}
/**
* Invalidates the layout, indicating that if the layout manager
* has cached information it should be discarded.
*
* @param container The container for which the cached information should be discarded
*/
public void invalidateLayout(Container container)
{
}
/**
* Lays out the specified container.
*
* @param parent The container to be laid out
*/
public void layoutContainer(Container parent)
{
synchronized (parent.getTreeLock())
{
// Pass 1: build the grid
List<List<ExtendedGridLayoutConstraints>> gridRows = new ArrayList<List<ExtendedGridLayoutConstraints>>();
Set<ExtendedGridLayoutConstraints> colspans = new HashSet<ExtendedGridLayoutConstraints>();
Set<ExtendedGridLayoutConstraints> rowspans = new HashSet<ExtendedGridLayoutConstraints>();
Dimension gridSize = buildGrid(parent,gridRows,colspans,rowspans);
// Pass 2: compute minimum, preferred and maximum column widths / row heights
int[][] layoutSizes = new int[6][];
Dimension preferredSize = getSize(parent,LayoutSize.PREFERRED,true,gridSize,gridRows,colspans,rowspans,layoutSizes);
int[] minimumColWidths = layoutSizes[0];
int[] minimumRowHeights = layoutSizes[1];
int[] preferredColWidths = layoutSizes[2];
int[] preferredRowHeights = layoutSizes[3];
int[] maximumColWidths = layoutSizes[4];
int[] maximumRowHeights = layoutSizes[5];
// Pass 3: redistribute free space
Dimension parentSize = parent.getSize();
Insets insets = parent.getInsets();
int freeWidth = parentSize.width
- insets.left - insets.right
- (gridSize.width - 1) * hgap
- distanceToBorders.left - distanceToBorders.right;
int freeHeight = parentSize.height
- insets.top - insets.bottom
- (gridSize.height - 1) * vgap
- distanceToBorders.top - distanceToBorders.bottom;
redistributeSpace(preferredSize.width,
freeWidth,
0,gridSize.width,
preferredColWidths,
minimumColWidths,
maximumColWidths);
redistributeSpace(preferredSize.height,
freeHeight,
0,gridSize.height,
preferredRowHeights,
minimumRowHeights,
maximumRowHeights);
// Pass 4: layout components
for (int row=0, y=insets.top+distanceToBorders.top ; row<gridSize.height ; y+=preferredRowHeights[row]+vgap, row++)
{
List<ExtendedGridLayoutConstraints> gridRow = gridRows.get(row);
for (int col=0, x=insets.left+distanceToBorders.left ; col<gridSize.width; x+=preferredColWidths[col]+hgap, col++)
{
ExtendedGridLayoutConstraints cell = gridRow.get(col);
if ((null != cell) && (null != cell.getComponent()) && !cell.isPlaceholder())
{
Component component = cell.getComponent();
Dimension maxSize = component.getMaximumSize();
int fromCol = cell.getCol();
int colspan = cell.getEffectiveColspan();
int toCol = fromCol + colspan;
int width = 0;
for (int col2=fromCol ; col2<toCol ; col2++)
{
width += preferredColWidths[col2];
}
width += (colspan - 1) * hgap;
int fromRow = cell.getRow();
int rowspan = cell.getEffectiveRowspan();
int toRow = fromRow + rowspan;
int height = 0;
for (int row2=fromRow ; row2<toRow ; row2++)
{
height += preferredRowHeights[row2];
}
height += (rowspan - 1) * vgap;
int xCorrection = 0;
int yCorrection = 0;
if (width > maxSize.width)
{
xCorrection = (int)((width - maxSize.width) * component.getAlignmentX());
width = maxSize.width;
}
if (height > maxSize.height)
{
yCorrection = (int)((height-maxSize.height) * component.getAlignmentY());
height = maxSize.height;
}
component.setBounds(x + xCorrection, y + yCorrection, width, height);
}
}
}
}
}
/**
* Redistributs free space (positive or negative) to all available
* columns or rows while taking elements maximum and minimum sizes into
* account if possible.
*
* @param totalSize The cumulated preferred sizes of the components
* @param freeSize The available space for displaying components
* without any gaps between components or between
* the grid and the borders of the parent container
* @param start The start in the arrays of rows or columns inclusive
* @param stop The stop in the arrays of rows or columns exclusive
* @param preferredElementSizes The preferredSizes of the rows or columns.
* After invocation of this method, this array
* holds the sizes that should be used
* @param minimumElementSizes The minimumSizes of the rows or columns
* @param maximumElementSizes The maximumSizes of the rows or columns
*/
private void redistributeSpace(int totalSize, int freeSize,
int start, int stop,
int[] preferredElementSizes,
int[] minimumElementSizes,
int[] maximumElementSizes)
{
if (totalSize != freeSize)
{
boolean grow = totalSize < freeSize;
// calculate the size that is available for redistribution
freeSize = (freeSize - totalSize) * (grow ? 1 : -1);
while (freeSize > 0)
{
// calculate the amount of elements that can be resized without violating
// the minimum and maximum sizes and their current cumulated size
int modifyableAmount = 0;
long modifySize = 0;
for (int i=start ; i<stop ; i++)
{
if ((grow && (preferredElementSizes[i] < maximumElementSizes[i])) ||
(!grow && (preferredElementSizes[i] > minimumElementSizes[i])))
{
modifyableAmount++;
modifySize += preferredElementSizes[i];
}
}
boolean checkBounds = true;
// if all elements are at their minimum or maximum size, resize all elements
if (0 == modifyableAmount)
{
for (int i=start ; i<stop ; i++)
{
modifySize += preferredElementSizes[i];
}
checkBounds = false;
modifyableAmount = stop - start;
}
// to prevent an endless loop if the container gets resized to a very small amount
if (modifySize == 0)
{
break;
}
// resize the elements
if (freeSize < modifyableAmount)
{
for (int i=start ; i<stop ; i++)
{
if ((freeSize != 0) &&
(!checkBounds ||
(checkBounds &&
(grow && (preferredElementSizes[i] < maximumElementSizes[i])) ||
(!grow && (preferredElementSizes[i] > minimumElementSizes[i])))))
{
preferredElementSizes[i] += (grow ? 1 : -1);
if (0 > preferredElementSizes[i])
{
preferredElementSizes[i] = 0;
}
freeSize--;
}
}
}
else
{
long modifySizeAddition = 0;
double factor = (double)(freeSize + modifySize) / (double)modifySize;
for (int i=start ; i<stop ; i++)
{
long modifyableSize = (checkBounds ? (grow ? maximumElementSizes[i] - preferredElementSizes[i] : preferredElementSizes[i] - minimumElementSizes[i]) : Integer.MAX_VALUE - preferredElementSizes[i]);
long elementModifySize = Math.abs(Math.round((factor * preferredElementSizes[i]) - preferredElementSizes[i]));
if (elementModifySize <= modifyableSize)
{
preferredElementSizes[i] += (grow ? elementModifySize : -elementModifySize);
modifySizeAddition += (grow ? elementModifySize : -elementModifySize);
freeSize -= elementModifySize;
}
else
{
preferredElementSizes[i] += (grow ? modifyableSize : -modifyableSize);
modifySizeAddition += (grow ? modifyableSize : -modifyableSize);
freeSize -= modifyableSize;
}
if (0 > preferredElementSizes[i])
{
preferredElementSizes[i] = 0;
}
}
modifySize += modifySizeAddition;
}
}
}
}
/**
* Calculates the minimum, preferred or maximum size dimensions
* for the specified container, given the components it contains.
*
* @param parent The container to be laid out
* @param layoutSize if {@code LayoutSize.MINIMUM} compute minimum layout size,
* if {@code LayoutSize.PREFERRED} compute preferred layout size,
* if {@code LayoutSize.MAXIMUM} compute maximum layout size,
* if {@code fillRawSizes} is {@code true}, the layout size is computed
* without applying gaps between components or between
* the grid and the borders of the parent container
* @param fillRawSizes Whether to fill the resultArrays with the raw
* row heights and column widths and whether to apply
* gaps between components or between
* the grid and the borders of the parent container
* when computing the layout size
* @param gridSize The amount of rows and columns in the grid
* @param gridRows The grid holding the constraints for the components
* @param colspans In this {@code Set} the constraints which are part
* of a colspan are stored
* @param rowspans In this {@code Set} the constraints which are part
* of a rowspan are stored
* @param resultArrays If {@code fillRawSizes} is {@code true}, the first six arrays
* get filled with the raw row heights and column widths.
* resultArrays[0] = resultMinimumColWidths;
* resultArrays[1] = resultMinimumRowHeights;
* resultArrays[2] = resultPreferredColWidths;
* resultArrays[3] = resultPreferredRowHeights;
* resultArrays[4] = resultMaximumColWidths;
* resultArrays[5] = resultMaximumRowHeights;
* @return The minimum, preferred or maximum size dimensions for the specified container
* @throws IllegalArgumentException If {@code fillRawSizes == true} and {@code resultArrays.length < 6}
*/
private Dimension getSize(Container parent, LayoutSize layoutSize, boolean fillRawSizes,
Dimension gridSize, List<List<ExtendedGridLayoutConstraints>> gridRows,
Set<ExtendedGridLayoutConstraints> colspans,
Set<ExtendedGridLayoutConstraints> rowspans,
int[][] resultArrays)
{
if (fillRawSizes && (resultArrays.length < 6))
{
throw new IllegalArgumentException("If fillRawSizes is true, resultArrays.length must be >= 6 (" + resultArrays.length + ')');
}
int[] minimumColWidths = new int[gridSize.width];
int[] minimumRowHeights = new int[gridSize.height];
int[] preferredColWidths = new int[gridSize.width];
int[] preferredRowHeights = new int[gridSize.height];
int[] maximumColWidths = new int[gridSize.width];
int[] maximumRowHeights = new int[gridSize.height];
Arrays.fill(minimumColWidths,0);
Arrays.fill(minimumRowHeights,0);
Arrays.fill(preferredColWidths,0);
Arrays.fill(preferredRowHeights,0);
Arrays.fill(maximumColWidths,0);
Arrays.fill(maximumRowHeights,0);
// get the maximum of the minimum sizes,
// the maximum of the preferred sizes and
// the minimum of the maximum sizes
// of all rows and columns, not taking
// rowspans and colspans into account
for (int row=0 ; row<gridSize.height ; row++)
{
List<ExtendedGridLayoutConstraints> gridRow = gridRows.get(row);
for (int col=0 ; col<gridSize.width ; col++)
{
ExtendedGridLayoutConstraints cell = gridRow.get(col);
if ((null != cell) && (null != cell.getComponent()))
{
Component component = cell.getComponent();
Dimension minimumSize = component.getMinimumSize();
Dimension preferredSize = component.getPreferredSize();
Dimension maximumSize = component.getMaximumSize();
if (!colspans.contains(cell))
{
minimumColWidths[col] = Math.max(minimumColWidths[col],minimumSize.width);
preferredColWidths[col] = Math.max(preferredColWidths[col],preferredSize.width);
maximumColWidths[col] = Math.max(maximumColWidths[col],maximumSize.width);
}
if (!rowspans.contains(cell))
{
minimumRowHeights[row] = Math.max(minimumRowHeights[row],minimumSize.height);
preferredRowHeights[row] = Math.max(preferredRowHeights[row],preferredSize.height);
maximumRowHeights[row] = Math.max(maximumRowHeights[row],maximumSize.height);
}
}
}
}
// correct cases where
// minimumColWidths[col] <= preferredColWidths[col] <= maximumColWidths[col]
// is not true by clipping to the minimumColWidths and maximumColWidths
for (int col=0 ; col<gridSize.width ; col++)
{
if (minimumColWidths[col] >= maximumColWidths[col])
{
maximumColWidths[col] = minimumColWidths[col];
preferredColWidths[col] = minimumColWidths[col];
}
else if (preferredColWidths[col] < minimumColWidths[col])
{
preferredColWidths[col] = minimumColWidths[col];
}
else if (preferredColWidths[col] > maximumColWidths[col])
{
preferredColWidths[col] = maximumColWidths[col];
}
}
// plug in the colspans and correct the minimum, preferred and
// maximum column widths the colspans are part of
for (ExtendedGridLayoutConstraints cell : colspans)
{
int fromCol = cell.getCol();
int colspan = cell.getEffectiveColspan();
int toCol = fromCol + colspan;
int currentMinimumColWidth = 0;
int currentPreferredColWidth = 0;
int currentMaximumColWidth = 0;
for (int col=fromCol ; col<toCol ; col++)
{
int minimumColWidth = minimumColWidths[col];
if ((Integer.MAX_VALUE-minimumColWidth) < currentMinimumColWidth)
{
currentMinimumColWidth = Integer.MAX_VALUE;
}
else
{
currentMinimumColWidth += minimumColWidth;
}
int preferredColWidth = preferredColWidths[col];
if ((Integer.MAX_VALUE-preferredColWidth) < currentPreferredColWidth)
{
currentPreferredColWidth = Integer.MAX_VALUE;
}
else
{
currentPreferredColWidth += preferredColWidth;
}
int maximumColWidth = maximumColWidths[col];
if ((Integer.MAX_VALUE-maximumColWidth) < currentMaximumColWidth)
{
currentMaximumColWidth = Integer.MAX_VALUE;
}
else
{
currentMaximumColWidth += maximumColWidth;
}
}
Component component = cell.getComponent();
int wantedMaximumColWidth = component.getMaximumSize().width - ((colspan - 1) * hgap);
if (currentMaximumColWidth < wantedMaximumColWidth)
{
redistributeSpace(currentMaximumColWidth,
wantedMaximumColWidth,
fromCol,toCol,
maximumColWidths,
maximumColWidths,
maximumColWidths);
}
int wantedMinimumColWidth = component.getMinimumSize().width - ((colspan - 1) * hgap);
if (currentMinimumColWidth < wantedMinimumColWidth)
{
redistributeSpace(currentMinimumColWidth,
wantedMinimumColWidth,
fromCol,toCol,
minimumColWidths,
minimumColWidths,
maximumColWidths);
}
int wantedPreferredColWidth = component.getPreferredSize().width - ((colspan - 1) * hgap);
if (currentPreferredColWidth < wantedPreferredColWidth)
{
redistributeSpace(currentPreferredColWidth,
wantedPreferredColWidth,
fromCol,toCol,
preferredColWidths,
minimumColWidths,
maximumColWidths);
}
}
// correct cases where
// minimumColWidths[col] <= preferredColWidths[col] <= maximumColWidths[col]
// is not true by clipping to the minimumColWidths and maximumColWidths
for (int col=0 ; col<gridSize.width ; col++)
{
if (minimumColWidths[col] >= maximumColWidths[col])
{
maximumColWidths[col] = minimumColWidths[col];
preferredColWidths[col] = minimumColWidths[col];
}
else if (preferredColWidths[col] < minimumColWidths[col])
{
preferredColWidths[col] = minimumColWidths[col];
}
else if (preferredColWidths[col] > maximumColWidths[col])
{
preferredColWidths[col] = maximumColWidths[col];
}
}
// correct cases where
// minimumRowHeights[row] <= preferredRowHeights[row] <= maximumRowHeights[row]
// is not true by clipping to the minimumRowHeights and maximumRowHeights
for (int row=0 ; row<gridSize.height ; row++)
{
if (minimumRowHeights[row] >= maximumRowHeights[row])
{
maximumRowHeights[row] = minimumRowHeights[row];
preferredRowHeights[row] = minimumRowHeights[row];
}
else if (preferredRowHeights[row] < minimumRowHeights[row])
{
preferredRowHeights[row] = minimumRowHeights[row];
}
else if (preferredRowHeights[row] > maximumRowHeights[row])
{
preferredRowHeights[row] = maximumRowHeights[row];
}
}
// plug in the rowspans and correct the minimum, preferred and
// maximum row heights the rowspans are part of
for (ExtendedGridLayoutConstraints cell : rowspans)
{
int fromRow = cell.getRow();
int rowspan = cell.getEffectiveRowspan();
int toRow = fromRow + rowspan;
int currentMinimumRowHeight = 0;
int currentPreferredRowHeight = 0;
int currentMaximumRowHeight = 0;
for (int row=fromRow ; row<toRow ; row++)
{
int minimumRowHeight = minimumRowHeights[row];
if ((Integer.MAX_VALUE-minimumRowHeight) < currentMinimumRowHeight)
{
currentMinimumRowHeight = Integer.MAX_VALUE;
}
else
{
currentMinimumRowHeight += minimumRowHeight;
}
int preferredRowHeight = preferredRowHeights[row];
if ((Integer.MAX_VALUE-preferredRowHeight) < currentPreferredRowHeight)
{
currentPreferredRowHeight = Integer.MAX_VALUE;
}
else
{
currentPreferredRowHeight += preferredRowHeight;
}
int maximumRowHeight = maximumRowHeights[row];
if ((Integer.MAX_VALUE-maximumRowHeight) < currentMaximumRowHeight)
{
currentMaximumRowHeight = Integer.MAX_VALUE;
}
else
{
currentMaximumRowHeight += maximumRowHeight;
}
}
Component component = cell.getComponent();
int wantedMaximumRowHeight = component.getMaximumSize().height - ((rowspan - 1) * vgap);
if (currentMaximumRowHeight < wantedMaximumRowHeight)
{
redistributeSpace(currentMaximumRowHeight,
wantedMaximumRowHeight,
fromRow,toRow,
maximumRowHeights,
maximumRowHeights,
maximumRowHeights);
}
int wantedMinimumRowHeight = component.getMinimumSize().height - ((rowspan - 1) * vgap);
if (currentMinimumRowHeight < wantedMinimumRowHeight)
{
redistributeSpace(currentMinimumRowHeight,
wantedMinimumRowHeight,
fromRow,toRow,
minimumRowHeights,
minimumRowHeights,
maximumRowHeights);
}
int wantedPreferredRowHeight = component.getPreferredSize().height - ((rowspan - 1) * vgap);
if (currentPreferredRowHeight < wantedPreferredRowHeight)
{
redistributeSpace(currentPreferredRowHeight,
wantedPreferredRowHeight,
fromRow,toRow,
preferredRowHeights,
minimumRowHeights,
maximumRowHeights);
}
}
// correct cases where
// minimumRowHeights[row] <= preferredRowHeights[row] <= maximumRowHeights[row]
// is not true by clipping to the minimumRowHeights and maximumRowHeights
for (int row=0 ; row<gridSize.height ; row++)
{
if (minimumRowHeights[row] >= maximumRowHeights[row])
{
maximumRowHeights[row] = minimumRowHeights[row];
preferredRowHeights[row] = minimumRowHeights[row];
}
else if (preferredRowHeights[row] < minimumRowHeights[row])
{
preferredRowHeights[row] = minimumRowHeights[row];
}
else if (preferredRowHeights[row] > maximumRowHeights[row])
{
preferredRowHeights[row] = maximumRowHeights[row];
}
}
// copies the computed sizes to the result arrays
if (fillRawSizes)
{
resultArrays[0] = minimumColWidths;
resultArrays[1] = minimumRowHeights;
resultArrays[2] = preferredColWidths;
resultArrays[3] = preferredRowHeights;
resultArrays[4] = maximumColWidths;
resultArrays[5] = maximumRowHeights;
}
// sums up the sizes for return value
int[] colWidths;
int[] rowHeights;
switch (layoutSize)
{
case MINIMUM:
colWidths = minimumColWidths;
rowHeights = minimumRowHeights;
break;
case PREFERRED:
colWidths = preferredColWidths;
rowHeights = preferredRowHeights;
break;
case MAXIMUM:
colWidths = maximumColWidths;
rowHeights = maximumRowHeights;
break;
default:
throw new InternalError("Missing case branch for LayoutSize: " + layoutSize);
}
long totalWidth = 0;
long totalHeight = 0;
for (int width : colWidths)
{
totalWidth += width;
}
for (int height : rowHeights)
{
totalHeight += height;
}
// add space between components or between
// componetns and the borders of the parent container
if (!fillRawSizes)
{
Insets insets = parent.getInsets();
totalWidth += insets.left + insets.right + ((gridSize.width - 1) * hgap) + distanceToBorders.left + distanceToBorders.right;
totalHeight += insets.top + insets.bottom + ((gridSize.height - 1) * vgap) + distanceToBorders.top + distanceToBorders.bottom;
}
// clip the size to Integer.MAX_VALUE if too big
if (totalWidth > Integer.MAX_VALUE)
{
totalWidth = Integer.MAX_VALUE;
}
if (totalHeight > Integer.MAX_VALUE)
{
totalHeight = Integer.MAX_VALUE;
}
return new Dimension((int)totalWidth,(int)totalHeight);
}
/**
* Builds up the grid for the specified container,
* given the components it contains.
*
* @param parent The container to be laid out
* @param gridRows In this {@code List<List>} the grid gets stored
* @param colspans In this {@code Set} the constraints which are part
* of a colspan get stored
* @param rowspans In this {@code Set} the constraints which are part
* of a rowspan get stored
* @return The amount of rows and columns in the grid
*/
private Dimension buildGrid(Container parent, List<List<ExtendedGridLayoutConstraints>> gridRows,
Set<ExtendedGridLayoutConstraints> colspans, Set<ExtendedGridLayoutConstraints> rowspans)
{
// put the parent's components in source rows
List<List<ExtendedGridLayoutConstraints>> rows = new ArrayList<List<ExtendedGridLayoutConstraints>>();
Component[] components = parent.getComponents();
for (Component component : components)
{
if (component.isVisible())
{
ExtendedGridLayoutConstraints constraints = lookupConstraints(component).getWorkCopy();
int rowNumber = constraints.getRow();
for (int i=rowNumber, c=rows.size() ; i>=c ; i--)
{
rows.add(new ArrayList<ExtendedGridLayoutConstraints>());
}
List<ExtendedGridLayoutConstraints> row = rows.get(rowNumber);
row.add(constraints);
}
}
// initialize the rowIterators, gridRowIterators and gridRows
List<Iterator<ExtendedGridLayoutConstraints>> rowIterators = new ArrayList<Iterator<ExtendedGridLayoutConstraints>>();
List<ListIterator<ExtendedGridLayoutConstraints>> gridRowIterators = new ArrayList<ListIterator<ExtendedGridLayoutConstraints>>();
boolean haveNext = false;
for (List<ExtendedGridLayoutConstraints> row : rows)
{
Iterator<ExtendedGridLayoutConstraints> rowIterator = row.iterator();
rowIterators.add(rowIterator);
if (rowIterator.hasNext())
{
haveNext = true;
}
List<ExtendedGridLayoutConstraints> gridRow = new ArrayList<ExtendedGridLayoutConstraints>();
gridRows.add(gridRow);
gridRowIterators.add(gridRow.listIterator());
}
// build the grid
int col = -1;
while (haveNext)
{
col++;
haveNext = false;
for (int row=0, c=gridRows.size() ; row<c ; row++)
{
Iterator<ExtendedGridLayoutConstraints> rowIterator = rowIterators.get(row);
ListIterator<ExtendedGridLayoutConstraints> gridRowIterator = gridRowIterators.get(row);
// look for a rowspan in the previous row
if (row > 0)
{
ExtendedGridLayoutConstraints rowspanSource = gridRows.get(row-1).get(col);
if (null != rowspanSource)
{
ExtendedGridLayoutConstraints rowspanPlaceholder = rowspanSource.getRowspanPlaceholder(true);
if (null != rowspanPlaceholder)
{
rowspans.add(rowspanSource);
gridRowIterator.add(rowspanPlaceholder);
if (null != rowspanPlaceholder.getColspanPlaceholder(false))
{
switch (rowspanPlaceholder.getColspan())
{
case REMAINDER:
break;
default:
haveNext = true;
}
}
else if (rowIterator.hasNext())
{
haveNext = true;
}
continue;
}
}
}
// look for a colspan in the previous column
if (gridRowIterator.hasPrevious())
{
ExtendedGridLayoutConstraints colspanSource = gridRowIterator.previous();
gridRowIterator.next();
if (null != colspanSource)
{
ExtendedGridLayoutConstraints colspanPlaceholder = colspanSource.getColspanPlaceholder(true);
if (null != colspanPlaceholder)
{
colspans.add(colspanSource);
gridRowIterator.add(colspanPlaceholder);
if (null != colspanPlaceholder.getColspanPlaceholder(false))
{
switch (colspanPlaceholder.getColspan())
{
case REMAINDER:
break;
default:
haveNext = true;
}
}
else if (rowIterator.hasNext())
{
haveNext = true;
}
continue;
}
}
}
// add a new element or null
if (rowIterator.hasNext())
{
ExtendedGridLayoutConstraints newConstraints = rowIterator.next();
newConstraints.setCol(col);
gridRowIterator.add(newConstraints);
if (null != newConstraints.getColspanPlaceholder(false))
{
switch (newConstraints.getColspan())
{
case REMAINDER:
break;
default:
haveNext = true;
}
}
else if (rowIterator.hasNext())
{
haveNext = true;
}
}
else
{
gridRowIterator.add(null);
}
}
}
// check the last gridRow for rowspans and probably add rows for these
haveNext = false;
int gridRowsSize = gridRows.size();
if (gridRowsSize > 0)
{
for (ExtendedGridLayoutConstraints cell :
gridRows.get(gridRows.size() - 1))
{
if ((null != cell) && ((REMAINDER != cell.getRowspan())
&& (null != cell.getRowspanPlaceholder(false))))
{
haveNext = true;
break;
}
}
while (haveNext)
{
haveNext = false;
ListIterator<ExtendedGridLayoutConstraints> gridRowIterator =
gridRows.get(gridRows.size()-1).listIterator();
List<ExtendedGridLayoutConstraints> gridRow = new ArrayList<ExtendedGridLayoutConstraints>();
gridRows.add(gridRow);
ListIterator<ExtendedGridLayoutConstraints> newGridRowIterator = gridRow.listIterator();
while (gridRowIterator.hasNext())
{
ExtendedGridLayoutConstraints cell = gridRowIterator.next();
if ((null != cell) &&
(null != cell.getRowspanPlaceholder(false)))
{
rowspans.add(cell);
ExtendedGridLayoutConstraints rowspanPlaceholder = cell.getRowspanPlaceholder(true);
newGridRowIterator.add(rowspanPlaceholder);
}
else
{
newGridRowIterator.add(null);
}
}
gridRowIterator = gridRow.listIterator();
while (gridRowIterator.hasNext())
{
ExtendedGridLayoutConstraints cell = gridRowIterator.next();
if ((null != cell) &&
((REMAINDER != cell.getRowspan()) &&
(null != cell.getRowspanPlaceholder(false))))
{
haveNext = true;
break;
}
}
}
}
return new Dimension(col+1,gridRows.size());
}
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
*
* @return a string representation of the object.
*/
public String toString()
{
return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap
+ ",distanceToBorders=" + distanceToBorders
+ ",comptable=" + comptable + "]";
}
}