| /* |
| * 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(); |
| } |
| } |