/*
* Copyright (c) 2011, grossmann, Nikolaus Moll
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the jo-widgets.org nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL jo-widgets.org BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.jowidgets.impl.layout;
import java.util.HashMap;
import java.util.Map;
import org.jowidgets.api.layout.BorderLayoutConstraints;
import org.jowidgets.api.widgets.IContainer;
import org.jowidgets.api.widgets.IControl;
import org.jowidgets.common.types.Dimension;
import org.jowidgets.common.types.Rectangle;
import org.jowidgets.common.widgets.layout.ILayouter;
import org.jowidgets.util.Assert;
final class BorderLayoutImpl extends AbstractCachingLayout implements ILayouter {
private final IContainer container;
private final int gapX;
private final int gapY;
private final int marginTop;
private final int marginBottom;
private final int marginLeft;
private final int marginRight;
private Map<BorderLayoutConstraints, IControl> controlsMap;
BorderLayoutImpl(
final IContainer container,
final int marginLeft,
final int marginRight,
final int marginTop,
final int marginBottom,
final int gapX,
final int gapY) {
Assert.paramNotNull(container, "container");
this.container = container;
this.gapX = gapX;
this.gapY = gapY;
this.marginLeft = marginLeft;
this.marginRight = marginRight;
this.marginTop = marginTop;
this.marginBottom = marginBottom;
}
@Override
public void layout() {
final Rectangle clientArea = container.getClientArea();
final Map<BorderLayoutConstraints, IControl> controls = getControls();
final int rowCount = getRowCount(controls);
final int centerColumnCount = getCenterColumnCount(controls);
final Dimension minimumSize = calcControlsSize(minimumPolicy());
final int x = clientArea.getX() + marginLeft;
int y = clientArea.getY() + marginTop;
// do not allow sizes less than minimum size
final int totalHeight = Math.max(clientArea.getHeight() - marginTop - marginBottom, minimumSize.getHeight());
final int totalWidth = Math.max(clientArea.getWidth() - marginLeft - marginRight, minimumSize.getWidth());
final IControl topControl = controls.get(BorderLayoutConstraints.TOP);
final IControl bottomControl = controls.get(BorderLayoutConstraints.BOTTOM);
final IControl leftControl = controls.get(BorderLayoutConstraints.LEFT);
final IControl centerControl = controls.get(BorderLayoutConstraints.CENTER);
final IControl rightControl = controls.get(BorderLayoutConstraints.RIGHT);
final int[] minimumHeights = new int[] {
minimumPolicy().getSize(topControl).getHeight(), calcCenterHeight(controls, minimumPolicy()),
minimumPolicy().getSize(bottomControl).getHeight()};
final int[] preferredHeights = new int[] {
preferredPolicy().getSize(topControl).getHeight(), calcCenterHeight(controls, preferredPolicy()),
preferredPolicy().getSize(bottomControl).getHeight()};
final int[] minimumWidths = new int[] {
minimumPolicy().getSize(leftControl).getWidth(),
minimumPolicy().getSize(leftControl).getWidth()
+ minimumPolicy().getSize(centerControl).getWidth()
+ minimumPolicy().getSize(rightControl).getWidth(), minimumPolicy().getSize(rightControl).getWidth()};
final int[] preferredWidths = new int[] {
preferredPolicy().getSize(leftControl).getWidth(),
preferredPolicy().getSize(leftControl).getWidth()
+ preferredPolicy().getSize(centerControl).getWidth()
+ preferredPolicy().getSize(rightControl).getWidth(), preferredPolicy().getSize(rightControl).getWidth()};
final int[] usedHeights = calcRatio(minimumHeights, preferredHeights, totalHeight - (rowCount - 1) * gapY);
final int[] usedWidths = calcRatio(minimumWidths, preferredWidths, totalWidth - (centerColumnCount - 1) * gapX);
// TODO MG,NM BorderLayout - calculate gaps after top, before bottom, after left and before right
final int gapYAfterTop = gapY;
final int gapYBeforeBottom = gapY;
final int gapXAfterLeft = gapX;
final int gapXBeforeRight = gapX;
if (topControl != null) {
topControl.setPosition(x, y);
topControl.setSize(totalWidth, usedHeights[0]);
y = y + usedHeights[0] + gapYAfterTop;
}
int centerX = x;
if (leftControl != null) {
leftControl.setPosition(centerX, y);
leftControl.setSize(usedWidths[0], usedHeights[1]);
centerX = centerX + usedWidths[0] + gapXAfterLeft;
}
if (centerControl != null) {
centerControl.setPosition(centerX, y);
centerControl.setSize(usedWidths[1], usedHeights[1]);
centerX = centerX + usedWidths[1];
}
if (rightControl != null) {
centerX = centerX + gapXBeforeRight;
rightControl.setPosition(centerX, y);
rightControl.setSize(usedWidths[2], usedHeights[1]);
}
y = y + usedHeights[1];
if (bottomControl != null) {
y = y + gapYBeforeBottom;
bottomControl.setPosition(x, y);
bottomControl.setSize(totalWidth, usedHeights[2]);
}
}
private int[] calcRatio(final int[] minSizes, final int[] prefSizes, final int totalSize) {
final int totalMin = minSizes[0] + minSizes[1] + minSizes[2];
final int totalPref = prefSizes[0] + prefSizes[1] + prefSizes[2];
if (totalSize < totalMin) {
return minSizes;
}
final int[] result = new int[3];
if (totalSize >= totalPref) {
result[0] = prefSizes[0];
result[2] = prefSizes[2];
}
else {
final int availableSize = totalSize - totalMin;
final double ratio = (double) availableSize / (totalPref - totalMin);
result[0] = (int) (ratio * (prefSizes[0] - minSizes[0]) + minSizes[0]);
result[2] = (int) (ratio * (prefSizes[2] - minSizes[2]) + minSizes[2]);
}
result[1] = totalSize - result[0] - result[2];
return result;
}
private int calcCenterHeight(final Map<BorderLayoutConstraints, IControl> controls, final ISizeProvider policy) {
int height = 0;
height = Math.max(policy.getSize(controls.get(BorderLayoutConstraints.LEFT)).getHeight(), height);
height = Math.max(policy.getSize(controls.get(BorderLayoutConstraints.CENTER)).getHeight(), height);
height = Math.max(policy.getSize(controls.get(BorderLayoutConstraints.RIGHT)).getHeight(), height);
return height;
}
private int getRowCount(final Map<BorderLayoutConstraints, IControl> controls) {
int result = 0;
if (controls.get(BorderLayoutConstraints.TOP) != null) {
result++;
}
if (controls.get(BorderLayoutConstraints.BOTTOM) != null) {
result++;
}
if (controls.get(BorderLayoutConstraints.CENTER) != null
|| controls.get(BorderLayoutConstraints.LEFT) != null
|| controls.get(BorderLayoutConstraints.RIGHT) != null) {
result++;
}
return result;
}
private int getCenterColumnCount(final Map<BorderLayoutConstraints, IControl> controls) {
int result = 0;
if (controls.get(BorderLayoutConstraints.LEFT) != null) {
result++;
}
if (controls.get(BorderLayoutConstraints.RIGHT) != null) {
result++;
}
if (controls.get(BorderLayoutConstraints.CENTER) != null) {
result++;
}
return result;
}
@Override
public void invalidate() {
super.invalidate();
controlsMap = null;
}
private Dimension calcControlsSize(final ISizeProvider policy) {
final Map<BorderLayoutConstraints, IControl> controls = getControls();
int width = 0;
int height = 0;
int horizontalCount = 0;
int verticalCount = 0;
final IControl centerControl = controls.get(BorderLayoutConstraints.CENTER);
if (centerControl != null) {
final Dimension size = policy.getSize(centerControl);
width = size.getWidth();
height = size.getHeight();
horizontalCount++;
verticalCount++;
}
final IControl leftControl = controls.get(BorderLayoutConstraints.LEFT);
if (leftControl != null) {
final Dimension size = policy.getSize(leftControl);
width = width + size.getWidth();
height = Math.max(height, size.getHeight());
horizontalCount++;
}
final IControl rightControl = controls.get(BorderLayoutConstraints.RIGHT);
if (rightControl != null) {
final Dimension size = policy.getSize(rightControl);
width = width + size.getWidth();
height = Math.max(height, size.getHeight());
horizontalCount++;
}
width = width + Math.max(0, (horizontalCount - 1)) * gapX;
final IControl topControl = controls.get(BorderLayoutConstraints.TOP);
if (topControl != null) {
final Dimension size = policy.getSize(topControl);
height = height + size.getHeight();
width = Math.max(width, size.getWidth());
verticalCount++;
}
final IControl bottomControl = controls.get(BorderLayoutConstraints.BOTTOM);
if (bottomControl != null) {
final Dimension size = policy.getSize(bottomControl);
height = height + size.getHeight();
width = Math.max(width, size.getWidth());
verticalCount++;
}
height = height + Math.max(0, (verticalCount - 1)) * gapY;
return new Dimension(width, height);
}
private Dimension calcSize(final ISizeProvider policy) {
final Dimension controlsSize = calcControlsSize(policy);
final int width = controlsSize.getWidth() + marginLeft + marginRight;
final int height = controlsSize.getHeight() + marginTop + marginBottom;
return container.computeDecoratedSize(new Dimension(width, height));
}
private Map<BorderLayoutConstraints, IControl> getControls() {
if (controlsMap == null) {
controlsMap = new HashMap<BorderLayoutConstraints, IControl>();
for (final IControl control : container.getChildren()) {
Object constraints = control.getLayoutConstraints();
if (constraints != null && !(constraints instanceof BorderLayoutConstraints)) {
throw new IllegalStateException("Constraints muts be instance of '"
+ BorderLayoutConstraints.class.getName()
+ "'");
}
else if (constraints == null) {
constraints = BorderLayoutConstraints.CENTER;
}
final BorderLayoutConstraints blConstraints = (BorderLayoutConstraints) constraints;
if (controlsMap.get(blConstraints) != null) {
throw new IllegalStateException("Container has more than one control with the constraint '"
+ blConstraints
+ "'");
}
else {
controlsMap.put(blConstraints, control);
}
}
}
return controlsMap;
}
@Override
protected Dimension calculateMinSize() {
return calcSize(minimumPolicy());
}
@Override
protected Dimension calculatePreferredSize() {
return calcSize(preferredPolicy());
}
}