/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.forms;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
public class BellowsLayout extends Layout {
public int alignment = SWT.BEGINNING;
public int direction = SWT.HORIZONTAL;
public int marginWidth = 0;
public int marginHeight = 0;
public int marginLeft = 0;
public int marginRight = 0;
public int marginTop = 0;
public int marginBottom = 0;
public int spacing = 0;
private int cachedWHint = SWT.DEFAULT;
private int cachedHHint = SWT.DEFAULT;
private Point cachedSize = null;
private static interface IAdjust {
int adjust(int width, int adjustment);
}
private static final IAdjust EXPAND = new IAdjust() {
public int adjust(int width, int adjustment) {
return width + adjustment;
}
};
private static final IAdjust SHRINK = new IAdjust() {
public int adjust(int width, int adjustment) {
return width - adjustment;
}
};
public BellowsLayout() {
}
public BellowsLayout(int direction) {
this.direction = direction;
}
protected Point computeSize(Composite composite, int wHint, int hHint,
boolean flushCache) {
if (wHint < 0 || hHint < 0) {
if (flushCache || this.cachedSize == null
|| wHint != this.cachedWHint || hHint != this.cachedHHint) {
layout(composite, getWidth(wHint, hHint),
getHeight(wHint, hHint), null, flushCache);
}
return new Point(this.cachedSize.x, this.cachedSize.y);
} else {
return new Point(wHint, hHint);
}
}
protected void layout(Composite composite, boolean flushCache) {
Rectangle bounds = composite.getClientArea();
int wHint = getWidth(bounds);
int hHint = getHeight(bounds);
layout(composite, wHint, hHint, bounds, flushCache);
}
private static class Adjustable implements Comparable<Adjustable> {
final int controlIndex;
final int adjustableWidth;
public Adjustable(int controlIndex, int extraWidth) {
this.controlIndex = controlIndex;
this.adjustableWidth = extraWidth;
}
public int compareTo(Adjustable that) {
return this.adjustableWidth - that.adjustableWidth;
}
public int hashCode() {
return controlIndex ^ adjustableWidth;
}
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Adjustable))
return false;
Adjustable that = (Adjustable) obj;
return this.controlIndex == that.controlIndex;
}
@Override
public String toString() {
return "{controlIndex=" + controlIndex //$NON-NLS-1$
+ ",adjustableWidth=" + adjustableWidth //$NON-NLS-1$
+ "}"; //$NON-NLS-1$
}
}
private void layout(Composite composite, int wHint, int hHint,
Rectangle bounds, boolean flushCache) {
Control[] allControls = composite.getChildren();
int count = 0;
Control[] controls = new Control[allControls.length];
int[] prefWidths = new int[allControls.length];
int[] prefHeights = new int[allControls.length];
int[] cellWidths = new int[allControls.length];
Map<Integer, SortedSet<Adjustable>> softShrinkables = new HashMap<Integer, SortedSet<Adjustable>>(
allControls.length);
SortedSet<Adjustable> hardShrinkables = new TreeSet<Adjustable>();
Map<Integer, SortedSet<Adjustable>> softExpandables = new HashMap<Integer, SortedSet<Adjustable>>(
allControls.length);
SortedSet<Adjustable> hardExpandables = new TreeSet<Adjustable>();
int childHeightHint = hHint < 0 ? hHint : Math.max(
0,
hHint - getHeight(marginWidth, marginHeight) * 2
- getHeight(marginLeft, marginTop)
- getHeight(marginRight, marginBottom));
int actualHeight = 0;
int actualWidth = 0;
Control control;
BellowsData data;
Point size;
int controlIndex;
Integer adjustPriority;
SortedSet<Adjustable> adjustableSet;
int adjustableWidth;
int total = allControls.length;
for (int i = 0; i < total; i++) {
control = allControls[i];
data = getLayoutData(control);
if (data.exclude)
continue;
controlIndex = count;
count++;
controls[controlIndex] = control;
size = data.computeSize(
control,
data.minorAlignment == SWT.FILL ? getWidth(SWT.DEFAULT,
childHeightHint) : SWT.DEFAULT,
data.minorAlignment == SWT.FILL ? getHeight(SWT.DEFAULT,
childHeightHint) : SWT.DEFAULT, flushCache);
prefHeights[controlIndex] = getHeight(size);
prefWidths[controlIndex] = cellWidths[controlIndex] = getWidth(size);
actualHeight = Math.max(actualHeight, prefHeights[controlIndex]);
actualWidth += cellWidths[controlIndex];
if (data.expandable) {
adjustableWidth = data.hardMaximum == 0 ? Integer.MAX_VALUE
: data.hardMaximum
- Math.max(prefWidths[controlIndex],
data.softMaximum);
if (adjustableWidth > 0) {
hardExpandables.add(new Adjustable(controlIndex,
adjustableWidth));
}
adjustableWidth = data.softMaximum == 0 ? Integer.MAX_VALUE
: data.softMaximum - prefWidths[controlIndex];
if (adjustableWidth > 0) {
adjustPriority = Integer.valueOf(data.expandPriority);
adjustableSet = softExpandables.get(adjustPriority);
if (adjustableSet == null) {
adjustableSet = new TreeSet<Adjustable>();
softExpandables.put(adjustPriority, adjustableSet);
}
adjustableSet.add(new Adjustable(controlIndex,
adjustableWidth));
}
}
if (data.shrinkable) {
adjustableWidth = Math.min(prefWidths[controlIndex],
data.softMinimum) - Math.max(0, data.hardMinimum);
if (adjustableWidth > 0) {
hardShrinkables.add(new Adjustable(controlIndex,
adjustableWidth));
}
adjustableWidth = prefWidths[controlIndex]
- Math.max(0, data.softMinimum);
if (adjustableWidth > 0) {
adjustPriority = Integer.valueOf(data.shrinkPriority);
adjustableSet = softShrinkables.get(adjustPriority);
if (adjustableSet == null) {
adjustableSet = new TreeSet<Adjustable>();
softShrinkables.put(adjustPriority, adjustableSet);
}
adjustableSet.add(new Adjustable(controlIndex,
adjustableWidth));
}
}
}
actualWidth += getWidth(marginWidth, marginHeight) * 2
+ getWidth(marginLeft, marginTop)
+ getWidth(marginRight, marginBottom) + spacing * (count - 1);
actualHeight += getHeight(marginWidth, marginHeight) * 2
+ getHeight(marginLeft, marginTop)
+ getHeight(marginRight, marginBottom);
if (wHint >= 0 && wHint != actualWidth && count > 0) {
int adjustment;
int adjustables;
int singleAdjustment;
Integer[] adjustPriorities;
IAdjust adjust;
Map<Integer, SortedSet<Adjustable>> softAdjustables;
SortedSet<Adjustable> hardAdjustables;
if (wHint < actualWidth) {
adjustment = actualWidth - wHint;
adjust = SHRINK;
softAdjustables = softShrinkables;
hardAdjustables = hardShrinkables;
} else {
adjustment = wHint - actualWidth;
adjust = EXPAND;
softAdjustables = softExpandables;
hardAdjustables = hardExpandables;
}
adjustPriorities = softAdjustables.keySet().toArray(
new Integer[softAdjustables.size()]);
Arrays.sort(adjustPriorities);
for (int i = adjustPriorities.length - 1; i >= 0; i--) {
adjustPriority = adjustPriorities[i];
adjustableSet = softAdjustables.get(adjustPriority);
if (!adjustableSet.isEmpty()) {
adjustables = adjustableSet.size();
for (Adjustable adjustable : adjustableSet) {
singleAdjustment = Math.min(adjustment / adjustables,
adjustable.adjustableWidth);
adjustment -= singleAdjustment;
actualWidth = adjust.adjust(actualWidth,
singleAdjustment);
cellWidths[adjustable.controlIndex] = adjust.adjust(
cellWidths[adjustable.controlIndex],
singleAdjustment);
adjustables--;
if (adjustment <= 0)
break;
}
if (adjustment <= 0)
break;
}
}
if (adjustment > 0) {
adjustables = hardAdjustables.size();
for (Adjustable adjustable : hardAdjustables) {
singleAdjustment = Math.min(adjustment / adjustables,
adjustable.adjustableWidth);
adjustment -= singleAdjustment;
actualWidth = adjust.adjust(actualWidth, singleAdjustment);
cellWidths[adjustable.controlIndex] = adjust.adjust(
cellWidths[adjustable.controlIndex],
singleAdjustment);
adjustables--;
if (adjustment <= 0)
break;
}
}
}
if (bounds == null) {
// Calculate preferred size:
if (direction == SWT.HORIZONTAL) {
this.cachedSize = new Point(actualWidth, actualHeight);
} else {
this.cachedSize = new Point(actualHeight, actualWidth);
}
} else {
// Layout children controls within bounds:
total = count;
int x = getX(bounds) + getWidth(marginWidth, marginHeight)
+ getWidth(marginLeft, marginTop);
int y = getY(bounds) + getHeight(marginWidth, marginHeight)
+ getHeight(marginLeft, marginTop);
int gap;
switch (alignment) {
case SWT.END:
case SWT.BOTTOM:
case SWT.RIGHT:
x += wHint - actualWidth;
gap = 0;
break;
case SWT.CENTER:
x += (wHint - actualWidth) / 2;
gap = 0;
break;
case SWT.FILL:
gap = wHint - actualWidth;
break;
default:
gap = 0;
}
int controlX, controlY, controlWidth, controlHeight, singleGap;
for (int i = 0; i < total; i++) {
controlIndex = i;
control = controls[controlIndex];
data = getLayoutData(control);
switch (data.majorAlignment) {
case SWT.BEGINNING:
case SWT.LEFT:
case SWT.TOP:
controlWidth = prefWidths[controlIndex];
controlX = x;
break;
case SWT.END:
case SWT.RIGHT:
case SWT.BOTTOM:
controlWidth = prefWidths[controlIndex];
controlX = x + cellWidths[controlIndex] - controlWidth;
break;
case SWT.CENTER:
controlWidth = prefWidths[controlIndex];
controlX = x + (cellWidths[controlIndex] - controlWidth)
/ 2;
break;
default:
controlWidth = cellWidths[controlIndex];
controlX = x;
break;
}
switch (data.minorAlignment) {
case SWT.BEGINNING:
case SWT.LEFT:
case SWT.TOP:
controlHeight = prefHeights[controlIndex];
controlY = y;
break;
case SWT.END:
case SWT.RIGHT:
case SWT.BOTTOM:
controlHeight = prefHeights[controlIndex];
controlY = y + childHeightHint - controlHeight;
break;
case SWT.CENTER:
controlHeight = prefHeights[controlIndex];
controlY = y + (childHeightHint - controlHeight) / 2;
break;
default:
controlHeight = childHeightHint;
controlY = y;
break;
}
if (direction == SWT.HORIZONTAL) {
control.setBounds(controlX, controlY, controlWidth,
controlHeight);
} else {
control.setBounds(controlY, controlX, controlHeight,
controlWidth);
}
x += cellWidths[controlIndex] + spacing;
if (gap != 0) {
singleGap = gap / (total - i);
gap -= singleGap;
x += singleGap;
}
}
}
}
private int getWidth(Point size) {
return direction == SWT.HORIZONTAL ? size.x : size.y;
}
private int getWidth(Rectangle rect) {
return direction == SWT.HORIZONTAL ? rect.width : rect.height;
}
private int getWidth(int width, int height) {
return direction == SWT.HORIZONTAL ? width : height;
}
private int getHeight(Point size) {
return direction == SWT.HORIZONTAL ? size.y : size.x;
}
private int getHeight(Rectangle rect) {
return direction == SWT.HORIZONTAL ? rect.height : rect.width;
}
private int getHeight(int width, int height) {
return direction == SWT.HORIZONTAL ? height : width;
}
private int getX(Rectangle rect) {
return direction == SWT.HORIZONTAL ? rect.x : rect.y;
}
private int getY(Rectangle rect) {
return direction == SWT.HORIZONTAL ? rect.y : rect.x;
}
private BellowsData getLayoutData(Control control) {
Object data = control.getLayoutData();
if (data == null && !(data instanceof BellowsData)) {
data = new BellowsData();
control.setLayoutData(data);
}
return (BellowsData) data;
}
@Override
protected boolean flushCache(Control control) {
this.cachedSize = null;
BellowsData data = getLayoutData(control);
data.flushCache();
return true;
}
}