/*******************************************************************************
* Copyright 2013 Geoscience Australia
*
* 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 au.gov.ga.earthsci.common.ui.widgets;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
/**
* Layout used by the {@link PageLinks} component; a modification of SWT's
* {@link RowLayout}.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public final class PageLinksLayout extends Layout
{
/**
* type specifies whether the layout places controls in rows or columns.
*
* The default value is HORIZONTAL.
*
* Possible values are:
* <ul>
* <li>HORIZONTAL: Position the controls horizontally from left to right</li>
* <li>VERTICAL: Position the controls vertically from top to bottom</li>
* </ul>
*
* @since 2.0
*/
public int type = SWT.HORIZONTAL;
/**
* marginWidth specifies the number of pixels of horizontal margin that will
* be placed along the left and right edges of the layout.
*
* The default value is 0.
*
* @since 3.0
*/
public int marginWidth = 0;
/**
* marginHeight specifies the number of pixels of vertical margin that will
* be placed along the top and bottom edges of the layout.
*
* The default value is 0.
*
* @since 3.0
*/
public int marginHeight = 0;
/**
* spacing specifies the number of pixels between the edge of one cell and
* the edge of its neighbouring cell.
*
* The default value is 3.
*/
public int spacing = 3;
/**
* wrap specifies whether a control will be wrapped to the next row if there
* is insufficient space on the current row.
*
* The default value is true.
*/
public boolean wrap = true;
/**
* pack specifies whether all controls in the layout take their preferred
* size. If pack is false, all controls will have the same size which is the
* size required to accommodate the largest preferred height and the largest
* preferred width of all the controls in the layout.
*
* The default value is true.
*/
public boolean pack = true;
/**
* fill specifies whether the controls in a row should be all the same
* height for horizontal layouts, or the same width for vertical layouts.
*
* The default value is false.
*
* @since 3.0
*/
public boolean fill = false;
/**
* center specifies whether the controls in a row should be centered
* vertically in each cell for horizontal layouts, or centered horizontally
* in each cell for vertical layouts.
*
* The default value is false.
*
* @since 3.4
*/
public boolean center = false;
/**
* justify specifies whether the controls in a row should be fully
* justified, with any extra space placed between the controls.
*
* The default value is false.
*/
public boolean justify = false;
/**
* marginLeft specifies the number of pixels of horizontal margin that will
* be placed along the left edge of the layout.
*
* The default value is 3.
*/
public int marginLeft = 3;
/**
* marginTop specifies the number of pixels of vertical margin that will be
* placed along the top edge of the layout.
*
* The default value is 3.
*/
public int marginTop = 3;
/**
* marginRight specifies the number of pixels of horizontal margin that will
* be placed along the right edge of the layout.
*
* The default value is 3.
*/
public int marginRight = 3;
/**
* marginBottom specifies the number of pixels of vertical margin that will
* be placed along the bottom edge of the layout.
*
* The default value is 3.
*/
public int marginBottom = 3;
/**
* Constructs a new instance of this class.
*/
public PageLinksLayout()
{
}
/**
* Constructs a new instance of this class given the type.
*
* @param type
* the type of row layout
*
* @since 2.0
*/
public PageLinksLayout(int type)
{
this.type = type;
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache)
{
Point extent;
if (type == SWT.HORIZONTAL)
{
extent = layoutHorizontal(composite, false, (wHint != SWT.DEFAULT) && wrap, wHint, flushCache);
}
else
{
extent = layoutVertical(composite, false, (hHint != SWT.DEFAULT) && wrap, hHint, flushCache);
}
if (wHint != SWT.DEFAULT)
{
extent.x = wHint;
}
if (hHint != SWT.DEFAULT)
{
extent.y = hHint;
}
return extent;
}
Point computeSize(Control control, boolean flushCache)
{
int wHint = SWT.DEFAULT, hHint = SWT.DEFAULT;
RowData data = (RowData) control.getLayoutData();
if (data != null)
{
wHint = data.width;
hHint = data.height;
}
return control.computeSize(wHint, hHint, flushCache);
}
@Override
protected boolean flushCache(Control control)
{
return true;
}
String getName()
{
String string = getClass().getName();
int index = string.lastIndexOf('.');
if (index == -1)
{
return string;
}
return string.substring(index + 1, string.length());
}
@Override
protected void layout(Composite composite, boolean flushCache)
{
Rectangle clientArea = composite.getClientArea();
if (type == SWT.HORIZONTAL)
{
layoutHorizontal(composite, true, wrap, clientArea.width, flushCache);
}
else
{
layoutVertical(composite, true, wrap, clientArea.height, flushCache);
}
}
Point layoutHorizontal(Composite composite, boolean move, boolean wrap, int width, boolean flushCache)
{
Control[] children = composite.getChildren();
int count = 0;
for (int i = 0; i < children.length; i++)
{
Control control = children[i];
RowData data = (RowData) control.getLayoutData();
if (data == null || !data.exclude)
{
children[count++] = children[i];
}
}
if (count == 0)
{
return new Point(marginLeft + marginWidth * 2 + marginRight, marginTop + marginHeight * 2 + marginBottom);
}
int childWidth = 0, childHeight = 0, maxHeight = 0;
if (!pack)
{
for (int i = 0; i < count; i++)
{
Control child = children[i];
Point size = computeSize(child, flushCache);
childWidth = Math.max(childWidth, size.x);
childHeight = Math.max(childHeight, size.y);
}
maxHeight = childHeight;
}
int clientX = 0, clientY = 0;
if (move)
{
Rectangle rect = composite.getClientArea();
clientX = rect.x;
clientY = rect.y;
}
int[] wraps = null;
boolean wrapped = false;
Rectangle[] bounds = null;
if (move && (justify || fill || center))
{
bounds = new Rectangle[count];
wraps = new int[count];
}
int maxX = 0, x = marginLeft + marginWidth, y = marginTop + marginHeight;
for (int i = 0; i < count; i++)
{
Control child = children[i];
if (pack)
{
Point size = computeSize(child, flushCache);
childWidth = size.x;
childHeight = size.y;
}
if (wrap && (i != 0) && (x + childWidth > width))
{
wrapped = true;
if (move && (justify || fill || center))
{
wraps[i - 1] = maxHeight;
}
x = marginLeft + marginWidth;
y += spacing + maxHeight;
if (pack)
{
maxHeight = 0;
}
}
if (pack || fill || center)
{
maxHeight = Math.max(maxHeight, childHeight);
}
if (move)
{
int childX = x + clientX, childY = y + clientY;
if (justify || fill || center)
{
bounds[i] = new Rectangle(childX, childY, childWidth, childHeight);
}
else
{
child.setBounds(childX, childY, childWidth, childHeight);
}
}
x += spacing + childWidth;
maxX = Math.max(maxX, x);
}
maxX = Math.max(clientX + marginLeft + marginWidth, maxX - spacing);
if (!wrapped)
{
maxX += marginRight + marginWidth;
}
if (move && (justify || fill || center))
{
int space = 0, margin = 0;
if (!wrapped)
{
space = Math.max(0, (width - maxX) / (count + 1));
margin = Math.max(0, ((width - maxX) % (count + 1)) / 2);
}
else
{
if (fill || justify || center)
{
int last = 0;
if (count > 0)
{
wraps[count - 1] = maxHeight;
}
for (int i = 0; i < count; i++)
{
if (wraps[i] != 0)
{
int wrapCount = i - last + 1;
if (justify)
{
int wrapX = 0;
for (int j = last; j <= i; j++)
{
wrapX += bounds[j].width + spacing;
}
space = Math.max(0, (width - wrapX) / (wrapCount + 1));
margin = Math.max(0, ((width - wrapX) % (wrapCount + 1)) / 2);
}
for (int j = last; j <= i; j++)
{
if (justify)
{
bounds[j].x += (space * (j - last + 1)) + margin;
}
if (fill)
{
bounds[j].height = wraps[i];
}
else
{
if (center)
{
bounds[j].y += Math.max(0, (wraps[i] - bounds[j].height) / 2);
}
}
}
last = i + 1;
}
}
}
}
//MODIFICATION
int extraSpace = width - maxX;
space = 1 + (extraSpace - 1) / (count - 1);
int extra = 0;
for (int i = 0; i < count; i++)
{
if (!wrapped)
{
if (justify)
{
//bounds[i].x += (space * (i + 1)) + margin;
bounds[i].x += extra;
if (extra + space > extraSpace)
{
space = extraSpace - extra;
}
extra += space;
}
if (fill)
{
bounds[i].height = maxHeight;
}
else
{
if (center)
{
bounds[i].y += Math.max(0, (maxHeight - bounds[i].height) / 2);
}
}
}
children[i].setBounds(bounds[i]);
}
//MODIFICATION
}
return new Point(maxX, y + maxHeight + marginBottom + marginHeight);
}
Point layoutVertical(Composite composite, boolean move, boolean wrap, int height, boolean flushCache)
{
Control[] children = composite.getChildren();
int count = 0;
for (int i = 0; i < children.length; i++)
{
Control control = children[i];
RowData data = (RowData) control.getLayoutData();
if (data == null || !data.exclude)
{
children[count++] = children[i];
}
}
if (count == 0)
{
return new Point(marginLeft + marginWidth * 2 + marginRight, marginTop + marginHeight * 2 + marginBottom);
}
int childWidth = 0, childHeight = 0, maxWidth = 0;
if (!pack)
{
for (int i = 0; i < count; i++)
{
Control child = children[i];
Point size = computeSize(child, flushCache);
childWidth = Math.max(childWidth, size.x);
childHeight = Math.max(childHeight, size.y);
}
maxWidth = childWidth;
}
int clientX = 0, clientY = 0;
if (move)
{
Rectangle rect = composite.getClientArea();
clientX = rect.x;
clientY = rect.y;
}
int[] wraps = null;
boolean wrapped = false;
Rectangle[] bounds = null;
if (move && (justify || fill || center))
{
bounds = new Rectangle[count];
wraps = new int[count];
}
int maxY = 0, x = marginLeft + marginWidth, y = marginTop + marginHeight;
for (int i = 0; i < count; i++)
{
Control child = children[i];
if (pack)
{
Point size = computeSize(child, flushCache);
childWidth = size.x;
childHeight = size.y;
}
if (wrap && (i != 0) && (y + childHeight > height))
{
wrapped = true;
if (move && (justify || fill || center))
{
wraps[i - 1] = maxWidth;
}
x += spacing + maxWidth;
y = marginTop + marginHeight;
if (pack)
{
maxWidth = 0;
}
}
if (pack || fill || center)
{
maxWidth = Math.max(maxWidth, childWidth);
}
if (move)
{
int childX = x + clientX, childY = y + clientY;
if (justify || fill || center)
{
bounds[i] = new Rectangle(childX, childY, childWidth, childHeight);
}
else
{
child.setBounds(childX, childY, childWidth, childHeight);
}
}
y += spacing + childHeight;
maxY = Math.max(maxY, y);
}
maxY = Math.max(clientY + marginTop + marginHeight, maxY - spacing);
if (!wrapped)
{
maxY += marginBottom + marginHeight;
}
if (move && (justify || fill || center))
{
int space = 0, margin = 0;
if (!wrapped)
{
space = Math.max(0, (height - maxY) / (count + 1));
margin = Math.max(0, ((height - maxY) % (count + 1)) / 2);
}
else
{
if (fill || justify || center)
{
int last = 0;
if (count > 0)
{
wraps[count - 1] = maxWidth;
}
for (int i = 0; i < count; i++)
{
if (wraps[i] != 0)
{
int wrapCount = i - last + 1;
if (justify)
{
int wrapY = 0;
for (int j = last; j <= i; j++)
{
wrapY += bounds[j].height + spacing;
}
space = Math.max(0, (height - wrapY) / (wrapCount + 1));
margin = Math.max(0, ((height - wrapY) % (wrapCount + 1)) / 2);
}
for (int j = last; j <= i; j++)
{
if (justify)
{
bounds[j].y += (space * (j - last + 1)) + margin;
}
if (fill)
{
bounds[j].width = wraps[i];
}
else
{
if (center)
{
bounds[j].x += Math.max(0, (wraps[i] - bounds[j].width) / 2);
}
}
}
last = i + 1;
}
}
}
}
//MODIFICATION
int extraSpace = height - maxY;
space = 1 + (extraSpace - 1) / (count - 1);
int extra = 0;
for (int i = 0; i < count; i++)
{
if (!wrapped)
{
if (justify)
{
//bounds[i].y += (space * (i + 1)) + margin;
bounds[i].y += extra;
if (extra + space > extraSpace)
{
space = extraSpace - extra;
}
extra += space;
}
if (fill)
{
bounds[i].width = maxWidth;
}
else
{
if (center)
{
bounds[i].x += Math.max(0, (maxWidth - bounds[i].width) / 2);
}
}
}
children[i].setBounds(bounds[i]);
}
//MODIFICATION
}
return new Point(x + maxWidth + marginRight + marginWidth, maxY);
}
/**
* Returns a string containing a concise, human-readable description of the
* receiver.
*
* @return a string representation of the layout
*/
@Override
public String toString()
{
String string = getName() + " {"; //$NON-NLS-1$
string += "type=" + ((type != SWT.HORIZONTAL) ? "SWT.VERTICAL" : "SWT.HORIZONTAL") + " "; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
if (marginWidth != 0)
{
string += "marginWidth=" + marginWidth + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (marginHeight != 0)
{
string += "marginHeight=" + marginHeight + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (marginLeft != 0)
{
string += "marginLeft=" + marginLeft + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (marginTop != 0)
{
string += "marginTop=" + marginTop + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (marginRight != 0)
{
string += "marginRight=" + marginRight + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (marginBottom != 0)
{
string += "marginBottom=" + marginBottom + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
if (spacing != 0)
{
string += "spacing=" + spacing + " "; //$NON-NLS-1$ //$NON-NLS-2$
}
string += "wrap=" + wrap + " "; //$NON-NLS-1$ //$NON-NLS-2$
string += "pack=" + pack + " "; //$NON-NLS-1$ //$NON-NLS-2$
string += "fill=" + fill + " "; //$NON-NLS-1$ //$NON-NLS-2$
string += "justify=" + justify + " "; //$NON-NLS-1$ //$NON-NLS-2$
string = string.trim();
string += "}"; //$NON-NLS-1$
return string;
}
}