int optimizeGaps()

in java/form/src/org/netbeans/modules/form/layoutdesign/LayoutOperations.java [1059:1626]


    int optimizeGaps(LayoutInterval group, int dimension) {
        for (int i=0; i < group.getSubIntervalCount(); i++) {
            LayoutInterval li = group.getSubInterval(i);
            if (li.isEmptySpace() && group.getSubIntervalCount() > 1) {
                // remove container supporting gap
                layoutModel.removeInterval(group, i);
                i--;
            }
        }
        if (group.getSubIntervalCount() <= 1) {
            return -1;
        }

        // 1) Determine which intervals have ending gaps for optimization, what's
        // their alignment and resizability, and whether whole group should be
        // processed or just a part (subgroup).
        boolean anyAlignedLeading = false; // if false the group is open at leading edge
        boolean anyAlignedTrailing = false; // if false the group is open at trailing edge
        boolean contentResizing = false;
        IntervalSet processLeading = null;
        IntervalSet processTrailing = null;
        boolean subGroupLeading = false;
        boolean subGroupTrailing = false;

        {
        // 1a) Analyze where the ending gaps are and how aligned.
        IntervalSet[] alignedGaps = new IntervalSet[] { new IntervalSet(), new IntervalSet() };
        IntervalSet[] alignedNoGaps = new IntervalSet[] { new IntervalSet(), new IntervalSet() };
        IntervalSet[] unalignedFixedGaps = new IntervalSet[] { new IntervalSet(), new IntervalSet() };
        IntervalSet[] unalignedResGaps = new IntervalSet[] { new IntervalSet(), new IntervalSet() };
        IntervalSet[] unalignedNoGaps = new IntervalSet[] { new IntervalSet(), new IntervalSet() };

        for (int i=0; i < group.getSubIntervalCount(); i++) {
            LayoutInterval li = group.getSubInterval(i);

            boolean leadAlign = false;
            boolean trailAlign = false;
            LayoutInterval leadingGap = null;
            LayoutInterval trailingGap = null;
            boolean leadGapRes = false;
            boolean trailGapRes = false;
            boolean contentRes = false;
            boolean noResizing = false;

            if (li.isSequential()) {
                // find out effective alignment of the sequence content without border gaps
                for (int j=0; j < li.getSubIntervalCount(); j++) {
                    LayoutInterval sub = li.getSubInterval(j);
                    if (j == 0 && sub.isEmptySpace()) {
                        leadingGap = sub;
                        leadGapRes = LayoutInterval.wantResize(sub);
                    } else if (j+1 == li.getSubIntervalCount() && sub.isEmptySpace()) {
                        trailingGap = sub;
                        trailGapRes = LayoutInterval.wantResize(sub);
                    } else if (!contentRes && LayoutInterval.wantResize(sub)) {
                        contentRes = true;
                    }
                }
                if (!contentRes) {
                    if (leadGapRes || trailGapRes) {
                        leadAlign = trailGapRes && !leadGapRes;
                        trailAlign = leadGapRes && !trailGapRes;
                    } else {
                        noResizing = true;
                    }
                }
            } else if (LayoutInterval.wantResize(li)) {
                contentRes = true;
            } else {
                noResizing = true;
            }
            if (contentRes) {
                leadAlign = trailAlign = true;
            } else if (noResizing) {
                int alignment = li.getAlignment();
                leadAlign = (alignment == LEADING);
                trailAlign = (alignment == TRAILING);
            }

            if (leadingGap != null) {
                if (leadAlign) {
                    alignedGaps[LEADING].add(li, !noResizing);
                } else if (leadGapRes) {
                    unalignedResGaps[LEADING].add(li, true);
                } else {
                    unalignedFixedGaps[LEADING].add(li, false);
                }
            } else if (!LayoutUtils.hasSideGaps(li, LEADING, true)) {
                if (leadAlign) {
                    alignedNoGaps[LEADING].add(li, !noResizing);
                } else {
                    unalignedNoGaps[LEADING].add(li, false);
                }
            }
            if (trailingGap != null) {
                if (trailAlign) {
                    alignedGaps[TRAILING].add(li, !noResizing);
                } else if (trailGapRes) {
                    unalignedResGaps[TRAILING].add(li, true);
                } else {
                    unalignedFixedGaps[TRAILING].add(li, false);
                }
            } else if (!LayoutUtils.hasSideGaps(li, TRAILING, true)) {
                if (trailAlign) {
                    alignedNoGaps[TRAILING].add(li, !noResizing);
                } else {
                    unalignedNoGaps[TRAILING].add(li, false);
                }
            }
        }

        // 1b) Find out what gaps to optimize on each side of the group.
        IntervalSet[] alignedVariants = countAlignedVariants(alignedGaps, unalignedFixedGaps, unalignedResGaps);
        IntervalSet[] unalignedVariants = countUnalignedVariants(unalignedFixedGaps, unalignedResGaps, unalignedNoGaps, dimension);
        IntervalSet[] leadingVariants = new IntervalSet[] { alignedVariants[LEADING], unalignedVariants[LEADING] };
        IntervalSet[] trailingVariants = new IntervalSet[] { alignedVariants[TRAILING], unalignedVariants[TRAILING] };
        IntervalSet bestLeading = null;
        IntervalSet bestTrailing = null;

        for (int i=0; i < leadingVariants.length; i++) {
            IntervalSet iSet = leadingVariants[i];
            if (bestLeading == null || iSet.count() > bestLeading.count()) {
                bestLeading = iSet;
            }
        }
        for (int i=0; i < trailingVariants.length; i++) {
            IntervalSet iSet = trailingVariants[i];
            if (bestTrailing == null || iSet.count() > bestTrailing.count()) {
                bestTrailing = iSet;
            }
        }
        if (bestLeading.count() < group.getSubIntervalCount()
                && bestTrailing.count() < group.getSubIntervalCount()) {
            // can't optimize everything on both sides, so check combinations and
            // look for a suitable subgroup
            IntervalSet bestCombine = null;
            for (int i=0; i < leadingVariants.length; i++) {
                IntervalSet lSet = leadingVariants[i];
                for (int j=0; j < trailingVariants.length; j++) {
                    IntervalSet tSet = trailingVariants[j];
                    IntervalSet comSet = new IntervalSet();
                    for (int ii=0; ii < group.getSubIntervalCount(); ii++) {
                        LayoutInterval li = group.getSubInterval(ii);
                        if (lSet.contains(li) && tSet.contains(li)) {
                            comSet.add(li, LayoutInterval.wantResize(li));
                        }
                    }
                    if (bestCombine == null || comSet.count() > bestCombine.count()) {
                        bestCombine = comSet;
                    }
                }
            }
            if (bestCombine != null) {
                IntervalSet bestSingle = bestLeading.count() > bestTrailing.count()
                                         ? bestLeading : bestTrailing;
                if (bestSingle.count() - bestCombine.count() >= bestCombine.count()) {
                    // subgroup for one side
                    if (bestSingle == bestLeading) {
                        bestTrailing.clear();
                    } else {
                        bestLeading.clear();
                    }
                } else { // subgroup for both sides
                    bestLeading = bestCombine;
                    bestTrailing = bestCombine;
                }
            }
        }

        processLeading = bestLeading;
        if (processLeading.count() < 2) {
            processLeading.clear();
        }
        processTrailing = bestTrailing;
        if (processTrailing.count() < 2) {
            processTrailing.clear();
        }
        subGroupLeading = processLeading.count() < group.getSubIntervalCount();
        subGroupTrailing = processTrailing.count() < group.getSubIntervalCount();

        if (processLeading.count() > 0 || processTrailing.count() > 0) {
            // now when knowing which intervals are relevant for optimization,
            // determine their alignment and resizability
            for (int i=0; i < group.getSubIntervalCount(); i++) {
                LayoutInterval li = group.getSubInterval(i);
                boolean isL = processLeading.contains(li);
                boolean isT = processTrailing.contains(li);
                if (!isL && !isT) {
                    continue;
                }

                boolean leadAlign = false;
                boolean trailAlign = false;
                boolean contentRes = false;
                boolean noResizing = false;

                if (li.isSequential()) {
                    boolean leadGapRes = false;
                    boolean trailGapRes = false;
                    for (int j=0; j < li.getSubIntervalCount(); j++) {
                        LayoutInterval sub = li.getSubInterval(j);
                        if (j == 0 && isL && sub.isEmptySpace()) {
                            leadGapRes = LayoutInterval.wantResize(sub);
                        } else if (j+1 == li.getSubIntervalCount() && isT && sub.isEmptySpace()) {
                            trailGapRes = LayoutInterval.wantResize(sub);
                        } else if (!contentRes && LayoutInterval.wantResize(sub)) {
                            contentRes = true;
                        }
                    }
                    if (!contentRes) {
                        if (leadGapRes || trailGapRes) {
                            leadAlign = trailGapRes && !leadGapRes;
                            trailAlign = leadGapRes && !trailGapRes;
                        } else {
                            noResizing = true;
                        }
                    }
                } else if (LayoutInterval.wantResize(li)) {
                    contentRes = true;
                } else {
                    noResizing = true;
                }
                if (contentRes) {
                    leadAlign = trailAlign = true;
                } else if (noResizing) {
                    int alignment = li.getAlignment();
                    leadAlign = (alignment == LEADING);
                    trailAlign = (alignment == TRAILING);
                }

                if (leadAlign && isL) {
                    anyAlignedLeading = true;
                }
                if (trailAlign && isT) {
                    anyAlignedTrailing = true;
                }
                if (contentRes) {
                    contentResizing = true;
                }
            }
        } else {
            contentResizing = LayoutInterval.wantResize(group);
        }
        }

        // 2) Remove gaps where needed (to be substituted, or if just invalid).
        boolean defaultLeadingPadding = false;
        boolean defaultTrailingPadding = false;
        PaddingType leadingPadding = null;
        PaddingType trailingPadding = null;
        boolean effectiveExplicitGapLeading = false;
        boolean effectiveExplicitGapTrailing = false;
        boolean resizingGapLeading = false;
        boolean resizingGapTrailing = false;
        LayoutInterval zeroGapLeading = null;
        LayoutInterval zeroGapTrailing = null;
        boolean validLeadingGapRemoved = false;
        boolean validTrailingGapRemoved = false;
        int commonGapLeadingSize = Integer.MIN_VALUE;
        int commonGapTrailingSize = Integer.MIN_VALUE;
        boolean mayNeedSecondPass = false;
        List<LayoutInterval> reduceToZeroGapsLeading = new LinkedList<>();
        List<LayoutInterval> reduceToZeroGapsTrailing = new LinkedList<>();

        for (int i=0; i < group.getSubIntervalCount(); i++) {
            LayoutInterval li = group.getSubInterval(i);
            if (!li.isSequential() || li.getSubIntervalCount() == 0) {
                continue;
            }
            LayoutInterval gap = li.getSubInterval(0);
            if (gap.isEmptySpace()) {
                boolean process = processLeading.contains(li);
                if (!isEndingGapUsable(li, dimension, LEADING, process, contentResizing)) {
                    // default gap that would not work
                    layoutModel.removeInterval(gap);
                    gap = null;
                }
                if (gap != null && process) {
                    if (isEndingGapEffective(li, dimension, LEADING)) {
                        if (gap.getPreferredSize() == NOT_EXPLICITLY_DEFINED) {
                            // default padding to be used as common gap
                            defaultLeadingPadding = true;
                            leadingPadding = gap.getPaddingType();
                        } else {
                            effectiveExplicitGapLeading = true;
                        }
                        if (commonGapLeadingSize == Integer.MIN_VALUE) {
                            commonGapLeadingSize = (gap.getMinimumSize() != USE_PREFERRED_SIZE)
                                    ? gap.getMinimumSize() : gap.getPreferredSize();
                        }
                    }
                    if (gap.getMaximumSize() >= Short.MAX_VALUE) {
                        if (anyAlignedLeading) {
                            reduceToZeroGapsLeading.add(gap); // will change to zero gap instead
                            gap = null;
                        } else {
                            if (li.getAlignment() == LEADING) { // need to change alignment as we removed resizing gap
                                layoutModel.setIntervalAlignment(li, TRAILING);
                            }
                            resizingGapLeading = true;
                            if (gap.getPreferredSize() == 0) {
                                zeroGapLeading = gap;
                            }
                        }
                    }
                    if (gap != null) {
                        layoutModel.removeInterval(gap);
                        validLeadingGapRemoved = true;
                    }
                }
            }

            gap = li.getSubInterval(li.getSubIntervalCount() - 1);
            if (gap.isEmptySpace()) {
                boolean process = processTrailing.contains(li);
                if (!isEndingGapUsable(li, dimension, TRAILING, process, contentResizing)) {
                    // default gap that would not work
                    layoutModel.removeInterval(gap);
                    gap = null;
                }
                if (gap != null && process) {
                    if (isEndingGapEffective(li, dimension, TRAILING)) {
                        if (gap.getPreferredSize() == NOT_EXPLICITLY_DEFINED) {
                            // default padding to be used as common gap
                            defaultTrailingPadding = true;
                            trailingPadding = gap.getPaddingType();
                        } else {
                            effectiveExplicitGapTrailing = true;
                        }
                        if (commonGapTrailingSize == Integer.MIN_VALUE) {
                            commonGapTrailingSize = (gap.getMinimumSize() != USE_PREFERRED_SIZE)
                                    ? gap.getMinimumSize() : gap.getPreferredSize();
                        }
                    }
                    if (gap.getMaximumSize() >= Short.MAX_VALUE) {
                        if (anyAlignedTrailing) {
                            reduceToZeroGapsTrailing.add(gap); // will change to zero gap instead
                            gap = null;
                        } else {
                            if (li.getAlignment() == TRAILING) { // need to change alignment as we removed resizing gap
                                layoutModel.setIntervalAlignment(li, LEADING);
                            }
                            resizingGapTrailing = true;
                            if (gap.getPreferredSize() == 0) {
                                zeroGapTrailing = gap;
                            }
                        }
                    }
                    if (gap != null) {
                        layoutModel.removeInterval(gap);
                        validTrailingGapRemoved = true;
                    }
                }
            }

            if (li.getSubIntervalCount() == 1) {
                // only one interval remained in sequence - cancel the sequence
                layoutModel.removeInterval(group, i); // removes li from group
                LayoutInterval sub = layoutModel.removeInterval(li, 0); // removes last interval from li
                layoutModel.setIntervalAlignment(sub, li.getRawAlignment());
                layoutModel.addInterval(sub, group, i);
                if (processLeading.contains(li)) {
                    processLeading.intervals.remove(li);
                    processLeading.intervals.add(sub);
                }
                if (processTrailing.contains(li)) {
                    processTrailing.intervals.remove(li);
                    processTrailing.intervals.add(sub);
                }
                if (sub.isParallel()) {
                    mayNeedSecondPass = true;
                }
            }
        }
        if (!validLeadingGapRemoved && !validTrailingGapRemoved) {
            return -1;
        }

        if ((resizingGapLeading || resizingGapTrailing)
            && (!LayoutInterval.canResize(group) || contentResizing)) {
            // removed a resizing gap, but it should be fixed when out of the group
            if (!subGroupLeading) {
                resizingGapLeading = false;
            }
            if (!subGroupTrailing) {
                resizingGapTrailing = false;
            }
            if (!contentResizing) { // after removing resizing gaps the group with suppressed resizing has only fixed content
                enableGroupResizing(group);
            }
        }

        // 3) Create new L and T gaps to substitute removed gaps.
        int[] groupOuterPos = group.getCurrentSpace().positions[dimension];
        assert groupOuterPos[LEADING] > Short.MIN_VALUE && groupOuterPos[TRAILING] > Short.MIN_VALUE;
        int groupInnerPosLeading = processLeading.count() > 0 ?
                LayoutUtils.getPositionWithoutGap(processLeading.intervals, dimension, LEADING) :
                groupOuterPos[LEADING];
        int groupInnerPosTrailing = processTrailing.count() > 0 ?
                LayoutUtils.getPositionWithoutGap(processTrailing.intervals, dimension, TRAILING) :
                groupOuterPos[TRAILING];

        LayoutInterval leadingGap = null;
        LayoutInterval trailingGap = null;
        if (validLeadingGapRemoved) {
            if (!anyAlignedLeading) { // group is open at leading edge
                int size = groupInnerPosLeading - groupOuterPos[LEADING];
                if (size > 0 || defaultLeadingPadding) {
                    leadingGap = new LayoutInterval(SINGLE);
                    if (defaultLeadingPadding) {
                        leadingGap.setPaddingType(leadingPadding);
                    } else if (effectiveExplicitGapLeading) {
                        leadingGap.setPreferredSize(size);
                        if (resizingGapLeading) {
                            if (size < 0 || commonGapLeadingSize < 0 || commonGapLeadingSize <= size) {
                                leadingGap.setMinimumSize(commonGapLeadingSize);
                            }
                        } else if (size != NOT_EXPLICITLY_DEFINED) {
                            leadingGap.setMinimumSize(USE_PREFERRED_SIZE);
                            leadingGap.setMaximumSize(USE_PREFERRED_SIZE);
                        }
                    }
                    if (resizingGapLeading) {
                        leadingGap.setMaximumSize(Short.MAX_VALUE);
                    }
                    leadingGap.setAttribute(LayoutInterval.ATTR_FLEX_SIZEDEF);
                } else if (size == 0) {
                    leadingGap = zeroGapLeading;
                }
            } else {
                leadingGap = new LayoutInterval(SINGLE);
                leadingGap.setSize(commonGapLeadingSize);
                if (commonGapLeadingSize == DEFAULT) {
                    leadingGap.setPaddingType(leadingPadding);
                }
                if (!reduceToZeroGapsLeading.isEmpty()) {
                    int sizeDiff = groupInnerPosLeading - groupOuterPos[LEADING];
                    for (LayoutInterval gap : reduceToZeroGapsLeading) {
                        int gapSize = gap.getPreferredSize() - sizeDiff;
                        if (gapSize < 0) {
                            gapSize = 0;
                        }
                        layoutModel.setIntervalSize(gap, 0, gapSize, Short.MAX_VALUE);
                    }
                }
            }
        }
        if (validTrailingGapRemoved) {
            if (!anyAlignedTrailing) { // group is open at trailing edge
                int size = groupOuterPos[TRAILING] - groupInnerPosTrailing;
                if (size > 0 || defaultTrailingPadding) {
                    trailingGap = new LayoutInterval(SINGLE);
                    if (defaultTrailingPadding) {
                        trailingGap.setPaddingType(trailingPadding);
                    } else if (effectiveExplicitGapTrailing) {
                        trailingGap.setPreferredSize(size);
                        if (resizingGapTrailing) {
                            if (size < 0 || commonGapTrailingSize < 0 || commonGapTrailingSize <= size) {
                                trailingGap.setMinimumSize(commonGapTrailingSize);
                            }
                        } else if (size != NOT_EXPLICITLY_DEFINED) {
                            trailingGap.setMinimumSize(USE_PREFERRED_SIZE);
                            trailingGap.setMaximumSize(USE_PREFERRED_SIZE);
                        }
                    }
                    if (resizingGapTrailing) {
                        trailingGap.setMaximumSize(Short.MAX_VALUE);
                    }
                    trailingGap.setAttribute(LayoutInterval.ATTR_FLEX_SIZEDEF);
                } else if (size == 0) {
                    trailingGap = zeroGapTrailing;
                }
            } else {
                trailingGap = new LayoutInterval(SINGLE);
                trailingGap.setSize(commonGapTrailingSize);
                if (commonGapTrailingSize == DEFAULT) {
                    trailingGap.setPaddingType(trailingPadding);
                }
                if (!reduceToZeroGapsTrailing.isEmpty()) {
                    int sizeDiff = groupOuterPos[TRAILING] - groupInnerPosTrailing;
                    for (LayoutInterval gap : reduceToZeroGapsTrailing) {
                        int gapSize = gap.getPreferredSize() - sizeDiff;
                        if (gapSize < 0) {
                            gapSize = 0;
                        }
                        layoutModel.setIntervalSize(gap, 0, gapSize, Short.MAX_VALUE);
                    }
                }
            }
        }

        // 4) Place the L/T subst. gaps outside the group, or create sub-group.
        if ((leadingGap != null && subGroupLeading)
               || (trailingGap != null && subGroupTrailing)) {
            // have a gap to put next to a subgroup (stays inside 'group')
            LayoutInterval subGroup = new LayoutInterval(PARALLEL);
            subGroup.setGroupAlignment(group.getGroupAlignment());
            int commonAlignment = -1;
            for (int i=0; i < group.getSubIntervalCount();) {
                LayoutInterval li = group.getSubInterval(i);
                if ((subGroupLeading && processLeading.contains(li))
                        || (subGroupTrailing && processTrailing.contains(li))) {
                    int align = li.getAlignment();
                    if (commonAlignment == -1) {
                        commonAlignment = align;
                    } else if (align != commonAlignment) {
                        commonAlignment = LayoutRegion.ALL_POINTS;
                    }
                    layoutModel.removeInterval(group, i);
                    layoutModel.addInterval(li, subGroup, -1);
                } else {
                    i++;
                }
            }
            LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
            if (subGroupLeading && leadingGap != null) {
                layoutModel.addInterval(leadingGap, seq, -1);
                leadingGap = null;
            }
            seq.add(subGroup, -1);
            if (subGroupTrailing && trailingGap != null) {
                layoutModel.addInterval(trailingGap, seq, -1);
                trailingGap = null;
            }
            layoutModel.addInterval(seq, group, -1);
            if (commonAlignment != LayoutRegion.ALL_POINTS
                    && seq.getAlignment() != commonAlignment) {
                seq.setAlignment(commonAlignment);
            }
            int pos[] = subGroup.getCurrentSpace().positions[dimension];
            pos[LEADING] = groupInnerPosLeading;
            pos[TRAILING] = groupInnerPosTrailing;
            pos[CENTER] = (groupInnerPosLeading + groupInnerPosTrailing) / 2;
        }

        boolean someGapOutsideGroup = (leadingGap != null || trailingGap != null);
        boolean originalRootGroup = group.getParent() == null;
        if (someGapOutsideGroup && !originalRootGroup) {
            if (leadingGap != null) {
                groupOuterPos[LEADING] = groupInnerPosLeading;
            }
            if (trailingGap != null) {
                groupOuterPos[TRAILING] = groupInnerPosTrailing;
            }
            groupOuterPos[CENTER] = (groupOuterPos[LEADING] + groupOuterPos[TRAILING]) / 2;
        }
        if (leadingGap != null) {
            group = insertGap(leadingGap, group, groupInnerPosLeading, dimension, LEADING);
        }
        if (trailingGap != null) {
            group = insertGap(trailingGap, group, groupInnerPosTrailing, dimension, TRAILING);
        }
        if (someGapOutsideGroup && originalRootGroup && group.getParent() != null) {
            group.getCurrentSpace().set(dimension, groupInnerPosLeading, groupInnerPosTrailing);
        }

        int idx = (someGapOutsideGroup && group.getParent() != null)
                ? group.getParent().indexOf(group) : -1;

        if (mayNeedSecondPass) {
            int count = group.getSubIntervalCount();
            mergeParallelGroups(group);
            if (group.getSubIntervalCount() > count) {
                idx = optimizeGaps(group, dimension);
            }
        }
        return idx;
    }