blob: 3d30817c8bbb7715b34f0e75dc20e6cc5e2de90f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*******************************************************************************/
package org.eclipse.scout.rt.ui.rap;
import java.util.TreeSet;
import org.eclipse.scout.rt.ui.rap.extension.IUiDecoration;
import org.eclipse.scout.rt.ui.rap.extension.UiDecorationExtensionPoint;
import org.eclipse.scout.rt.ui.rap.util.RwtLayoutUtility;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
class LogicalGridLayoutInfo {
LogicalGridData[/* component count */] gridDatas;
Control[/* component count */] components;
int[/* component count */] componentWidths;
int[/* component count */] componentHeights;
private int m_hgap;
private int m_vgap;
private boolean m_flushCache;
int cols; /* number of cells horizontally */
int rows; /* number of cells vertically */
int[/* column */][/* min,pref,max */] width;
int[/* row */][/* min,pref,max */] height;
int[/*column*/] widthHints;
double[/* column */] weightX;
double[/* row */] weightY;
LogicalGridLayoutInfo(Control[] components, LogicalGridData[] cons, int hgap, int vgap, int wHint, boolean flushCache) {
this.components = components;
m_hgap = hgap;
m_vgap = vgap;
m_flushCache = flushCache;
// create a modifiable copy of the grid datas
this.gridDatas = new LogicalGridData[cons.length];
for (int i = 0; i < cons.length; i++) {
this.gridDatas[i] = new LogicalGridData(cons[i]);
}
if (components.length == 0) {
this.cols = 0;
this.rows = 0;
this.width = new int[0][0];
this.height = new int[0][0];
this.weightX = new double[0];
this.weightY = new double[0];
return;
}
// eliminate unused rows and columns
TreeSet<Integer> usedCols = new TreeSet<Integer>();
TreeSet<Integer> usedRows = new TreeSet<Integer>();
// ticket 86645 use member gridDatas instead of param cons
for (LogicalGridData gd : gridDatas) {
if (gd.gridx < 0) {
gd.gridx = 0;
}
if (gd.gridy < 0) {
gd.gridy = 0;
}
if (gd.gridw < 1) {
gd.gridw = 1;
}
if (gd.gridh < 1) {
gd.gridh = 1;
}
for (int x = gd.gridx; x < gd.gridx + gd.gridw; x++) {
usedCols.add(x);
}
for (int y = gd.gridy; y < gd.gridy + gd.gridh; y++) {
usedRows.add(y);
}
}
int maxCol = usedCols.last();
for (int x = maxCol; x >= 0; x--) {
if (!usedCols.contains(x)) {
// eliminate column
// ticket 86645 use member gridDatas instead of param cons
for (LogicalGridData gd : gridDatas) {
if (gd.gridx > x) {
gd.gridx--;
}
}
}
}
int maxRow = usedRows.last();
for (int y = maxRow; y >= 0; y--) {
if (!usedRows.contains(y)) {
// eliminate row
// ticket 86645 use member gridDatas instead of param cons
for (LogicalGridData gd : gridDatas) {
if (gd.gridy > y) {
// ticket 86645
gd.gridy--;
}
}
}
}
//
this.componentWidths = new int[this.components.length];
this.componentHeights = new int[this.components.length];
this.cols = usedCols.size();
this.rows = usedRows.size();
this.width = new int[cols][3];
this.height = new int[rows][3];
this.weightX = new double[cols];
this.weightY = new double[rows];
initializeInfo(hgap, vgap, wHint);
}
private void initializeInfo(int hgap, int vgap, int wHint) {
int compCount = components.length;
//cleanup constraints
for (int i = 0; i < compCount; i++) {
LogicalGridData cons = gridDatas[i];
if (cons.gridx < 0) {
cons.gridx = 0;
}
if (cons.gridy < 0) {
cons.gridy = 0;
}
if (cons.gridw < 1) {
cons.gridw = 1;
}
if (cons.gridh < 1) {
cons.gridh = 1;
}
if (cons.gridx >= cols) {
cons.gridx = cols - 1;
}
if (cons.gridy >= rows) {
cons.gridy = rows - 1;
}
if (cons.gridx + cons.gridw - 1 >= cols) {
cons.gridw = cols - cons.gridx;
}
if (cons.gridy + cons.gridh >= rows) {
cons.gridh = rows - cons.gridy;
}
}
//layout first the widths then the heights
//pass 1 only computes widths
for (int i = 0; i < compCount; i++) {
Control comp = components[i];
LogicalGridData cons = gridDatas[i];
if (cons.widthHint > 0) {
componentWidths[i] = cons.widthHint;
}
else {
componentWidths[i] = uiSizeInPixel(comp, SWT.DEFAULT, m_flushCache).x;
}
}
initializeColumns(componentWidths, hgap);
//pass 2 computes heights based on with hints (use pref width when hint is empty)
if (wHint == SWT.DEFAULT) {
widthHints = null;
}
else {
widthHints = layoutSizes(wHint - Math.max(0, (cols - 1) * hgap), width, weightX);
}
for (int i = 0; i < compCount; i++) {
Control comp = components[i];
LogicalGridData cons = gridDatas[i];
if (cons.heightHint > 0) {
componentHeights[i] = cons.heightHint;
}
else {
componentHeights[i] = uiSizeInPixel(comp, getWidthHint(cons), false).y;
}
}
initializeRows(componentHeights, vgap);
}
private void initializeColumns(int[] compSize, int hgap) {
int compCount = compSize.length;
int[] prefWidths = new int[cols];
boolean[] fixedWidths = new boolean[cols];
for (int i = 0; i < compCount; i++) {
LogicalGridData cons = gridDatas[i];
if (cons.gridw == 1) {
int prefw;
if (cons.widthHint > 0) {
prefw = cons.widthHint;
}
else if (cons.useUiWidth) {
prefw = compSize[i];
}
else {
prefw = logicalWidthInPixel(cons);
}
for (int j = cons.gridx; j < cons.gridx + cons.gridw && j < cols; j++) {
prefWidths[j] = Math.max(prefWidths[j], prefw);
if (cons.weightx == 0) {
fixedWidths[j] = true;
}
}
}
}
for (int i = 0; i < compCount; i++) {
LogicalGridData cons = gridDatas[i];
if (cons.gridw > 1) {
int hSpan = cons.gridw;
int spanWidth;
int distWidth;
// pref
spanWidth = 0;
for (int j = cons.gridx; j < cons.gridx + cons.gridw && j < cols; j++) {
if (!fixedWidths[j]) {
spanWidth += prefWidths[j];
}
}
if (cons.widthHint > 0) {
distWidth = cons.widthHint - spanWidth - (hSpan - 1) * hgap;
}
else if (cons.useUiWidth) {
distWidth = compSize[i] - spanWidth - (hSpan - 1) * hgap;
}
else {
distWidth = logicalWidthInPixel(cons) - spanWidth - (hSpan - 1) * hgap;
}
if (distWidth > 0) {
int equalWidth = (distWidth + spanWidth) / hSpan;
int remainder = (distWidth + spanWidth) % hSpan;
int last = -1;
for (int j = cons.gridx; j < cons.gridx + cons.gridw && j < cols; j++) {
if (fixedWidths[j]) {
prefWidths[last = j] = prefWidths[j];
}
else {
prefWidths[last = j] = Math.max(equalWidth, prefWidths[j]);
}
if (cons.weightx == 0) {
fixedWidths[j] = true;
}
}
if (last > -1) {
prefWidths[last] += remainder;
}
}
}
}
for (int i = 0; i < cols; i++) {
if (fixedWidths[i]) {
width[i][LogicalGridLayout.MIN] = prefWidths[i];
width[i][LogicalGridLayout.PREF] = prefWidths[i];
width[i][LogicalGridLayout.MAX] = prefWidths[i];
}
else {
width[i][LogicalGridLayout.MIN] = 15;// must be exactly 0!
width[i][LogicalGridLayout.PREF] = prefWidths[i];
width[i][LogicalGridLayout.MAX] = 10240;
}
}
// averaged column weights, normalized so that sum of weights is equal to
// 1.0
for (int i = 0; i < cols; i++) {
if (fixedWidths[i]) {
weightX[i] = 0;
}
else {
double weightSum = 0;
int weightCount = 0;
for (int k = 0; k < compCount; k++) {
LogicalGridData cons = gridDatas[k];
if (cons.weightx > 0 && cons.gridx <= i && i <= cons.gridx + cons.gridw - 1) {
weightSum += cons.weightx / cons.gridw;
weightCount++;
}
}
weightX[i] = weightCount > 0 ? weightSum / weightCount : 0;
}
}
double sumWeightX = 0;
for (int i = 0; i < cols; i++) {
sumWeightX += weightX[i];
}
if (sumWeightX >= 1e-6) {
double f = 1.0 / sumWeightX;
for (int i = 0; i < cols; i++) {
weightX[i] = weightX[i] * f;
}
}
}
private void initializeRows(int[] compSize, int vgap) {
int compCount = compSize.length;
int[] prefHeights = new int[rows];
boolean[] fixedHeights = new boolean[rows];
for (int i = 0; i < compCount; i++) {
LogicalGridData cons = gridDatas[i];
if (cons.gridh == 1) {
int prefh;
if (cons.heightHint > 0) {
prefh = cons.heightHint;
}
else if (cons.useUiHeight) {
prefh = compSize[i];
}
else {
prefh = logicalHeightInPixel(cons);
}
for (int j = cons.gridy; j < cons.gridy + cons.gridh && j < rows; j++) {
prefHeights[j] = Math.max(prefHeights[j], prefh);
if (cons.weighty == 0) {
fixedHeights[j] = true;
}
}
}
}
for (int i = 0; i < compCount; i++) {
LogicalGridData cons = gridDatas[i];
if (cons.gridh > 1) {
int vspan = cons.gridh;
int spanHeight;
int distHeight;
// pref
spanHeight = 0;
for (int j = cons.gridy; j < cons.gridy + cons.gridh && j < rows; j++) {
spanHeight += prefHeights[j];
}
if (cons.heightHint > 0) {
distHeight = cons.heightHint - spanHeight - (vspan - 1) * vgap;
}
else if (cons.useUiHeight) {
distHeight = compSize[i] - spanHeight - (vspan - 1) * vgap;
}
else {
distHeight = logicalHeightInPixel(cons) - spanHeight - (vspan - 1) * vgap;
}
if (distHeight > 0) {
int equalHeight = (distHeight + spanHeight) / vspan;
int remainder = (distHeight + spanHeight) % vspan;
int last = -1;
for (int j = cons.gridy; j < cons.gridy + cons.gridh && j < rows; j++) {
prefHeights[last = j] = Math.max(equalHeight, prefHeights[j]);
if (cons.weighty == 0) {
fixedHeights[j] = true;
}
}
if (last > -1) {
prefHeights[last] += remainder;
}
}
}
}
for (int i = 0; i < rows; i++) {
if (fixedHeights[i]) {
height[i][LogicalGridLayout.MIN] = prefHeights[i];
height[i][LogicalGridLayout.PREF] = prefHeights[i];
height[i][LogicalGridLayout.MAX] = prefHeights[i];
}
else {
height[i][LogicalGridLayout.MIN] = 0;// must be exactly 0!
height[i][LogicalGridLayout.PREF] = prefHeights[i];
height[i][LogicalGridLayout.MAX] = 10240;
}
}
// averaged row weights, normalized so that sum of weights is equal to 1.0
for (int i = 0; i < rows; i++) {
if (fixedHeights[i]) {
weightY[i] = 0;
}
else {
double weightSum = 0;
int weightCount = 0;
for (int k = 0; k < compCount; k++) {
LogicalGridData cons = gridDatas[k];
if (cons.weighty > 0 && cons.gridy <= i && i <= cons.gridy + cons.gridh - 1) {
weightSum += cons.weighty / cons.gridh;
weightCount++;
}
}
weightY[i] = weightCount > 0 ? weightSum / weightCount : 0;
}
}
double sumWeightY = 0;
for (int i = 0; i < rows; i++) {
sumWeightY += weightY[i];
}
if (sumWeightY >= 1e-6) {
double f = 1.0 / sumWeightY;
for (int i = 0; i < rows; i++) {
weightY[i] = weightY[i] * f;
}
}
}
/**
* calculate grid cells (gaps are not included in the grid cell bounds)
*/
Rectangle[][] layoutCellBounds(Rectangle clientArea) {
int[] w = layoutSizes(clientArea.width - Math.max(0, (cols - 1) * m_hgap), width, weightX);
int[] h = layoutSizes(clientArea.height - Math.max(0, (rows - 1) * m_vgap), height, weightY);
Rectangle[][] cellBounds = new Rectangle[rows][cols];
int y = clientArea.y;
for (int r = 0; r < cellBounds.length; r++) {
int x = clientArea.x;
for (int c = 0; c < cellBounds[r].length; c++) {
cellBounds[r][c] = new Rectangle(x, y, w[c], h[r]);
x += w[c];
x += m_hgap;
}
y += h[r];
y += m_vgap;
}
return cellBounds;
}
private int[] layoutSizes(int targetSize, int[][] sizes, double[] weights) {
int[] outSizes = new int[sizes.length];
if (targetSize <= 0) {
return new int[sizes.length];
}
int sumSize = 0;
float[] tmpWeight = new float[weights.length];
float sumWeight = 0;
for (int i = 0; i < sizes.length; i++) {
outSizes[i] = sizes[i][LogicalGridLayout.PREF];
sumSize += outSizes[i];
tmpWeight[i] = (float) weights[i];
/**
* auto correction: if weight is 0 and min / max sizes are NOT equal then
* set weight to 1; if weight<eps set it to 0
*/
if (tmpWeight[i] < LogicalGridLayout.EPS) {
if (sizes[i][LogicalGridLayout.MAX] > sizes[i][LogicalGridLayout.MIN]) {
tmpWeight[i] = 1;
}
else {
tmpWeight[i] = 0;
}
}
sumWeight += tmpWeight[i];
}
// normalize weights
if (sumWeight > 0) {
for (int i = 0; i < tmpWeight.length; i++) {
tmpWeight[i] = tmpWeight[i] / sumWeight;
}
}
int deltaInt = targetSize - sumSize;
// expand or shrink
if (Math.abs(deltaInt) > 0) {
// setup accumulators
float[] accWeight = new float[tmpWeight.length];
if (deltaInt > 0) {
// expand
boolean hasTargets = true;
while (deltaInt > 0 && hasTargets) {
hasTargets = false;
for (int i = 0; i < outSizes.length && deltaInt > 0; i++) {
if (tmpWeight[i] > 0 && outSizes[i] < sizes[i][LogicalGridLayout.MAX]) {
hasTargets = true;
accWeight[i] += tmpWeight[i];
if (accWeight[i] > 0) {
accWeight[i] -= 1;
outSizes[i] += 1;
deltaInt -= 1;
}
}
}
}
}
else {// delta<0
// shrink
boolean hasTargets = true;
while (deltaInt < 0 && hasTargets) {
hasTargets = false;
for (int i = 0; i < outSizes.length && deltaInt < 0; i++) {
if (tmpWeight[i] > 0 && outSizes[i] > sizes[i][LogicalGridLayout.MIN]) {
hasTargets = true;
accWeight[i] += tmpWeight[i];
if (accWeight[i] > 0) {
accWeight[i] -= 1;
outSizes[i] -= 1;
deltaInt += 1;
}
}
}
}
}
}
return outSizes;
}
private int getWidthHint(LogicalGridData cons) {
if (widthHints == null || cons == null) {
return SWT.DEFAULT;
}
int tmp = (cons.gridw - 1) * m_hgap;
for (int k = cons.gridx; k < cons.gridx + cons.gridw; k++) {
if (k >= 0 && k < widthHints.length) {
tmp += widthHints[k];
}
}
return tmp;
}
private static int logicalWidthInPixel(LogicalGridData cons) {
int gridW = cons.gridw;
IUiDecoration deco = UiDecorationExtensionPoint.getLookAndFeel();
return gridW * deco.getLogicalGridLayoutDefaultColumnWidth() + Math.max(0, gridW - 1) * deco.getLogicalGridLayoutHorizontalGap();
}
private static int logicalHeightInPixel(LogicalGridData cons) {
int gridH = cons.gridh;
IUiDecoration deco = UiDecorationExtensionPoint.getLookAndFeel();
return gridH * deco.getLogicalGridLayoutRowHeight() + Math.max(0, gridH - 1) * deco.getLogicalGridLayoutVerticalGap();
}
private static Point uiSizeInPixel(Control c, int wHint, boolean flushCache) {
return RwtLayoutUtility.computeSizeEx(c, wHint, SWT.DEFAULT, flushCache);
}
}