blob: e5dbd5396519b9893ada69561d333fc1790b2ded [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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 org.apache.pivot.wtk.skin;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.Utils;
import org.apache.pivot.wtk.BoxPane;
import org.apache.pivot.wtk.BoxPaneListener;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.HorizontalAlignment;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Orientation;
import org.apache.pivot.wtk.VerticalAlignment;
/**
* Box pane skin.
*/
public class BoxPaneSkin extends ContainerSkin implements BoxPaneListener {
private HorizontalAlignment horizontalAlignment = HorizontalAlignment.LEFT;
private VerticalAlignment verticalAlignment = VerticalAlignment.TOP;
private Insets padding = Insets.NONE;
private int spacing = 4;
private boolean fill = false;
@Override
public void install(final Component component) {
super.install(component);
BoxPane boxPane = (BoxPane) component;
boxPane.getBoxPaneListeners().add(this);
}
@Override
public int getPreferredWidth(final int height) {
BoxPane boxPane = (BoxPane) getComponent();
int preferredWidth = 0;
Orientation orientation = boxPane.getOrientation();
if (orientation == Orientation.HORIZONTAL) {
int heightUpdated = height;
// Include padding in constraint
if (heightUpdated != -1) {
heightUpdated = Math.max(heightUpdated - padding.getHeight(), 0);
}
// Preferred width is the sum of the preferred widths of all components
int j = 0;
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
preferredWidth += component.getPreferredWidth(fill ? heightUpdated : -1);
j++;
}
}
// Include spacing
if (j > 1) {
preferredWidth += spacing * (j - 1);
}
} else {
// Preferred width is the maximum preferred width of all components
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
preferredWidth = Math.max(preferredWidth, component.getPreferredWidth());
}
}
}
// Include left and right padding values
preferredWidth += padding.getWidth();
return preferredWidth;
}
@Override
public int getPreferredHeight(final int width) {
BoxPane boxPane = (BoxPane) getComponent();
int preferredHeight = 0;
Orientation orientation = boxPane.getOrientation();
if (orientation == Orientation.HORIZONTAL) {
// Preferred height is the maximum preferred height of all components
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
preferredHeight = Math.max(preferredHeight, component.getPreferredHeight());
}
}
} else {
int widthUpdated = width;
// Include padding in constraint
if (widthUpdated != -1) {
widthUpdated = Math.max(widthUpdated - padding.getWidth(), 0);
}
// Preferred height is the sum of the preferred heights of all
// components
int j = 0;
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
preferredHeight += component.getPreferredHeight(fill ? widthUpdated : -1);
j++;
}
}
// Include spacing
if (j > 1) {
preferredHeight += spacing * (j - 1);
}
}
// Include top and bottom padding values
preferredHeight += padding.getHeight();
return preferredHeight;
}
@Override
public Dimensions getPreferredSize() {
BoxPane boxPane = (BoxPane) getComponent();
int preferredWidth = 0;
int preferredHeight = 0;
int j = 0;
switch (boxPane.getOrientation()) {
case HORIZONTAL:
// Preferred width is the sum of the preferred widths of all components
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
Dimensions preferredSize = component.getPreferredSize();
preferredWidth += preferredSize.width;
preferredHeight = Math.max(preferredSize.height, preferredHeight);
j++;
}
}
// Include spacing
if (j > 1) {
preferredWidth += spacing * (j - 1);
}
break;
case VERTICAL:
// Preferred height is the sum of the preferred heights of all components
for (int i = 0, n = boxPane.getLength(); i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
Dimensions preferredSize = component.getPreferredSize();
preferredWidth = Math.max(preferredSize.width, preferredWidth);
preferredHeight += preferredSize.height;
j++;
}
}
// Include spacing
if (j > 1) {
preferredHeight += spacing * (j - 1);
}
break;
default:
break;
}
// Include padding
preferredWidth += padding.getWidth();
preferredHeight += padding.getHeight();
return new Dimensions(preferredWidth, preferredHeight);
}
@Override
public int getBaseline(final int width, final int height) {
BoxPane boxPane = (BoxPane) getComponent();
int baseline = -1;
int contentHeight = 0;
switch (boxPane.getOrientation()) {
case HORIZONTAL:
if (fill) {
int clientHeight = Math.max(height - padding.getHeight(), 0);
for (Component component : boxPane) {
if (component.isVisible()) {
int componentWidth = component.getPreferredWidth(clientHeight);
baseline = Math.max(baseline,
component.getBaseline(componentWidth, clientHeight));
}
}
} else {
contentHeight = 0;
for (Component component : boxPane) {
if (component.isVisible()) {
contentHeight = Math.max(contentHeight, component.getPreferredHeight());
}
}
for (Component component : boxPane) {
if (component.isVisible()) {
Dimensions size = component.getPreferredSize();
int componentBaseline = component.getBaseline(size.width, size.height);
if (componentBaseline != -1) {
switch (verticalAlignment) {
case CENTER:
componentBaseline += (contentHeight - size.height) / 2;
break;
case BOTTOM:
componentBaseline += contentHeight - size.height;
break;
case TOP:
break;
default:
break;
}
}
baseline = Math.max(baseline, componentBaseline);
}
}
}
break;
case VERTICAL:
int clientWidth = Math.max(width - padding.getWidth(), 0);
for (Component component : boxPane) {
if (component.isVisible()) {
Dimensions size;
if (fill) {
size = new Dimensions(clientWidth,
component.getPreferredHeight(clientWidth));
} else {
size = component.getPreferredSize();
}
if (baseline == -1) {
baseline = component.getBaseline(size.width, size.height);
if (baseline != -1) {
baseline += contentHeight;
}
}
contentHeight += size.height + spacing;
}
}
contentHeight -= spacing;
break;
default:
break;
}
if (baseline != -1) {
if (fill) {
baseline += padding.top;
} else {
switch (verticalAlignment) {
case TOP:
baseline += padding.top;
break;
case CENTER:
baseline += (height - contentHeight) / 2;
break;
case BOTTOM:
baseline += height - (contentHeight + padding.bottom);
break;
default:
break;
}
}
}
return baseline;
}
@Override
public void layout() {
BoxPane boxPane = (BoxPane) getComponent();
int n = boxPane.getLength();
int width = getWidth();
int height = getHeight();
Orientation orientation = boxPane.getOrientation();
if (orientation == Orientation.HORIZONTAL) {
int preferredWidth = getPreferredWidth(fill ? height : -1);
// Determine the starting x-coordinate
int x = 0;
switch (horizontalAlignment) {
case CENTER:
x = (width - preferredWidth) / 2;
break;
case RIGHT:
x = width - preferredWidth;
break;
case LEFT:
break;
default:
break;
}
x += padding.left;
// Lay out the components
for (int i = 0; i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
int componentWidth = 0;
int componentHeight = 0;
int y = 0;
if (fill) {
componentHeight = Math.max(height - padding.getHeight(), 0);
componentWidth = component.getPreferredWidth(componentHeight);
} else {
Dimensions preferredComponentSize = component.getPreferredSize();
componentWidth = preferredComponentSize.width;
componentHeight = preferredComponentSize.height;
}
switch (verticalAlignment) {
case TOP:
y = padding.top;
break;
case CENTER:
y = (height - componentHeight) / 2;
break;
case BOTTOM:
y = height - padding.bottom - componentHeight;
break;
default:
break;
}
// Set the component's size and position
component.setSize(componentWidth, componentHeight);
component.setLocation(x, y);
// Increment the x-coordinate
x += componentWidth + spacing;
}
}
} else {
int preferredHeight = getPreferredHeight(fill ? width : -1);
// Determine the starting y-coordinate
int y = 0;
switch (verticalAlignment) {
case CENTER:
y = (height - preferredHeight) / 2;
break;
case BOTTOM:
y = height - preferredHeight;
break;
case TOP:
break;
default:
break;
}
y += padding.top;
// Lay out the components
for (int i = 0; i < n; i++) {
Component component = boxPane.get(i);
if (component.isVisible()) {
int componentWidth = 0;
int componentHeight = 0;
int x = 0;
if (fill) {
componentWidth = Math.max(width - padding.getWidth(), 0);
componentHeight = component.getPreferredHeight(componentWidth);
} else {
Dimensions preferredComponentSize = component.getPreferredSize();
componentWidth = preferredComponentSize.width;
componentHeight = preferredComponentSize.height;
}
switch (horizontalAlignment) {
case LEFT:
x = padding.left;
break;
case CENTER:
x = (width - componentWidth) / 2;
break;
case RIGHT:
x = width - padding.right - componentWidth;
break;
default:
break;
}
// Set the component's size and position
component.setSize(componentWidth, componentHeight);
component.setLocation(x, y);
// Increment the y-coordinate
y += componentHeight + spacing;
}
}
}
}
/**
* @return The horizontal alignment of the BoxPane's components within the pane.
*/
public HorizontalAlignment getHorizontalAlignment() {
return horizontalAlignment;
}
/**
* Sets the horizontal alignment of the BoxPane's components within the
* pane. <p>If the orientation of the pane is HORIZONTAL, this means the
* collective alignment all the components as group, which are still laid
* out close together according to the value of the <code>spacing</code>
* style. <p>If the orientation of the pane is VERTICAL, this means the
* alignment of each individual component within the pane. It has no effect
* if the <code>fill</code> style is true.
*
* @param horizontalAlignment The new horizontal alignment for our children.
*/
public void setHorizontalAlignment(final HorizontalAlignment horizontalAlignment) {
Utils.checkNull(horizontalAlignment, "horizontalAlignment");
this.horizontalAlignment = horizontalAlignment;
invalidateComponent();
}
/**
* @return The vertical alignment of the BoxPane's components within the pane.
*/
public VerticalAlignment getVerticalAlignment() {
return verticalAlignment;
}
/**
* Sets the vertical alignment of the BoxPane's components within the pane.
* <p>If the orientation of the pane is VERTICAL, this means the collective
* alignment all the components as group, which are still laid out close
* together according to the value of the <code>spacing</code> style. <p>If
* the orientation of the pane is HORIZONTAL, this means the alignment of
* each individual component within the pane. It has no effect if the
* <code>fill</code> style is true.
*
* @param verticalAlignment The new horizontal alignment for our children.
*/
public void setVerticalAlignment(final VerticalAlignment verticalAlignment) {
Utils.checkNull(verticalAlignment, "verticalAlignment");
this.verticalAlignment = verticalAlignment;
invalidateComponent();
}
/**
* @return The amount of space between the edge of the BoxPane and its
* components.
*/
public Insets getPadding() {
return padding;
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components.
*
* @param padding The new padding values for all edges.
*/
public void setPadding(final Insets padding) {
Utils.checkNull(padding, "padding");
this.padding = padding;
invalidateComponent();
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components.
*
* @param padding A dictionary with keys in the set {top, left, bottom, right}.
*/
public final void setPadding(final Dictionary<String, ?> padding) {
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components.
*
* @param padding A sequence with values in the order [top, left, bottom, right].
*/
public final void setPadding(final Sequence<?> padding) {
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components, uniformly on all four edges.
*
* @param padding The new padding value for all edges.
*/
public final void setPadding(final int padding) {
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components, uniformly on all four edges.
*
* @param padding The integer value to use for padding on all edges.
*/
public final void setPadding(final Number padding) {
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the BoxPane and its
* components.
*
* @param padding A string containing an integer or a JSON dictionary with
* keys left, top, bottom, and/or right.
*/
public final void setPadding(final String padding) {
setPadding(Insets.decode(padding));
}
/**
* @return The amount of space between the BoxPane's components.
*/
public int getSpacing() {
return spacing;
}
/**
* Sets the amount of space to leave between the BoxPane's components.
*
* @param spacing The new amount of spacing between components.
*/
public void setSpacing(final int spacing) {
Utils.checkNonNegative(spacing, "spacing");
this.spacing = spacing;
invalidateComponent();
}
/**
* Sets the amount of space to leave between the BoxPane's components.
*
* @param spacing The new amount of spacing to use between components.
*/
public final void setSpacing(final Number spacing) {
Utils.checkNull(spacing, "spacing");
setSpacing(spacing.intValue());
}
/**
* @return A value indicating whether the BoxPane's components fill the
* available space in the pane in the dimension orthogonal to its
* orientation.
*/
public boolean getFill() {
return fill;
}
/**
* Sets whether the BoxPane's components fill to the edges of the pane.
*
* @param fill If <b>true</b>, the components are given all the available
* space in the dimension orthogonal to the pane's orientation (e.g.,
* vertically in a BoxPane with orientation=horizontal). It has no effect on
* the layout of components in the direction of the pane's orientation,
* which are always given exactly their preferred size. If <b>false</b>, the
* pane's components are laid out to their preferred size, regardless of the
* size of the BoxPane. Their alignment along the axis orthogonal to the
* pane's orientation is controlled by the corresponding alignment style
* (e.g., verticalAlignment in a BoxPane with orientation=horizontal). <p>
* Note that to Scale Up Images, other that fill=true it will be needed to
* set even the preferredWidth/preferredHeight as with container
* preferredHeight/preferredWidth just set, depending on the pane's
* orientation.
*/
public void setFill(final boolean fill) {
this.fill = fill;
invalidateComponent();
}
// Box pane events
@Override
public void orientationChanged(final BoxPane boxPane) {
invalidateComponent();
}
}