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

net.sf.jguiraffe.gui.layout.PercentLayoutBase Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
/*
 * Copyright 2006-2010 The JGUIraffe Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.jguiraffe.gui.layout;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * 

* The main class of the percent layout manager. *

*

* Percent layout provides a table-like layout, which is organized in columns * and rows, each of which defined by a {@link CellConstraints} * object. With these constraints objects it is possible to set a cell's * alignment and its minimum size. A cell can also be assigned weight factors * for its height and width. If there is more space available than needed by the * existing cells, the remaining space is divided and assigned to cells with a * weight factor greater than 0. So each cell can be given a certain percentage * of the remaining space, thus the name of this layout manager. *

*

* Sometimes certain columns or rows in the layout should have the same size, * even if they contain components with different preferred or minimum sizes. To * achieve this, {@link CellGroup} objects can be added to this * layout manager. These objects define the indices of the columns and rows, * which belong to the same group. All cells in a group have the same initial * size. To ensure that the affected cells have always the same size, their * weight factors must also be equal. *

*

* This class is an abstract base class that implements the complete layouting * algorithm. There will be concrete implementations for different layout types * that are based on the central percent layout functionality. These classes * will serve as adapters for specific layouts; they will create a layout * description that can be handled by this base class. *

*

* The family of percent layout classes is independent on a concrete GUI * library. It can work together e.g. with Swing or SWT. To achieve this access * to the managed GUI components is encapsulated by the * {@link net.sf.jguiraffe.gui.layout.PercentLayoutPlatformAdapter} * interface. A platform specific implementation of this interface must be * passed to an instance of this class. *

*

* Note: The {@code PercentLayout} class is not thread safe. It should be * accessed by a single thread (the GUI thread) only. *

* * @author Oliver Heger * @version $Id: PercentLayoutBase.java 195 2010-08-30 19:54:41Z oheger $ */ public abstract class PercentLayoutBase implements Serializable { /** * The serial version UID. */ private static final long serialVersionUID = 20090730L; /** Constant for the delimiters for cell constraints. */ private static final String CONSTRAINTS_DELIMITERS = " ,;"; /** Stores the column constraints. */ private CellConstraints[] columnConstraints; /** Stores the row constraints. */ private CellConstraints[] rowConstraints; /** Stores information about the contained components. */ private CellData[][] cells; /** The builder for creating constraints objects. */ private transient CellConstraints.Builder constraintsBuilder; /** Stores a reference to the associated platform adapter. */ private PercentLayoutPlatformAdapter platformAdapter; /** Stores the column groups. */ private Collection columnGroups; /** Stores the row groups. */ private Collection rowGroups; /** Stores information about components that span multiple columns. */ private final List multiColumns = new LinkedList(); /** Stores information about components that span multiple rows. */ private final List multiRows = new LinkedList(); /** Stores the total weight factor for all columns. */ private int totalWeightX = -1; /** Stores the total weight factor for all rows. */ private int totalWeightY = -1; /** Helper flag that avoids re-entrance of the initCells() method. */ private volatile boolean inInit; /** A flag whether this layout can shrink below its preferred size. */ private boolean canShrink; /** * Creates a new, uninitialized instance of PercentLayoutBase. * If this constructor is used, the concrete implementation of the * {@link #initCells(PercentLayoutPlatformAdapter)} method must perform all * initialization. */ protected PercentLayoutBase() { super(); } /** * Creates a new instance of PercentLayoutBase and sets the * numbers of the rows and columns. The constraints for the cells are set to * default values. * * @param cols the number of columns * @param rows the number of rows */ protected PercentLayoutBase(int cols, int rows) { this(); initDimensions(cols, rows); } /** * Creates a new instance of PercentLayoutBase and initializes * it. The constraints for the columns and rows are specified in the passed * collections, which must contain instances of * {@link CellConstraints}. * * @param colConstr a collection with column constraints * @param rowConstr a collection with row constraints */ protected PercentLayoutBase( Collection colConstr, Collection rowConstr) { this(); if (colConstr == null || colConstr.size() < 1 || rowConstr == null || rowConstr.size() < 1) { throw new IllegalArgumentException( "Undefined column or row constraints!"); } initFromCollections(colConstr, rowConstr); } /** * Creates a new instance of PercentLayoutBase and initializes * it. The column and row constraints are defined as strings. These strings * must contain valid specifications of cell constraints as defined in the * documentation of {@link CellConstraints}. As separators * between two cell definitions the following characters can be used: " * ,;". * * @param colConstr a string defining column constraints * @param rowConstr a string defining row constraints */ protected PercentLayoutBase(String colConstr, String rowConstr) { this(); Collection cols = parseConstraints(colConstr, true); Collection rows = parseConstraints(rowConstr, false); initFromCollections(cols, rows); } /** * Returns the platform adapter associated with this layout class. * * @return the platform adapter */ public PercentLayoutPlatformAdapter getPlatformAdapter() { return platformAdapter; } /** * Sets the platform adapter for this layout manager. This adapter allows * access to and manipulation of the managed components. * * @param platformAdapter the platform adapter to use */ public void setPlatformAdapter(PercentLayoutPlatformAdapter platformAdapter) { this.platformAdapter = platformAdapter; } /** * Returns a flag whether this layout can shrink below its preferred size. * * @return a flag whether this layout can shrink below its preferred size */ public boolean isCanShrink() { return canShrink; } /** * Sets a flag whether this layout can shrink below its preferred size. If * this flag is set and the space available for the hosting container * becomes smaller than the layout's preferred size, the layout tries to * reduce its size further using the minimum size defined for the components * contained. * * @param canShrink the shrink flag */ public void setCanShrink(boolean canShrink) { this.canShrink = canShrink; } /** * Returns the number of columns in this layout. * * @return the number of columns */ public int getColumnCount() { ensureInit(); return columnConstraints.length; } /** * Returns the number of rows in this layout. * * @return the number of rows */ public int getRowCount() { ensureInit(); return rowConstraints.length; } /** * Returns the column constraints object for the column with the given * index. * * @param idx the index (0 based) * @return the column constraints object for this column */ public CellConstraints getColumnConstraints(int idx) { return getInternalAllColumnConstraints()[idx]; } /** * Sets the column constraints object for the column with the given index. * * @param idx the index of the column (0 based) * @param cc the constraints object */ public void setColumnConstraints(int idx, CellConstraints cc) { columnConstraints[idx] = cc; totalWeightX = -1; } /** * Returns an array with the current column constraints. * * @return the column constraints */ public CellConstraints[] getAllColumnConstraints() { return getInternalAllColumnConstraints().clone(); } /** * Returns the row constraints object for the row with the given index. * * @param idx the index (0 based) * @return the row constraints object for this row */ public CellConstraints getRowConstraints(int idx) { return getInternalAllRowConstraints()[idx]; } /** * Sets the row constraints object for the row with the given index. * * @param idx the index of the row (0 based) * @param cc the constraints object */ public void setRowConstraints(int idx, CellConstraints cc) { rowConstraints[idx] = cc; totalWeightY = -1; } /** * Returns an array with the current row constraints. * * @return the row constraints */ public CellConstraints[] getAllRowConstraints() { return getInternalAllRowConstraints().clone(); } /** * Returns an unmodifiable collection with the column groups defined for * this layout. This collection may be empty, but never null. * * @return a collection with the CellGroup objects for columns */ public Collection getColumnGroups() { ensureInit(); return unmodifiableCellGroups(columnGroups); } /** * Adds a CellGroup object for columns to this layout manager. * This causes the columns defined by this group object to have the same * width (as long as their weight factors are equal). * * @param grp the group object */ public void addColumnGroup(CellGroup grp) { if (columnGroups == null) { columnGroups = new LinkedList(); } columnGroups.add(grp); } /** * Returns an unmodifiable collection with the row groups defined for this * layout. This collection may be empty, but never null . * * @return a collection with the CellGroup objects for rows */ public Collection getRowGroups() { return unmodifiableCellGroups(rowGroups); } /** * Adds a CellGroup object for rows to this layout manager. * This causes the rows defined by this group object to have the same height * (as long as their weight factors are equal). * * @param grp the group object */ public void addRowGroup(CellGroup grp) { if (rowGroups == null) { rowGroups = new LinkedList(); } rowGroups.add(grp); } /** * Clears all cached values. Can be called if something has changed at the * associated container. */ public void flushCache() { totalWeightX = -1; totalWeightY = -1; cells = null; } /** * Removes the specified component from this layout. * * @param comp the component to remove * @return a flag whether the component was found and could be removed */ public boolean removeComponent(Object comp) { for (int i = 0; i < getColumnCount(); i++) { for (int j = 0; j < getRowCount(); j++) { Object c = getComponent(i, j); if (c != null && comp == c) { PercentData pd = getPercentData(i, j); if (pd.getSpanX() > 1) { removeMultiSpanComponent(multiColumns, comp); } if (pd.getSpanY() > 1) { removeMultiSpanComponent(multiRows, comp); } cells[i][j] = null; return true; } } } return false; } /** * Returns a reference to the associated platform adapter. If no such * adapter has been set, an IllegalStateException exception is * thrown. * * @return the platform adapter * @throws IllegalStateException if no platform adapter is set */ protected final PercentLayoutPlatformAdapter fetchPlatformAdapter() { if (getPlatformAdapter() == null) { throw new IllegalStateException( "No platform adapter set for this percent layout!"); } return getPlatformAdapter(); } /** * Returns the builder instance for creating {@link CellConstraints} * objects. Each instance of this class is associated with such a builder. * Sub classes or clients can use it for creating their constraints. * * @return the builder instance for creating {@link CellConstraints} objects */ public final CellConstraints.Builder getConstraintsBuilder() { if (constraintsBuilder == null) { // create on demand // this is not thread-safe; however, we have stated in the class // comment that the whole class is not thread-safe constraintsBuilder = new CellConstraints.Builder(); } return constraintsBuilder; } /** * Initializes the dimensions of the table that holds the layout. Constructs * the internal arrays with the appropriate sizes and initializes them with * default values. * * @param cols the number of columns * @param rows the number of rows */ protected final void initDimensions(int cols, int rows) { if (cols < 1 || rows < 1) { throw new IllegalArgumentException( "Number of columns or rows must be greater 0!"); } columnConstraints = new CellConstraints[cols]; CellConstraints defColumn = getConstraintsBuilder().defaultColumn() .create(); for (int i = 0; i < cols; i++) { columnConstraints[i] = defColumn; } rowConstraints = new CellConstraints[rows]; CellConstraints defRow = getConstraintsBuilder().defaultRow().create(); for (int i = 0; i < rows; i++) { rowConstraints[i] = defRow; } } /** * Returns the internal array of all cell constraints objects for the * layout's column. This method can be used by subclasses for direct * read-only access to the layout's column constraints. The public * {@link #getAllColumnConstraints()} method returns a defensive copy of * this array. So for performance reasons this method should be used by * subclasses. * * @return the array with the layout's column constraints objects */ protected final CellConstraints[] getInternalAllColumnConstraints() { ensureInit(); return columnConstraints; } /** * Returns the internal array of all cell constraints objects for the * layout's rows. This method can be used by subclasses for direct read-only * access to the layout's row constraints. The public * {@link #getAllRowConstraints()} method returns a defensive copy of this * array. So for performance reasons this method should be used by * subclasses. * * @return the array with the layout's row constraints objects */ protected final CellConstraints[] getInternalAllRowConstraints() { ensureInit(); return rowConstraints; } /** * Returns the component at the specified position of this layout. * * @param col the column * @param row the row * @return the component at this position (null if this cell is not * occupied) */ protected Object getComponent(int col, int row) { CellData cd = getCellData(col, row); return (cd != null) ? cd.getComponent() : null; } /** * Returns the constraints object for the component at the specified * position of this layout. * * @param col the column * @param row the row * @return the constraints at this position (null if this cell is not * occupied) */ protected PercentData getPercentData(int col, int row) { CellData cd = getCellData(col, row); return (cd != null) ? cd.getConstraints() : null; } /** * Checks the specified constraints object. This method ensures that the * passed in constraints object is an instance of {@link PercentData} and * that only valid values for column and row number and the spans are * accepted. If invalid values are detected, an {@code * IllegalStateException} exception is thrown. * * @param constraintsObj the constraints to check * @return the percent data object to use * @throws IllegalStateException if invalid constraints are detected */ protected PercentData checkConstraints(Object constraintsObj) { if (!(constraintsObj instanceof PercentData)) { throw new IllegalStateException( "A constraints object of type PercentData must be provided: " + constraintsObj); } PercentData constraints = (PercentData) constraintsObj; if (constraints.getColumn() + constraints.getSpanX() > getColumnCount() || constraints.getRow() + constraints.getSpanY() > getRowCount()) { throw new IllegalStateException("Invalid column or row span: " + constraints); } return constraints; } /** * Initializes the specified cell in the table-like layout. Information * about the component and its associated constraints are stored. This * method also checks whether the constrains object is of type * {@link PercentData} and that its indices and dimensions are * valid. * * @param component the component * @param constraints the constraints of this component */ protected void initCell(Object component, Object constraints) { PercentData pd = checkConstraints(constraints); CellData cd = new CellData(component, pd); cells[pd.getColumn()][pd.getRow()] = cd; } /** * Resets the states of all cells in the layout. Uses the current column and * row count (which must have been initialized before). */ protected void clearCells() { clearCells(getColumnCount(), getRowCount()); } /** * Resets the states of all cells in the layout. After this method was * called the layout does not contain any information about components and * their constraints. * * @param columns the number of columns * @param rows the number of rows */ protected void clearCells(int columns, int rows) { cells = new CellData[columns][rows]; } /** * Returns a list with {@code CellData} objects for the components that span * multiple columns. This method exists mainly for testing purposes. * * @return a list with {@code CellData} objects for multi-column components */ List getMultiColumnData() { ensureInit(); return multiColumns; } /** * Returns a list with {@code CellData} objects for the components that span * multiple rows. This method exists mainly for testing purposes. * * @return a list with {@code CellData} objects for multi-row components */ List getMultiRowData() { ensureInit(); return multiRows; } /** * Calculates the final cell sizes in one direction (horizontal or * vertical). This method takes all factors related to sizing into account: * the cell constraints, cell groups, weight factors, and the shrinking * flag. The resulting array with cell sizes can then be passed to * {@link #performLayout(Object, int[], int[], int[], int[])}. * * @param constraints an array with the constraints for the columns or rows * @param count the number of cells in the opposite dimension (i.e. if * columns are calculated, the number of rows and vice versa) * @param cellGroups a collection with the cell groups * @param container the container this layout belongs to * @param containerSize the total size available for the container * @param vert a flag if this calculation is for the X or Y direction * @return an array with the cell sizes */ public int[] calcSizes(CellConstraints[] constraints, int count, Collection cellGroups, Object container, int containerSize, boolean vert) { // get cell sizes for preferred size int[] sizes = calcCellSizesWithGroups(constraints, count, cellGroups, container, false, vert); // is there enough space? int requiredSpace = sumUpSizes(sizes); if (requiredSpace > containerSize && isCanShrink()) { // no, try again with minimum size sizes = calcCellSizesWithGroups(constraints, count, cellGroups, container, true, vert); } // distribute remaining space applyWeightFactors(sizes, containerSize, vert); return sizes; } /** * Layouts all components that are contained in the associated container. * This method determines the positions and sizes of all affected * components. * * @param container the container * @param colSizes an array with the sizes of all columns * @param rowSizes an array with the sizes of all rows * @param colPos the start positions of all columns * @param rowPos the start positions of all rows */ public void performLayout(Object container, int[] colSizes, int[] rowSizes, int[] colPos, int[] rowPos) { Rectangle rect = new Rectangle(); for (int col = 0; col < getColumnCount(); col++) { for (int row = 0; row < getRowCount(); row++) { if (getPercentData(col, row) != null) { alignComponent(rect, col, row, colSizes, colPos[col], col, container, false); alignComponent(rect, col, row, rowSizes, rowPos[row], row, container, true); setComponentBounds(col, row, rect); } } } } /** * Returns the preferred size of this layout. This method applies all cell * constraints to determine the optimum size of the layout. * * @param container the associated container object * @return the preferred layout size */ public Dimension calcPreferredLayoutSize(Object container) { return calcLayoutSize(container, false); } /** * Returns the minimum size of this layout. The behavior of this method * depends on the {@code canShrink} flag: if this flag is false, it * returns the same result as {@link #calcPreferredLayoutSize(Object)} - * because the layout cannot shrink below its preferred size. Otherwise, a * size calculation is performed based on the component's minimum size * rather than their preferred sizes. Note that this mainly makes a * difference if cell constraints are used with the {@link CellSize} * preferred. * * @param container the associated container object * @return the minimum layout size */ public Dimension calcMinimumLayoutSize(Object container) { return calcLayoutSize(container, isCanShrink()); } /** * Calculates either the preferred or the minimum layout size. * * @param container the associated container object * @param minimum flag for minimum (true) or preferred (false) * size * @return the corresponding layout size */ private Dimension calcLayoutSize(Object container, boolean minimum) { int[] colSizes = calcCellSizesWithGroups(getInternalAllColumnConstraints(), getRowCount(), getColumnGroups(), container, minimum, false); int[] rowSizes = calcCellSizesWithGroups(getInternalAllRowConstraints(), getColumnCount(), getRowGroups(), container, minimum, true); return new Dimension(sumUpSizes(colSizes), sumUpSizes(rowSizes)); } /** * Initializes this instance from the given collections with * {@link CellConstraints} objects. * * @param colConstr a collection with column constraints object * @param rowConstr a collection with row constraints object */ protected final void initFromCollections( Collection colConstr, Collection rowConstr) { columnConstraints = copyConstraints(colConstr); rowConstraints = copyConstraints(rowConstr); } /** * Calculates the minimum size of either the columns or the rows in the * layout. Because of the passed in orientation flag this method can operate * on both columns and rows. * * @param constraints an array with the constraints for the columns or rows * @param count the number of cells in the opposite dimension (i.e. if * columns are calculated, the number of rows and vice versa) * @param container the container this layout belongs to * @param minimum a flag whether the minimum size should be returned * @param vert a flag if this calculation is for the X or Y direction * @return an array with the cell sizes */ protected int[] calcCellSizes(CellConstraints[] constraints, int count, Object container, boolean minimum, boolean vert) { int[] sizes = new int[constraints.length]; for (int i = 0; i < constraints.length; i++) { sizes[i] = calcComponentSizes(constraints[i], i, count, container, minimum, vert); } handleMultiSpans(sizes, constraints, vert ? multiRows : multiColumns, container, minimum, vert); return sizes; } /** * Calculates the size of either a column or a row in the layout. This * method iterates over all the components in the actual column or row. * Depending on their constraints either their minimum, their preferred or a * specified fix size is fetched, and the maximum of these sizes is * determined. Only components with a span of 1 are taken into account. With * the {@code minimum} parameter it is possible to force the method to * always return the minimum size. This is required if there is less space * available than is required for the preferred width. * * @param constraints the constraints object for the actual column or row * @param index the index of the actual column or row * @param count the number of cells in the opposite dimension * @param container the container this layout belongs to * @param minimum a flag whether the minimum size should be returned * @param vert a flag if this calculation is for the X or Y direction * @return the size of the actual column or row */ protected int calcComponentSizes(CellConstraints constraints, int index, int count, Object container, boolean minimum, boolean vert) { int size = constraints.getMinSize().toPixel(getSizeHandler(), container, vert); int colIdx = 0; int rowIdx = 0; if (vert) { rowIdx = index; } else { colIdx = index; } for (int i = 0; i < count; i++) { if (vert) { colIdx = i; } else { rowIdx = i; } PercentData pd = getPercentData(colIdx, rowIdx); if (pd != null && 1 == getOrientationValue(pd.getSpanX(), pd.getSpanY(), vert)) { int cellSize = calcCellSize(pd, colIdx, rowIdx, container, minimum, vert); if (cellSize > size) { size = cellSize; } } } return size; } /** * Checks the sizes of components that span multiple cells. For these cells * it must be tested whether their size fits into the cell sizes so far * calculated. If this is not the case, cells, for which this is possible, * must be enlarged. * * @param sizes an array with the so far calculated cell sizes * @param constraints an array with all cell constraints * @param components the list with the multi span components * @param container the container object * @param minimum a flag whether the minimum size should be returned * @param vert the orientation flag */ protected void handleMultiSpans(int[] sizes, CellConstraints[] constraints, List components, Object container, boolean minimum, boolean vert) { for (CellData cd : components) { PercentData pd = cd.getConstraints(); int size = calcComponentSize(pd, cd.getComponent(), container, minimum, vert); int idx1 = getOrientationValue(pd.getColumn(), pd.getRow(), vert); int idx2 = idx1 + getOrientationValue(pd.getSpanX(), pd.getSpanY(), vert); // determine available size int cellSize = 0; for (int i = idx1; i < idx2; i++) { cellSize += sizes[i]; } if (size > cellSize) { enlargeCells(sizes, constraints, idx1, idx2, size - cellSize); } } } /** * Enlarges the cells in the specified index range by the given amount. This * method determines how many cells can be enlarged (by inspecting their * cell constraints). If there are any, they will be enlarged by the same * part. * * @param sizes an array with the so far calculated cell sizes * @param constraints the constraints * @param idx1 the first index * @param idx2 the end index (excluding) * @param amount the enlargement amount */ private void enlargeCells(int[] sizes, CellConstraints[] constraints, int idx1, int idx2, int amount) { int cnt = 0; for (int i = idx1; i < idx2; i++) { if (constraints[i].getCellSize() != CellSize.NONE) { cnt++; } } if (cnt > 0) { int factor = amount / cnt; int modulo = amount % cnt; for (int i = idx1; i < idx2 && cnt > 0; i++) { if (constraints[i].getCellSize() != CellSize.NONE) { sizes[i] += factor + ((modulo-- > 0) ? 1 : 0); cnt--; } } } } /** * Determines the size of a single cell. Evaluates the constraints of this * cell and depending on the size value either the minimum, the preferred or * a fixed size is returned. * * @param pd the constraints object * @param colIdx the column index * @param rowIdx the row index * @param container the container this layout belongs to * @param minimum a flag whether the minimum size should be returned * @param vert a flag if this calculation is for the X or Y direction * @return the cell's size */ protected int calcCellSize(PercentData pd, int colIdx, int rowIdx, Object container, boolean minimum, boolean vert) { return calcComponentSize(pd, getComponent(colIdx, rowIdx), container, minimum, vert); } /** * Determines the size of a component based on the given constraints object. * * @param pd the constraints object * @param comp the affected component * @param container the container this layout belongs to * @param minimum a flag whether the minimum size should be returned * @param vert a flag if this calculation is for the X or Y direction * @return the component's size */ protected int calcComponentSize(PercentData pd, Object comp, Object container, boolean minimum, boolean vert) { CellConstraints constr = constraintsFor(pd, vert); int sz; if (constr.getCellSize() == CellSize.NONE) { sz = 0; } else if (minimum || constr.getCellSize() == CellSize.MINIMUM) { sz = fetchPlatformAdapter().getMinimumComponentSize(comp, vert); } else { sz = fetchPlatformAdapter().getPreferredComponentSize(comp, vert); } return Math.max(sz, constr.getMinSize().toPixel(getSizeHandler(), container, vert)); } /** * Applies the defined cell groups to the so far calculated cell sizes. * * @param sizes an array with the (initial or minimum) cell sizes * @param cellGroups a collection with the defined cell groups */ protected void applyCellGroups(int[] sizes, Collection cellGroups) { for (CellGroup group : cellGroups) { group.apply(sizes); } } /** * Calculates the sizes of all columns or rows. This is a convenience * method, which combines calls to calcCellSizes() and * applyCellGroups(). * * @param constraints an array with the constraints for the columns or rows * @param count the number of cells in the opposite dimension (i.e. if * columns are calculated, the number of rows and vice versa) * @param cellGroups a collection with the defined cell groups * @param container the container this layout belongs to * @param minimum a flag whether the minimum size should be returned * @param vert a flag if this calculation is for the X or Y direction * @return an array with the cell sizes * @throws NullPointerException if a required parameter is missing */ protected int[] calcCellSizesWithGroups(CellConstraints[] constraints, int count, Collection cellGroups, Object container, boolean minimum, boolean vert) { int[] sizes = calcCellSizes(constraints, count, container, minimum, vert); applyCellGroups(sizes, cellGroups); return sizes; } /** * Helper method for calculating the total weight factor. * * @param constraints an array with cell constraints * @return the total weight factor */ protected int calcTotalWeight(CellConstraints[] constraints) { int result = 0; for (CellConstraints cc : constraints) { result += cc.getWeight(); } return result; } /** * Returns the total weight factor for columns. * * @return the total column weight */ protected int getTotalWeightX() { if (totalWeightX < 0) { totalWeightX = calcTotalWeight(columnConstraints); } return totalWeightX; } /** * Returns the total weight factor for rows. * * @return the total row weight */ protected int getTotalWeightY() { if (totalWeightY < 0) { totalWeightY = calcTotalWeight(rowConstraints); } return totalWeightY; } /** * Processes the cells with a weight factor larger than 0. The remaining * available space is calculated and divided between the cells with a * defined weight factor. * * @param sizes the cell sizes without weight factors * @param containerSize the size of the container (without insets) * @param constraints the cell constraints * @param totalWeight the total weight factor */ protected void applyWeightFactors(int[] sizes, int containerSize, CellConstraints[] constraints, int totalWeight) { if (totalWeight > 0) { int remaining = containerSize - sumUpSizes(sizes); if (remaining > 0) { for (int i = 0; i < constraints.length; i++) { if (constraints[i].getWeight() > 0) { sizes[i] += (remaining * constraints[i].getWeight()) / totalWeight; } } } } } /** * Processes the cells with a weight factor larger than 0. The remaining * available space is calculated and divided between the cells with a * defined weight factor. * * @param sizes the cell sizes without weight factors * @param containerSize the size of the container (without insets) * @param vert a flag if this calculation is for the X or Y direction */ protected void applyWeightFactors(int[] sizes, int containerSize, boolean vert) { if (vert) { applyWeightFactors(sizes, containerSize, rowConstraints, getTotalWeightY()); } else { applyWeightFactors(sizes, containerSize, columnConstraints, getTotalWeightX()); } } /** * Calculates the start positions of all cells in a column or row. * * @param sizes the cell sizes (must not be null) * @param startPos the start position * @return an array with the start positions for all cells * @throws NullPointerException if the array with sizes is null */ public int[] calcCellPositions(int[] sizes, int startPos) { int[] pos = new int[sizes.length]; int p = startPos; for (int i = 0; i < sizes.length; p += sizes[i++]) { pos[i] = p; } return pos; } /** * Aligns the specified component. * * @param bounds stores the bounds of the component * @param colIdx the column index * @param rowIdx the row index * @param sizes an array with the sizes of all cells * @param startPos the start position of the actual cell * @param idx the actual cell index * @param container the container this layout belongs to * @param vert a flag if this calculation is for the X or Y direction */ protected void alignComponent(Rectangle bounds, int colIdx, int rowIdx, int[] sizes, int startPos, int idx, Object container, boolean vert) { // Determine available space, including cell spanning PercentData pd = getPercentData(colIdx, rowIdx); int span = getOrientationValue(pd.getSpanX(), pd.getSpanY(), vert); int availSpace = 0; for (int i = 0; i < span; i++) { availSpace += sizes[idx + i]; } // Determine alignment int pos; int cellSize; if (constraintsFor(pd, vert).getAlignment() == CellAlignment.FULL) { // component fills the whole area pos = 0; cellSize = availSpace; } else { cellSize = calcCellSize(pd, colIdx, rowIdx, container, false, vert); CellAlignment align = constraintsFor(pd, vert).getAlignment(); if (align == CellAlignment.CENTER) { pos = (availSpace - cellSize) >> 1; } else if (align == CellAlignment.END) { pos = availSpace - cellSize; } else { pos = 0; } } if (vert) { bounds.y = startPos + pos; bounds.height = cellSize; } else { bounds.x = startPos + pos; bounds.width = cellSize; } } /** * Helper method for copying a collection with constraints objects into an * array. If a constraints object is null , an exception is thrown. * * @param constr the collection with the constraints * @return an array with constraints * @throws IllegalArgumentException if the collection contains a null * constraint */ private static CellConstraints[] copyConstraints( Collection constr) { CellConstraints[] result = new CellConstraints[constr.size()]; Iterator it = constr.iterator(); for (int idx = 0; it.hasNext(); idx++) { result[idx] = it.next(); if (result[idx] == null) { throw new IllegalArgumentException( "Cell constraints must not be null!"); } } return result; } /** * Helper method for parsing a string with cell definitions. * * @param constr the string * @param col a flag if column or row definitions are to be parsed * @return a collection with the corresponding constraints objects * @throws IllegalArgumentException if the string is invalid */ private Collection parseConstraints(String constr, boolean col) { CellAlignment oldAlign = getConstraintsBuilder().getDefaultAlignment(); getConstraintsBuilder().setDefaultAlignment( col ? CellAlignment.FULL : CellAlignment.CENTER); try { getConstraintsBuilder().reset(); Collection result = new LinkedList(); if (constr != null) { StringTokenizer tok = new StringTokenizer(constr, CONSTRAINTS_DELIMITERS); while (tok.hasMoreTokens()) { result.add(getConstraintsBuilder().fromString( tok.nextToken())); } } if (result.size() < 1) { throw new IllegalArgumentException( "Undefined cell constraints!"); } return result; } finally { getConstraintsBuilder().setDefaultAlignment(oldAlign); } } /** * Helper method for extracting a value from a 2D vector with the specified * orientation. * * @param v1 the x value * @param v2 the y value * @param vert the orientation flag (true for the y value, * false for the x value) * @return the extracted value */ public static int getOrientationValue(int v1, int v2, boolean vert) { return vert ? v2 : v1; } /** * Returns the minimum size of the component at the specified column and row * position in the given orientation. * * @param col the column index * @param row the row index * @param vert the orientation flag * @return the minimum size of the specified component */ protected int getMinimumComponentSize(int col, int row, boolean vert) { return fetchPlatformAdapter().getMinimumComponentSize( getComponent(col, row), vert); } /** * Returns the preferred size of the component at the specified column and * row position in the given orientation. * * @param col the column index * @param row the row index * @param vert the orientation flag * @return the minimum size of the specified component */ protected int getPreferredComponentSize(int col, int row, boolean vert) { return fetchPlatformAdapter().getPreferredComponentSize( getComponent(col, row), vert); } /** * Sets the bounds of the component at the specified column and row * position. This method is called after the exact position of this * component has been determined by the layout algorithm. * * @param col the column index * @param row the row index * @param bounds the new bounds for this component */ protected void setComponentBounds(int col, int row, Rectangle bounds) { fetchPlatformAdapter().setComponentBounds(getComponent(col, row), bounds); } /** * Returns the currently used size handler implementation. * * @return the size handler implementation */ protected UnitSizeHandler getSizeHandler() { return fetchPlatformAdapter().getSizeHandler(); } /** * Returns the constraints object for the specified percent data and the * given orientation. If the percent data contains already a constraints * object for the given orientation, this object is returned. Otherwise the * constraints of the hosting cell (the target cell) are returned. * * @param pd the percent data object * @param vert the orientation flag * @return the constraints for this percent data object */ protected CellConstraints constraintsFor(PercentData pd, boolean vert) { CellConstraints c = pd.getConstraints(vert); if (c == null) { c = vert ? getRowConstraints(targetRow(pd)) : getColumnConstraints(targetColumn(pd)); } return c; } /** * Initializes the whole layout. This method is called on first access to * the layout information or whenever the layout changes. A concrete sub * class must here implement its initialization algorithm, which creates a * valid percent layout. * * @param adapter the currently used platform adapter */ protected abstract void initCells(PercentLayoutPlatformAdapter adapter); /** * Determines the target column of the given constraints object. If a target * column is set, it is used. Otherwise the column of the data object is * returned. * * @param pd the {@code PercentData} object * @return the target column */ private static int targetColumn(PercentData pd) { return (pd.getTargetColumn() <= PercentData.POS_UNDEF) ? pd.getColumn() : pd.getTargetColumn(); } /** * Determines the target row of the given constraints object. If a target * row is set, it is used. Otherwise the row of the data object is returned. * * @param pd the {@code PercentData} object * @return the target row */ private static int targetRow(PercentData pd) { return (pd.getTargetRow() <= PercentData.POS_UNDEF) ? pd.getRow() : pd .getTargetRow(); } /** * Returns the CellData object for the specified cell. Ensures * that the cells have been initialized. * * @param col the column * @param row the row * @return the cell data object at this position (can be null) */ private CellData getCellData(int col, int row) { ensureInit(); return cells[col][row]; } /** * Ensures that the layout has been initialized. This method is called by * several accessor methods. */ private void ensureInit() { if (!inInit && cells == null) { inInit = true; try { initCells(fetchPlatformAdapter()); initMultiCells(); } finally { inInit = false; } } } /** * Helper method for obtaining information about components that span * multiple cells. If these components are associated a target cell, they * are stored at a special location. */ private void initMultiCells() { int cols = getColumnCount(); int rows = getRowCount(); multiColumns.clear(); multiRows.clear(); for (int i = 0; i < cols; i++) { for (int j = 0; j < rows; j++) { PercentData pd = getPercentData(i, j); if (pd != null) { if (pd.getSpanX() > 1) { multiColumns.add(getCellData(i, j)); } if (pd.getSpanY() > 1) { multiRows.add(getCellData(i, j)); } } } } } /** * Calculates the width or the height of the associated component. This * method calculates the sum of the already determined cell sizes. * * @param sizes an array with the cell sizes * @return the layout size */ private static int sumUpSizes(int[] sizes) { int result = 0; for (int sz : sizes) { result += sz; } return result; } /** * Removes a component from the multi span list. * * @param lst the multi span list * @param comp the component to remove */ private static void removeMultiSpanComponent(List lst, Object comp) { for (Iterator it = lst.iterator(); it.hasNext();) { CellData cd = it.next(); if (cd.getComponent() == comp) { it.remove(); break; } } } /** * Helper method for returning an unmodifiable collection of cell groups. * * @param groups the source collection with cell groups * @return the unmodifiable collection */ private static Collection unmodifiableCellGroups( Collection groups) { if (groups == null) { return Collections.emptyList(); } else { return Collections.unmodifiableCollection(groups); } } /** * A helper class for storing information about a cell in the layout. */ static class CellData { /** Stores the component that occupies this cell. */ private final Object component; /** Stores the percent data object for this cell. */ private final PercentData constraints; /** * Creates a new instance of CellData and initializes it. * * @param comp the component * @param constr the constraints */ public CellData(Object comp, PercentData constr) { component = comp; constraints = constr; } /** * Returns the component stored in this cell. * * @return the component */ public Object getComponent() { return component; } /** * Returns the constraints. * * @return the constraints */ public PercentData getConstraints() { return constraints; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy