/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.codename1.ui.layouts; import com.codename1.ui.Component; import com.codename1.ui.Container; import com.codename1.ui.Display; import com.codename1.ui.geom.*; import com.codename1.ui.plaf.Style; import java.util.HashMap; /** * <p>A border layout lays out a container, arranging and resizing its * components to fit in five regions: north, south, east, west, and center. * Each region may contain no more than one component, and is identified by a * corresponding constant: NORTH, SOUTH, EAST, WEST, and CENTER. * When adding a component to a container with a border layout, use one of * these five constants.</p> * <p> * The border layout scales all of the components within it to match the available * constraints. The NORTH & SOUTH components use their preferred height but * are stretched to take up the full width available. The EAST & WEST do the same * for the reverse axis however they leave room for the NORTH/SOUTH entries if they * are defined.<br> * The CENTER constraint will take up the rest of the available space regardless of its preferred * size. This is normally very useful, however in some cases we would prefer that the center * component will actually position itself in the middle of the available space. For this we have * the <code>setCenterBehavior</code> method. * </p> * <p> * Because of its scaling behavior scrolling a border layout makes no sense. However it is a * common mistake to apply a border layout to a scrollable container or trying to make a border * layout scrollable. That is why the {@link com.codename1.ui.Container} class explicitly blocks * scrolling on a BorderLayout.<br> * Typical usage of this class: * </p> * <script src="https://gist.github.com/codenameone/23e642b1a749e2f37e68.js"></script> * <img src="https://www.codenameone.com/img/developer-guide/border-layout.png" alt="Border Layout" /> * * <p> * When defining the center behavior we can get very different results: * </p> *<script src="https://gist.github.com/codenameone/108aa105386ed7c340ad.js"></script> * <img src="https://www.codenameone.com/img/developer-guide/border-layout-center.png" alt="Border Layout Center" /> * * <p>Notice that in the case of RTL (right to left language also known as bidi) the * EAST and WEST values are implicitly reversed as shown in this image: * </p> * <img src="https://www.codenameone.com/img/developer-guide/border-layout-RTL.png" alt="Border Layout bidi/RTL" /> * * <p> * You can read further in the <a href="https://www.codenameone.com/manual/basics.html#_border_layout">BorderLayout section in the developer guide</a>. * </p> * * @author Nir Shabi, Shai Almog */ public class BorderLayout extends Layout { private boolean scaleEdges = true; /** * Defines the behavior of the component placed in the center position of the layout, by default it is scaled to the available space */ public static final int CENTER_BEHAVIOR_SCALE = 0; /** * Defines the behavior of the component placed in the center position of the layout, places the component in the center of * the space available to the center component. */ public static final int CENTER_BEHAVIOR_CENTER = 1; /** * Defines the behavior of the component placed in the center position of the layout, places the component in the center of * the surrounding container */ public static final int CENTER_BEHAVIOR_CENTER_ABSOLUTE = 2; /** * Deprecated due to spelling mistake, use CENTER_BEHAVIOR_TOTAL_BELOW * The center component takes up the entire screens and the sides are automatically placed on top of it thus creating * a layered effect * @deprecated Deprecated due to spelling mistake, use CENTER_BEHAVIOR_TOTAL_BELOW */ public static final int CENTER_BEHAVIOR_TOTAL_BELLOW = 3; /** * The center component takes up the entire screens and the sides are automatically placed on top of it thus creating * a layered effect */ public static final int CENTER_BEHAVIOR_TOTAL_BELOW = 3; private Component portraitNorth; private Component portraitSouth; private Component portraitCenter; private Component portraitWest; private Component portraitEast; private Component overlay; private HashMap<String, String> landscapeSwap; /** * Defines the behavior of the center component to one of the constants defined in this class */ private int centerBehavior; /** * The north layout constraint (top of container). */ public static final String NORTH = "North"; /** * The south layout constraint (bottom of container). */ public static final String SOUTH = "South"; /** * The center layout constraint (middle of container) */ public static final String CENTER = "Center"; /** * The west layout constraint (left of container). */ public static final String WEST = "West"; /** * The east layout constraint (right of container). */ public static final String EAST = "East"; /** * Overlay on top of the other layout components */ public static final String OVERLAY = "Overlay"; /** * Creates a new instance of BorderLayout */ public BorderLayout() { } /** * Creates a new instance of BorderLayout with absolute behavior * @param behavior identical value as the setCenterBehavior method */ public BorderLayout(int behavior) { setCenterBehavior(behavior); } /** * {@inheritDoc} */ public void addLayoutComponent(Object name, Component comp, Container c) { // helper check for a common mistake... if (name == null) { throw new IllegalArgumentException("Cannot add component to BorderLayout Container without constraint parameter"); } // allows us to work with Component constraints too which makes some code simpler if(name instanceof Integer) { switch(((Integer)name).intValue()) { case Component.TOP: name = NORTH; break; case Component.BOTTOM: name = SOUTH; break; case Component.LEFT: name = WEST; break; case Component.RIGHT: name = EAST; break; case Component.CENTER: name = CENTER; break; default: throw new IllegalArgumentException("BorderLayout Container expects one of the constraints BorderLayout.NORTH/SOUTH/EAST/WEST/CENTER"); } } Component previous = null; /* Assign the component to one of the known regions of the layout. */ if (CENTER.equals(name)) { previous = portraitCenter; portraitCenter = comp; } else if (NORTH.equals(name)) { previous = portraitNorth; portraitNorth = comp; } else if (SOUTH.equals(name)) { previous = portraitSouth; portraitSouth = comp; } else if (EAST.equals(name)) { previous = portraitEast; portraitEast = comp; } else if (WEST.equals(name)) { previous = portraitWest; portraitWest = comp; } else if (OVERLAY.equals(name)) { previous = overlay; overlay = comp; } else { throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name); } if (previous != null && previous != comp) { c.removeComponent(previous); } } /** * {@inheritDoc} */ public void removeLayoutComponent(Component comp) { if (comp == portraitCenter) { portraitCenter = null; } else if (comp == portraitNorth) { portraitNorth = null; } else if (comp == portraitSouth) { portraitSouth = null; } else if (comp == portraitEast) { portraitEast = null; } else if (comp == portraitWest) { portraitWest = null; } else if (comp == overlay) { overlay = null; } } /** * Returns the component constraint * * @param comp the component whose constraint is queried * @return one of the constraints defined in this class */ public Object getComponentConstraint(Component comp) { if (comp == portraitCenter) { return CENTER; } else if (comp == portraitNorth) { return NORTH; } else if (comp == portraitSouth) { return SOUTH; } else if (comp == portraitEast) { return EAST; } else if (comp == overlay) { return OVERLAY; } else { if(comp == portraitWest) { return WEST; } } return null; } /** * {@inheritDoc} */ public void layoutContainer(Container target) { Style s = target.getStyle(); int top = s.getPaddingTop(); int bottom = target.getLayoutHeight() - target.getBottomGap() - s.getPaddingBottom(); int left = s.getPaddingLeft(target.isRTL()); int right = target.getLayoutWidth() - target.getSideGap() - s.getPaddingRight(target.isRTL()); int targetWidth = target.getWidth(); int targetHeight = target.getHeight(); boolean rtl = target.isRTL(); if (rtl) { left+=target.getSideGap(); } Component east = getEast(); Component west = getWest(); Component south = getSouth(); Component north = getNorth(); Component center = getCenter(); if (north != null) { Component c = north; positionTopBottom(target, c, right, left, targetHeight); c.setY(top + c.getStyle().getMarginTop()); top += (c.getHeight() + c.getStyle().getMarginTop() + c.getStyle().getMarginBottom()); } if (south != null) { Component c = south; positionTopBottom(target, c, right, left, targetHeight); c.setY(bottom - c.getHeight() - c.getStyle().getMarginBottom()); bottom -= (c.getHeight() + c.getStyle().getMarginTop() + c.getStyle().getMarginBottom()); } Component realEast = east; Component realWest = west; if (rtl) { realEast = west; realWest = east; } if (realEast != null) { Component c = realEast; positionLeftRight(realEast, targetWidth, bottom, top); c.setX(right - c.getWidth() - c.getStyle().getMarginRight(rtl)); right -= (c.getWidth() + c.getStyle().getHorizontalMargins()); } if (realWest != null) { Component c = realWest; positionLeftRight(realWest, targetWidth, bottom, top); c.setX(left + c.getStyle().getMarginLeft(rtl)); left += (c.getWidth() + c.getStyle().getMarginLeftNoRTL() + c.getStyle().getMarginRightNoRTL()); } if (center != null) { Component c = center; int w = right - left - c.getStyle().getMarginLeftNoRTL() - c.getStyle().getMarginRightNoRTL(); int h = bottom - top - c.getStyle().getMarginTop() - c.getStyle().getMarginBottom(); int x = left + c.getStyle().getMarginLeft(rtl); int y = top + c.getStyle().getMarginTop(); switch(centerBehavior) { case CENTER_BEHAVIOR_CENTER_ABSOLUTE: { Dimension d = c.getPreferredSize(); if(d.getWidth() < w) { int newX = (s.getPaddingLeft(rtl) - s.getPaddingRight(rtl) ) + targetWidth / 2 - d.getWidth() / 2; if(newX > x) { x = newX; } w = d.getWidth(); } int append = 0; int th = targetHeight; if(north != null) { append = north.getHeight(); th -= append; } if(south != null) { th -= south.getHeight(); append += south.getHeight(); } if(d.getHeight() < h) { int newY = (s.getPaddingTop() + th) / 2 - d.getHeight() / 2 + append; if(newY > y) { y = newY; } h = d.getHeight(); } break; } case CENTER_BEHAVIOR_CENTER: { Dimension d = c.getPreferredSize(); if(d.getWidth() < w) { x += w / 2 - d.getWidth() / 2; w = d.getWidth(); } if(d.getHeight() < h) { y += h / 2 - d.getHeight() / 2; h = d.getHeight(); } break; } case CENTER_BEHAVIOR_TOTAL_BELOW: { w = targetWidth; h = targetHeight; x = s.getPaddingLeft(rtl); y = s.getPaddingTop();; } } c.setWidth(w); c.setHeight(h); c.setX(x); c.setY(y); } if (overlay != null) { Component c = overlay; c.setWidth(targetWidth); c.setHeight(targetHeight); c.setX(0); c.setY(0); } } /** * Position the east/west component variables */ private void positionLeftRight(Component c, int targetWidth, int bottom, int top) { int y = top + c.getStyle().getMarginTop(); int h = bottom - top - c.getStyle().getMarginTop() - c.getStyle().getMarginBottom(); if(scaleEdges) { c.setY(y); c.setHeight(h); } else { int ph = c.getPreferredH(); if(ph < h) { c.setHeight(ph); c.setY(y + (h - ph) / 2); } else { c.setY(y); c.setHeight(h); } } c.setWidth(Math.min(targetWidth, c.getPreferredW())); } private void positionTopBottom(Component target, Component c, int right, int left, int targetHeight) { int w = right - left - c.getStyle().getMarginLeftNoRTL() - c.getStyle().getMarginRightNoRTL(); int x = left + c.getStyle().getMarginLeft(target.isRTL()); if(scaleEdges) { c.setWidth(w); c.setX(x); } else { int pw = c.getPreferredW(); if(pw < w) { c.setWidth(pw); c.setX(x + (w - pw) / 2); } else { c.setWidth(w); c.setX(x); } } c.setHeight(Math.min(targetHeight, c.getPreferredH())); //verify I want to use tge prefered size } private Dimension dim = new Dimension(0, 0); /** * {@inheritDoc} */ public Dimension getPreferredSize(Container parent) { dim.setWidth(0); dim.setHeight(0); Component east = getEast(); Component west = getWest(); Component south = getSouth(); Component north = getNorth(); Component center = getCenter(); if (east != null) { dim.setWidth(east.getPreferredW() + east.getStyle().getMarginLeftNoRTL() + east.getStyle().getMarginRightNoRTL()); dim.setHeight(Math.max(east.getPreferredH() + east.getStyle().getMarginTop() + east.getStyle().getMarginBottom(), dim.getHeight())); } if (west != null) { dim.setWidth(dim.getWidth() + west.getPreferredW() + west.getStyle().getMarginLeftNoRTL() + west.getStyle().getMarginRightNoRTL()); dim.setHeight(Math.max(west.getPreferredH() + west.getStyle().getMarginTop() + west.getStyle().getMarginBottom(), dim.getHeight())); } if (center != null) { dim.setWidth(dim.getWidth() + center.getPreferredW() + center.getStyle().getMarginLeftNoRTL() + center.getStyle().getMarginRightNoRTL()); dim.setHeight(Math.max(center.getPreferredH() + center.getStyle().getMarginTop() + center.getStyle().getMarginBottom(), dim.getHeight())); } if (north != null) { dim.setWidth(Math.max(north.getPreferredW() + north.getStyle().getMarginLeftNoRTL() + north.getStyle().getMarginRightNoRTL(), dim.getWidth())); dim.setHeight(dim.getHeight() + north.getPreferredH() + north.getStyle().getMarginTop() + north.getStyle().getMarginBottom()); } if (south != null) { dim.setWidth(Math.max(south.getPreferredW() + south.getStyle().getMarginLeftNoRTL() + south.getStyle().getMarginRightNoRTL(), dim.getWidth())); dim.setHeight(dim.getHeight() + south.getPreferredH() + south.getStyle().getMarginTop() + south.getStyle().getMarginBottom()); } dim.setWidth(dim.getWidth() + parent.getStyle().getPaddingLeftNoRTL() + parent.getStyle().getPaddingRightNoRTL()); dim.setHeight(dim.getHeight() + parent.getStyle().getPaddingTop() + parent.getStyle().getPaddingBottom()); return dim; } private boolean isLandscape() { Display d = Display.getInstance(); return !d.isPortrait(); } /** * Returns the component at the given constraint */ private Component getComponentAtIgnoreLandscape(String constraint) { if(constraint != null) { if(constraint.equals(NORTH)) { return portraitNorth; } if(constraint.equals(SOUTH)) { return portraitSouth; } if(constraint.equals(EAST)) { return portraitEast; } if(constraint.equals(WEST)) { return portraitWest; } if(constraint.equals(CENTER)) { return portraitCenter; } } return null; } private Component getComponentImpl(Component noLandscape, String orientation) { if(landscapeSwap != null && isLandscape()) { String s = (String)landscapeSwap.get(orientation); if(s != null) { return getComponentAtIgnoreLandscape(s); } } return noLandscape; } /** * Returns the component in the south location * * @return the component in the constraint */ public Component getSouth() { return getComponentImpl(portraitSouth, SOUTH); } /** * Returns the component in the center location * * @return the component in the constraint */ public Component getCenter() { return getComponentImpl(portraitCenter, CENTER); } /** * Returns the component in the north location * * @return the component in the constraint */ public Component getNorth() { return getComponentImpl(portraitNorth, NORTH); } /** * Returns the component in the east location * * @return the component in the constraint */ public Component getEast() { return getComponentImpl(portraitEast, EAST); } /** * Returns the component in the west location * * @return the component in the constraint */ public Component getWest() { return getComponentImpl(portraitWest, WEST); } /** * {@inheritDoc} */ public String toString() { return "BorderLayout"; } /** * This method allows swapping positions within the border layout when the layout * orientation changes to landscape or if the layout starts off as landscape. * * @param portraitPosition the position for the component when in portrait (this position * should always be used when adding a component to the layout). One of NORTH/SOUTH/EAST/WEST/CENTER. * @param landscapePosition the destination position to use in landscape */ public void defineLandscapeSwap(String portraitPosition, String landscapePosition) { if(landscapeSwap == null) { landscapeSwap = new HashMap<String, String>(); } landscapeSwap.put(portraitPosition, landscapePosition); landscapeSwap.put(landscapePosition, portraitPosition); } /** * Returns the landscape swap destination for the given border layout element if such * a destination is defined. * * @param portraitPosition the constraint used when placing the component * @return the constraint to use when in landscape or null if undefined */ public String getLandscapeSwap(String portraitPosition) { if(landscapeSwap == null) { return null; } return (String)landscapeSwap.get(portraitPosition); } /** * {@inheritDoc} */ public boolean equals(Object o) { if(super.equals(o) && centerBehavior == ((BorderLayout)o).centerBehavior) { if(landscapeSwap == ((BorderLayout)o).landscapeSwap) { return true; } if(landscapeSwap != null) { return landscapeSwap.equals(((BorderLayout)o).landscapeSwap); } } return false; } /** * Indicates that the center shouldn't grow and should be placed exactly in the center of the layout * * @return the absoluteCenter * @deprecated use center behavior instead */ public boolean isAbsoluteCenter() { return centerBehavior == CENTER_BEHAVIOR_CENTER; } /** * Indicates that the center shouldn't grow and should be placed exactly in the center of the layout * * @param absoluteCenter the absoluteCenter to set * @deprecated use center behavior instead */ public void setAbsoluteCenter(boolean absoluteCenter) { if(absoluteCenter) { setCenterBehavior(CENTER_BEHAVIOR_CENTER); } else { setCenterBehavior(CENTER_BEHAVIOR_SCALE); } } /** * Defines the behavior of the center component to one of the constants defined in this class * * @return the centerBehavior */ public int getCenterBehavior() { return centerBehavior; } /** * Defines the behavior of the center component to one of the constants defined in this class * * @param centerBehavior the centerBehavior to set */ public void setCenterBehavior(int centerBehavior) { this.centerBehavior = centerBehavior; } /** * {@inheritDoc} */ public boolean isOverlapSupported(){ return centerBehavior == CENTER_BEHAVIOR_TOTAL_BELOW || overlay != null; } /** * Stretches the edge components (NORTH/EAST/WEST/SOUTH) * @return the scaleEdges */ public boolean isScaleEdges() { return scaleEdges; } /** * Stretches the edge components (NORTH/EAST/WEST/SOUTH) * @param scaleEdges the scaleEdges to set */ public void setScaleEdges(boolean scaleEdges) { this.scaleEdges = scaleEdges; } /** * {@inheritDoc} */ public boolean isConstraintTracking() { return false; } /** * {@inheritDoc} */ public boolean obscuresPotential(Container parent) { return getCenter() != null; } /** * Convenience method that creates a border layout container and places the given component in the center * @param center the center component * @return the created component */ public static Container center(Component center) { return Container.encloseIn(new BorderLayout(), center, BorderLayout.CENTER); } /** * Convenience method that creates a border layout container and places the given component in the center * east and west respectively * @param center the center component * @param east component or null to ignore * @param west component or null to ignore * @return the created component */ public static Container centerEastWest(Component center, Component east, Component west) { Container c; if(center != null) { c = center(center); } else { c = new Container(new BorderLayout()); } if(east != null) { c.add(BorderLayout.EAST, east); } if(west != null) { c.add(BorderLayout.WEST, west); } return c; } /** * Convenience method that creates a border layout absolute center container and places the given component in the center * east and west respectively * @param center the center component * @param east component or null to ignore * @param west component or null to ignore * @return the created component */ public static Container centerAbsoluteEastWest(Component center, Component east, Component west) { Container c; if(center != null) { c = centerAbsolute(center); } else { c = new Container(new BorderLayout(CENTER_BEHAVIOR_CENTER_ABSOLUTE)); } if(east != null) { c.add(BorderLayout.EAST, east); } if(west != null) { c.add(BorderLayout.WEST, west); } return c; } /** * Convenience method that creates a border layout container and places the given component in the center * with the {@link #CENTER_BEHAVIOR_CENTER} constraint applied * @param center the center component * @return the created component */ public static Container centerCenter(Component center) { return Container.encloseIn(new BorderLayout(CENTER_BEHAVIOR_CENTER), center, BorderLayout.CENTER); } /** * Convenience method that creates a border layout container and places the given component in the center * with the {@link #CENTER_BEHAVIOR_CENTER_ABSOLUTE} constraint applied * @param center the center component * @return the created component */ public static Container centerAbsolute(Component center) { return Container.encloseIn(new BorderLayout(CENTER_BEHAVIOR_CENTER_ABSOLUTE), center, BorderLayout.CENTER); } /** * Convenience method that creates a border layout container and places the given component in the north * @param north the north component * @return the created component */ public static Container north(Component north) { return Container.encloseIn(new BorderLayout(), north, BorderLayout.NORTH); } /** * Convenience method that creates a border layout container and places the given component in the south * @param south the south component * @return the created component */ public static Container south(Component south) { return Container.encloseIn(new BorderLayout(), south, BorderLayout.SOUTH); } /** * Convenience method that creates a border layout container and places the given component in the east * @param east the east component * @return the created component */ public static Container east(Component east) { return Container.encloseIn(new BorderLayout(), east, BorderLayout.EAST); } /** * Convenience method that creates a border layout container and places the given component in the west * @param west the west component * @return the created component */ public static Container west(Component west) { return Container.encloseIn(new BorderLayout(), west, BorderLayout.WEST); } }