All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jidesoft.swing.JideBoxLayout Maven / Gradle / Ivy

There is a newer version: 3.6.18
Show newest version
/*
 * @(#)JideBoxLayout.java
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.dialog.JideOptionPane;
import com.jidesoft.utils.SecurityUtils;
import com.jidesoft.utils.SystemInfo;

import javax.swing.*;
import java.awt.*;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * JideBoxLayout is very similar to BoxLayout in the way that all components are arranged either from left to right or
 * from top to bottom. Different \ from BoxLayout, there are three possible constraints when adding component to this
 * layout - FIX, FLEXIBLE and VARY. 
  • FIX: use the preferred size of the component and size is fixed *
  • FLEXIBLE: respect the preferred size of the component but size can be changed.
  • VARY: ignore preferred size. * Its size is calculated based how much area left.
This is the default layout manager for {@link * com.jidesoft.swing.JideSplitPane}. */ public class JideBoxLayout implements LayoutManager2, Serializable { private static final Logger LOGGER = Logger.getLogger(JideBoxLayout.class.getName()); /** * True if resetToPreferredSizes has been invoked. */ private boolean doReset = true; /** * Axis, 0 for horizontal, or 1 for vertical. */ protected int _axis; protected Container _target; private int _gap = 0; protected int[] _componentSizes; /** * For FIX component, the width (or height if vertical) is and will always be the preferred width. */ public static final String FIX = "fix"; /** * FLEXIBLE components try to keep the preferred width. If there isn't enough space, all FLEXIBLE components will * shrink proportionally. */ public static final String FLEXIBLE = "flexible"; /** * For VARY component, the width will always be whatever width left. You can allow add multiple FIX or FLEXIBLE * components but only one VARY component is allowed. */ public static final String VARY = "vary"; private final HashMap _constraintMap = new HashMap(); /** * Specifies that components should be laid out left to right. */ public static final int X_AXIS = 0; /** * Specifies that components should be laid out top to bottom. */ public static final int Y_AXIS = 1; /** * Specifies that components should be laid out in the direction of a line of text as determined by the target * container's ComponentOrientation property. */ public static final int LINE_AXIS = 2; /** * Specifies that components should be laid out in the direction that lines flow across a page as determined by the * target container's ComponentOrientation property. */ public static final int PAGE_AXIS = 3; private boolean _resetWhenInvalidate = true; private boolean _alwaysLayout = true; private static final long serialVersionUID = -183922972679053590L; /** * Creates a layout manager that will lay out components along the given axis. * * @param target the container that needs to be laid out * @throws AWTError if the value of axis is invalid */ public JideBoxLayout(Container target) { this(target, X_AXIS); } /** * @param target the container that needs to be laid out * @param axis the axis to lay out components along. Can be one of: JideBoxLayout.X_AXIS, * JideBoxLayout.Y_AXIS, JideBoxLayout.LINE_AXIS or * JideBoxLayout.PAGE_AXIS */ public JideBoxLayout(Container target, int axis) { this(target, axis, 0); } /** * @param target the container that needs to be laid out * @param axis the axis to lay out components along. Can be one of: JideBoxLayout.X_AXIS, * JideBoxLayout.Y_AXIS, JideBoxLayout.LINE_AXIS or * JideBoxLayout.PAGE_AXIS * @param gap the gap */ public JideBoxLayout(Container target, int axis, int gap) { if (axis != X_AXIS && axis != Y_AXIS && axis != LINE_AXIS && axis != PAGE_AXIS) { throw new AWTError("Invalid axis"); } _axis = axis; _target = target; _gap = gap; } /** * Lays out the specified container. * * @param container the container to be laid out */ public void layoutContainer(Container container) { synchronized (container.getTreeLock()) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(this + " layoutContainer started"); } Dimension containerSize = container.getSize(); if (containerSize.height <= 0 || containerSize.width <= 0) { return; } Insets insets = _target.getInsets(); if (doReset) { _componentSizes = new int[_target.getComponentCount()]; int availableSize = getAvailableSize(containerSize, insets); availableSize -= getGapSize(); if (availableSize <= 0) { return; } boolean success = calculateComponentSizes(availableSize, 0, _target.getComponentCount()); if (!success) { if (!isAlwaysLayout() && "false".equals(SecurityUtils.getProperty("JideBoxLayout.alwaysLayout", "false"))) { for (int i = 0; i < _target.getComponentCount(); i++) { Component comp = _target.getComponent(i); setComponentToSize(comp, 0, 0, insets, containerSize); // set size to zero to clear the layout } redoLayout(container); } return; } doReset = false; if (_componentSizes.length == 0) { container.repaint(); // repaint when the last component is removed. } } else { int totalSize = 0; for (int componentSize : _componentSizes) { totalSize += componentSize; } boolean containerResized = totalSize + getGapSize() != getSizeForPrimaryAxis(containerSize); if (containerResized) { int availableSize = getAvailableSize(containerSize, insets); availableSize -= getGapSize(); if (availableSize <= 0) { return; } boolean success = calculateComponentSizes(availableSize, 0, _target.getComponentCount()); if (!success) { if (!isAlwaysLayout() && "false".equals(SecurityUtils.getProperty("JideBoxLayout.alwaysLayout", "false"))) { for (int i = 0; i < _target.getComponentCount(); i++) { Component comp = _target.getComponent(i); setComponentToSize(comp, 0, 0, insets, containerSize); // set size to zero to clear the layout } redoLayout(container); } return; } } } ComponentOrientation o = _target.getComponentOrientation(); boolean ltr = o.isLeftToRight(); int location = getSizeForPrimaryAxis(insets, true); boolean needRedoLayout = false; if (!ltr && resolveAxis(_axis, o) == X_AXIS) { location = containerSize.width - location; } for (int i = 0; i < _target.getComponentCount(); i++) { Component comp = _target.getComponent(i); int oldSize = getPreferredSizeOfComponent(comp); if (!ltr && resolveAxis(_axis, o) == X_AXIS) { location -= _componentSizes[i]; setComponentToSize(comp, _componentSizes[i], location, insets, containerSize); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("layoutContainer index: " + i + " size: " + _componentSizes[i]); } if (_componentSizes[i] != 0) location -= _gap; } else { setComponentToSize(comp, _componentSizes[i], location, insets, containerSize); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("layoutContainer index: " + i + " size: " + _componentSizes[i]); } location += _componentSizes[i]; if (_componentSizes[i] != 0) location += _gap; } int newSize = getPreferredSizeOfComponent(comp); if (newSize != oldSize) { needRedoLayout = true; } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("layoutContainer ended"); } if (_target instanceof JideOptionPane) { for (int i = 0; i < container.getComponentCount(); i++) { container.getComponent(i).invalidate(); } if (needRedoLayout) { redoLayout(container); } } } } private void redoLayout(Container container) { Component parent = container.getParent(); while (parent != null) { if (parent instanceof Dialog) { break; } parent = parent.getParent(); } if (parent != null) { ((Dialog) parent).pack(); } } protected boolean calculateComponentSizes(int availableSize, int startIndex, int endIndex) { int availableSizeExcludeFixed = availableSize; int varMinSize = 0; int flexMinSize = 0; int varIndex = -1; int totalFlexSize = 0; int totalFlexSizeMinusMin = 0; int lastFlexIndex = -1; int lastNoneZeroFlexIndex = -1; for (int i = startIndex; i < endIndex; i++) { Component comp = _target.getComponent(i); if (!comp.isVisible()) { continue; } Object constraint = _constraintMap.get(comp); int minimumSize = getSizeForPrimaryAxis(comp.getMinimumSize()); int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(comp, i)); if (FIX.equals(constraint)) { availableSizeExcludeFixed -= Math.max(preferredSize, minimumSize); } else if (VARY.equals(constraint)) { varIndex = i; getPreferredSizeOf(comp, i); // there is a bug in jdk1.5 which minimum size returns a large number if preferred size is not call. varMinSize = minimumSize; } else /* if (FLEXIBLE.equals(constraint)) */ { if (preferredSize > minimumSize) { totalFlexSizeMinusMin += preferredSize - minimumSize; } totalFlexSize += preferredSize; flexMinSize += minimumSize; lastFlexIndex = i; // prevent the zero width column has a column width like 1 or 2 eventually if (preferredSize != 0) { lastNoneZeroFlexIndex = i; } } } if (!isAlwaysLayout() && "false".equals(SecurityUtils.getProperty("JideBoxLayout.alwaysLayout", "false")) && availableSizeExcludeFixed - varMinSize < 0) { return false; } boolean hasVary = varIndex != -1; boolean expand = availableSizeExcludeFixed - varMinSize >= totalFlexSize; if (!hasVary || (hasVary && !expand)) { double resizeRatio; if (expand) { resizeRatio = totalFlexSize == 0 ? 0 : (double) (availableSizeExcludeFixed - varMinSize) / (double) totalFlexSize; } else { resizeRatio = totalFlexSizeMinusMin == 0 ? 0 : (double) (availableSizeExcludeFixed - varMinSize - flexMinSize) / (double) totalFlexSizeMinusMin; } for (int i = startIndex; i < endIndex; i++) { Component comp = _target.getComponent(i); if (!comp.isVisible()) { setComponentSize(i, 0); } else { Object constraint = _constraintMap.get(comp); int minimumSize = getSizeForPrimaryAxis(comp.getMinimumSize()); int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(comp, i)); if (FIX.equals(constraint)) { setComponentSize(i, Math.max(preferredSize, minimumSize)); } else if (VARY.equals(constraint)) { setComponentSize(i, varMinSize); } else /* if (FLEXIBLE.equals(constraint)) */ { if (expand) { setComponentSize(i, (int) (preferredSize * resizeRatio)); } else { setComponentSize(i, minimumSize + (int) ((preferredSize - minimumSize) * resizeRatio)); } } } } } else { // if (expand && hasVary) { // VARY component get all extra spaces. for (int i = startIndex; i < endIndex; i++) { Component comp = _target.getComponent(i); if (!comp.isVisible()) { setComponentSize(i, 0); } else { Object constraint = _constraintMap.get(comp); int minimumSize = getSizeForPrimaryAxis(comp.getMinimumSize()); int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(comp, i)); if (FIX.equals(constraint)) { setComponentSize(i, Math.max(preferredSize, minimumSize)); } else if (VARY.equals(constraint)) { setComponentSize(i, availableSizeExcludeFixed - totalFlexSize); } else /* if (FLEXIBLE.equals(constraint)) */ { setComponentSize(i, Math.max(preferredSize, minimumSize)); } } } } int totalActualSize = 0; for (int i = startIndex; i < endIndex; i++) { totalActualSize += _componentSizes[i]; } if (totalActualSize != availableSize) { if (varIndex != -1) { setComponentSizeByGap(varIndex, availableSize - totalActualSize); } else if (lastNoneZeroFlexIndex != -1) { setComponentSizeByGap(lastNoneZeroFlexIndex, availableSize - totalActualSize); } else if (lastFlexIndex != -1) { setComponentSizeByGap(lastFlexIndex, availableSize - totalActualSize); } } return true; } private void setComponentSizeByGap(int index, int gap) { if (SystemInfo.isJdk15Above() && _target.getComponent(index).isMinimumSizeSet()) { setComponentSize(index, Math.max(_componentSizes[index] + gap, getSizeForPrimaryAxis(_target.getComponent(index).getMinimumSize()))); } else { setComponentSize(index, _componentSizes[index] + gap); } } private void setComponentSize(int index, int size) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("setComponentSize index: " + index + " size: " + size); } _componentSizes[index] = size; } /** * If the layout manager uses a per-component string, adds the component comp to the layout, * associating it with the string specified by name. * * @param name the string to be associated with the component * @param component the component to be added */ public void addLayoutComponent(String name, Component component) { layoutReset(); } /** * Returns the minimum size needed to contain the children. The width is the sum of all the children min widths and * the height is the largest of the children minimum heights. */ public Dimension minimumLayoutSize(Container container) { int minPrimary = 0; int minSecondary = 0; Insets insets = _target.getInsets(); synchronized (container.getTreeLock()) { for (int i = 0; i < _target.getComponentCount(); i++) { Component comp = _target.getComponent(i); if (!comp.isVisible()) { continue; } Object constraint = _constraintMap.get(comp); Dimension minimumSize = comp.getMinimumSize(); if (FIX.equals(constraint)) { minPrimary += getPreferredSizeOfComponent(comp); } else { minPrimary += getSizeForPrimaryAxis(minimumSize); } int secSize = getSizeForSecondaryAxis(minimumSize); if (secSize > minSecondary) minSecondary = secSize; } if (insets != null) { minPrimary += getSizeForPrimaryAxis(insets, true) + getSizeForPrimaryAxis(insets, false); minSecondary += getSizeForSecondaryAxis(insets, true) + getSizeForSecondaryAxis(insets, false); } } ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { return new Dimension(minPrimary + getGapSize(), minSecondary); } else { return new Dimension(minSecondary, minPrimary + getGapSize()); } } /** * Returns the preferred size needed to contain the children. The width is the sum of all the children preferred * widths and the height is the largest of the children preferred heights. */ public Dimension preferredLayoutSize(Container container) { int prePrimary = 0; int preSecondary = 0; Insets insets = _target.getInsets(); synchronized (container.getTreeLock()) { for (int i = 0; i < _target.getComponentCount(); i++) { Component comp = _target.getComponent(i); if (!comp.isVisible()) { continue; } Dimension preferredSize = getPreferredSizeOf(comp, i); prePrimary += getSizeForPrimaryAxis(preferredSize); int secSize = getSizeForSecondaryAxis(preferredSize); if (secSize > preSecondary) preSecondary = secSize; } if (insets != null) { prePrimary += getSizeForPrimaryAxis(insets, true) + getSizeForPrimaryAxis(insets, false); preSecondary += getSizeForSecondaryAxis(insets, true) + getSizeForSecondaryAxis(insets, false); } } ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { return new Dimension(prePrimary + getGapSize(), preSecondary); } else { return new Dimension(preSecondary, prePrimary + getGapSize()); } } private int getGapSize() { if (_gap == 0) { return 0; } else { int count = 0; for (int i = 0; i < _target.getComponentCount(); i++) { if (_target.getComponent(i).isVisible()) { count++; } } return Math.max(0, (count - 1)) * _gap; } } /** * Removes the specified component from the layout. * * @param comp the component to be removed */ public void removeLayoutComponent(Component comp) { _constraintMap.remove(comp); if (comp instanceof JideSplitPaneDivider) layoutReset(); } // // LayoutManager2 // /** * Adds the specified component to the layout, using the specified constraint object. * * @param comp the component to be added * @param constraints where/how the component is added to the layout. */ public void addLayoutComponent(Component comp, Object constraints) { if (constraints == null) _constraintMap.put(comp, FLEXIBLE); else _constraintMap.put(comp, constraints); layoutReset(); } private void layoutReset() { doReset = true; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(this + " layoutReset"); } } /** * Returns the alignment along the x axis. This specifies how the component would like to be aligned relative to * other components. The value should be a number between 0 and 1 where 0 represents alignment along the origin, 1 * is aligned the furthest away from the origin, 0.5 is centered, etc. */ public synchronized float getLayoutAlignmentX(Container target) { return 0.0f; } /** * Returns the alignment along the y axis. This specifies how the component would like to be aligned relative to * other components. The value should be a number between 0 and 1 where 0 represents alignment along the origin, 1 * is aligned the furthest away from the origin, 0.5 is centered, etc. */ public synchronized float getLayoutAlignmentY(Container target) { return 0.0f; } /** * Invalidates the layout, indicating that if the layout manager has cached information it should be discarded. */ public synchronized void invalidateLayout(Container c) { if (isResetWhenInvalidate() || componentCountChanged(c)) { layoutReset(); } } protected boolean componentCountChanged(Container c) { if (_componentSizes == null) { return true; } int oldLength = 0; for (int _componentSize : _componentSizes) { if (_componentSize > 0) { oldLength++; } } int newLength = 0; for (int i = 0; i < c.getComponentCount(); i++) { if (c.getComponent(i).isVisible()) { newLength++; } } return newLength != oldLength; } /** * Returns the maximum layout size, which is Integer.MAX_VALUE in both directions. */ public Dimension maximumLayoutSize(Container target) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Returns the width of the passed in Components preferred size. * * @param c the component * @return the preferred size of the component. */ protected int getPreferredSizeOfComponent(Component c) { return getSizeForPrimaryAxis(c.getPreferredSize()); } /** * Returns the width of the passed in Components minimum size. * * @param c the component * @return the minimum size of the component. */ int getMinimumSizeOfComponent(Component c) { return getSizeForPrimaryAxis(c.getMinimumSize()); } /** * Returns the width of the passed in component. * * @param c the component * @return the size of the component. */ protected int getSizeOfComponent(Component c) { return getSizeForPrimaryAxis(c.getSize()); } /** * Returns the available width based on the container size and Insets. * * @param containerSize the size of the container * @param insets the insets * @return the available size. */ protected int getAvailableSize(Dimension containerSize, Insets insets) { if (insets == null) return getSizeForPrimaryAxis(containerSize); return (getSizeForPrimaryAxis(containerSize) - (getSizeForPrimaryAxis(insets, true) + getSizeForPrimaryAxis(insets, false))); } /** * Returns the left inset, unless the Insets are null in which case 0 is returned. * * @param insets the insets * @return the initial location. */ protected int getInitialLocation(Insets insets) { if (insets != null) return getSizeForPrimaryAxis(insets, true); return 0; } /** * Sets the width of the component c to be size, placing its x location at location, y to the insets.top and height * to the containersize.height less the top and bottom insets. * * @param c the component * @param size the size of the component * @param location the location of the component * @param insets the insets of the component * @param containerSize the size of the container */ protected void setComponentToSize(Component c, int size, int location, Insets insets, Dimension containerSize) { if (insets != null) { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { c.setBounds(location, Math.max(insets.top, 0), Math.max(size, 0), Math.max(containerSize.height - (insets.top + insets.bottom), 0)); } else { c.setBounds(Math.max(insets.left, 0), location, Math.max(containerSize.width - (insets.left + insets.right), 0), Math.max(size, 0)); } } else { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { c.setBounds(location, 0, Math.max(size, 0), Math.max(containerSize.height, 0)); } else { c.setBounds(0, location, Math.max(containerSize.width, 0), Math.max(size, 0)); } } } /* * If the axis == JideBoxLayout.X_AXIS, the width is returned, otherwise the height. */ int getSizeForPrimaryAxis(Dimension size) { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { return size.width; } else { return size.height; } } /* * If the axis == X_AXIS, the height is returned, otherwise the width. */ int getSizeForSecondaryAxis(Dimension size) { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { return size.height; } else { return size.width; } } /* * Returns a particular value of the inset identified by the axis and isTop

. axis isTop 0 true - * left 0 false - right 1 true - top 1 false - bottom */ int getSizeForPrimaryAxis(Insets insets, boolean isTop) { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { if (isTop) { return insets.left; } else { return insets.right; } } else { if (isTop) { return insets.top; } else { return insets.bottom; } } } /* * Returns a particular value of the inset identified by the axis and isTop

. axis isTop 0 true - * left 0 false - right 1 true - top 1 false - bottom */ int getSizeForSecondaryAxis(Insets insets, boolean isTop) { ComponentOrientation o = _target.getComponentOrientation(); if (resolveAxis(_axis, o) == X_AXIS) { if (isTop) { return insets.top; } else { return insets.bottom; } } else { if (isTop) { return insets.left; } else { return insets.right; } } } /** * Gets the map of constraints. * * @return the map of constraints */ public Map getConstraintMap() { return _constraintMap; } /** * Given one of the 4 axis values, resolve it to an absolute axis. The relative axis values, PAGE_AXIS and LINE_AXIS * are converted to their absolute counterpart given the target's ComponentOrientation value. The absolute axes, * X_AXIS and Y_AXIS are returned unmodified. * * @param axis the axis to resolve * @param o the ComponentOrientation to resolve against * @return the resolved axis */ protected static int resolveAxis(int axis, ComponentOrientation o) { int absoluteAxis; if (axis == LINE_AXIS) { absoluteAxis = o.isHorizontal() ? X_AXIS : Y_AXIS; } else if (axis == PAGE_AXIS) { absoluteAxis = o.isHorizontal() ? Y_AXIS : X_AXIS; } else { absoluteAxis = axis; } return absoluteAxis; } /** * Gets the gap between each component. * * @return the gap between each component. */ public int getGap() { return _gap; } /** * Sets the gap between each component. Make sure you cal doLayout() after you change the gap. * * @param gap the gap */ public void setGap(int gap) { _gap = gap; } protected Dimension getPreferredSizeOf(Component comp, int atIndex) { // for JTextArea, the preferred size only returns the height of one line if the size is never set. if (comp instanceof JTextArea && getSizeForSecondaryAxis(comp.getSize()) == 0) { Insets insets = _target.getInsets(); ComponentOrientation o = _target.getComponentOrientation(); Dimension size = _target.getSize(); if (resolveAxis(_axis, o) == X_AXIS) { comp.setSize(new Dimension(comp.getPreferredSize().width, size.height - insets.top - insets.bottom)); } else { comp.setSize(new Dimension(size.width - insets.left - insets.right, comp.getPreferredSize().height)); } } Dimension preferredSize = comp.getPreferredSize(); Dimension minimumSize = comp.getMinimumSize(); if (preferredSize.height < minimumSize.height) { preferredSize.height = minimumSize.height; } if (preferredSize.width < minimumSize.width) { preferredSize.width = minimumSize.width; } Dimension maximumSize = comp.getMaximumSize(); if (preferredSize.height > maximumSize.height && maximumSize.height != 32767) { // 32767 is hard-coded inside Swing. If you set maximum size to null, the maximum size is 32767. preferredSize.height = maximumSize.height; } if (preferredSize.width > maximumSize.width && maximumSize.height != 32767) { preferredSize.width = maximumSize.width; } return preferredSize; } /** * Checks of the layout should be reset when {@link #invalidateLayout(java.awt.Container)} is called. * * @return true or false. */ public boolean isResetWhenInvalidate() { return _resetWhenInvalidate; } /** * Sets the flag if the layout should be reset when {@link #invalidateLayout(java.awt.Container)} is called. * * @param resetWhenInvalidate the flag */ public void setResetWhenInvalidate(boolean resetWhenInvalidate) { _resetWhenInvalidate = resetWhenInvalidate; } /** * Gets the axis. * * @return the axis. */ public int getAxis() { return _axis; } /** * Sets the axis. After changing the axis, you need to call doLayout method on the container which has this layout. * * @param axis the new axis. */ public void setAxis(int axis) { _axis = axis; } /** * Checks if the alwaysLayout flag is true. If true, the layout manager will layout the components even there is no * way to satisfy the minimum size requirements from all FIXED components. By default, it is true. * * @return true or false. */ public boolean isAlwaysLayout() { return _alwaysLayout; } /** * Sets the alwaysLayout flag. * * @param alwaysLayout true to always layout components even there is no way to satisfy the minimum size * requirements from all FIXED components. */ public void setAlwaysLayout(boolean alwaysLayout) { _alwaysLayout = alwaysLayout; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy