/* * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com), * modifications by Nikolaus Moll * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * Neither the name of the MiG InfoCom AB nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * */ package org.jowidgets.impl.layout.miglayout; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeSet; import java.util.WeakHashMap; /** * Holds components in a grid. Does most of the logic behind the layout manager. */ final class GridCommon { public static final boolean TEST_GAPS = true; private static final Float[] GROW_100 = new Float[] {ResizeConstraintCommon.WEIGHT_100}; private static final DimConstraintCommon DOCK_DIM_CONSTRAINT = new DimConstraintCommon(); static { DOCK_DIM_CONSTRAINT.setGrowPriority(0); } @SuppressWarnings("rawtypes") private static WeakHashMap[] parentRowColSizesMap = null; private static WeakHashMap<Object, LinkedHashMap<Integer, Cell>> parentGridPosMap = null; /** * This is the maximum grid position for "normal" components. Docking components use the space out to * <code>MAX_DOCK_GRID</code> and below 0. */ private static final int MAX_GRID = 30000; /** * Docking components will use the grid coordinates <code>-MAX_DOCK_GRID -> 0</code> and * <code>MAX_GRID -> MAX_DOCK_GRID</code>. */ private static final int MAX_DOCK_GRID = 32767; /** * A constraint used for gaps. */ private static final ResizeConstraintCommon GAP_RC_CONST = new ResizeConstraintCommon( 200, ResizeConstraintCommon.WEIGHT_100, 50, null); private static final ResizeConstraintCommon GAP_RC_CONST_PUSH = new ResizeConstraintCommon( 200, ResizeConstraintCommon.WEIGHT_100, 50, ResizeConstraintCommon.WEIGHT_100); /** * The constraints. Never <code>null</code>. */ private final LCCommon lc; /** * The parent that is layout out and this grid is done for. Never <code>null</code>. */ private final IContainerWrapperCommon container; /** * An x, y array implemented as a sparse array to accommodate for any grid size without wasting memory (or rather 15 bit * (0-MAX_GRID * 0-MAX_GRID). */ private final LinkedHashMap<Integer, Cell> grid = new LinkedHashMap<Integer, Cell>(); // [(y << 16) + x] -> Cell. null key for absolute positioned compwraps private HashMap<Integer, BoundSizeCommon> wrapGapMap = null; // Row or Column index depending in the dimension that "wraps". Normally row indexes but may be column indexes if "flowy". 0 means before first row/col. /** * The size of the grid. Row count and column count. */ private final TreeSet<Integer> rowIndexes = new TreeSet<Integer>(); private final TreeSet<Integer> colIndexes = new TreeSet<Integer>(); /** * The row and column specifications. */ private final ACCommon rowConstr; private final ACCommon colConstr; /** * The in the constructor calculated min/pref/max sizes of the rows and columns. */ private FlowSizeSpec colFlowSpecs = null; private FlowSizeSpec rowFlowSpecs = null; /** * Components that are connections in one dimension (such as baseline alignment for instance) are grouped together and stored * here. * One for each row/column. */ private final ArrayList<LinkedDimGroup>[] colGroupLists; private final ArrayList<LinkedDimGroup>[] rowGroupLists; //[(start)row/col number] /** * The in the constructor calculated min/pref/max size of the whole grid. */ private int[] width = null; private int[] height = null; /** * If debug is on contains the bounds for things to paint when calling * {@link IContainerWrapperCommon#paintDebugCell(int, int, int, int)} */ private ArrayList<int[]> debugRects = null; // [x, y, width, height] /** * If any of the absolute coordinates for component bounds has links the name of the target is in this Set. * Since it requires some memory and computations this is checked at the creation so that * the link information is only created if needed later. * <p> * The boolean is true for groups id:s and null for normal id:s. */ private HashMap<String, Boolean> linkTargetIDs = null; private final int dockOffY; private final int dockOffX; private final Float[] pushXs; private final Float[] pushYs; private final ArrayList<AbstractLayoutCallbackCommon> callbackList; /** * Constructor. * * @param container The container that will be laid out. * @param lc The form flow constraints. * @param rowConstr The rows specifications. If more cell rows are required, the last element will be used for when there is * no corresponding element in this array. * @param colConstr The columns specifications. If more cell rows are required, the last element will be used for when there * is no corresponding element in this array. * @param ccMap The map containing the parsed constraints for each child component of <code>parent</code>. Will not be * alterted. * @param callbackList A list of callbacks or <code>null</code> if none. Will not be alterted. */ GridCommon( final IContainerWrapperCommon container, final LCCommon lc, final ACCommon rowConstr, final ACCommon colConstr, final Map<IComponentWrapperCommon, CCCommon> ccMap, final ArrayList<AbstractLayoutCallbackCommon> callbackList) { this.lc = lc; this.rowConstr = rowConstr; this.colConstr = colConstr; this.container = container; this.callbackList = callbackList; final int wrap = lc.getWrapAfter() != 0 ? lc.getWrapAfter() : (lc.isFlowX() ? colConstr : rowConstr).getConstaints().length; final IComponentWrapperCommon[] comps = container.getComponents(); boolean hasTagged = false; // So we do not have to sort if it will not do any good boolean hasPushX = false; boolean hasPushY = false; boolean hitEndOfRow = false; final int[] cellXY = new int[2]; final ArrayList<int[]> spannedRects = new ArrayList<int[]>(2); final DimConstraintCommon[] specs = (lc.isFlowX() ? rowConstr : colConstr).getConstaints(); int sizeGroupsX = 0; int sizeGroupsY = 0; int[] dockInsets = null; // top, left, bottom, right insets for docks. MigLayoutToolkitImpl.getMigLinkHandler().clearTemporaryBounds(container.getLayout()); for (int i = 0; i < comps.length;) { final IComponentWrapperCommon comp = comps[i]; final CCCommon rootCc = getCC(comp, ccMap); addLinkIDs(rootCc); int hideMode = comp.isVisible() ? -1 : rootCc.getHideMode() != -1 ? rootCc.getHideMode() : lc.getHideMode(); if (hideMode == 3) { // To work with situations where there are components that does not have a layout manager, or not this one. setLinkedBounds(comp, rootCc, comp.getX(), comp.getY(), comp.getWidth(), comp.getHeight(), rootCc.isExternal()); i++; continue; // The "external" component should not be handled further. } if (rootCc.getHorizontal().getSizeGroup() != null) { sizeGroupsX++; } if (rootCc.getVertical().getSizeGroup() != null) { sizeGroupsY++; } // Special treatment of absolute positioned components. UnitValueCommon[] pos = getPos(comp, rootCc); BoundSizeCommon[] cbSz = getCallbackSize(comp); if (pos != null || rootCc.isExternal()) { final CompWrap cw = new CompWrap(comp, rootCc, hideMode, pos, cbSz); final Cell cell = grid.get(null); if (cell == null) { grid.put(null, new Cell(cw)); } else { cell.compWraps.add(cw); } if (rootCc.isBoundsInGrid() == false || rootCc.isExternal()) { setLinkedBounds( comp, rootCc, comp.getX(), comp.getY(), comp.getWidth(), comp.getHeight(), rootCc.isExternal()); i++; continue; } } if (rootCc.getDockSide() != -1) { if (dockInsets == null) { dockInsets = new int[] {-MAX_DOCK_GRID, -MAX_DOCK_GRID, MAX_DOCK_GRID, MAX_DOCK_GRID}; } addDockingCell(dockInsets, rootCc.getDockSide(), new CompWrap(comp, rootCc, hideMode, pos, cbSz)); i++; continue; } final Boolean cellFlowX = rootCc.getFlowX(); Cell cell = null; if (rootCc.isNewline()) { wrap(cellXY, rootCc.getNewlineGapSize()); } else if (hitEndOfRow) { wrap(cellXY, null); } hitEndOfRow = false; final boolean rowNoGrid = lc.isNoGrid() || ((DimConstraintCommon) MigLayoutToolkitImpl.getMigLayoutUtil() .getIndexSafe(specs, lc.isFlowX() ? cellXY[1] : cellXY[0])).isNoGrid(); // Move to a free y, x if no absolute grid specified final int cx = rootCc.getCellX(); final int cy = rootCc.getCellY(); if ((cx < 0 || cy < 0) && rowNoGrid == false && rootCc.getSkip() == 0) { // 3.7.2: If skip, don't find an empty cell first. while (isCellFree(cellXY[1], cellXY[0], spannedRects) == false) { if (Math.abs(increase(cellXY, 1)) >= wrap) { wrap(cellXY, null); } } } else { if (cx >= 0 && cy >= 0) { if (cy >= 0) { cellXY[0] = cx; cellXY[1] = cy; } else { // Only one coordinate is specified. Use the current row (flowx) or column (flowy) to fill in. if (lc.isFlowX()) { cellXY[0] = cx; } else { cellXY[1] = cx; } } } cell = getCell(cellXY[1], cellXY[0]); // Might be null } // Skip a number of cells. Changed for 3.6.1 to take wrap into account and thus "skip" to the next and possibly more rows. final int skipCount = rootCc.getSkip(); for (int s = 0; s < skipCount; s++) { do { if (Math.abs(increase(cellXY, 1)) >= wrap) { wrap(cellXY, null); } } while (isCellFree(cellXY[1], cellXY[0], spannedRects) == false); } // If cell is not created yet, create it and set it. if (cell == null) { final int spanx = Math .min(rowNoGrid && lc.isFlowX() ? LayoutUtilCommon.INF : rootCc.getSpanX(), MAX_GRID - cellXY[0]); final int spany = Math .min(rowNoGrid && !lc.isFlowX() ? LayoutUtilCommon.INF : rootCc.getSpanY(), MAX_GRID - cellXY[1]); cell = new Cell(spanx, spany, cellFlowX != null ? cellFlowX : lc.isFlowX()); setCell(cellXY[1], cellXY[0], cell); // Add a rectangle so we can know that spanned cells occupy more space. if (spanx > 1 || spany > 1) { spannedRects.add(new int[] {cellXY[0], cellXY[1], spanx, spany}); } } // Add the one, or all, components that split the grid position to the same Cell. boolean wrapHandled = false; int splitLeft = rowNoGrid ? LayoutUtilCommon.INF : rootCc.getSplit() - 1; boolean splitExit = false; final boolean spanRestOfRow = (lc.isFlowX() ? rootCc.getSpanX() : rootCc.getSpanY()) == LayoutUtilCommon.INF; for (; splitLeft >= 0 && i < comps.length; splitLeft--) { final IComponentWrapperCommon compAdd = comps[i]; final CCCommon cc = getCC(compAdd, ccMap); addLinkIDs(cc); final boolean visible = compAdd.isVisible(); hideMode = visible ? -1 : cc.getHideMode() != -1 ? cc.getHideMode() : lc.getHideMode(); if (cc.isExternal() || hideMode == 3) { i++; splitLeft++; // Added for 3.5.5 so that these components does not "take" a split slot. continue; // To work with situations where there are components that does not have a layout manager, or not this one. } hasPushX |= (visible || hideMode > 1) && (cc.getPushX() != null); hasPushY |= (visible || hideMode > 1) && (cc.getPushY() != null); if (cc != rootCc) { // If not first in a cell if (cc.isNewline() || cc.isBoundsInGrid() == false || cc.getDockSide() != -1) { break; } if (splitLeft > 0 && cc.getSkip() > 0) { splitExit = true; break; } pos = getPos(compAdd, cc); cbSz = getCallbackSize(compAdd); } final CompWrap cw = new CompWrap(compAdd, cc, hideMode, pos, cbSz); cell.compWraps.add(cw); cell.hasTagged |= cc.getTag() != null; hasTagged |= cell.hasTagged; if (cc != rootCc) { if (cc.getHorizontal().getSizeGroup() != null) { sizeGroupsX++; } if (cc.getVertical().getSizeGroup() != null) { sizeGroupsY++; } } i++; if ((cc.isWrap() || (spanRestOfRow && splitLeft == 0))) { if (cc.isWrap()) { wrap(cellXY, cc.getWrapGapSize()); } else { hitEndOfRow = true; } wrapHandled = true; break; } } if (wrapHandled == false && rowNoGrid == false) { final int span = lc.isFlowX() ? cell.spanx : cell.spany; if (Math.abs((lc.isFlowX() ? cellXY[0] : cellXY[1])) + span >= wrap) { hitEndOfRow = true; } else { increase(cellXY, splitExit ? span - 1 : span); } } } // If there were size groups, calculate the largest values in the groups (for min/pref/max) and enforce them on the rest in the group. if (sizeGroupsX > 0 || sizeGroupsY > 0) { final HashMap<String, int[]> sizeGroupMapX = sizeGroupsX > 0 ? new HashMap<String, int[]>(sizeGroupsX) : null; final HashMap<String, int[]> sizeGroupMapY = sizeGroupsY > 0 ? new HashMap<String, int[]>(sizeGroupsY) : null; final ArrayList<CompWrap> sizeGroupCWs = new ArrayList<CompWrap>(Math.max(sizeGroupsX, sizeGroupsY)); for (final Cell cell : grid.values()) { for (int i = 0; i < cell.compWraps.size(); i++) { final CompWrap cw = cell.compWraps.get(i); final String sgx = cw.cc.getHorizontal().getSizeGroup(); final String sgy = cw.cc.getVertical().getSizeGroup(); if (sgx != null || sgy != null) { if (sgx != null && sizeGroupMapX != null) { addToSizeGroup(sizeGroupMapX, sgx, cw.horSizes); } if (sgy != null && sizeGroupMapY != null) { addToSizeGroup(sizeGroupMapY, sgy, cw.verSizes); } sizeGroupCWs.add(cw); } } } // Set/equalize the sizeGroups to same the values. for (final CompWrap cw : sizeGroupCWs) { if (sizeGroupMapX != null) { cw.setSizes(sizeGroupMapX.get(cw.cc.getHorizontal().getSizeGroup()), true); // Target method handles null sizes } if (sizeGroupMapY != null) { cw.setSizes(sizeGroupMapY.get(cw.cc.getVertical().getSizeGroup()), false); // Target method handles null sizes } } } // Component loop // If there were size groups, calculate the largest values in the groups (for min/pref/max) and enforce them on the rest in the group. if (sizeGroupsX > 0 || sizeGroupsY > 0) { final HashMap<String, int[]> sizeGroupMapX = sizeGroupsX > 0 ? new HashMap<String, int[]>(sizeGroupsX) : null; final HashMap<String, int[]> sizeGroupMapY = sizeGroupsY > 0 ? new HashMap<String, int[]>(sizeGroupsY) : null; final ArrayList<CompWrap> sizeGroupCWs = new ArrayList<CompWrap>(Math.max(sizeGroupsX, sizeGroupsY)); for (final Cell cell : grid.values()) { for (int i = 0; i < cell.compWraps.size(); i++) { final CompWrap cw = cell.compWraps.get(i); final String sgx = cw.cc.getHorizontal().getSizeGroup(); final String sgy = cw.cc.getVertical().getSizeGroup(); if (sgx != null || sgy != null) { if (sgx != null && sizeGroupMapX != null) { addToSizeGroup(sizeGroupMapX, sgx, cw.horSizes); } if (sgy != null && sizeGroupMapY != null) { addToSizeGroup(sizeGroupMapY, sgy, cw.verSizes); } sizeGroupCWs.add(cw); } } } // Set/equalize the sizeGroups to same the values. for (final CompWrap cw : sizeGroupCWs) { if (sizeGroupMapX != null) { cw.setSizes(sizeGroupMapX.get(cw.cc.getHorizontal().getSizeGroup()), true); // Target method handles null sizes } if (sizeGroupMapY != null) { cw.setSizes(sizeGroupMapY.get(cw.cc.getVertical().getSizeGroup()), false); // Target method handles null sizes } } } if (hasTagged) { sortCellsByPlatform(grid.values(), container); } // Calculate gaps now that the cells are filled and we know all adjacent components. final boolean ltr = MigLayoutToolkitImpl.getMigLayoutUtil().isLeftToRight(lc, container); for (final Cell cell : grid.values()) { final ArrayList<CompWrap> cws = cell.compWraps; final int lastI = cws.size() - 1; for (int i = 0; i <= lastI; i++) { final CompWrap cw = cws.get(i); final IComponentWrapperCommon cwBef = i > 0 ? cws.get(i - 1).comp : null; final IComponentWrapperCommon cwAft = i < lastI ? cws.get(i + 1).comp : null; final String tag = getCC(cw.comp, ccMap).getTag(); final CCCommon ccBef = cwBef != null ? getCC(cwBef, ccMap) : null; final CCCommon ccAft = cwAft != null ? getCC(cwAft, ccMap) : null; cw.calcGaps(cwBef, ccBef, cwAft, ccAft, tag, cell.flowx, ltr); } } dockOffX = getDockInsets(colIndexes); dockOffY = getDockInsets(rowIndexes); // Add synthetic indexes for empty rows and columns so they can get a size int iSz = rowConstr.getCount(); for (int i = 0; i < iSz; i++) { rowIndexes.add(i); } iSz = colConstr.getCount(); for (int i = 0; i < iSz; i++) { colIndexes.add(i); } colGroupLists = divideIntoLinkedGroups(false); rowGroupLists = divideIntoLinkedGroups(true); pushXs = hasPushX || lc.isFillX() ? getDefaultPushWeights(false) : null; pushYs = hasPushY || lc.isFillY() ? getDefaultPushWeights(true) : null; if (MigLayoutToolkitImpl.getMigLayoutUtil().isDesignTime(container)) { saveGrid(container, grid); } } private static CCCommon getCC(final IComponentWrapperCommon comp, final Map<IComponentWrapperCommon, CCCommon> ccMap) { final CCCommon cc = ccMap.get(comp); return cc != null ? cc : new CCCommon(); } private void addLinkIDs(final CCCommon cc) { final String[] linkIDs = cc.getLinkTargets(); for (final String linkID : linkIDs) { if (linkTargetIDs == null) { linkTargetIDs = new HashMap<String, Boolean>(); } linkTargetIDs.put(linkID, null); } } /** * If the container (parent) that this grid is laying out has changed its bounds, call this method to * clear any cached values. */ public void invalidateContainerSize() { colFlowSpecs = null; } /** * Does the actual layout. Uses many values calculated in the constructor. * * @param bounds The bounds to layout against. Normally that of the parent. [x, y, width, height]. * @param alignX The alignment for the x-axis. * @param alignY The alignment for the y-axis. * @param debug If debug information should be saved in {@link #debugRects}. * @param checkPrefChange If a check should be done to see if the setting of any new bounds changes the preferred size * of a component. * @return If the layout has probably changed the preferred size and there is need for a new layout (normally only SWT). */ public boolean layout( final int[] bounds, final UnitValueCommon alignX, final UnitValueCommon alignY, final boolean debug, final boolean checkPrefChange) { if (debug) { debugRects = new ArrayList<int[]>(); } checkSizeCalcs(); resetLinkValues(true, true); layoutInOneDim(bounds[2], alignX, false, pushXs); layoutInOneDim(bounds[3], alignY, true, pushYs); HashMap<String, Integer> endGrpXMap = null; HashMap<String, Integer> endGrpYMap = null; final int compCount = container.getComponentCount(); // Transfer the calculated bound from the ComponentWrappers to the actual Components. boolean layoutAgain = false; if (compCount > 0) { for (int j = 0; j < (linkTargetIDs != null ? 2 : 1); j++) { // First do the calculations (maybe more than once) then set the bounds when done boolean doAgain; int count = 0; do { doAgain = false; for (final Cell cell : grid.values()) { final ArrayList<CompWrap> compWraps = cell.compWraps; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); if (j == 0) { doAgain |= doAbsoluteCorrections(cw, bounds); if (doAgain == false) { // If we are going to do this again, do not bother this time around if (cw.cc.getHorizontal().getEndGroup() != null) { endGrpXMap = addToEndGroup(endGrpXMap, cw.cc.getHorizontal().getEndGroup(), cw.x + cw.w); } if (cw.cc.getVertical().getEndGroup() != null) { endGrpYMap = addToEndGroup(endGrpYMap, cw.cc.getVertical().getEndGroup(), cw.y + cw.h); } } // @since 3.7.2 Needed or absolute "pos" pointing to "visual" or "container" didn't work if // their bounds changed during the layout cycle. At least not in SWT. if (linkTargetIDs != null && (linkTargetIDs.containsKey("visual") || linkTargetIDs.containsKey("container"))) { layoutAgain = true; } } if (linkTargetIDs == null || j == 1) { if (cw.cc.getHorizontal().getEndGroup() != null) { cw.w = endGrpXMap.get(cw.cc.getHorizontal().getEndGroup()) - cw.x; } if (cw.cc.getVertical().getEndGroup() != null) { cw.h = endGrpYMap.get(cw.cc.getVertical().getEndGroup()) - cw.y; } cw.x += bounds[0]; cw.y += bounds[1]; layoutAgain |= cw.transferBounds(checkPrefChange && !layoutAgain); if (callbackList != null) { for (final AbstractLayoutCallbackCommon callback : callbackList) { callback.correctBounds(cw.comp); } } } } } clearGroupLinkBounds(); if (++count > ((compCount << 3) + 10)) { //CHECKSTYLE:OFF System.err.println("Unstable cyclic dependency in absolute linked values!"); //CHECKSTYLE:ON break; } } while (doAgain); } } // Add debug shapes for the "cells". Use the CompWraps as base for inding the cells. if (debug) { for (final Cell cell : grid.values()) { final ArrayList<CompWrap> compWraps = cell.compWraps; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); final LinkedDimGroup hGrp = getGroupContaining(colGroupLists, cw); final LinkedDimGroup vGrp = getGroupContaining(rowGroupLists, cw); if (hGrp != null && vGrp != null) { debugRects.add( new int[] { hGrp.lStart + bounds[0] - (hGrp.fromEnd ? hGrp.lSize : 0), vGrp.lStart + bounds[1] - (vGrp.fromEnd ? vGrp.lSize : 0), hGrp.lSize, vGrp.lSize}); } } } } return layoutAgain; } public void paintDebug() { if (debugRects != null) { container.paintDebugOutline(); final ArrayList<int[]> painted = new ArrayList<int[]>(); for (int i = 0; i < debugRects.size(); i++) { final int[] r = debugRects.get(i); if (painted.contains(r) == false) { container.paintDebugCell(r[0], r[1], r[2], r[3]); painted.add(r); } } for (final Cell cell : grid.values()) { final ArrayList<CompWrap> compWraps = cell.compWraps; for (int i = 0; i < compWraps.size(); i++) { compWraps.get(i).comp.paintDebugOutline(); } } } } public IContainerWrapperCommon getContainer() { return container; } public int[] getWidth() { checkSizeCalcs(); return width.clone(); } public int[] getHeight() { checkSizeCalcs(); return height.clone(); } private void checkSizeCalcs() { if (colFlowSpecs == null) { colFlowSpecs = calcRowsOrColsSizes(true); rowFlowSpecs = calcRowsOrColsSizes(false); width = getMinPrefMaxSumSize(true); height = getMinPrefMaxSumSize(false); if (linkTargetIDs == null) { resetLinkValues(false, true); } else { // This call makes some components flicker on SWT. They get their bounds changed twice since // the change might affect the absolute size adjustment below. There's no way around this that // I know of. layout(new int[4], null, null, false, false); resetLinkValues(false, false); } adjustSizeForAbsolute(true); adjustSizeForAbsolute(false); } } private UnitValueCommon[] getPos(final IComponentWrapperCommon cw, final CCCommon cc) { UnitValueCommon[] cbPos = null; if (callbackList != null) { for (int i = 0; i < callbackList.size() && cbPos == null; i++) { cbPos = callbackList.get(i).getPosition(cw); // NOT a copy! } } // If one is null, return the other (which many also be null) final UnitValueCommon[] ccPos = cc.getPos(); // A copy!! if (cbPos == null || ccPos == null) { return cbPos != null ? cbPos : ccPos; } // Merge for (int i = 0; i < 4; i++) { final UnitValueCommon cbUv = cbPos[i]; if (cbUv != null) { ccPos[i] = cbUv; } } return ccPos; } private BoundSizeCommon[] getCallbackSize(final IComponentWrapperCommon cw) { if (callbackList != null) { for (final AbstractLayoutCallbackCommon callback : callbackList) { final BoundSizeCommon[] bs = callback.getSize(cw); // NOT a copy! if (bs != null) { return bs; } } } return null; } private static int getDockInsets(final TreeSet<Integer> set) { int c = 0; for (final Integer i : set) { if (i < -MAX_GRID) { c++; } else { break; // Since they are sorted we can break } } return c; } /** * @param cw Never <code>null</code>. * @param cc Never <code>null</code>. * @param external The bounds should be stored even if they are not in {@link #linkTargetIDs}. * @return If a change has been made. */ private boolean setLinkedBounds( final IComponentWrapperCommon cw, final CCCommon cc, final int x, final int y, final int w, final int h, final boolean external) { String id = cc.getId() != null ? cc.getId() : cw.getLinkId(); if (id == null) { return false; } String gid = null; final int grIx = id.indexOf('.'); if (grIx != -1) { gid = id.substring(0, grIx); id = id.substring(grIx + 1); } final Object lay = container.getLayout(); boolean changed = false; if (external || (linkTargetIDs != null && linkTargetIDs.containsKey(id))) { changed = MigLayoutToolkitImpl.getMigLinkHandler().setBounds(lay, id, x, y, w, h, !external, false); } if (gid != null && (external || (linkTargetIDs != null && linkTargetIDs.containsKey(gid)))) { if (linkTargetIDs == null) { linkTargetIDs = new HashMap<String, Boolean>(4); } linkTargetIDs.put(gid, Boolean.TRUE); changed |= MigLayoutToolkitImpl.getMigLinkHandler().setBounds(lay, gid, x, y, w, h, !external, true); } return changed; } /** * Go to next cell. * * @param p The point to increase * @param cnt How many cells to advance. * @return The new value in the "incresing" dimension. */ private int increase(final int[] p, final int cnt) { if (lc.isFlowX()) { p[0] = p[0] + cnt; return p[0]; } else { p[1] = p[1] + cnt; return p[1]; } } /** * Wraps to the next row or column depending on if horizontal flow or vertical flow is used. * * @param cellXY The point to wrap and thus set either x or y to 0 and increase the other one. * @param gapSize The gaps size specified in a "wrap XXX" or "newline XXX" or <code>null</code> if none. */ private void wrap(final int[] cellXY, final BoundSizeCommon gapSize) { final boolean flowx = lc.isFlowX(); cellXY[0] = flowx ? 0 : cellXY[0] + 1; cellXY[1] = flowx ? cellXY[1] + 1 : 0; if (gapSize != null) { if (wrapGapMap == null) { wrapGapMap = new HashMap<Integer, BoundSizeCommon>(8); } wrapGapMap.put(cellXY[flowx ? 1 : 0], gapSize); } // add the row/column so that the gap in the last row/col will not be removed. if (flowx) { rowIndexes.add(cellXY[1]); } else { colIndexes.add(cellXY[0]); } } /** * Sort components (normally buttons in a button bar) so they appear in the correct order. * * @param cells The cells to sort. * @param parent The parent. */ private static void sortCellsByPlatform(final Collection<Cell> cells, final IContainerWrapperCommon parent) { final String order = MigLayoutToolkitImpl.getMigPlatformDefaults().getButtonOrder(); final String orderLo = order.toLowerCase(); final int unrelSize = MigLayoutToolkitImpl.getMigPlatformDefaults().convertToPixels(1, "u", true, 0, parent, null); if (unrelSize == UnitConverterCommon.UNABLE) { throw new IllegalArgumentException("'unrelated' not recognized by PlatformDefaults!"); } final int[] gapUnrel = new int[] {unrelSize, unrelSize, LayoutUtilCommon.NOT_SET}; final int[] flGap = new int[] {0, 0, LayoutUtilCommon.NOT_SET}; for (final Cell cell : cells) { if (cell.hasTagged == false) { continue; } CompWrap prevCW = null; boolean nextUnrel = false; boolean nextPush = false; final ArrayList<CompWrap> sortedList = new ArrayList<CompWrap>(cell.compWraps.size()); final int iSz = orderLo.length(); for (int i = 0; i < iSz; i++) { final char c = orderLo.charAt(i); if (c == '+' || c == '_') { nextUnrel = true; if (c == '+') { nextPush = true; } } else { final String tag = PlatformDefaultsCommon.getTagForChar(c); if (tag != null) { final int jSz = cell.compWraps.size(); for (int j = 0; j < jSz; j++) { final CompWrap cw = cell.compWraps.get(j); if (!tag.equals(cw.cc.getTag())) { continue; } if (Character.isUpperCase(order.charAt(i))) { final int min = MigLayoutToolkitImpl.getMigPlatformDefaults() .getMinimumButtonWidth() .getPixels(0, parent, cw.comp); if (min > cw.horSizes[LayoutUtilCommon.MIN]) { cw.horSizes[LayoutUtilCommon.MIN] = min; } correctMinMax(cw.horSizes); } sortedList.add(cw); if (nextUnrel) { (prevCW != null ? prevCW : cw).mergeGapSizes(gapUnrel, cell.flowx, prevCW == null); if (nextPush) { cw.forcedPushGaps = 1; nextUnrel = false; nextPush = false; } } // "unknown" components will always get an Unrelated gap. if (c == 'u') { nextUnrel = true; } prevCW = cw; } } } } // If we have a gap that was supposed to push but no more components was found to but the "gap before" then compensate. if (sortedList.size() > 0) { CompWrap cw = sortedList.get(sortedList.size() - 1); if (nextUnrel) { cw.mergeGapSizes(gapUnrel, cell.flowx, false); if (nextPush) { cw.forcedPushGaps |= 2; } } // Remove first and last gap if not set explicitly. if (cw.cc.getHorizontal().getGapAfter() == null) { cw.setGaps(flGap, 3); } cw = sortedList.get(0); if (cw.cc.getHorizontal().getGapBefore() == null) { cw.setGaps(flGap, 1); } } // Exchange the unsorted CompWraps for the sorted one. if (cell.compWraps.size() == sortedList.size()) { cell.compWraps.clear(); } else { cell.compWraps.removeAll(sortedList); } cell.compWraps.addAll(sortedList); } } private Float[] getDefaultPushWeights(final boolean isRows) { final ArrayList<LinkedDimGroup>[] groupLists = isRows ? rowGroupLists : colGroupLists; Float[] pushWeightArr = GROW_100; // Only create specific if any of the components have grow. int ix = 1; for (int i = 0; i < groupLists.length; i++, ix += 2) { final ArrayList<LinkedDimGroup> grps = groupLists[i]; Float rowPushWeight = null; for (final LinkedDimGroup grp : grps) { for (int c = 0; c < grp.ldgCompWraps.size(); c++) { final CompWrap cw = grp.ldgCompWraps.get(c); final int hideMode = cw.comp.isVisible() ? -1 : cw.cc.getHideMode() != -1 ? cw.cc.getHideMode() : lc.getHideMode(); final Float pushWeight = hideMode < 2 ? (isRows ? cw.cc.getPushY() : cw.cc.getPushX()) : null; if (rowPushWeight == null || (pushWeight != null && pushWeight.floatValue() > rowPushWeight.floatValue())) { rowPushWeight = pushWeight; } } } if (rowPushWeight != null) { if (pushWeightArr == GROW_100) { pushWeightArr = new Float[(groupLists.length << 1) + 1]; } pushWeightArr[ix] = rowPushWeight; } } return pushWeightArr; } private void clearGroupLinkBounds() { if (linkTargetIDs == null) { return; } for (final Map.Entry<String, Boolean> o : linkTargetIDs.entrySet()) { if (o.getValue() == Boolean.TRUE) { MigLayoutToolkitImpl.getMigLinkHandler().clearBounds(container.getLayout(), o.getKey()); } } } private void resetLinkValues(final boolean parentSize, final boolean compLinks) { final Object lay = container.getLayout(); if (compLinks) { MigLayoutToolkitImpl.getMigLinkHandler().clearTemporaryBounds(lay); } final boolean defIns = !hasDocks(); final int parW = parentSize ? lc.getWidth().constrain(container.getWidth(), getParentSize(container, true), container) : 0; final int parH = parentSize ? lc.getHeight().constrain(container.getHeight(), getParentSize(container, false), container) : 0; final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final int insX = layoutUtil.getInsets(lc, 0, defIns).getPixels(0, container, null); final int insY = layoutUtil.getInsets(lc, 1, defIns).getPixels(0, container, null); final int visW = parW - insX - layoutUtil.getInsets(lc, 2, defIns).getPixels(0, container, null); final int visH = parH - insY - layoutUtil.getInsets(lc, 3, defIns).getPixels(0, container, null); MigLayoutToolkitImpl.getMigLinkHandler().setBounds(lay, "visual", insX, insY, visW, visH, true, false); MigLayoutToolkitImpl.getMigLinkHandler().setBounds(lay, "container", 0, 0, parW, parH, true, false); } /** * Returns the {@link GridCommon.miginfocom.layout.Grid.LinkedDimGroup} that has the * {@link GridCommon.miginfocom.layout.Grid.CompWrap} <code>cw</code>. * * @param groupLists The lists to search in. * @param cw The component wrap to find. * @return The linked group or <code>null</code> if none had the component wrap. */ private static LinkedDimGroup getGroupContaining(final ArrayList<LinkedDimGroup>[] groupLists, final CompWrap cw) { for (final ArrayList<LinkedDimGroup> groups : groupLists) { final int jSz = groups.size(); for (int j = 0; j < jSz; j++) { final ArrayList<CompWrap> cwList = groups.get(j).ldgCompWraps; final int kSz = cwList.size(); for (int k = 0; k < kSz; k++) { if (cwList.get(k) == cw) { return groups.get(j); } } } } return null; } private boolean doAbsoluteCorrections(final CompWrap cw, final int[] bounds) { boolean changed = false; int[] stSz = getAbsoluteDimBounds(cw, bounds[2], true); if (stSz != null) { cw.setDimBounds(stSz[0], stSz[1], true); } stSz = getAbsoluteDimBounds(cw, bounds[3], false); if (stSz != null) { cw.setDimBounds(stSz[0], stSz[1], false); } // If there is a link id, store the new bounds. if (linkTargetIDs != null) { changed = setLinkedBounds(cw.comp, cw.cc, cw.x, cw.y, cw.w, cw.h, false); } return changed; } private void adjustSizeForAbsolute(final boolean isHor) { final int[] curSizes = isHor ? width : height; final Cell absCell = grid.get(null); if (absCell == null || absCell.compWraps.size() == 0) { return; } final ArrayList<CompWrap> cws = absCell.compWraps; int maxEnd = 0; final int cwSz = absCell.compWraps.size(); for (int j = 0; j < cwSz + 3; j++) { // "Do Again" max absCell.compWraps.size() + 3 times. boolean doAgain = false; for (int i = 0; i < cwSz; i++) { final CompWrap cw = cws.get(i); final int[] stSz = getAbsoluteDimBounds(cw, 0, isHor); final int end = stSz[0] + stSz[1]; if (maxEnd < end) { maxEnd = end; } // If there is a link id, store the new bounds. if (linkTargetIDs != null) { doAgain |= setLinkedBounds(cw.comp, cw.cc, stSz[0], stSz[0], stSz[1], stSz[1], false); } } if (doAgain == false) { break; } // We need to check this again since the coords may be smaller this round. maxEnd = 0; clearGroupLinkBounds(); } maxEnd += MigLayoutToolkitImpl.getMigLayoutUtil().getInsets(lc, isHor ? 3 : 2, !hasDocks()).getPixels(0, container, null); if (curSizes[LayoutUtilCommon.MIN] < maxEnd) { curSizes[LayoutUtilCommon.MIN] = maxEnd; } if (curSizes[LayoutUtilCommon.PREF] < maxEnd) { curSizes[LayoutUtilCommon.PREF] = maxEnd; } } private int[] getAbsoluteDimBounds(final CompWrap cw, final int refSize, final boolean isHor) { if (cw.cc.isExternal()) { if (isHor) { return new int[] {cw.comp.getX(), cw.comp.getWidth()}; } else { return new int[] {cw.comp.getY(), cw.comp.getHeight()}; } } final int[] plafPad = lc.isVisualPadding() ? cw.comp.getVisualPadding() : null; final UnitValueCommon[] pad = cw.cc.getPadding(); // If no changes do not create a lot of objects if (cw.pos == null && plafPad == null && pad == null) { return null; } // Set start int st = isHor ? cw.x : cw.y; int sz = isHor ? cw.w : cw.h; // If absolute, use those coordinates instead. if (cw.pos != null) { final UnitValueCommon stUV = cw.pos != null ? cw.pos[isHor ? 0 : 1] : null; final UnitValueCommon endUV = cw.pos != null ? cw.pos[isHor ? 2 : 3] : null; final int minSz = cw.getSize(LayoutUtilCommon.MIN, isHor); final int maxSz = cw.getSize(LayoutUtilCommon.MAX, isHor); sz = Math.min(Math.max(cw.getSize(LayoutUtilCommon.PREF, isHor), minSz), maxSz); if (stUV != null) { st = stUV.getPixels(stUV.getUnit() == UnitValueToolkitCommon.ALIGN ? sz : refSize, container, cw.comp); if (endUV != null) { // if (endUV == null && cw.cc.isBoundsIsGrid() == true) sz = Math.min(Math.max((isHor ? (cw.x + cw.w) : (cw.y + cw.h)) - st, minSz), maxSz); } } if (endUV != null) { if (stUV != null) { // if (stUV != null || cw.cc.isBoundsIsGrid()) { sz = Math.min(Math.max(endUV.getPixels(refSize, container, cw.comp) - st, minSz), maxSz); } else { st = endUV.getPixels(refSize, container, cw.comp) - sz; } } } // If constraint has padding -> correct the start/size if (pad != null) { UnitValueCommon uv = pad[isHor ? 1 : 0]; final int p = uv != null ? uv.getPixels(refSize, container, cw.comp) : 0; st += p; uv = pad[isHor ? 3 : 2]; sz += -p + (uv != null ? uv.getPixels(refSize, container, cw.comp) : 0); } // If the plaf converter has padding -> correct the start/size if (plafPad != null) { final int p = plafPad[isHor ? 1 : 0]; st += p; sz += -p + (plafPad[isHor ? 3 : 2]); } return new int[] {st, sz}; } private void layoutInOneDim( final int refSize, final UnitValueCommon align, final boolean isRows, final Float[] defaultPushWeights) { final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final boolean fromEnd = !(isRows ? lc.isTopToBottom() : layoutUtil.isLeftToRight(lc, container)); final DimConstraintCommon[] primDCs = (isRows ? rowConstr : colConstr).getConstaints(); final FlowSizeSpec fss = isRows ? rowFlowSpecs : colFlowSpecs; final ArrayList<LinkedDimGroup>[] rowCols = isRows ? rowGroupLists : colGroupLists; final int[] rowColSizes = layoutUtil .calculateSerial(fss.sizes, fss.resConstsInclGaps, defaultPushWeights, LayoutUtilCommon.PREF, refSize); if (layoutUtil.isDesignTime(container)) { final TreeSet<Integer> indexes = isRows ? rowIndexes : colIndexes; final int[] ixArr = new int[indexes.size()]; int ix = 0; for (final Integer i : indexes) { ixArr[ix++] = i; } putSizesAndIndexes(container.getComponent(), rowColSizes, ixArr, isRows); } int curPos = align != null ? align.getPixels(refSize - LayoutUtilCommon.sum(rowColSizes), container, null) : 0; if (fromEnd) { curPos = refSize - curPos; } for (int i = 0; i < rowCols.length; i++) { final ArrayList<LinkedDimGroup> linkedGroups = rowCols[i]; final int scIx = i - (isRows ? dockOffY : dockOffX); final int bIx = i << 1; final int bIx2 = bIx + 1; curPos += (fromEnd ? -rowColSizes[bIx] : rowColSizes[bIx]); final DimConstraintCommon primDC = scIx >= 0 ? primDCs[scIx >= primDCs.length ? primDCs.length - 1 : scIx] : DOCK_DIM_CONSTRAINT; final int rowSize = rowColSizes[bIx2]; for (final LinkedDimGroup group : linkedGroups) { int groupSize = rowSize; if (group.span > 1) { groupSize = LayoutUtilCommon .sum(rowColSizes, bIx2, Math.min((group.span << 1) - 1, rowColSizes.length - bIx2 - 1)); } group.layout(primDC, curPos, groupSize, group.span); } curPos += (fromEnd ? -rowSize : rowSize); } } private static void addToSizeGroup(final HashMap<String, int[]> sizeGroups, final String sizeGroup, final int[] size) { final int[] sgSize = sizeGroups.get(sizeGroup); if (sgSize == null) { sizeGroups.put( sizeGroup, new int[] {size[LayoutUtilCommon.MIN], size[LayoutUtilCommon.PREF], size[LayoutUtilCommon.MAX]}); } else { sgSize[LayoutUtilCommon.MIN] = Math.max(size[LayoutUtilCommon.MIN], sgSize[LayoutUtilCommon.MIN]); sgSize[LayoutUtilCommon.PREF] = Math.max(size[LayoutUtilCommon.PREF], sgSize[LayoutUtilCommon.PREF]); sgSize[LayoutUtilCommon.MAX] = Math.min(size[LayoutUtilCommon.MAX], sgSize[LayoutUtilCommon.MAX]); } } private static HashMap<String, Integer> addToEndGroup( HashMap<String, Integer> endGroups, final String endGroup, final int end) { if (endGroup != null) { if (endGroups == null) { endGroups = new HashMap<String, Integer>(2); } final Integer oldEnd = endGroups.get(endGroup); if (oldEnd == null || end > oldEnd) { endGroups.put(endGroup, end); } } return endGroups; } /** * Calculates Min, Preferred and Max size for the columns OR rows. * * @param isHor If it is the horizontal dimension to calculate. * @return The sizes in a {@link GridCommon.miginfocom.layout.Grid.FlowSizeSpec}. */ private FlowSizeSpec calcRowsOrColsSizes(final boolean isHor) { final ArrayList<LinkedDimGroup>[] groupsLists = isHor ? colGroupLists : rowGroupLists; final Float[] defPush = isHor ? pushXs : pushYs; int refSize = isHor ? container.getWidth() : container.getHeight(); final BoundSizeCommon cSz = isHor ? lc.getWidth() : lc.getHeight(); if (cSz.isUnset() == false) { refSize = cSz.constrain(refSize, getParentSize(container, isHor), container); } final DimConstraintCommon[] primDCs = (isHor ? colConstr : rowConstr).getConstaints(); final TreeSet<Integer> primIndexes = isHor ? colIndexes : rowIndexes; final int[][] rowColBoundSizes = new int[primIndexes.size()][]; final HashMap<String, int[]> sizeGroupMap = new HashMap<String, int[]>(2); final DimConstraintCommon[] allDCs = new DimConstraintCommon[primIndexes.size()]; final Iterator<Integer> primIt = primIndexes.iterator(); for (int r = 0; r < rowColBoundSizes.length; r++) { final int cellIx = primIt.next(); final int[] rowColSizes = new int[3]; if (cellIx >= -MAX_GRID && cellIx <= MAX_GRID) { // If not dock cell allDCs[r] = primDCs[cellIx >= primDCs.length ? primDCs.length - 1 : cellIx]; } else { allDCs[r] = DOCK_DIM_CONSTRAINT; } final ArrayList<LinkedDimGroup> groups = groupsLists[r]; final int[] groupSizes = new int[] { getTotalGroupsSizeParallel(groups, LayoutUtilCommon.MIN, false), getTotalGroupsSizeParallel(groups, LayoutUtilCommon.PREF, false), LayoutUtilCommon.INF}; correctMinMax(groupSizes); final BoundSizeCommon dimSize = allDCs[r].getSize(); for (int sType = LayoutUtilCommon.MIN; sType <= LayoutUtilCommon.MAX; sType++) { int rowColSize = groupSizes[sType]; final UnitValueCommon uv = dimSize.getSize(sType); if (uv != null) { // If the size of the column is a link to some other size, use that instead final int unit = uv.getUnit(); if (unit == UnitValueToolkitCommon.PREF_SIZE) { rowColSize = groupSizes[LayoutUtilCommon.PREF]; } else if (unit == UnitValueToolkitCommon.MIN_SIZE) { rowColSize = groupSizes[LayoutUtilCommon.MIN]; } else if (unit == UnitValueToolkitCommon.MAX_SIZE) { rowColSize = groupSizes[LayoutUtilCommon.MAX]; } else { rowColSize = uv.getPixels(refSize, container, null); } } else if (cellIx >= -MAX_GRID && cellIx <= MAX_GRID && rowColSize == 0) { final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); rowColSize = layoutUtil.isDesignTime(container) ? layoutUtil.getDesignTimeEmptySize() : 0; // Empty rows with no size set gets XX pixels if design time } rowColSizes[sType] = rowColSize; } correctMinMax(rowColSizes); addToSizeGroup(sizeGroupMap, allDCs[r].getSizeGroup(), rowColSizes); rowColBoundSizes[r] = rowColSizes; } // Set/equalize the size groups to same the values. if (sizeGroupMap.size() > 0) { for (int r = 0; r < rowColBoundSizes.length; r++) { if (allDCs[r].getSizeGroup() != null) { rowColBoundSizes[r] = sizeGroupMap.get(allDCs[r].getSizeGroup()); } } } // Add the gaps final ResizeConstraintCommon[] resConstrs = getRowResizeConstraints(allDCs); final boolean[] fillInPushGaps = new boolean[allDCs.length + 1]; final int[][] gapSizes = getRowGaps(allDCs, refSize, isHor, fillInPushGaps); final FlowSizeSpec fss = mergeSizesGapsAndResConstrs(resConstrs, fillInPushGaps, rowColBoundSizes, gapSizes); // Spanning components are not handled yet. Check and adjust the multi-row min/pref they enforce. adjustMinPrefForSpanningComps(allDCs, defPush, fss, groupsLists); return fss; } private static int getParentSize(final IComponentWrapperCommon cw, final boolean isHor) { final IComponentWrapperCommon p = cw.getParent(); return p != null ? (isHor ? cw.getWidth() : cw.getHeight()) : 0; } private int[] getMinPrefMaxSumSize(final boolean isHor) { final int[][] sizes = isHor ? colFlowSpecs.sizes : rowFlowSpecs.sizes; final int[] retSizes = new int[3]; final BoundSizeCommon sz = isHor ? lc.getWidth() : lc.getHeight(); for (int i = 0; i < sizes.length; i++) { if (sizes[i] == null) { continue; } final int[] size = sizes[i]; for (int sType = LayoutUtilCommon.MIN; sType <= LayoutUtilCommon.MAX; sType++) { if (sz.getSize(sType) != null) { if (i == 0) { retSizes[sType] = sz.getSize(sType).getPixels(getParentSize(container, isHor), container, null); } } else { int s = size[sType]; if (s != LayoutUtilCommon.NOT_SET) { if (sType == LayoutUtilCommon.PREF) { int bnd = size[LayoutUtilCommon.MAX]; if (bnd != LayoutUtilCommon.NOT_SET && bnd < s) { s = bnd; } bnd = size[LayoutUtilCommon.MIN]; if (bnd > s) { s = bnd; } } retSizes[sType] += s; // MAX compensated below. } // So that MAX is always correct. if (size[LayoutUtilCommon.MAX] == LayoutUtilCommon.NOT_SET || retSizes[LayoutUtilCommon.MAX] > LayoutUtilCommon.INF) { retSizes[LayoutUtilCommon.MAX] = LayoutUtilCommon.INF; } } } } correctMinMax(retSizes); return retSizes; } private static ResizeConstraintCommon[] getRowResizeConstraints(final DimConstraintCommon[] specs) { final ResizeConstraintCommon[] resConsts = new ResizeConstraintCommon[specs.length]; for (int i = 0; i < resConsts.length; i++) { resConsts[i] = specs[i].resize; } return resConsts; } private static ResizeConstraintCommon[] getComponentResizeConstraints( final ArrayList<CompWrap> compWraps, final boolean isHor) { final ResizeConstraintCommon[] resConsts = new ResizeConstraintCommon[compWraps.size()]; for (int i = 0; i < resConsts.length; i++) { final CCCommon fc = compWraps.get(i).cc; resConsts[i] = fc.getDimConstraint(isHor).resize; // Always grow docking components in the correct dimension. final int dock = fc.getDockSide(); if (isHor ? (dock == 0 || dock == 2) : (dock == 1 || dock == 3)) { final ResizeConstraintCommon dc = resConsts[i]; resConsts[i] = new ResizeConstraintCommon( dc.shrinkPrio, dc.shrink, dc.growPrio, ResizeConstraintCommon.WEIGHT_100); } } return resConsts; } private static boolean[] getComponentGapPush(final ArrayList<CompWrap> compWraps, final boolean isHor) { // Make one element bigger and or the after gap with the next before gap. final boolean[] barr = new boolean[compWraps.size() + 1]; for (int i = 0; i < barr.length; i++) { boolean push = i > 0 && compWraps.get(i - 1).isPushGap(isHor, false); if (push == false && i < (barr.length - 1)) { push = compWraps.get(i).isPushGap(isHor, true); } barr[i] = push; } return barr; } /** * Returns the row gaps in pixel sizes. One more than there are <code>specs</code> sent in. * * @param specs * @param refSize * @param isHor * @param fillInPushGaps If the gaps are pushing. <b>NOTE!</b> this argument will be filled in and thus changed! * @return The row gaps in pixel sizes. One more than there are <code>specs</code> sent in. */ private int[][] getRowGaps( final DimConstraintCommon[] specs, final int refSize, final boolean isHor, final boolean[] fillInPushGaps) { BoundSizeCommon defGap = isHor ? lc.getGridGapX() : lc.getGridGapY(); if (defGap == null) { defGap = isHor ? MigLayoutToolkitImpl.getMigPlatformDefaults().getGridGapX() : MigLayoutToolkitImpl.getMigPlatformDefaults().getGridGapY(); } final int[] defGapArr = defGap.getPixelSizes(refSize, container, null); final boolean defIns = !hasDocks(); final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final UnitValueCommon firstGap = layoutUtil.getInsets(lc, isHor ? 1 : 0, defIns); final UnitValueCommon lastGap = layoutUtil.getInsets(lc, isHor ? 3 : 2, defIns); final int[][] retValues = new int[specs.length + 1][]; int wgIx = 0; for (int i = 0; i < retValues.length; i++) { final DimConstraintCommon specBefore = i > 0 ? specs[i - 1] : null; final DimConstraintCommon specAfter = i < specs.length ? specs[i] : null; // No gap if between docking components. final boolean edgeBefore = (specBefore == DOCK_DIM_CONSTRAINT || specBefore == null); final boolean edgeAfter = (specAfter == DOCK_DIM_CONSTRAINT || specAfter == null); if (edgeBefore && edgeAfter) { continue; } final BoundSizeCommon wrapGapSize = (wrapGapMap == null || isHor == lc.isFlowX() ? null : wrapGapMap.get(Integer.valueOf(wgIx++))); if (wrapGapSize == null) { final int[] gapBefore = specBefore != null ? specBefore.getRowGaps(container, null, refSize, false) : null; final int[] gapAfter = specAfter != null ? specAfter.getRowGaps(container, null, refSize, true) : null; if (edgeBefore && gapAfter == null && firstGap != null) { final int bef = firstGap.getPixels(refSize, container, null); retValues[i] = new int[] {bef, bef, bef}; } else if (edgeAfter && gapBefore == null && firstGap != null) { final int aft = lastGap.getPixels(refSize, container, null); retValues[i] = new int[] {aft, aft, aft}; } else { retValues[i] = gapAfter != gapBefore ? mergeSizes(gapAfter, gapBefore) : new int[] {defGapArr[0], defGapArr[1], defGapArr[2]}; } if (specBefore != null && specBefore.isGapAfterPush() || specAfter != null && specAfter.isGapBeforePush()) { fillInPushGaps[i] = true; } } else { if (wrapGapSize.isUnset()) { retValues[i] = new int[] {defGapArr[0], defGapArr[1], defGapArr[2]}; } else { retValues[i] = wrapGapSize.getPixelSizes(refSize, container, null); } fillInPushGaps[i] = wrapGapSize.getGapPush(); } } return retValues; } private static int[][] getGaps(final ArrayList<CompWrap> compWraps, final boolean isHor) { final int compCount = compWraps.size(); final int[][] retValues = new int[compCount + 1][]; retValues[0] = compWraps.get(0).getGaps(isHor, true); for (int i = 0; i < compCount; i++) { final int[] gap1 = compWraps.get(i).getGaps(isHor, false); final int[] gap2 = i < compCount - 1 ? compWraps.get(i + 1).getGaps(isHor, true) : null; retValues[i + 1] = mergeSizes(gap1, gap2); } return retValues; } private boolean hasDocks() { return (dockOffX > 0 || dockOffY > 0 || rowIndexes.last() > MAX_GRID || colIndexes.last() > MAX_GRID); } /** * Adjust min/pref size for columns(or rows) that has components that spans multiple columns (or rows). * * @param specs The specs for the columns or rows. Last index will be used if <code>count</code> is greater than this array's * length. * @param defPush The default grow weight if the specs does not have anyone that will grow. Comes from "push" in the CC. * @param fss * @param groupsLists */ private void adjustMinPrefForSpanningComps( final DimConstraintCommon[] specs, final Float[] defPush, final FlowSizeSpec fss, final ArrayList<LinkedDimGroup>[] groupsLists) { for (int r = groupsLists.length - 1; r >= 0; r--) { // Since 3.7.3 Iterate from end to start. Will solve some multiple spanning components hard to solve problems. final ArrayList<LinkedDimGroup> groups = groupsLists[r]; for (final LinkedDimGroup group : groups) { if (group.span == 1) { continue; } final int[] sizes = group.getMinPrefMax(); for (int s = LayoutUtilCommon.MIN; s <= LayoutUtilCommon.PREF; s++) { final int cSize = sizes[s]; if (cSize == LayoutUtilCommon.NOT_SET) { continue; } int rowSize = 0; final int sIx = (r << 1) + 1; final int len = Math.min((group.span << 1), fss.sizes.length - sIx) - 1; for (int j = sIx; j < sIx + len; j++) { final int sz = fss.sizes[j][s]; if (sz != LayoutUtilCommon.NOT_SET) { rowSize += sz; } } if (rowSize < cSize && len > 0) { int newRowSize = 0; for (int eagerness = 0; eagerness < 4 && newRowSize < cSize; eagerness++) { newRowSize = fss.expandSizes(specs, defPush, cSize, sIx, len, s, eagerness); } } } } } } /** * For one dimension divide the component wraps into logical groups. One group for component wraps that share a common * something, * line the property to layout by base line. * * @param isRows If rows, and not columns, are to be divided. * @return One <code>ArrayList<LinkedDimGroup></code> for every row/column. */ private ArrayList<LinkedDimGroup>[] divideIntoLinkedGroups(final boolean isRows) { final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final boolean fromEnd = !(isRows ? lc.isTopToBottom() : layoutUtil.isLeftToRight(lc, container)); final TreeSet<Integer> primIndexes = isRows ? rowIndexes : colIndexes; final TreeSet<Integer> secIndexes = isRows ? colIndexes : rowIndexes; final DimConstraintCommon[] primDCs = (isRows ? rowConstr : colConstr).getConstaints(); @SuppressWarnings("unchecked") final ArrayList<LinkedDimGroup>[] groupLists = new ArrayList[primIndexes.size()]; int gIx = 0; for (final int i : primIndexes) { final DimConstraintCommon dc; if (i >= -MAX_GRID && i <= MAX_GRID) { // If not dock cell dc = primDCs[i >= primDCs.length ? primDCs.length - 1 : i]; } else { dc = DOCK_DIM_CONSTRAINT; } final ArrayList<LinkedDimGroup> groupList = new ArrayList<LinkedDimGroup>(2); groupLists[gIx++] = groupList; for (final Integer ix : secIndexes) { final Cell cell = isRows ? getCell(i, ix) : getCell(ix, i); if (cell == null || cell.compWraps.size() == 0) { continue; } int span = (isRows ? cell.spany : cell.spanx); if (span > 1) { span = convertSpanToSparseGrid(i, span, primIndexes); } final boolean isPar = (cell.flowx == isRows); if ((isPar == false && cell.compWraps.size() > 1) || span > 1) { final int linkType = isPar ? LinkedDimGroup.TYPE_PARALLEL : LinkedDimGroup.TYPE_SERIAL; final LinkedDimGroup lg = new LinkedDimGroup("p," + ix, span, linkType, !isRows, fromEnd); lg.setCompWraps(cell.compWraps); groupList.add(lg); } else { for (int cwIx = 0; cwIx < cell.compWraps.size(); cwIx++) { final CompWrap cw = cell.compWraps.get(cwIx); final boolean rowBaselineAlign = (isRows && lc.isTopToBottom() && dc.getAlignOrDefault(!isRows) == MigLayoutToolkitImpl.getMigUnitValueToolkit().BASELINE_IDENTITY); // Disable baseline for bottomToTop since I can not verify it working. final boolean isBaseline = isRows && cw.isBaselineAlign(rowBaselineAlign); final String linkCtx = isBaseline ? "baseline" : null; // Find a group with same link context and put it in that group. boolean foundList = false; final int lastGl = groupList.size() - 1; for (int glIx = 0; glIx <= lastGl; glIx++) { final LinkedDimGroup group = groupList.get(glIx); if (group.linkCtx == linkCtx || linkCtx != null && linkCtx.equals(group.linkCtx)) { group.addCompWrap(cw); foundList = true; break; } } // If none found and at last add a new group. if (foundList == false) { final int linkType = isBaseline ? LinkedDimGroup.TYPE_BASELINE : LinkedDimGroup.TYPE_PARALLEL; final LinkedDimGroup lg = new LinkedDimGroup(linkCtx, 1, linkType, !isRows, fromEnd); lg.addCompWrap(cw); groupList.add(lg); } } } } } return groupLists; } /** * Spanning is specified in the uncompressed grid number. They can for instance be more than 60000 for the outer * edge dock grid cells. When the grid is compressed and indexed after only the cells that area occupied the span * is erratic. This method use the row/col indexes and corrects the span to be correct for the compressed grid. * * @param span The span un the uncompressed grid. <code>LayoutUtil.INF</code> will be interpreted to span the rest * of the column/row excluding the surrounding docking components. * @param indexes The indexes in the correct dimension. * @return The converted span. */ private static int convertSpanToSparseGrid(final int curIx, final int span, final TreeSet<Integer> indexes) { final int lastIx = curIx + span; int retSpan = 1; for (final Integer ix : indexes) { if (ix <= curIx) { continue; // We have not arrived to the correct index yet } if (ix >= lastIx) { break; } retSpan++; } return retSpan; } private boolean isCellFree(final int r, final int c, final ArrayList<int[]> occupiedRects) { if (getCell(r, c) != null) { return false; } for (final int[] rect : occupiedRects) { if (rect[0] <= c && rect[1] <= r && rect[0] + rect[2] > c && rect[1] + rect[3] > r) { return false; } } return true; } private Cell getCell(final int r, final int c) { return grid.get(Integer.valueOf((r << 16) + c)); } private void setCell(final int r, final int c, final Cell cell) { if (c < 0 || r < 0) { throw new IllegalArgumentException("Cell position cannot be negative. row: " + r + ", col: " + c); } if (c > MAX_GRID || r > MAX_GRID) { throw new IllegalArgumentException("Cell position out of bounds. Out of cells. row: " + r + ", col: " + c); } rowIndexes.add(r); colIndexes.add(c); grid.put((r << 16) + c, cell); } /** * Adds a docking cell. That cell is outside the normal cell indexes. * * @param dockInsets The current dock insets. Will be updated! * @param side top == 0, left == 1, bottom = 2, right = 3. * @param cw The compwrap to put in a cell and add. */ private void addDockingCell(final int[] dockInsets, final int side, final CompWrap cw) { final int r; final int c; int spanx = 1; int spany = 1; switch (side) { case 0: case 2: r = side == 0 ? dockInsets[0]++ : dockInsets[2]--; c = dockInsets[1]; spanx = dockInsets[3] - dockInsets[1] + 1; // The +1 is for cell 0. colIndexes.add(dockInsets[3]); // Make sure there is a receiving cell break; case 1: case 3: c = side == 1 ? dockInsets[1]++ : dockInsets[3]--; r = dockInsets[0]; spany = dockInsets[2] - dockInsets[0] + 1; // The +1 is for cell 0. rowIndexes.add(dockInsets[2]); // Make sure there is a receiving cell break; default: throw new IllegalArgumentException("Internal error 123."); } rowIndexes.add(r); colIndexes.add(c); grid.put((r << 16) + c, new Cell(cw, spanx, spany, spanx > 1)); } /** * A simple representation of a cell in the grid. Contains a number of component wraps and if they span more than one cell. */ private static final class Cell { private final int spanx; private final int spany; private final boolean flowx; private final ArrayList<CompWrap> compWraps = new ArrayList<CompWrap>(1); private boolean hasTagged = false; // If one or more components have styles and need to be checked by the component sorter private Cell(final CompWrap cw) { this(cw, 1, 1, true); } private Cell(final int spanx, final int spany, final boolean flowx) { this(null, spanx, spany, flowx); } private Cell(final CompWrap cw, final int spanx, final int spany, final boolean flowx) { if (cw != null) { compWraps.add(cw); } this.spanx = spanx; this.spany = spany; this.flowx = flowx; } } /** * A number of component wraps that share a layout "something" <b>in one dimension</b> */ private static final class LinkedDimGroup { private static final int TYPE_SERIAL = 0; private static final int TYPE_PARALLEL = 1; private static final int TYPE_BASELINE = 2; private final String linkCtx; private final int span; private final int linkType; private final boolean isHor; private final boolean fromEnd; private ArrayList<CompWrap> ldgCompWraps = new ArrayList<CompWrap>(4); private int[] sizes = null; private int lStart = 0; private int lSize = 0; // Currently mostly for debug painting private LinkedDimGroup( final String linkCtx, final int span, final int linkType, final boolean isHor, final boolean fromEnd) { this.linkCtx = linkCtx; this.span = span; this.linkType = linkType; this.isHor = isHor; this.fromEnd = fromEnd; } private void addCompWrap(final CompWrap cw) { ldgCompWraps.add(cw); sizes = null; } private void setCompWraps(final ArrayList<CompWrap> cws) { if (ldgCompWraps != cws) { ldgCompWraps = cws; sizes = null; } } private void layout(final DimConstraintCommon dc, final int start, final int size, final int spanCount) { lStart = start; lSize = size; if (ldgCompWraps.size() == 0) { return; } final IContainerWrapperCommon parent = ldgCompWraps.get(0).comp.getParent(); if (linkType == TYPE_PARALLEL) { layoutParallel(parent, ldgCompWraps, dc, start, size, isHor, fromEnd); } else if (linkType == TYPE_BASELINE) { layoutBaseline(parent, ldgCompWraps, dc, start, size, LayoutUtilCommon.PREF, spanCount); } else { layoutSerial(parent, ldgCompWraps, dc, start, size, isHor, spanCount, fromEnd); } } /** * Returns the min/pref/max sizes for this cell. Returned array <b>must not be altered</b> * * @return A shared min/pref/max array of sizes. Always of length 3 and never <code>null</code>. Will always be of type * STATIC and PIXEL. */ private int[] getMinPrefMax() { if (sizes == null && ldgCompWraps.size() > 0) { sizes = new int[3]; for (int sType = LayoutUtilCommon.MIN; sType <= LayoutUtilCommon.PREF; sType++) { if (linkType == TYPE_PARALLEL) { sizes[sType] = getTotalSizeParallel(ldgCompWraps, sType, isHor); } else if (linkType == TYPE_BASELINE) { final int[] aboveBelow = getBaselineAboveBelow(ldgCompWraps, sType, false); sizes[sType] = aboveBelow[0] + aboveBelow[1]; } else { sizes[sType] = getTotalSizeSerial(ldgCompWraps, sType, isHor); } } sizes[LayoutUtilCommon.MAX] = LayoutUtilCommon.INF; } return sizes; } } /** * Wraps a {@link java.awt.Component} together with its constraint. Caches a lot of information about the component so * for instance not the preferred size has to be calculated more than once. */ private static final class CompWrap { private final IComponentWrapperCommon comp; private final CCCommon cc; private final UnitValueCommon[] pos; private int[][] gaps; // [top,left(actually before),bottom,right(actually after)][min,pref,max] private final int[] horSizes = new int[3]; private final int[] verSizes = new int[3]; private int x = LayoutUtilCommon.NOT_SET; private int y = LayoutUtilCommon.NOT_SET; private int w = LayoutUtilCommon.NOT_SET; private int h = LayoutUtilCommon.NOT_SET; private int forcedPushGaps = 0; // 1 == before, 2 = after. Bitwise. private CompWrap( final IComponentWrapperCommon c, final CCCommon cc, final int eHideMode, final UnitValueCommon[] pos, final BoundSizeCommon[] callbackSz) { this.comp = c; this.cc = cc; this.pos = pos; if (eHideMode <= 0) { final BoundSizeCommon hBS = (callbackSz != null && callbackSz[0] != null) ? callbackSz[0] : cc.getHorizontal().getSize(); final BoundSizeCommon vBS = (callbackSz != null && callbackSz[1] != null) ? callbackSz[1] : cc.getVertical().getSize(); int wHint = -1; int hHint = -1; // Added for v3.7 if (comp.getWidth() > 0 && comp.getHeight() > 0) { hHint = comp.getHeight(); wHint = comp.getWidth(); } for (int i = LayoutUtilCommon.MIN; i <= LayoutUtilCommon.MAX; i++) { horSizes[i] = getSize(hBS, i, true, hHint); verSizes[i] = getSize(vBS, i, false, wHint > 0 ? wHint : horSizes[i]); } correctMinMax(horSizes); correctMinMax(verSizes); } if (eHideMode > 1) { gaps = new int[4][]; for (int i = 0; i < gaps.length; i++) { gaps[i] = new int[3]; } } } private int getSize(final BoundSizeCommon uvs, final int sizeType, final boolean isHor, final int sizeHint) { if (uvs == null || uvs.getSize(sizeType) == null) { switch (sizeType) { case LayoutUtilCommon.MIN: return isHor ? comp.getMinimumWidth(sizeHint) : comp.getMinimumHeight(sizeHint); case LayoutUtilCommon.PREF: return isHor ? comp.getPreferredWidth(sizeHint) : comp.getPreferredHeight(sizeHint); default: return isHor ? comp.getMaximumWidth(sizeHint) : comp.getMaximumHeight(sizeHint); } } final IContainerWrapperCommon par = comp.getParent(); return uvs.getSize(sizeType).getPixels(isHor ? par.getWidth() : par.getHeight(), par, comp); } private void calcGaps( final IComponentWrapperCommon before, final CCCommon befCC, final IComponentWrapperCommon after, final CCCommon aftCC, final String tag, final boolean flowX, final boolean isLTR) { final IContainerWrapperCommon par = comp.getParent(); final int parW = par.getWidth(); final int parH = par.getHeight(); final BoundSizeCommon befGap = before != null ? (flowX ? befCC.getHorizontal() : befCC.getVertical()).getGapAfter() : null; final BoundSizeCommon aftGap = after != null ? (flowX ? aftCC.getHorizontal() : aftCC.getVertical()).getGapBefore() : null; mergeGapSizes( cc.getVertical().getComponentGaps(par, comp, befGap, (flowX ? null : before), tag, parH, 0, isLTR), false, true); mergeGapSizes( cc.getHorizontal().getComponentGaps(par, comp, befGap, (flowX ? before : null), tag, parW, 1, isLTR), true, true); mergeGapSizes( cc.getVertical().getComponentGaps(par, comp, aftGap, (flowX ? null : after), tag, parH, 2, isLTR), false, false); mergeGapSizes( cc.getHorizontal().getComponentGaps(par, comp, aftGap, (flowX ? after : null), tag, parW, 3, isLTR), true, false); } private void setDimBounds(final int start, final int size, final boolean isHor) { if (isHor) { x = start; w = size; } else { y = start; h = size; } } private boolean isPushGap(final boolean isHor, final boolean isBefore) { if (isHor && ((isBefore ? 1 : 2) & forcedPushGaps) != 0) { return true; // Forced } final DimConstraintCommon dc = cc.getDimConstraint(isHor); final BoundSizeCommon s = isBefore ? dc.getGapBefore() : dc.getGapAfter(); return s != null && s.getGapPush(); } /** * @return If the preferred size have changed because of the new bounds. */ private boolean transferBounds(final boolean checkPrefChange) { comp.setBounds(x, y, w, h); if (checkPrefChange && w != horSizes[LayoutUtilCommon.PREF]) { final BoundSizeCommon vSz = cc.getVertical().getSize(); if (vSz.getPreferred() == null) { if (comp.getPreferredHeight(-1) != verSizes[LayoutUtilCommon.PREF]) { return true; } } } return false; } private void setSizes(final int[] sizes, final boolean isHor) { if (sizes == null) { return; } final int[] s = isHor ? horSizes : verSizes; s[LayoutUtilCommon.MIN] = sizes[LayoutUtilCommon.MIN]; s[LayoutUtilCommon.PREF] = sizes[LayoutUtilCommon.PREF]; s[LayoutUtilCommon.MAX] = sizes[LayoutUtilCommon.MAX]; } private void setGaps(final int[] minPrefMax, final int ix) { if (gaps == null) { gaps = new int[][] {null, null, null, null}; } gaps[ix] = minPrefMax; } private void mergeGapSizes(final int[] sizes, final boolean isHor, final boolean isTL) { if (gaps == null) { gaps = new int[][] {null, null, null, null}; } if (sizes == null) { return; } final int gapIX = getGapIx(isHor, isTL); int[] oldGaps = gaps[gapIX]; if (oldGaps == null) { oldGaps = new int[] {0, 0, LayoutUtilCommon.INF}; gaps[gapIX] = oldGaps; } oldGaps[LayoutUtilCommon.MIN] = Math.max(sizes[LayoutUtilCommon.MIN], oldGaps[LayoutUtilCommon.MIN]); oldGaps[LayoutUtilCommon.PREF] = Math.max(sizes[LayoutUtilCommon.PREF], oldGaps[LayoutUtilCommon.PREF]); oldGaps[LayoutUtilCommon.MAX] = Math.min(sizes[LayoutUtilCommon.MAX], oldGaps[LayoutUtilCommon.MAX]); } private int getGapIx(final boolean isHor, final boolean isTL) { return isHor ? (isTL ? 1 : 3) : (isTL ? 0 : 2); } private int getSizeInclGaps(final int sizeType, final boolean isHor) { return filter(sizeType, getGapBefore(sizeType, isHor) + getSize(sizeType, isHor) + getGapAfter(sizeType, isHor)); } private int getSize(final int sizeType, final boolean isHor) { return filter(sizeType, isHor ? horSizes[sizeType] : verSizes[sizeType]); } private int getGapBefore(final int sizeType, final boolean isHor) { final int[] localGaps = getGaps(isHor, true); return localGaps != null ? filter(sizeType, localGaps[sizeType]) : 0; } private int getGapAfter(final int sizeType, final boolean isHor) { final int[] localGaps = getGaps(isHor, false); return localGaps != null ? filter(sizeType, localGaps[sizeType]) : 0; } private int[] getGaps(final boolean isHor, final boolean isTL) { return gaps[getGapIx(isHor, isTL)]; } private int filter(final int sizeType, final int size) { if (size == LayoutUtilCommon.NOT_SET) { return sizeType != LayoutUtilCommon.MAX ? 0 : LayoutUtilCommon.INF; } return constrainSize(size); } private boolean isBaselineAlign(final boolean defValue) { final Float g = cc.getVertical().getGrow(); if (g != null && g != 0) { return false; } final UnitValueCommon al = cc.getVertical().getAlign(); return (al != null ? al == MigLayoutToolkitImpl.getMigUnitValueToolkit().BASELINE_IDENTITY : defValue) && comp.hasBaseline(); } private int getBaseline(final int sizeType) { return comp.getBaseline(getSize(sizeType, true), getSize(sizeType, false)); } } //*************************************************************************************** //* Helper Methods //*************************************************************************************** private static void layoutBaseline( final IContainerWrapperCommon parent, final ArrayList<CompWrap> compWraps, final DimConstraintCommon dc, final int start, final int size, final int sizeType, final int spanCount) { final int[] aboveBelow = getBaselineAboveBelow(compWraps, sizeType, true); final int blRowSize = aboveBelow[0] + aboveBelow[1]; final CCCommon cc = compWraps.get(0).cc; // Align for the whole baseline component array UnitValueCommon align = cc.getVertical().getAlign(); if (spanCount == 1 && align == null) { align = dc.getAlignOrDefault(false); } if (align == MigLayoutToolkitImpl.getMigUnitValueToolkit().BASELINE_IDENTITY) { align = MigLayoutToolkitImpl.getMigUnitValueToolkit().CENTER; } final int offset = start + aboveBelow[0] + (align != null ? Math.max(0, align.getPixels(size - blRowSize, parent, null)) : 0); final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); cw.y += offset; if (cw.y + cw.h > start + size) { cw.h = start + size - cw.y; } } } private static void layoutSerial( final IContainerWrapperCommon parent, final ArrayList<CompWrap> compWraps, final DimConstraintCommon dc, final int start, final int size, final boolean isHor, final int spanCount, final boolean fromEnd) { final FlowSizeSpec fss = mergeSizesGapsAndResConstrs( getComponentResizeConstraints(compWraps, isHor), getComponentGapPush(compWraps, isHor), getComponentSizes(compWraps, isHor), getGaps(compWraps, isHor)); final Float[] pushW = dc.isFill() ? GROW_100 : null; final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final int[] sizes = layoutUtil.calculateSerial(fss.sizes, fss.resConstsInclGaps, pushW, LayoutUtilCommon.PREF, size); setCompWrapBounds(parent, sizes, compWraps, dc.getAlignOrDefault(isHor), start, size, isHor, fromEnd); } private static void setCompWrapBounds( final IContainerWrapperCommon parent, final int[] allSizes, final ArrayList<CompWrap> compWraps, final UnitValueCommon rowAlign, final int start, final int size, final boolean isHor, final boolean fromEnd) { final int totSize = LayoutUtilCommon.sum(allSizes); final CCCommon cc = compWraps.get(0).cc; final UnitValueCommon align = correctAlign(cc, rowAlign, isHor, fromEnd); int cSt = start; final int slack = size - totSize; if (slack > 0 && align != null) { final int al = Math.min(slack, Math.max(0, align.getPixels(slack, parent, null))); cSt += (fromEnd ? -al : al); } int bIx = 0; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); if (fromEnd) { cSt -= allSizes[bIx++]; cw.setDimBounds(cSt - allSizes[bIx], allSizes[bIx], isHor); cSt -= allSizes[bIx++]; } else { cSt += allSizes[bIx++]; cw.setDimBounds(cSt, allSizes[bIx], isHor); cSt += allSizes[bIx++]; } } } private static void layoutParallel( final IContainerWrapperCommon parent, final ArrayList<CompWrap> compWraps, final DimConstraintCommon dc, final int start, final int size, final boolean isHor, final boolean fromEnd) { final int[][] sizes = new int[compWraps.size()][]; // [compIx][gapBef,compSize,gapAft] for (int i = 0; i < sizes.length; i++) { final CompWrap cw = compWraps.get(i); final DimConstraintCommon cDc = cw.cc.getDimConstraint(isHor); final ResizeConstraintCommon[] resConstr = new ResizeConstraintCommon[] { cw.isPushGap(isHor, true) ? GAP_RC_CONST_PUSH : GAP_RC_CONST, cDc.resize, cw.isPushGap(isHor, false) ? GAP_RC_CONST_PUSH : GAP_RC_CONST,}; final int[][] sz = new int[][] { cw.getGaps(isHor, true), (isHor ? cw.horSizes : cw.verSizes), cw.getGaps(isHor, false)}; final Float[] pushW = dc.isFill() ? GROW_100 : null; final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); sizes[i] = layoutUtil.calculateSerial(sz, resConstr, pushW, LayoutUtilCommon.PREF, size); } final UnitValueCommon rowAlign = dc.getAlignOrDefault(isHor); setCompWrapBounds(parent, sizes, compWraps, rowAlign, start, size, isHor, fromEnd); } private static void setCompWrapBounds( final IContainerWrapperCommon parent, final int[][] sizes, final ArrayList<CompWrap> compWraps, final UnitValueCommon rowAlign, final int start, final int size, final boolean isHor, final boolean fromEnd) { for (int i = 0; i < sizes.length; i++) { final CompWrap cw = compWraps.get(i); final UnitValueCommon align = correctAlign(cw.cc, rowAlign, isHor, fromEnd); final int[] cSizes = sizes[i]; final int gapBef = cSizes[0]; final int cSize = cSizes[1]; // No Math.min(size, cSizes[1]) here! final int gapAft = cSizes[2]; int cSt = fromEnd ? start - gapBef : start + gapBef; final int slack = size - cSize - gapBef - gapAft; if (slack > 0 && align != null) { final int al = Math.min(slack, Math.max(0, align.getPixels(slack, parent, null))); cSt += (fromEnd ? -al : al); } cw.setDimBounds(fromEnd ? cSt - cSize : cSt, cSize, isHor); } } private static UnitValueCommon correctAlign( final CCCommon cc, final UnitValueCommon rowAlign, final boolean isHor, final boolean fromEnd) { UnitValueCommon align = (isHor ? cc.getHorizontal() : cc.getVertical()).getAlign(); if (align == null) { align = rowAlign; } if (align == MigLayoutToolkitImpl.getMigUnitValueToolkit().BASELINE_IDENTITY) { align = MigLayoutToolkitImpl.getMigUnitValueToolkit().CENTER; } if (fromEnd) { if (align == MigLayoutToolkitImpl.getMigUnitValueToolkit().LEFT) { align = MigLayoutToolkitImpl.getMigUnitValueToolkit().RIGHT; } else if (align == MigLayoutToolkitImpl.getMigUnitValueToolkit().RIGHT) { align = MigLayoutToolkitImpl.getMigUnitValueToolkit().LEFT; } } return align; } private static int[] getBaselineAboveBelow( final ArrayList<CompWrap> compWraps, final int sType, final boolean centerBaseline) { int maxAbove = Short.MIN_VALUE; int maxBelow = Short.MIN_VALUE; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); final int height = cw.getSize(sType, false); if (height >= LayoutUtilCommon.INF) { return new int[] {LayoutUtilCommon.INF / 2, LayoutUtilCommon.INF / 2}; } final int baseline = cw.getBaseline(sType); final int above = baseline + cw.getGapBefore(sType, false); maxAbove = Math.max(above, maxAbove); maxBelow = Math.max(height - baseline + cw.getGapAfter(sType, false), maxBelow); if (centerBaseline) { cw.setDimBounds(-baseline, height, false); } } return new int[] {maxAbove, maxBelow}; } private static int getTotalSizeParallel(final ArrayList<CompWrap> compWraps, final int sType, final boolean isHor) { int size = sType == LayoutUtilCommon.MAX ? LayoutUtilCommon.INF : 0; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap cw = compWraps.get(i); final int cwSize = cw.getSizeInclGaps(sType, isHor); if (cwSize >= LayoutUtilCommon.INF) { return LayoutUtilCommon.INF; } if (sType == LayoutUtilCommon.MAX ? cwSize < size : cwSize > size) { size = cwSize; } } return constrainSize(size); } private static int getTotalSizeSerial(final ArrayList<CompWrap> compWraps, final int sType, final boolean isHor) { int totSize = 0; int lastGapAfter = 0; final int iSz = compWraps.size(); for (int i = 0; i < iSz; i++) { final CompWrap wrap = compWraps.get(i); final int gapBef = wrap.getGapBefore(sType, isHor); if (gapBef > lastGapAfter) { totSize += gapBef - lastGapAfter; } totSize += wrap.getSize(sType, isHor); lastGapAfter = wrap.getGapAfter(sType, isHor); totSize += lastGapAfter; if (totSize >= LayoutUtilCommon.INF) { return LayoutUtilCommon.INF; } } return constrainSize(totSize); } private static int getTotalGroupsSizeParallel( final ArrayList<LinkedDimGroup> groups, final int sType, final boolean countSpanning) { int size = sType == LayoutUtilCommon.MAX ? LayoutUtilCommon.INF : 0; final int iSz = groups.size(); for (int i = 0; i < iSz; i++) { final LinkedDimGroup group = groups.get(i); if (countSpanning || group.span == 1) { final int grpSize = group.getMinPrefMax()[sType]; if (grpSize >= LayoutUtilCommon.INF) { return LayoutUtilCommon.INF; } if (sType == LayoutUtilCommon.MAX ? grpSize < size : grpSize > size) { size = grpSize; } } } return constrainSize(size); } /** * @param compWraps * @param isHor * @return Might contain LayoutUtil.NOT_SET */ private static int[][] getComponentSizes(final ArrayList<CompWrap> compWraps, final boolean isHor) { final int[][] compSizes = new int[compWraps.size()][]; for (int i = 0; i < compSizes.length; i++) { final CompWrap cw = compWraps.get(i); compSizes[i] = isHor ? cw.horSizes : cw.verSizes; } return compSizes; } /** * Merges sizes and gaps together with Resize Constraints. For gaps {@link #GAP_RC_CONST} is used. * * @param resConstr One resize constriant for every row/component. Can be lesser in length and the last element should be used * for missing elements. * @param gapPush If the corresponding gap should be considered pushing and thus want to take free space if left over. Should * be one more than resConstrs! * @param minPrefMaxSizes The sizes (min/pref/max) for every row/component. * @param gapSizes The gaps before and after each row/component packed in one double sized array. * @return A holder for the merged values. */ private static FlowSizeSpec mergeSizesGapsAndResConstrs( final ResizeConstraintCommon[] resConstr, final boolean[] gapPush, final int[][] minPrefMaxSizes, final int[][] gapSizes) { final int[][] sizes = new int[(minPrefMaxSizes.length << 1) + 1][]; // Make room for gaps around. final ResizeConstraintCommon[] resConstsInclGaps = new ResizeConstraintCommon[sizes.length]; sizes[0] = gapSizes[0]; int crIx = 1; for (int i = 0; i < minPrefMaxSizes.length; i++, crIx += 2) { // Component bounds and constraints resConstsInclGaps[crIx] = resConstr[i]; sizes[crIx] = minPrefMaxSizes[i]; sizes[crIx + 1] = gapSizes[i + 1]; if (sizes[crIx - 1] != null) { resConstsInclGaps[crIx - 1] = gapPush[i < gapPush.length ? i : gapPush.length - 1] ? GAP_RC_CONST_PUSH : GAP_RC_CONST; } if (i == (minPrefMaxSizes.length - 1) && sizes[crIx + 1] != null) { resConstsInclGaps[crIx + 1] = gapPush[(i + 1) < gapPush.length ? (i + 1) : gapPush.length - 1] ? GAP_RC_CONST_PUSH : GAP_RC_CONST; } } // Check for null and set it to 0, 0, 0. for (int i = 0; i < sizes.length; i++) { if (sizes[i] == null) { sizes[i] = new int[3]; } } return new FlowSizeSpec(sizes, resConstsInclGaps); } private static int[] mergeSizes(final int[] oldValues, final int[] newValues) { if (oldValues == null) { return newValues; } if (newValues == null) { return oldValues; } final int[] ret = new int[oldValues.length]; for (int i = 0; i < ret.length; i++) { ret[i] = mergeSizes(oldValues[i], newValues[i], true); } return ret; } private static int mergeSizes(final int oldValue, final int newValue, final boolean toMax) { if (oldValue == LayoutUtilCommon.NOT_SET || oldValue == newValue) { return newValue; } if (newValue == LayoutUtilCommon.NOT_SET) { return oldValue; } return toMax != oldValue > newValue ? newValue : oldValue; } private static int constrainSize(final int s) { return s > 0 ? (s < LayoutUtilCommon.INF ? s : LayoutUtilCommon.INF) : 0; } private static void correctMinMax(final int[] s) { if (s[LayoutUtilCommon.MIN] > s[LayoutUtilCommon.MAX]) { s[LayoutUtilCommon.MIN] = s[LayoutUtilCommon.MAX]; // Since MAX is almost always explicitly set use that } if (s[LayoutUtilCommon.PREF] < s[LayoutUtilCommon.MIN]) { s[LayoutUtilCommon.PREF] = s[LayoutUtilCommon.MIN]; } if (s[LayoutUtilCommon.PREF] > s[LayoutUtilCommon.MAX]) { s[LayoutUtilCommon.PREF] = s[LayoutUtilCommon.MAX]; } } private static final class FlowSizeSpec { private final int[][] sizes; // [row/col index][min, pref, max] private final ResizeConstraintCommon[] resConstsInclGaps; // [row/col index] private FlowSizeSpec(final int[][] sizes, final ResizeConstraintCommon[] resConstsInclGaps) { this.sizes = sizes; this.resConstsInclGaps = resConstsInclGaps; } /** * @param specs The specs for the columns or rows. Last index will be used of <code>fromIx + len</code> is greater than * this array's length. * @param targetSize The size to try to meet. * @param defGrow The default grow weight if the specs does not have anyone that will grow. Comes from "push" in the CC. * @param fromIx * @param len * @param sizeType * @param eagerness How eager the algorithm should be to try to expand the sizes. * <ul> * <li>0 - Grow only rows/columns which have the <code>sizeType</code> set to be the containing components AND * which has a grow weight > 0. * <li>1 - Grow only rows/columns which have the <code>sizeType</code> set to be the containing components AND * which has a grow weight > 0 OR unspecified. * <li>2 - Grow all rows/columns that have a grow weight > 0. * <li>3 - Grow all rows/columns that have a grow weight > 0 OR unspecified. * </ul> * @return The new size. */ private int expandSizes( final DimConstraintCommon[] specs, final Float[] defGrow, final int targetSize, final int fromIx, final int len, final int sizeType, final int eagerness) { final LayoutUtilCommon layoutUtil = MigLayoutToolkitImpl.getMigLayoutUtil(); final ResizeConstraintCommon[] resConstr = new ResizeConstraintCommon[len]; final int[][] sizesToExpand = new int[len][]; for (int i = 0; i < len; i++) { final int[] minPrefMax = sizes[i + fromIx]; sizesToExpand[i] = new int[] { minPrefMax[sizeType], minPrefMax[LayoutUtilCommon.PREF], minPrefMax[LayoutUtilCommon.MAX]}; if (eagerness <= 1 && i % 2 == 0) { // (i % 2 == 0) means only odd indexes, which is only rows/col indexes and not gaps. final int cIx = (i + fromIx - 1) >> 1; final DimConstraintCommon spec = (DimConstraintCommon) layoutUtil.getIndexSafe(specs, cIx); final BoundSizeCommon sz = spec.getSize(); if ((sizeType == LayoutUtilCommon.MIN && sz.getMin() != null && sz.getMin().getUnit() != UnitValueToolkitCommon.MIN_SIZE) || (sizeType == LayoutUtilCommon.PREF && sz.getPreferred() != null && sz.getPreferred().getUnit() != UnitValueToolkitCommon.PREF_SIZE)) { continue; } } resConstr[i] = (ResizeConstraintCommon) layoutUtil.getIndexSafe(resConstsInclGaps, i + fromIx); } final Float[] growW = (eagerness == 1 || eagerness == 3) ? extractSubArray(specs, defGrow, fromIx, len) : null; final int[] newSizes = layoutUtil.calculateSerial(sizesToExpand, resConstr, growW, LayoutUtilCommon.PREF, targetSize); int newSize = 0; for (int i = 0; i < len; i++) { final int s = newSizes[i]; sizes[i + fromIx][sizeType] = s; newSize += s; } return newSize; } } private static Float[] extractSubArray(final DimConstraintCommon[] specs, final Float[] arr, final int ix, final int len) { if (arr == null || arr.length < ix + len) { final Float[] growLastArr = new Float[len]; // Handle a group where some rows (first one/few and/or last one/few) are docks. for (int i = ix + len - 1; i >= 0; i -= 2) { final int specIx = (i >> 1); if (specs[specIx] != DOCK_DIM_CONSTRAINT) { growLastArr[i - ix] = ResizeConstraintCommon.WEIGHT_100; return growLastArr; } } return growLastArr; } final Float[] newArr = new Float[len]; for (int i = 0; i < len; i++) { newArr[i] = arr[ix + i]; } return newArr; } @SuppressWarnings({"unchecked", "rawtypes"}) private static synchronized void putSizesAndIndexes( final Object parComp, final int[] sizes, final int[] ixArr, final boolean isRows) { if (parentRowColSizesMap == null) { parentRowColSizesMap = new WeakHashMap[] {new WeakHashMap(4), new WeakHashMap(4)}; } parentRowColSizesMap[isRows ? 0 : 1].put(parComp, new int[][] {ixArr, sizes}); } static synchronized int[][] getSizesAndIndexes(final Object parComp, final boolean isRows) { if (parentRowColSizesMap == null) { return null; } return (int[][]) parentRowColSizesMap[isRows ? 0 : 1].get(parComp); } private static synchronized void saveGrid(final IComponentWrapperCommon parComp, final LinkedHashMap<Integer, Cell> grid) { if (parentGridPosMap == null) { // Lazy since only if designing in IDEs parentGridPosMap = new WeakHashMap<Object, LinkedHashMap<Integer, Cell>>(); } parentGridPosMap.put(parComp.getComponent(), grid); } static synchronized HashMap<Object, int[]> getGridPositions(final Object parComp) { if (parentGridPosMap == null) { return null; } final LinkedHashMap<Integer, Cell> grid = parentGridPosMap.get(parComp); if (grid == null) { return null; } final HashMap<Object, int[]> retMap = new HashMap<Object, int[]>(); for (final Map.Entry<Integer, Cell> e : grid.entrySet()) { final Cell cell = e.getValue(); final Integer xyInt = e.getKey(); if (xyInt != null) { final int x = xyInt & 0x0000ffff; final int y = xyInt >> 16; for (final CompWrap cw : cell.compWraps) { retMap.put(cw.comp.getComponent(), new int[] {x, y, cell.spanx, cell.spany}); } } } return retMap; } }