/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.framework.uitools; // JDK import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.Insets; import java.awt.LayoutManager2; import java.io.Serializable; import java.util.Hashtable; /** * This <code>GridLayout</code> class is a layout manager that lays out a * container's components in a rectangular grid but also provides a * <code>GridBagConstraints</code> to determine which component needs to keep * their preferred/minimum width and/or height. Compare to <code>GridLayout</code>, * this version does not lay out components that are not visible. * <p> * For instance: * <pre> * JPanel panel = new JPanel(new GrigLayout(1, 6));</pre> * creates a grid of 1 row and 6 columns. * <pre> * GridConstraints constraints = new GridConstraints(); * * panel.add(new ToolBarButton("Back")); * panel.add(new ToolBarButton("Next")); * * constraints.fill = GridConstraints.NONE; * panel.add(RigidBox.box(), constraints);<pre> * The rigid box will not be resized as Back and Next will be. * <pre> * panel.add(new JButton("Cancel")); * * constraints.fill = GridConstraints.NONE; * panel.add(RigidBox.box(10)); * * constraints.fill = GridConstraints.VERTICAL; * panel.add(new ToolBarButton("Help me on this"));</pre> * The last button will keep its horizontal size but the height will be the * height of the taller component. Also, it is not needed to add a constraints * object when the component can be resized both horizontally and vertically. * <p> * The layout will be: * <pre> * _______________________________________________________ * | ________ ________ ________ _________________ | * | | Back | | Next | |Cancel| |Help me on this| | * | -------- -------- -------- ----------------- | * ------------------------------------------------------- * </pre> * <b>Important:</b> <code>GridLayout</code> may give unexpected layout when * using a grid mxn. However, 1xn or mx1 works fine. * * @version 10.1.3 * @author Pascal Filion */ public class GridLayout implements LayoutManager2, Serializable { /** * This is the number of columns specified for the grid. The number of columns * can be changed at any time. This should be a non negative integer, where * '0' means 'any number' meaning that the number of Columns in that dimension * depends on the other dimension. */ private int columns; /** * */ private int componentIndex; /** * A cache of the component layout constraints. */ private Hashtable constraintsTable = new Hashtable(); /** * This is the horizontal gap (in pixels) which specifies the space between * columns. They can be changed at any time. This should be a non negative * integer. */ private int hgap; /** * This is the number of rows specified for the grid. The number of rows can * be changed at any time. This should be a non negative integer, where '0' * means 'any number' meaning that the number of Rows in that dimension * depends on the other dimension. */ private int rows; /** * This is the vertical gap (in pixels) which specifies the space between * rows. They can be changed at any time. This should be a non negative * integer. * */ private int vgap; /** * Creates a grid layout with a default of one column per component, in a * single row. */ public GridLayout() { super(); } /** * Creates a grid layout with the specified number of rows and columns. All * components in the layout are given equal size. * <p> * One, but not both, of <code>rowCount</code> and <code>columnCount</code> * can be zero, which means that any number of objects can be placed in a row * or in a column. * * @param rowCount The rows, with the value zero meaning any number of rows * @param columnCount The columns, with the value zero meaning */ public GridLayout(int rowCount, int columnCount) { super(); setRows(rowCount); setColumns(columnCount); } /** * Creates a grid layout with the specified number of rows and columns. All * components in the layout are given equal size. * <p> * In addition, the horizontal and vertical gaps are set to the specified * values. Horizontal gaps are placed at the left and right edges, and between * each of the columns. Vertical gaps are placed at the top and bottom edges, * and between each of the rows. * <p> * One, but not both, of <code>rows</code> and <code>cols</code> can be * zero, which means that any number of objects can be placed in a row or in a * column. * * @param rowCount The rows, with the value zero meaning any number of rows * @param columnCount The columns, with the value zero meaning any number of * columns * @param horizontalGap The horizontal gap * @param verticalGap The vertical gap */ public GridLayout(int rowCount, int columnCount, int horizontalGap, int verticalGap) { this(rowCount, columnCount); setHgap(horizontalGap); setVgap(verticalGap); } /** * Adds the specified component to the layout, using the specified constraint * object. If the constraints is null <code>null</code>, creates a default * constraints with <code>fill = BOTH</code>. * * @param component The component to be added * @param constraints Where/how the component is added to the layout */ public void addLayoutComponent(Component component, Object constraints) { if (constraints == null) { constraints = new GridBagConstraints(); ((GridBagConstraints) constraints).fill = GridBagConstraints.BOTH; } else if (!(constraints instanceof GridBagConstraints)) { throw new IllegalArgumentException("The constraints has to be GridBagConstraints"); } constraintsTable.put(component, ((GridBagConstraints) constraints).clone()); } /** * Adds the specified component with the specified name to the layout. * * @param name the name of the component * @param component the component to be added */ public void addLayoutComponent(String name, Component component) { } /** * Gets the number of columns in this layout. * * @return The number of columns in this layout */ public int getColumns() { return columns; } /** * Gets the constraints for the specified component. A copy of the actual * <code>GridBagConstraints</code> object is returned. * * @param component the component to be queried * @return The constraint for the specified component in this grid layout; a * copy of the actual constraint object is returned */ public GridBagConstraints getConstraints(Component component) { GridBagConstraints constraints = (GridBagConstraints) constraintsTable.get(component); if (constraints == null) { constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraintsTable.put(component, constraints); } return (GridBagConstraints) constraints.clone(); } /** * Gets the horizontal gap between components. * * @return The horizontal gap between components */ public int getHgap() { return hgap; } /** * Returns the alignment on the x axis. Determines alignment relative to other * components. * * @return 0.5f */ public float getLayoutAlignmentX(Container target) { return 0.5f; } /** * Returns the alignment on the y axis. Determines alignment relative to other * components. * * @return 0.5f */ public float getLayoutAlignmentY(Container target) { return 0.5f; } /** * Returns the size needed to layout the given parent based on the minimum * size of its children if the given useMinimumSize is <code>true</code> * otherwise use the preferred size of its children. * * @param parent The parent that needs to have a its size calculated * @param useMinimumSize <code>true</code> to use the minimum size of the * parent's children; <code>false</code> to use the preferred size * @return The size needed to propery layout the given container */ protected Dimension getLayoutSize(Container parent, boolean useMinimumSize) { synchronized (parent.getTreeLock()) { Dimension componentSize; Component component; GridBagConstraints constraints; Insets insets = parent.getInsets(); int horizontalInsets = insets.left + insets.right; int verticalInsets = insets.top + insets.bottom; int componentCount = parent.getComponentCount(); int resizabledWidthComponentCount = 0; int resizabledHeightComponentCount = 0; int rowCount = getRows(); int columnCount = getColumns(); int maxComponentWidth = 0; int maxComponentHeight = 0; int rigidComponentWidth = 0; int rigidComponentHeight = 0; // Make sure we do not count invisible component for (int index = componentCount; --index >= 0;) { component = parent.getComponent(index); if (!component.isVisible()) { // For single row if (columnCount < 2) rowCount--; // For single column if (rowCount < 2) columnCount--; componentCount--; } } // Based on the row count, determine the column count if (rowCount > 0) columnCount = (componentCount + rowCount - 1) / rowCount; else if (columnCount > 0) rowCount = (componentCount + columnCount - 1) / columnCount; else return new Dimension(); // Look in the parent to see if there are components implementing // ComponentResizable and returning false so that we won't count their size for (int index = parent.getComponentCount(); --index >= 0;) { component = parent.getComponent(index); if (!component.isVisible()) continue; constraints = getConstraints(component); // Ask the component its the desired size if (useMinimumSize) componentSize = component.getMinimumSize(); else componentSize = component.getPreferredSize(); if (constraints.fill != GridBagConstraints.BOTH) { // For a single row if ((rowCount < 2) && ((constraints.fill == GridBagConstraints.VERTICAL) || (constraints.fill == GridBagConstraints.NONE))) { rigidComponentWidth += componentSize.width; } else if (constraints.fill != GridBagConstraints.NONE) { resizabledWidthComponentCount++; maxComponentWidth = Math.max(maxComponentWidth, componentSize.width); } else maxComponentWidth = Math.max(maxComponentWidth, componentSize.width); // For a single column if ((columnCount < 2) && ((constraints.fill == GridBagConstraints.HORIZONTAL) || (constraints.fill == GridBagConstraints.NONE))) { rigidComponentHeight += componentSize.height; } else if (constraints.fill != GridBagConstraints.NONE) { resizabledHeightComponentCount++; maxComponentHeight = Math.max(maxComponentHeight, componentSize.height); } else maxComponentHeight = Math.max(maxComponentHeight, componentSize.height); } else { resizabledWidthComponentCount++; resizabledHeightComponentCount++; maxComponentWidth = Math.max(maxComponentWidth, componentSize.width); maxComponentHeight = Math.max(maxComponentHeight, componentSize.height); } } // Calculate the dimension needed by the parent int width = 0; int height = 0; // For a single row if (rowCount < 2) { width = horizontalInsets + rigidComponentWidth + (maxComponentWidth * resizabledWidthComponentCount) + (columnCount - 1) * getHgap(); height = verticalInsets + Math.max(rigidComponentHeight, maxComponentHeight) + (rowCount - 1) * getVgap(); } // For a single column else { width = horizontalInsets + Math.max(rigidComponentWidth, maxComponentWidth) + (columnCount - 1) * getHgap(); height = verticalInsets + rigidComponentHeight + (maxComponentHeight * resizabledHeightComponentCount) + (rowCount - 1) * getVgap(); } return new Dimension(width, height); } } /** * Returns the next visible component starting at the index. * * @param container The container to layout * @param index The index the begin the search of the first visible component * @return The next component visible from the index */ private Component getNextVisibleComponent(Container parent, int index) { Component component = null; int componentCount = parent.getComponentCount(); while (componentIndex < componentCount) { component = parent.getComponent(componentIndex++); if (component.isVisible()) return component; component.setBounds(0, 0, 0, 0); } return null; } /** * Gets the number of rows in this layout. * * @return The number of rows in this layout */ public int getRows() { return rows; } /** * Gets the vertical gap between components. * * @return The vertical gap between components. */ public int getVgap() { return vgap; } /** * Invalidates the layout, indicating that if the layout manager has cached * information it should be discarded. * * @param target The container to invalidate */ public void invalidateLayout(Container target) { } /** * Lays out the specified container using this layout. * <p> * This method reshapes the components in the specified target container in * order to satisfy the constraints of the <code>GridLayout</code> object. * <p> * The grid layout manager determines the size of individual components by * dividing the free space in the container into equal-sized portions * according to the number of rows and columns in the layout. The container's * free space equals the container's size minus any insets and any specified * horizontal or vertical gap. All components in a grid layout are given the * same size. * * @param parent The container in which to do the layout */ public void layoutContainer(Container parent) { if (parent == null) return; synchronized (parent.getTreeLock()) { int componentCount = parent.getComponentCount(); if (componentCount == 0) return; Component component; Dimension preferredSize; GridBagConstraints constraints; Insets insets = parent.getInsets(); boolean leftToRight = parent.getComponentOrientation().isLeftToRight(); int horizontalInsets = insets.left + insets.right; int verticalInsets = insets.top + insets.bottom; int width = parent.getWidth() - horizontalInsets; int height = parent.getHeight() - verticalInsets; int resizabledWidthComponentCount = 0; int resizabledHeightComponentCount = 0; int rowCount = getRows(); int columnCount = getColumns(); int maxComponentWidth = 0; int maxComponentHeight = 0; int rigidComponentWidth = 0; int rigidComponentWidthCount = 0; int rigidComponentHeight = 0; int rigidComponentHeightCount = 0; double resizeFactorWidth = 1; double resizeFactorHeight = 1; // Step 1. Make sure we do not count invisible component for (int index = componentCount; --index >= 0;) { component = parent.getComponent(index); if (!component.isVisible()) { // For single row if (columnCount < 2) rowCount--; // For single column else if (rowCount < 2) columnCount--; componentCount--; } } // Based on the row count, determine the column count if (rowCount > 0) columnCount = (componentCount + rowCount - 1) / rowCount; else if (columnCount > 0) rowCount = (componentCount + columnCount - 1) / columnCount; else return; // TODO: TO SEE IF WE CAN DO THAT // Step 2. Calculate the max width and height for resizable component // and keep track of the none resizable space for (int index = 0; index < parent.getComponentCount(); index++) { component = parent.getComponent(index); if (!component.isVisible()) continue; preferredSize = component.getPreferredSize(); constraints = getConstraints(component); if (constraints.fill != GridBagConstraints.BOTH) { // For a single row if ((rowCount < 2) && ((constraints.fill == GridBagConstraints.VERTICAL) || (constraints.fill == GridBagConstraints.NONE))) { rigidComponentWidth += preferredSize.width; rigidComponentWidthCount++; } else if (constraints.fill != GridBagConstraints.NONE) { resizabledWidthComponentCount++; maxComponentWidth = Math.max(maxComponentWidth, preferredSize.width); } else maxComponentWidth = Math.max(maxComponentWidth, preferredSize.width); // For a single column if ((columnCount < 2) && ((constraints.fill == GridBagConstraints.HORIZONTAL) || (constraints.fill == GridBagConstraints.NONE))) { rigidComponentHeight += preferredSize.height; rigidComponentHeightCount++; } else if (constraints.fill != GridBagConstraints.NONE) { resizabledHeightComponentCount++; maxComponentHeight = Math.max(maxComponentHeight, preferredSize.height); } else maxComponentHeight = Math.max(maxComponentHeight, preferredSize.height); } else { resizabledWidthComponentCount++; resizabledHeightComponentCount++; maxComponentWidth = Math.max(maxComponentWidth, preferredSize.width); maxComponentHeight = Math.max(maxComponentHeight, preferredSize.height); } } // Step 3. Calculate the remaining space that will be shared between resizabled components int widthLeft = width - horizontalInsets - (componentCount - 1) * getHgap(); int heightLeft = height - verticalInsets - (componentCount - 1) * getVgap(); if (resizabledWidthComponentCount == 0) resizabledWidthComponentCount = 1; // For a single row if (rowCount < 2) { widthLeft -= rigidComponentWidth; maxComponentWidth = widthLeft / resizabledWidthComponentCount; maxComponentHeight = parent.getHeight();// Since it's one row, the height is the height of the parent Math.max(maxComponentHeight, maxRigidComponentHeight); } // For a single column if (columnCount < 2) { maxComponentWidth = width;// Since it's one row, the width is the width of the parent Math.max(maxComponentWidth, maxRigidComponentWidth); heightLeft -= rigidComponentHeight; maxComponentHeight = heightLeft / resizabledWidthComponentCount; } if ((rigidComponentWidthCount > 0) && (rigidComponentWidth > width)) resizeFactorWidth = width / (double) rigidComponentWidth; if ((rigidComponentHeightCount > 0) && (rigidComponentHeight > height)) resizeFactorHeight = height / (double) rigidComponentHeight; // Step 4. Layout each column int x; int y = insets.top; componentIndex = 0; if (leftToRight) x = insets.left; else x = insets.right + width - insets.left; width = 0; height = 0; for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) { // Layout each row for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { component = getNextVisibleComponent(parent, componentIndex); if (component == null) break; constraints = getConstraints(component); // Use the preferred size of the component instead of the calculated size if (constraints.fill != GridBagConstraints.BOTH) { preferredSize = component.getPreferredSize(); if ((constraints.fill == GridBagConstraints.VERTICAL) || (constraints.fill == GridBagConstraints.NONE)) { width = (int) (preferredSize.width * resizeFactorWidth); } else { width = maxComponentWidth; } if ((constraints.fill == GridBagConstraints.HORIZONTAL) || (constraints.fill == GridBagConstraints.NONE)) { height = (int) (preferredSize.height * resizeFactorHeight); } else { height = maxComponentHeight; } if (leftToRight) component.setBounds(x, y, width, height); else component.setBounds(x - width, y, width, height); if (columnCount > 1) height = Math.max(maxComponentHeight, height); } // Use the calculated size else { width = maxComponentWidth; height = maxComponentHeight; if (leftToRight) component.setBounds(x, y, width, height); else component.setBounds(x - width, y, width, height); } if (leftToRight) x += width + getHgap(); else x -= (width - getHgap()); } if (leftToRight) x = insets.left; else x = insets.right + width - insets.left; y += height + getVgap(); } } } /** * Returns the maximum size of this component. * * @return <code>(Integer.MAX_VALUE, Integer.MAX_VALUE)</code> */ public Dimension maximumLayoutSize(Container target) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Returns the minimum layout size needed by the given container based on the * minimum size of its visible children. * * @param parent The container used to calculate the minimum size * @return The minimum layout size needed by the given container */ public Dimension minimumLayoutSize(Container parent) { return getLayoutSize(parent, true); } /** * Returns the preferred layout size needed by the given container based on * the preferred size of its visible children. * * @param parent The container used to calculate the preferred size * @return The preferred layout size needed by the given container */ public Dimension preferredLayoutSize(Container parent) { return getLayoutSize(parent, false); } /** * Removes the specified component from the layout. * * @param component the component to be removed */ public void removeLayoutComponent(Component component) { constraintsTable.remove(component); } /** * Sets the number of columns in this layout to the specified value. * * @param columns The number of columns in this layout */ public void setColumns(int columns) { this.columns = columns; } /** * Sets the constraints for the specified component in this layout. * * @param Component The component to be modified * @param constraints The constraints to be applied */ public void setConstraints(Component Component, GridBagConstraints constraints) { constraintsTable.put(Component, constraints.clone()); } /** * Sets the horizontal gap between components to the specified value. * * @param hgap The horizontal gap between components */ public void setHgap(int hgap) { this.hgap = hgap; } /** * Sets the number of rows in this layout to the specified value. * * @param rows The number of rows in this layout */ public void setRows(int rows) { this.rows = rows; } /** * Sets the vertical gap between components to the specified value. * * @param vgap The vertical gap between components */ public void setVgap(int vgap) { this.vgap = vgap; } }