/* * 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 + "]"; } }