0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.form.layoutdesign;
0043:
0044: import java.awt.BasicStroke;
0045: import java.awt.Dimension;
0046: import java.awt.Graphics2D;
0047: import java.awt.Image;
0048: import java.awt.Point;
0049: import java.awt.Rectangle;
0050: import java.awt.RenderingHints;
0051: import java.awt.Stroke;
0052: import java.util.*;
0053: import org.openide.loaders.DataObject;
0054: import org.openide.util.Utilities;
0055:
0056: public class LayoutDesigner implements LayoutConstants {
0057:
0058: private LayoutModel layoutModel;
0059:
0060: private VisualMapper visualMapper;
0061:
0062: private LayoutDragger dragger;
0063:
0064: private LayoutOperations operations;
0065:
0066: private Listener modelListener;
0067:
0068: private boolean imposeSize = true;
0069: private boolean optimizeStructure = true;
0070: private boolean visualStateUpToDate;
0071:
0072: // -----
0073:
0074: public LayoutDesigner(LayoutModel model, VisualMapper mapper) {
0075: layoutModel = model;
0076: visualMapper = mapper;
0077: operations = new LayoutOperations(model, mapper);
0078: modelListener = new Listener();
0079: modelListener.activate();
0080: }
0081:
0082: // -------
0083: // updates of the current visual state stored in the model
0084:
0085: public boolean updateCurrentState() {
0086: if (logTestCode()) {
0087: testCode.add("// > UPDATE CURRENT STATE"); //NOI18N
0088: }
0089: Object changeMark = layoutModel.getChangeMark();
0090: boolean changeRequired = imposeSize || optimizeStructure;
0091: Set<LayoutComponent> updatedContainers = changeRequired ? new HashSet<LayoutComponent>()
0092: : null;
0093: try {
0094: if (changeRequired) {
0095: modelListener.deactivate(); // some changes may happen...
0096: destroyRedundantGroups(updatedContainers);
0097: operations.mergeAdjacentGaps(updatedContainers);
0098: }
0099:
0100: updatePositions(updatedContainers);
0101: } finally {
0102: if (changeRequired) {
0103: modelListener.activate();
0104: }
0105: }
0106:
0107: if (changeRequired) {
0108: imposeSize = optimizeStructure = false;
0109:
0110: if (updatedContainers != null) {
0111: Iterator<LayoutComponent> it = updatedContainers
0112: .iterator();
0113: while (it.hasNext()) {
0114: LayoutComponent cont = it.next();
0115: visualMapper.rebuildLayout(cont.getId());
0116: }
0117: updatePositions(null);
0118: }
0119: }
0120:
0121: if (logTestCode()) {
0122: testCode.add("ld.updateCurrentState();"); //NOI18N
0123: testCode.add("// < UPDATE CURRENT STATE"); //NOI18N
0124: }
0125:
0126: visualStateUpToDate = true;
0127: return !changeMark.equals(layoutModel.getChangeMark());
0128: }
0129:
0130: public void externalSizeChangeHappened() {
0131: imposeSize = true;
0132: visualStateUpToDate = false;
0133: if (logTestCode()) {
0134: testCode.add("ld.externalSizeChangeHappened();"); // NOI18N
0135: }
0136: }
0137:
0138: void requireStructureOptimization() {
0139: optimizeStructure = true;
0140: visualStateUpToDate = false;
0141:
0142: Iterator it = layoutModel.getAllComponents();
0143: while (it.hasNext()) {
0144: LayoutComponent comp = (LayoutComponent) it.next();
0145: if (comp.isLayoutContainer()) {
0146: LayoutInterval[] roots = getActiveLayoutRoots(comp);
0147: for (int dim = 0; dim < DIM_COUNT; dim++) {
0148: cleanDesignAttrs(roots[dim]);
0149: }
0150: }
0151: }
0152: }
0153:
0154: private void updatePositions(Set<LayoutComponent> updatedContainers) {
0155: Iterator it = layoutModel.getAllComponents();
0156: while (it.hasNext()) {
0157: LayoutComponent comp = (LayoutComponent) it.next();
0158: if (!comp.isLayoutContainer())
0159: continue;
0160:
0161: if (optimizeStructure || imposeSize) {
0162: // make sure the layout definition reflects the current size
0163: if (imposeCurrentContainerSize(comp, null, false)) {
0164: updatedContainers.add(comp);
0165: if (optimizeStructure) {
0166: for (LayoutInterval[] roots : comp
0167: .getLayoutRoots()) {
0168: for (int dim = 0; dim < DIM_COUNT; dim++) {
0169: optimizeGaps(roots[dim], dim, true);
0170: destroyRedundantGroups(roots[dim]);
0171: }
0172: }
0173: }
0174: LayoutInterval[] roots = getActiveLayoutRoots(comp);
0175: for (int dim = 0; dim < DIM_COUNT; dim++) {
0176: updateDesignModifications(roots[dim], dim);
0177: }
0178: }
0179: } else { // just update the current visual positions (LayoutRegion)
0180: Rectangle bounds = visualMapper
0181: .getContainerInterior(comp.getId());
0182: if (bounds != null) {
0183: comp.setCurrentInterior(bounds);
0184: for (LayoutComponent subComp : comp
0185: .getSubcomponents()) {
0186: bounds = visualMapper
0187: .getComponentBounds(subComp.getId());
0188: int baseline = visualMapper
0189: .getBaselinePosition(subComp.getId(),
0190: bounds.width, bounds.height);
0191: subComp.setCurrentBounds(bounds, baseline);
0192: }
0193: for (LayoutInterval[] roots : comp.getLayoutRoots()) {
0194: for (int i = 0; i < DIM_COUNT; i++) {
0195: updateLayoutStructure(roots[i], i, false);
0196: }
0197: }
0198: }
0199: }
0200: }
0201: }
0202:
0203: // recursive
0204: /**
0205: * Updates current space (LayoutRegion) of all groups in the layout.
0206: * Also for all intervals having the preferred size explicitly defined the
0207: * pref size is updated according to the current state. Min and max sizes
0208: * as well if they are the same as pref.
0209: */
0210: void updateLayoutStructure(LayoutInterval interval, int dimension,
0211: boolean imposeGaps) {
0212: LayoutRegion space = interval.getCurrentSpace();
0213: boolean baseline = interval.getGroupAlignment() == BASELINE;
0214:
0215: boolean first = true;
0216: boolean firstResizingSpace = false;
0217: int leadingSpace = 0;
0218: boolean skipNext = false;
0219:
0220: Iterator it = interval.getSubIntervals();
0221: while (it.hasNext()) {
0222: LayoutInterval sub = (LayoutInterval) it.next();
0223: if (sub.isEmptySpace()) {
0224: if (!interval.isSequential()) {
0225: // filling gap in empty root group
0226: assert interval.getParent() == null;
0227: if (imposeGaps && space.isSet(dimension)) {
0228: imposeCurrentGapSize(sub,
0229: space.size(dimension), dimension);
0230: }
0231: } else if (first || !it.hasNext()) {
0232: // first or last gap in sequence
0233: int min = sub.getMinimumSize(true);
0234: int pref = sub.getPreferredSize(true);
0235: int max = sub.getMaximumSize(true);
0236: if ((min == pref || min == USE_PREFERRED_SIZE)
0237: && (pref == max || max == USE_PREFERRED_SIZE)
0238: && pref != NOT_EXPLICITLY_DEFINED) {
0239: // Fixed size gap
0240: if (first) {
0241: leadingSpace = pref;
0242: } else {
0243: space.reshape(dimension, TRAILING, pref);
0244: }
0245: } else {
0246: int currentPref = LayoutRegion.UNKNOWN;
0247: LayoutInterval sibling = LayoutInterval
0248: .getNeighbor(sub, SEQUENTIAL,
0249: first ? LEADING : TRAILING);
0250: if (sibling == null) {
0251: LayoutRegion rootSpace = LayoutInterval
0252: .getRoot(interval)
0253: .getCurrentSpace();
0254: if (first) {
0255: firstResizingSpace = true;
0256: leadingSpace = -rootSpace.positions[dimension][LEADING];
0257: } else { // last
0258: currentPref = rootSpace.positions[dimension][TRAILING]
0259: - space.positions[dimension][TRAILING];
0260: space.reshape(dimension, TRAILING,
0261: currentPref);
0262: }
0263: } else if (sibling.isEmptySpace()) {
0264: LayoutInterval parent = interval
0265: .getParent();
0266: LayoutInterval alignedParent = parent;
0267: int align = first ? LEADING : TRAILING;
0268: while (parent != null
0269: && LayoutInterval
0270: .isAlignedAtBorder(
0271: interval, parent,
0272: align)) {
0273: alignedParent = parent;
0274: parent = parent.getParent();
0275: }
0276: LayoutInterval parComp = LayoutUtils
0277: .getOutermostComponent(
0278: alignedParent, dimension,
0279: align);
0280: // [this assert is maybe too strong - it's ok if the component at least touches the border - but that's hard to determine here]
0281: // assert LayoutInterval.isAlignedAtBorder(parComp, interval.getParent(), first ? LEADING : TRAILING);
0282: LayoutRegion parSpace = parComp
0283: .getCurrentSpace();
0284: if (first) {
0285: firstResizingSpace = true;
0286: leadingSpace = -parSpace.positions[dimension][LEADING];
0287: } else {
0288: currentPref = parSpace.positions[dimension][TRAILING]
0289: - space.positions[dimension][TRAILING];
0290: space.reshape(dimension, TRAILING,
0291: currentPref);
0292: }
0293: } else {
0294: if (first) {
0295: firstResizingSpace = true;
0296: LayoutRegion sibSpace = sibling
0297: .getCurrentSpace();
0298: leadingSpace = -sibSpace.positions[dimension][TRAILING];
0299: } else {
0300: LayoutRegion sibSpace = sibling
0301: .getCurrentSpace();
0302: if (!sibling.isComponent()) {
0303: sibSpace.reset();
0304: updateLayoutStructure(sibling,
0305: dimension, imposeGaps);
0306: skipNext = true;
0307: }
0308: int sibPos = sibSpace.positions[dimension][LEADING];
0309: if (sibPos != LayoutRegion.UNKNOWN) { // Issue 62320
0310: currentPref = sibPos
0311: - space.positions[dimension][TRAILING];
0312: space.reshape(dimension, TRAILING,
0313: currentPref);
0314: }
0315: }
0316: }
0317:
0318: if (imposeGaps
0319: && (currentPref != LayoutRegion.UNKNOWN)) { // last resizing gap
0320: imposeCurrentGapSize(sub, currentPref,
0321: dimension);
0322: }
0323: }
0324: } else if (imposeGaps) {
0325: LayoutInterval sibling = LayoutInterval
0326: .getDirectNeighbor(sub, TRAILING, false);
0327: assert !sibling.isEmptySpace();
0328: LayoutRegion sibSpace = sibling.getCurrentSpace();
0329: if (!sibling.isComponent()) {
0330: sibSpace.reset();
0331: updateLayoutStructure(sibling, dimension,
0332: imposeGaps);
0333: skipNext = true;
0334: }
0335: int currentSize = LayoutRegion.distance(space,
0336: sibSpace, dimension, TRAILING, LEADING);
0337: imposeCurrentGapSize(sub, currentSize, dimension);
0338: }
0339: first = false;
0340: continue;
0341: }
0342:
0343: LayoutRegion subSpace = sub.getCurrentSpace();
0344: if (skipNext) { // this subgroup has been processed in advance
0345: skipNext = false;
0346: } else if (sub.isGroup()) {
0347: assert sub.getSubIntervalCount() > 0; // consistency check - there should be no empty groups
0348: subSpace.reset();
0349: /*if (interval.getParent() == null) {
0350: subSpace.set(space, dimension); // root space is known
0351: }*/
0352: updateLayoutStructure(sub, dimension, imposeGaps);
0353: }
0354: space.expand(subSpace);
0355:
0356: if (baseline && sub.isComponent()) {
0357: int baselinePos = subSpace.positions[dimension][BASELINE];
0358: if (baselinePos != LayoutRegion.UNKNOWN) {
0359: space.positions[dimension][BASELINE] = baselinePos;
0360: baseline = false;
0361: }
0362: }
0363: if (firstResizingSpace) {
0364: leadingSpace += space.positions[dimension][LEADING];
0365: firstResizingSpace = false;
0366: if (imposeGaps) {
0367: imposeCurrentGapSize(interval.getSubInterval(0),
0368: leadingSpace, dimension);
0369: }
0370: }
0371: first = false;
0372: }
0373: if (leadingSpace != 0) {
0374: space.reshape(dimension, LEADING, -leadingSpace);
0375: }
0376: }
0377:
0378: public void dumpTestcode(DataObject form) {
0379: LayoutTestUtils.dumpTestcode(testCode, form, getModelCounter());
0380: testCode = new ArrayList<String>();
0381: testCode0 = new ArrayList<String>();
0382: beforeMove = new ArrayList<String>();
0383: move1 = new ArrayList<String>();
0384: move2 = new ArrayList<String>();
0385: isMoving = false;
0386: }
0387:
0388: // -----
0389: // adding, moving, resizing
0390:
0391: /**
0392: * Determines a subset of components that can be visually dragged together.
0393: * If the components are in diferrent containers then no dragging is
0394: * possible (returns empty list). Otherwise the components that have the
0395: * same parent and are under the same layout roots are returned.
0396: * Nonexisting or parent-less components are ignored.
0397: * @param component Ids of components selected for dragging
0398: * @return List of Ids of components that can be dragged together
0399: */
0400: public List<String> getDraggableComponents(List<String> componentIds) {
0401: LayoutComponent container = null;
0402: List<String> draggable = null;
0403: int commonLayer = -1;
0404: for (String compId : componentIds) {
0405: LayoutComponent comp = layoutModel
0406: .getLayoutComponent(compId);
0407: if (comp == null || comp.getParent() == null) {
0408: continue;
0409: }
0410: if (container == null) {
0411: container = comp.getParent();
0412: } else if (comp.getParent() != container) {
0413: return Collections.emptyList();
0414: }
0415:
0416: int layerIndex = container.getLayoutRootsIndex(comp
0417: .getLayoutInterval(HORIZONTAL));
0418: assert layerIndex >= 0;
0419: if (layerIndex >= commonLayer) {
0420: if (commonLayer < 0) {
0421: draggable = new ArrayList<String>(componentIds
0422: .size());
0423: } else if (layerIndex > commonLayer) {
0424: draggable.clear();
0425: }
0426: commonLayer = layerIndex;
0427: draggable.add(compId);
0428: }
0429: }
0430: if (draggable == null)
0431: draggable = Collections.emptyList();
0432: return draggable;
0433: }
0434:
0435: /**
0436: * Checks whether given component is "unplaced" - i.e. in other than default
0437: * layer. Result of adding without mouse positioning (e.g. copying). In
0438: * current model such a component needs to be moved additionally by the user
0439: * to be placed in the default layer among other components.
0440: * @param compId id of the component
0441: * @return true if the component is placed in an additional layer
0442: */
0443: public boolean isUnplacedComponent(String compId) {
0444: LayoutComponent comp = layoutModel.getLayoutComponent(compId);
0445: if (comp != null) {
0446: LayoutComponent cont = comp.getParent();
0447: return cont != null
0448: && comp.getParentRoots()[HORIZONTAL] != cont
0449: .getDefaultLayoutRoot(HORIZONTAL);
0450: }
0451: return false;
0452: }
0453:
0454: public void startAdding(LayoutComponent[] comps,
0455: Rectangle[] bounds, Point hotspot, String defaultContId) {
0456: if (logTestCode()) {
0457: testCode.add("// > START ADDING"); //NOI18N
0458: }
0459: prepareDragger(comps, bounds, hotspot, LayoutDragger.ALL_EDGES);
0460: if (logTestCode()) {
0461: testCode.add("{"); // NOI18N
0462: // lc should be already filled in the MetaComponentCreator.getPrecreatedComponent
0463: LayoutTestUtils.writeLayoutComponentArray(testCode,
0464: "comps", "lc"); //NOI18N
0465: LayoutTestUtils.writeRectangleArray(testCode, "bounds",
0466: bounds); //NOI18N
0467: LayoutTestUtils.writeString(testCode, "defaultContId",
0468: defaultContId); //NOI18N
0469: testCode.add("Point hotspot = new Point("
0470: + new Double(hotspot.getX()).intValue() + "," + //NOI18N
0471: new Double(hotspot.getY()).intValue() + ");"); //NOI18N
0472: testCode
0473: .add("ld.startAdding(comps, bounds, hotspot, defaultContId);"); //NOI18N
0474: testCode.add("}"); //NOI18N
0475: }
0476: if (defaultContId != null) {
0477: setDragTarget(
0478: layoutModel.getLayoutComponent(defaultContId),
0479: comps, false);
0480: }
0481: if (logTestCode()) {
0482: testCode.add("// < START ADDING"); //NOI18N
0483: }
0484: }
0485:
0486: public void startMoving(String[] compIds, Rectangle[] bounds,
0487: Point hotspot) {
0488: if (logTestCode()) {
0489: testCode.add("// > START MOVING"); //NOI18N
0490: }
0491:
0492: LayoutComponent[] comps = new LayoutComponent[compIds.length];
0493: for (int i = 0; i < compIds.length; i++) {
0494: comps[i] = layoutModel.getLayoutComponent(compIds[i]);
0495: }
0496: prepareDragger(comps, bounds, hotspot, LayoutDragger.ALL_EDGES);
0497:
0498: if (logTestCode()) {
0499: testCode.add("{"); //NOI18N
0500: LayoutTestUtils.writeStringArray(testCode, "compIds",
0501: compIds); //NOI18N
0502: LayoutTestUtils.writeRectangleArray(testCode, "bounds",
0503: bounds); //NOI18N
0504: testCode.add("Point hotspot = new Point("
0505: + new Double(hotspot.getX()).intValue() + "," + //NOI18N
0506: new Double(hotspot.getY()).intValue() + ");"); //NOI18N
0507: testCode.add("ld.startMoving(compIds, bounds, hotspot);"); //NOI18N
0508: testCode.add("}"); //NOI18N
0509: }
0510:
0511: setDragTarget(comps[0].getParent(), comps, false);
0512:
0513: if (logTestCode()) {
0514: testCode.add("// < START MOVING"); //NOI18N
0515: }
0516: }
0517:
0518: // [change to one component only?]
0519: public void startResizing(String[] compIds, Rectangle[] bounds,
0520: Point hotspot, int[] resizeEdges, boolean inLayout) {
0521: if (logTestCode()) {
0522: testCode.add("// > START RESIZING"); //NOI18N
0523: }
0524:
0525: LayoutComponent[] comps = new LayoutComponent[compIds.length];
0526: for (int i = 0; i < compIds.length; i++) {
0527: comps[i] = layoutModel.getLayoutComponent(compIds[i]);
0528: }
0529:
0530: int[] edges = new int[DIM_COUNT];
0531: for (int i = 0; i < DIM_COUNT; i++) {
0532: edges[i] = resizeEdges[i] == LEADING
0533: || resizeEdges[i] == TRAILING ? resizeEdges[i]
0534: : LayoutRegion.NO_POINT;
0535: }
0536:
0537: prepareDragger(comps, bounds, hotspot, edges);
0538:
0539: if (logTestCode()) {
0540: testCode.add("{"); //NOI18N
0541: LayoutTestUtils.writeStringArray(testCode, "compIds",
0542: compIds); //NOI18N
0543: LayoutTestUtils.writeRectangleArray(testCode, "bounds",
0544: bounds); //NOI18N
0545: testCode.add("Point hotspot = new Point("
0546: + new Double(hotspot.getX()).intValue() + "," + //NOI18N
0547: new Double(hotspot.getY()).intValue() + ");"); //NOI18N
0548: LayoutTestUtils.writeIntArray(testCode, "resizeEdges",
0549: resizeEdges); //NOI18N
0550: testCode.add("boolean inLayout = " + inLayout + ";"); // NOI18N
0551: testCode
0552: .add("ld.startResizing(compIds, bounds, hotspot, resizeEdges, inLayout);"); //NOI18N
0553: testCode.add("}"); //NOI18N
0554: }
0555:
0556: if (inLayout) {
0557: setDragTarget(comps[0].getParent(), comps, true);
0558: } else {
0559: setDragTarget(null, null, true);
0560: }
0561:
0562: if (logTestCode()) {
0563: testCode.add("// < START RESIZING"); //NOI18N
0564: }
0565: }
0566:
0567: private void prepareDragger(LayoutComponent[] comps,
0568: Rectangle[] bounds, Point hotspot, int[] edges) {
0569: if (comps.length != bounds.length)
0570: throw new IllegalArgumentException();
0571:
0572: LayoutRegion[] movingFormation = new LayoutRegion[bounds.length];
0573: for (int i = 0; i < bounds.length; i++) {
0574: int baseline = visualMapper.getBaselinePosition(comps[i]
0575: .getId(), bounds[i].width, bounds[i].height);
0576: int baselinePos = baseline > 0 ? bounds[i].y + baseline
0577: : LayoutRegion.UNKNOWN;
0578: movingFormation[i] = new LayoutRegion();
0579: movingFormation[i].set(bounds[i], baselinePos);
0580: }
0581:
0582: dragger = new LayoutDragger(comps, movingFormation, new int[] {
0583: hotspot.x, hotspot.y }, edges, visualMapper);
0584: }
0585:
0586: /**
0587: * @param p mouse cursor position in coordinates of the whole design area;
0588: * it is adjusted if the position is changed (due to a snap effect)
0589: * @param containerId for container the cursor is currently moved over,
0590: * can be null if e.g. a root container is resized
0591: * @param autoPositioning if true, searching for optimal position will be
0592: * performed - a found position will be painted and the moving
0593: * component snapped to it
0594: * @param lockDimension if true, one dimension is locked for this move
0595: * (does not change); the dimension to lock must have aligned
0596: * position found in previous move steps - if this is true for both
0597: * dimensions then the one with smaller delta is chosen
0598: * @param bounds (output) bounds of moving components after the move
0599: */
0600: public void move(Point p, String containerId,
0601: boolean autoPositioning, boolean lockDimension,
0602: Rectangle[] bounds) {
0603:
0604: int x = (p != null) ? p.x : 0;
0605: int y = (p != null) ? p.y : 0;
0606:
0607: if (logTestCode()) {
0608: // this terrible code here is to store only two last move() calls
0609: if (!isMoving) {
0610: isMoving = true;
0611: // backup all current entries and clear the testcode list
0612: beforeMove = new ArrayList<String>();
0613: beforeMove.addAll(testCode);
0614: testCode = new ArrayList<String>();
0615: lastMovePoint = new Point(0, 0);
0616: }
0617:
0618: if (!((x == lastMovePoint.x) && (y == lastMovePoint.y))) {
0619: lastMovePoint = new Point(x, y);
0620: move1 = move2;
0621: testCode0 = testCode;
0622: }
0623:
0624: move2 = new ArrayList<String>();
0625: move2.add("// > MOVE");
0626: testCode = new ArrayList<String>();
0627: }
0628: if ((!visualStateUpToDate) || (dragger == null)) {
0629: return; // visual state of layout structure not updated yet (from last operation)
0630: }
0631:
0632: if (!dragger.isResizing()
0633: && (!lockDimension || dragger.getTargetContainer() == null)) {
0634: setDragTarget(layoutModel.getLayoutComponent(containerId),
0635: dragger.getMovingComponents(), false);
0636: }
0637:
0638: cursorPos[HORIZONTAL] = p.x;
0639: cursorPos[VERTICAL] = p.y;
0640:
0641: dragger.move(cursorPos, autoPositioning, lockDimension);
0642:
0643: p.x = cursorPos[HORIZONTAL];
0644: p.y = cursorPos[VERTICAL];
0645:
0646: if (bounds != null) {
0647: LayoutRegion[] current = dragger.getMovingBounds();
0648: for (int i = 0; i < current.length; i++) {
0649: current[i].toRectangle(bounds[i]);
0650: }
0651: }
0652:
0653: if (logTestCode()) {
0654: move2.add("{"); //NOI18N
0655: move2.add("Point p = new Point(" + x + "," + y + ");"); //NOI18N
0656: LayoutTestUtils.writeString(move2, "containerId",
0657: containerId); //NOI18N
0658: move2.add("boolean autoPositioning = " + autoPositioning
0659: + ";"); //NOI18N
0660: move2.add("boolean lockDimension = " + lockDimension + ";"); //NOI18N
0661: LayoutTestUtils
0662: .writeRectangleArray(move2, "bounds", bounds); //NOI18N
0663: move2
0664: .add("ld.move(p, containerId, autoPositioning, lockDimension, bounds);"); //NOI18N
0665: move2.add("}"); //NOI18N
0666: move2.add("// < MOVE"); //NOI18N
0667: }
0668: }
0669:
0670: private void setDragTarget(LayoutComponent targetContainer,
0671: LayoutComponent[] movingComps, boolean resizing) {
0672: LayoutComponent prevContainer = dragger.getTargetContainer();
0673: LayoutInterval[] roots;
0674: if (targetContainer != null) {
0675: roots = resizing && movingComps.length > 0 ? movingComps[0]
0676: .getParentRoots()
0677: : getActiveLayoutRoots(targetContainer);
0678: } else {
0679: roots = null;
0680: }
0681: dragger.setTargetContainer(targetContainer, roots);
0682:
0683: if (prevContainer != targetContainer) {
0684: updateDraggingVisibility(prevContainer, movingComps,
0685: resizing, false);
0686: updateDraggingVisibility(targetContainer, movingComps,
0687: resizing, true);
0688: }
0689: }
0690:
0691: // temporarily hide components from other layers when dragging in a container
0692: private void updateDraggingVisibility(LayoutComponent container,
0693: LayoutComponent[] movingComps, boolean resizing,
0694: boolean draggingIn) {
0695: if (container != null) {
0696: LayoutInterval[] targetRoots = resizing
0697: && movingComps.length > 0 ? movingComps[0]
0698: .getParentRoots() : getActiveLayoutRoots(container);
0699: for (LayoutComponent comp : container.getSubcomponents()) {
0700: for (LayoutComponent m : movingComps) {
0701: if (m == comp) {
0702: comp = null;
0703: break;
0704: }
0705: }
0706: if (comp != null
0707: && LayoutInterval.getRoot(comp
0708: .getLayoutInterval(HORIZONTAL)) != targetRoots[HORIZONTAL]) {
0709: visualMapper.setComponentVisibility(comp.getId(),
0710: !draggingIn);
0711: }
0712: }
0713: }
0714: }
0715:
0716: public void endMoving(boolean committed) {
0717: if (!committed && dragger == null)
0718: return; // redundant call
0719:
0720: if (logTestCode()) {
0721: if (committed) {
0722: beforeMove.addAll(testCode0);
0723: beforeMove.addAll(move1);
0724: beforeMove.addAll(testCode);
0725: beforeMove.addAll(move2);
0726: testCode = beforeMove;
0727: }
0728: testCode.add("// > END MOVING"); //NOI18N
0729: isMoving = false;
0730: }
0731: try {
0732: LayoutComponent targetContainer = dragger
0733: .getTargetContainer();
0734: LayoutComponent[] components = dragger
0735: .getMovingComponents();
0736: updateDraggingVisibility(targetContainer, components,
0737: dragger.isResizing(), false);
0738:
0739: if (committed) {
0740: if (targetContainer != null) {
0741: boolean newComponents = components[0].getParent() == null;
0742: // determine the interval to add in each dimension
0743: LayoutInterval[] addingInts;
0744: if (components.length > 1) {
0745: if (newComponents) { // adding multiple new components (not in layout)
0746: // (special case - moving multiple components from another layout)
0747: LayoutRegion movingSpace = dragger
0748: .getMovingSpace();
0749: int dx = movingSpace.positions[HORIZONTAL][LEADING];
0750: int dy = movingSpace.positions[HORIZONTAL][LEADING];
0751: LayoutRegion[] movingBounds = dragger
0752: .getMovingBounds();
0753: Map<LayoutComponent, Rectangle> compToRect = new HashMap<LayoutComponent, Rectangle>();
0754: for (int i = 0; i < components.length; i++) {
0755: Rectangle r = movingBounds[i]
0756: .toRectangle(new Rectangle());
0757: r.x -= dx;
0758: r.y -= dy;
0759: compToRect.put(components[i], r);
0760: }
0761: addingInts = LayoutModel
0762: .createIntervalsFromBounds(compToRect);
0763: } else { // moving multiple existing components (already in layout, no resizing)
0764: LayoutInterval[] commonParents = new LayoutInterval[DIM_COUNT];
0765: Map<LayoutComponent, LayoutComponent> compMap = new HashMap<LayoutComponent, LayoutComponent>();
0766: LayoutRegion origSpace = new LayoutRegion();
0767: for (LayoutComponent comp : components) {
0768: for (int dim = 0; dim < DIM_COUNT; dim++) {
0769: if (commonParents[dim] == null) {
0770: commonParents[dim] = comp
0771: .getLayoutInterval(dim);
0772: } else {
0773: commonParents[dim] = LayoutInterval
0774: .getCommonParent(
0775: commonParents[dim],
0776: comp
0777: .getLayoutInterval(dim));
0778: }
0779: }
0780: compMap.put(comp, comp); // moving the same components
0781: origSpace.expand(comp
0782: .getLayoutInterval(HORIZONTAL)
0783: .getCurrentSpace());
0784: }
0785: addingInts = new LayoutInterval[DIM_COUNT];
0786: for (int dim = 0; dim < DIM_COUNT; dim++) {
0787: addingInts[dim] = restrictedCopy(
0788: commonParents[dim], compMap,
0789: origSpace, dim, null);
0790: }
0791: // component intervals were moved to the new enclosing group,
0792: // so also remove the components from their container
0793: for (LayoutComponent comp : components) {
0794: layoutModel
0795: .removeComponent(comp, false);
0796: }
0797: }
0798: } else { // one component (adding/moving/resizing)
0799: addingInts = new LayoutInterval[DIM_COUNT];
0800: LayoutComponent comp = components[0];
0801: for (int dim = 0; dim < DIM_COUNT; dim++) {
0802: addingInts[dim] = comp
0803: .getLayoutInterval(dim);
0804: }
0805: }
0806:
0807: if (newComponents) { // ensure the interval size matches the real component size
0808: LayoutRegion[] movingBounds = dragger
0809: .getMovingBounds();
0810: for (int i = 0; i < components.length; i++) {
0811: LayoutComponent comp = components[i];
0812: Dimension preferred = visualMapper
0813: .getComponentPreferredSize(comp
0814: .getId());
0815: for (int dim = 0; dim < DIM_COUNT; dim++) {
0816: int size = movingBounds[i].size(dim);
0817: if (preferred == null
0818: || size != ((dim == HORIZONTAL) ? preferred.width
0819: : preferred.height)) {
0820: comp.getLayoutInterval(dim)
0821: .setPreferredSize(size);
0822: }
0823: }
0824: }
0825: }
0826:
0827: addComponents(components, targetContainer,
0828: addingInts, newComponents);
0829: } else { // resizing root container
0830: assert dragger.isResizing();
0831:
0832: modelListener.deactivate(); // do not react on model changes
0833:
0834: LayoutRegion space = dragger.getMovingBounds()[0];
0835: for (int dim = 0; dim < DIM_COUNT; dim++) {
0836: components[0].getLayoutInterval(dim)
0837: .setCurrentSpace(space);
0838: }
0839: if (components[0].isLayoutContainer()) {
0840: imposeCurrentContainerSize(components[0],
0841: dragger.getSizes(), true);
0842: }
0843: }
0844:
0845: if (dragger.isResizing()
0846: && components[0].isLayoutContainer())
0847: updateDesignModifications(components[0]);
0848:
0849: visualStateUpToDate = false;
0850: }
0851: } finally {
0852: dragger = null;
0853: if (logTestCode()) {
0854: testCode.add("ld.endMoving(" + committed + ");"); //NOI18N
0855: testCode.add("// < END MOVING"); //NOI18N
0856: }
0857: }
0858: }
0859:
0860: private void addComponents(LayoutComponent[] components,
0861: LayoutComponent targetContainer,
0862: LayoutInterval[] addingInts, boolean freshComponents) {
0863: try {
0864: LayoutFeeder layoutFeeder = new LayoutFeeder(operations,
0865: dragger, addingInts);
0866:
0867: // remove the components from original location if still there
0868: // (in case of resizing the feeder needed to know the original positions)
0869: for (LayoutComponent comp : components) {
0870: if (comp.getParent() != null) {
0871: if (dragger.isResizing(HORIZONTAL)) {
0872: layoutModel.removeComponentFromLinkSizedGroup(
0873: comp, HORIZONTAL);
0874: }
0875: if (dragger.isResizing(VERTICAL)) {
0876: layoutModel.removeComponentFromLinkSizedGroup(
0877: comp, VERTICAL);
0878: }
0879: layoutModel
0880: .removeComponentAndIntervals(comp, false);
0881: }
0882: }
0883:
0884: modelListener.deactivate(); // do not react on model changes during adding
0885:
0886: // add the components to the model (to the target container)
0887: for (LayoutComponent comp : components) {
0888: layoutModel.addComponent(comp, targetContainer, -1);
0889: }
0890:
0891: // add the components' intervals
0892: layoutFeeder.add();
0893: if (layoutFeeder.imposeSize) {
0894: imposeSize = true;
0895: }
0896: if (layoutFeeder.optimizeStructure) {
0897: optimizeStructure = true;
0898: } // if an overlap occurred we can't calculate the correct sizes
0899: // of resizing intervals, thus need to do real layout first (to
0900: // get the right picture), then update the actual sizes, and then
0901: // re-layout with design specific attributes (container resizing gap)
0902:
0903: for (int dim = 0; dim < DIM_COUNT; dim++) {
0904: destroyGroupIfRedundant(addingInts[dim],
0905: addingInts[dim].getParent());
0906: }
0907:
0908: for (LayoutComponent comp : components) {
0909: if ((freshComponents || dragger.isResizing())
0910: && comp.isLayoutContainer()) {
0911: // container size needs to be defined from inside before the layout is built
0912: imposeCurrentContainerSize(components[0], dragger
0913: .getSizes(), true);
0914: } else if (dragger.isResizing()) {
0915: // component might be resized to default size
0916: for (int i = 0; i < DIM_COUNT; i++) {
0917: if (dragger.snappedToDefaultSize(i))
0918: operations.resizeInterval(comp
0919: .getLayoutInterval(i),
0920: NOT_EXPLICITLY_DEFINED);
0921: }
0922: }
0923: }
0924:
0925: updateDesignModifications(targetContainer);
0926: } finally {
0927: if (!modelListener.isActive()) {
0928: modelListener.activate();
0929: }
0930: }
0931: }
0932:
0933: private void addUnspecified(LayoutComponent[] components,
0934: LayoutComponent targetContainer, LayoutInterval[] addingInts) {
0935: // add the components to the model (to the target container)
0936: for (LayoutComponent comp : components) {
0937: layoutModel.addComponent(comp, targetContainer, -1);
0938: }
0939: LayoutInterval[] targetRoots = layoutModel
0940: .addNewLayoutRoots(targetContainer);
0941: for (int dim = 0; dim < DIM_COUNT; dim++) {
0942: LayoutInterval interval = addingInts[dim];
0943: LayoutInterval seq = interval.isSequential() ? interval
0944: : new LayoutInterval(SEQUENTIAL);
0945: LayoutInterval gap = new LayoutInterval(SINGLE);
0946: boolean resizing = LayoutInterval.wantResize(interval);
0947: if (!resizing) {
0948: gap.setSizes(0, 0, Short.MAX_VALUE);
0949: }
0950: seq.add(gap, 0);
0951: if (interval != seq) {
0952: seq.add(interval, -1);
0953: interval.setAlignment(DEFAULT);
0954: }
0955: gap = new LayoutInterval(SINGLE);
0956: if (!resizing) {
0957: gap.setSizes(0, 0, Short.MAX_VALUE);
0958: }
0959: seq.add(gap, -1);
0960: targetRoots[dim].add(seq, -1);
0961: }
0962: }
0963:
0964: private void addToEmpty(LayoutComponent[] components,
0965: LayoutComponent targetContainer, LayoutInterval[] addingInts) {
0966: assert targetContainer.getSubComponentCount() == 0;
0967: for (LayoutComponent comp : components) {
0968: layoutModel.addComponent(comp, targetContainer, -1);
0969: }
0970: LayoutInterval[] roots = getActiveLayoutRoots(targetContainer);
0971: for (int dim = 0; dim < DIM_COUNT; dim++) {
0972: LayoutInterval root = roots[dim];
0973: assert root.isParallel();
0974: LayoutInterval interval = addingInts[dim];
0975: if (!interval.isParallel()) {
0976: layoutModel.addInterval(interval, root, -1);
0977: } else {
0978: while (interval.getSubIntervalCount() > 0) {
0979: layoutModel.addInterval(interval.remove(0), root,
0980: -1);
0981: }
0982: }
0983: }
0984: }
0985:
0986: /**
0987: * Creates copy of the given interval restricted to specified components
0988: * and region (<code>space</code>).
0989: *
0990: * @param interval interval whose restricted copy should be created.
0991: * @param componentMap components that determine the restriction.
0992: * @param space region (current space) that determine the restriction.
0993: * @param dimension dimension that restricted layout interval belongs to.
0994: * @param temp internal helper parameter for recursive invocation.
0995: * Pass <code>null</code> when you invoke this method.
0996: */
0997: private LayoutInterval restrictedCopy(LayoutInterval interval,
0998: Map<LayoutComponent, LayoutComponent> componentMap,
0999: LayoutRegion space, int dimension, List<Object> temp) {
1000: boolean processTemp = (temp == null);
1001: if (temp == null) {
1002: temp = new LinkedList<Object>();
1003: }
1004: if (interval.isGroup()) {
1005: boolean parallel = interval.isParallel();
1006: LayoutInterval copy = LayoutInterval.cloneInterval(
1007: interval, null);
1008: Iterator iter = interval.getSubIntervals();
1009: int compCount = 0; // Number of components already copied to the group
1010: boolean includeGap = false; // Helper variables that allow us to insert gaps
1011: int firstGapToInclude = 0; // instead of components that has been filtered out.
1012: int gapStart = interval.getCurrentSpace().positions[dimension][LEADING];
1013: while (iter.hasNext()) {
1014: LayoutInterval sub = (LayoutInterval) iter.next();
1015: LayoutInterval subCopy = restrictedCopy(sub,
1016: componentMap, space, dimension, temp);
1017: if (subCopy != null) {
1018: if (!sub.isEmptySpace()) {
1019: if (includeGap) {
1020: gapStart = Math
1021: .max(
1022: space.positions[dimension][LEADING],
1023: gapStart);
1024: int size = sub.getCurrentSpace().positions[dimension][LEADING]
1025: - gapStart;
1026: integrateGap(copy, size, firstGapToInclude);
1027: includeGap = false;
1028: }
1029: gapStart = sub.getCurrentSpace().positions[dimension][TRAILING];
1030: firstGapToInclude = copy.getSubIntervalCount();
1031: }
1032: if (sub.isComponent()) {
1033: // Remember where to add component intervals - they cannot
1034: // be moved immediately because the model listener would
1035: // destroy the adjacent intervals before we would be able
1036: // to copy them.
1037: temp.add(subCopy);
1038: temp.add(copy);
1039: temp
1040: .add(new Integer(subCopy
1041: .getRawAlignment()));
1042: temp.add(new Integer(copy.getSubIntervalCount()
1043: + compCount));
1044: compCount++;
1045: } else {
1046: layoutModel.addInterval(subCopy, copy, -1);
1047: }
1048: } else {
1049: if (!parallel) {
1050: includeGap = true;
1051: }
1052: }
1053: }
1054: if (includeGap) {
1055: gapStart = Math.max(
1056: space.positions[dimension][LEADING], gapStart);
1057: int gapEnd = Math
1058: .min(
1059: space.positions[dimension][TRAILING],
1060: interval.getCurrentSpace().positions[dimension][TRAILING]);
1061: integrateGap(copy, gapEnd - gapStart, firstGapToInclude);
1062: }
1063: if (copy.getSubIntervalCount() + compCount > 0) {
1064: if (processTemp) {
1065: // Insert component intervals
1066: iter = temp.iterator();
1067: while (iter.hasNext()) {
1068: LayoutInterval comp = (LayoutInterval) iter
1069: .next();
1070: LayoutInterval parent = (LayoutInterval) iter
1071: .next();
1072: int alignment = ((Integer) iter.next())
1073: .intValue();
1074: int index = ((Integer) iter.next()).intValue();
1075: if (comp.getParent() != null) { // component reused - not copied, just moved
1076: layoutModel.removeInterval(comp);
1077: }
1078: layoutModel.setIntervalAlignment(comp,
1079: alignment);
1080: layoutModel.addInterval(comp, parent, index);
1081: }
1082: // Consolidate the groups where the components has been added
1083: boolean active = modelListener.isActive();
1084: if (active)
1085: modelListener.deactivate();
1086: iter = temp.iterator();
1087: while (iter.hasNext()) {
1088: iter.next(); // skip the component
1089: LayoutInterval group = (LayoutInterval) iter
1090: .next();
1091: iter.next();
1092: iter.next(); // skip alignment and index
1093: LayoutInterval parent = group.getParent();
1094: while (group.getSubIntervalCount() == 1
1095: && parent != null) {
1096: LayoutInterval sub = group
1097: .getSubInterval(0);
1098: layoutModel.removeInterval(sub);
1099: int alignment = group.getAlignment();
1100: int index = layoutModel
1101: .removeInterval(group);
1102: layoutModel.setIntervalAlignment(sub,
1103: alignment);
1104: layoutModel.addInterval(sub, parent, index);
1105: group = sub;
1106: }
1107: }
1108: compCount = 0;
1109: if (active)
1110: modelListener.activate();
1111: }
1112: // consolidate copy
1113: if ((copy.getSubIntervalCount() == 1)
1114: && (compCount == 0)) {
1115: boolean active = modelListener.isActive();
1116: if (active)
1117: modelListener.deactivate();
1118: LayoutInterval subCopy = copy.getSubInterval(0);
1119: layoutModel.removeInterval(subCopy);
1120: layoutModel.setIntervalAlignment(subCopy, copy
1121: .getAlignment());
1122: if (copy.isSequential() && subCopy.isEmptySpace()) {
1123: copy = null;
1124: } else {
1125: copy = subCopy;
1126: }
1127: if (active)
1128: modelListener.activate();
1129: }
1130: return copy;
1131: } else {
1132: return null;
1133: }
1134: } else if (interval.isComponent()) {
1135: LayoutComponent comp = componentMap.get(interval
1136: .getComponent());
1137: if (comp != null) {
1138: if (comp != interval.getComponent()) { // there is a copied component available
1139: interval = LayoutInterval.cloneInterval(interval,
1140: comp.getLayoutInterval(dimension));
1141: } // otherwise the component will be moved from its original location
1142: return interval;
1143: } else { // skip this component
1144: return null;
1145: }
1146: } else {
1147: assert interval.isEmptySpace();
1148: int[] bounds = emptySpaceBounds(interval, dimension);
1149: int rangeStart = space.positions[dimension][LEADING];
1150: int rangeEnd = space.positions[dimension][TRAILING];
1151: if ((bounds[0] < rangeEnd) && (bounds[1] > rangeStart)) {
1152: LayoutInterval gap = new LayoutInterval(SINGLE);
1153: gap.setAttributes(interval.getAttributes());
1154: if ((bounds[0] < rangeStart) || (bounds[1] > rangeEnd)) {
1155: // Partial overlap with the provides space
1156: int min = interval.getMinimumSize();
1157: if (min >= 0)
1158: min = USE_PREFERRED_SIZE;
1159: int pref = Math.min(bounds[1], rangeEnd)
1160: - Math.max(bounds[0], rangeStart);
1161: int max = interval.getMaximumSize();
1162: if (max >= 0)
1163: max = USE_PREFERRED_SIZE;
1164: gap.setSizes(min, pref, max);
1165: } else {
1166: gap.setSizes(interval.getMinimumSize(), interval
1167: .getPreferredSize(), interval
1168: .getMaximumSize());
1169: }
1170: return gap;
1171: } else {
1172: // Outside the provided space
1173: return null;
1174: }
1175: }
1176: }
1177:
1178: /**
1179: * Helper method used by <code>restrictedCopy()</code> method.
1180: * Replaces empty spaces at the end of the sequential group
1181: * by an empty space of the specified size. Only empty spaces
1182: * with index >= boundary are replaced.
1183: *
1184: * @param seqGroup sequential group.
1185: * @param size size of the empty space that should be added.
1186: * @param boundary index in the sequential group that limits
1187: * the replacement of the empty spaces.
1188: */
1189: private void integrateGap(LayoutInterval seqGroup, int size,
1190: int boundary) {
1191: while ((seqGroup.getSubIntervalCount() > boundary)
1192: && seqGroup.getSubInterval(
1193: seqGroup.getSubIntervalCount() - 1)
1194: .isEmptySpace()) {
1195: layoutModel.removeInterval(seqGroup.getSubInterval(seqGroup
1196: .getSubIntervalCount() - 1));
1197: }
1198: if (size > 0) {
1199: LayoutInterval gap = new LayoutInterval(SINGLE);
1200: gap.setSize(size);
1201: layoutModel.addInterval(gap, seqGroup, -1);
1202: }
1203: }
1204:
1205: /**
1206: * Returns bounds (e.g. current space) of the empty space in the given dimension.
1207: *
1208: * @param emptySpace empty space.
1209: * @param dimenion dimension.
1210: * @return array whose the first item is the leading bound of the empty
1211: * space and the second item is the trailing bound.
1212: */
1213: private int[] emptySpaceBounds(LayoutInterval emptySpace,
1214: int dimension) {
1215: assert emptySpace.isEmptySpace();
1216: int leading, trailing;
1217: LayoutInterval parent = emptySpace.getParent();
1218: int index = parent.indexOf(emptySpace);
1219: if (index == 0) {
1220: leading = parent.getCurrentSpace().positions[dimension][LEADING];
1221: } else {
1222: leading = parent.getSubInterval(index - 1)
1223: .getCurrentSpace().positions[dimension][TRAILING];
1224: }
1225: if (index + 1 == parent.getSubIntervalCount()) {
1226: trailing = parent.getCurrentSpace().positions[dimension][TRAILING];
1227: } else {
1228: trailing = parent.getSubInterval(index + 1)
1229: .getCurrentSpace().positions[dimension][LEADING];
1230: }
1231: return new int[] { leading, trailing };
1232: }
1233:
1234: /**
1235: * Removes currently dragged components from layout model. Called when
1236: * the components were dragged out of the form (or to a container not
1237: * managed by this layout model).
1238: */
1239: public void removeDraggedComponents() {
1240: if (dragger != null) {
1241: LayoutComponent[] components = dragger
1242: .getMovingComponents();
1243: for (int i = 0; i < components.length; i++) {
1244: layoutModel.removeComponentAndIntervals(components[i],
1245: !components[i].isLayoutContainer());
1246: }
1247: endMoving(false);
1248: }
1249: }
1250:
1251: public void paintMoveFeedback(Graphics2D g) {
1252: if (dragger != null) { // Dragger might not be initialized yet
1253: dragger.paintMoveFeedback(g);
1254: }
1255: }
1256:
1257: /**
1258: * Paints layout information (alignment) for the selected component.
1259: *
1260: * @param g graphics object to use.
1261: * @param componentId ID of selected component.
1262: */
1263: public void paintSelection(Graphics2D g, String componentId) {
1264: LayoutComponent comp = layoutModel
1265: .getLayoutComponent(componentId);
1266: if ((comp != null) && (comp.getParent() != null)) {
1267: paintSelection(g, comp, HORIZONTAL);
1268: paintSelection(g, comp, VERTICAL);
1269: }
1270: }
1271:
1272: /**
1273: * Paints layout information (alignment) for the selected component
1274: * and specified dimension.
1275: *
1276: * @param g graphics object to use.
1277: * @param component selected layout component.
1278: * @param dimension dimension whose layout should be visualized.
1279: */
1280: private void paintSelection(Graphics2D g,
1281: LayoutComponent component, int dimension) {
1282: LayoutInterval interval = component
1283: .getLayoutInterval(dimension);
1284: if (component.isLinkSized(HORIZONTAL)
1285: || component.isLinkSized(VERTICAL)) {
1286: paintLinks(g, component);
1287: }
1288: // Paint baseline alignment
1289: if (interval.getAlignment() == BASELINE) {
1290: LayoutInterval alignedParent = interval.getParent();
1291: int oppDimension = (dimension == HORIZONTAL) ? VERTICAL
1292: : HORIZONTAL;
1293: LayoutRegion region = alignedParent.getCurrentSpace();
1294: int x = region.positions[dimension][BASELINE];
1295: int y1 = region.positions[oppDimension][LEADING];
1296: int y2 = region.positions[oppDimension][TRAILING];
1297: if ((y1 != LayoutRegion.UNKNOWN)
1298: && (y2 != LayoutRegion.UNKNOWN)) {
1299: if (dimension == HORIZONTAL) {
1300: g.drawLine(x, y1, x, y2);
1301: } else {
1302: g.drawLine(y1, x, y2, x);
1303: }
1304: }
1305: }
1306: int lastAlignment = -1;
1307: while (interval.getParent() != null) {
1308: LayoutInterval parent = interval.getParent();
1309: if (parent.getType() == SEQUENTIAL) {
1310: int alignment = LayoutInterval
1311: .getEffectiveAlignment(interval);
1312: int index = parent.indexOf(interval);
1313: int start, end;
1314: switch (alignment) {
1315: case LEADING:
1316: start = 0;
1317: end = index;
1318: lastAlignment = LEADING;
1319: break;
1320: case TRAILING:
1321: start = index + 1;
1322: end = parent.getSubIntervalCount();
1323: lastAlignment = TRAILING;
1324: break;
1325: default:
1326: switch (lastAlignment) {
1327: case LEADING:
1328: start = 0;
1329: end = index;
1330: break;
1331: case TRAILING:
1332: start = index + 1;
1333: end = parent.getSubIntervalCount();
1334: break;
1335: default:
1336: start = 0;
1337: end = parent.getSubIntervalCount();
1338: break;
1339: }
1340: }
1341: for (int i = start; i < end; i++) {
1342: LayoutInterval candidate = parent.getSubInterval(i);
1343: if (candidate.isEmptySpace()) {
1344: paintAlignment(
1345: g,
1346: candidate,
1347: dimension,
1348: LayoutInterval
1349: .getEffectiveAlignment(candidate));
1350: }
1351: }
1352: } else {
1353: int alignment = interval.getAlignment();
1354: if (!LayoutInterval.wantResizeInLayout(interval)) {
1355: lastAlignment = alignment;
1356: }
1357: paintAlignment(g, interval, dimension, lastAlignment);
1358: }
1359: interval = interval.getParent();
1360: }
1361: }
1362:
1363: private void paintLinks(Graphics2D g, LayoutComponent component) {
1364:
1365: if ((component.isLinkSized(HORIZONTAL))
1366: && (component.isLinkSized(VERTICAL))) {
1367: Map<Integer, List<String>> linkGroupsH = layoutModel
1368: .getLinkSizeGroups(HORIZONTAL);
1369: Map<Integer, List<String>> linkGroupsV = layoutModel
1370: .getLinkSizeGroups(VERTICAL);
1371: Integer linkIdH = new Integer(component
1372: .getLinkSizeId(HORIZONTAL));
1373: Integer linkIdV = new Integer(component
1374: .getLinkSizeId(VERTICAL));
1375:
1376: List<String> lH = linkGroupsH.get(linkIdH);
1377: List<String> lV = linkGroupsV.get(linkIdV);
1378:
1379: Set<String> merged = new HashSet<String>();
1380: for (int i = 0; i < lH.size(); i++) {
1381: merged.add(lH.get(i));
1382: }
1383: for (int i = 0; i < lV.size(); i++) {
1384: merged.add(lV.get(i));
1385: }
1386:
1387: Iterator<String> mergedIt = merged.iterator();
1388: while (mergedIt.hasNext()) {
1389: String id = mergedIt.next();
1390: LayoutComponent lc = layoutModel.getLayoutComponent(id);
1391: LayoutInterval interval = lc
1392: .getLayoutInterval(HORIZONTAL);
1393: LayoutRegion region = interval.getCurrentSpace();
1394: Image badge = null;
1395: if ((lV.contains(id)) && (lH.contains(id))) {
1396: badge = getLinkBadge(BOTH_DIMENSIONS);
1397: } else {
1398: if (lH.contains(lc.getId())) {
1399: badge = getLinkBadge(HORIZONTAL);
1400: }
1401: if (lV.contains(lc.getId())) {
1402: badge = getLinkBadge(VERTICAL);
1403: }
1404: }
1405: int x = region.positions[HORIZONTAL][TRAILING]
1406: - region.size(HORIZONTAL) / 4
1407: - (badge.getWidth(null) / 2);
1408: int y = region.positions[VERTICAL][LEADING]
1409: - (badge.getHeight(null));
1410: g.drawImage(badge, x, y, null);
1411: }
1412: } else {
1413: int dimension = (component.isLinkSized(HORIZONTAL)) ? HORIZONTAL
1414: : VERTICAL;
1415: Map map = layoutModel.getLinkSizeGroups(dimension);
1416:
1417: Integer linkId = new Integer(component
1418: .getLinkSizeId(dimension));
1419: List l = (List) map.get(linkId);
1420: Iterator mergedIt = l.iterator();
1421:
1422: while (mergedIt.hasNext()) {
1423: String id = (String) mergedIt.next();
1424: LayoutComponent lc = layoutModel.getLayoutComponent(id);
1425: LayoutInterval interval = lc
1426: .getLayoutInterval(dimension);
1427: LayoutRegion region = interval.getCurrentSpace();
1428: Image badge = getLinkBadge(dimension);
1429: int x = region.positions[HORIZONTAL][TRAILING]
1430: - region.size(HORIZONTAL) / 4
1431: - (badge.getWidth(null) / 2);
1432: int y = region.positions[VERTICAL][LEADING]
1433: - (badge.getHeight(null));
1434: g.drawImage(badge, x, y, null);
1435: }
1436: }
1437: }
1438:
1439: private Image linkBadgeBoth = null;
1440: private Image linkBadgeHorizontal = null;
1441: private Image linkBadgeVertical = null;
1442:
1443: private static final int BOTH_DIMENSIONS = 2;
1444:
1445: private Image getLinkBadge(int dimension) {
1446: if (dimension == (BOTH_DIMENSIONS)) {
1447: if (linkBadgeBoth == null) {
1448: linkBadgeBoth = Utilities
1449: .loadImage("org/netbeans/modules/form/resources/sameboth.png"); //NOI18N
1450: }
1451: return linkBadgeBoth;
1452: }
1453: if (dimension == HORIZONTAL) {
1454: if (linkBadgeHorizontal == null) {
1455: linkBadgeHorizontal = Utilities
1456: .loadImage("org/netbeans/modules/form/resources/samewidth.png"); //NOI18N
1457: }
1458: return linkBadgeHorizontal;
1459: }
1460: if (dimension == VERTICAL) {
1461: if (linkBadgeVertical == null) {
1462: linkBadgeVertical = Utilities
1463: .loadImage("org/netbeans/modules/form/resources/sameheight.png"); //NOI18N
1464: }
1465: return linkBadgeVertical;
1466: }
1467: return null;
1468: }
1469:
1470: private void paintAlignment(Graphics2D g, LayoutInterval interval,
1471: int dimension, int alignment) {
1472: LayoutInterval parent = interval.getParent();
1473: boolean baseline = parent.isParallel()
1474: && (parent.getGroupAlignment() == BASELINE);
1475: LayoutRegion group = parent.getCurrentSpace();
1476: int opposite = (dimension == HORIZONTAL) ? VERTICAL
1477: : HORIZONTAL;
1478: int x1, x2, y;
1479: if (interval.isEmptySpace()) {
1480: int index = parent.indexOf(interval);
1481: int[] ya, yb;
1482: boolean x1group, x2group;
1483: if (index == 0) {
1484: x1 = group.positions[dimension][baseline ? BASELINE
1485: : LEADING];
1486: ya = visualIntervalPosition(parent, opposite, LEADING);
1487: x1group = LayoutInterval.getFirstParent(interval,
1488: PARALLEL).getParent() != null;
1489: } else {
1490: LayoutInterval x1int = parent.getSubInterval(index - 1);
1491: if (x1int.isParallel()
1492: && (x1int.getGroupAlignment() == BASELINE)) {
1493: x1 = x1int.getCurrentSpace().positions[dimension][BASELINE];
1494: } else {
1495: if (x1int.isEmptySpace())
1496: return;
1497: x1 = x1int.getCurrentSpace().positions[dimension][TRAILING];
1498: }
1499: ya = visualIntervalPosition(x1int, opposite, TRAILING);
1500: x1group = x1int.isGroup();
1501: }
1502: if (index + 1 == parent.getSubIntervalCount()) {
1503: x2 = group.positions[dimension][baseline ? BASELINE
1504: : TRAILING];
1505: yb = visualIntervalPosition(parent, opposite, TRAILING);
1506: x2group = LayoutInterval.getFirstParent(interval,
1507: PARALLEL).getParent() != null;
1508: } else {
1509: LayoutInterval x2int = parent.getSubInterval(index + 1);
1510: if (x2int.isParallel()
1511: && (x2int.getGroupAlignment() == BASELINE)) {
1512: x2 = x2int.getCurrentSpace().positions[dimension][BASELINE];
1513: } else {
1514: if (x2int.isEmptySpace())
1515: return;
1516: x2 = x2int.getCurrentSpace().positions[dimension][LEADING];
1517: }
1518: yb = visualIntervalPosition(x2int, opposite, LEADING);
1519: x2group = x2int.isGroup();
1520: }
1521: if ((x1 == LayoutRegion.UNKNOWN)
1522: || (x2 == LayoutRegion.UNKNOWN))
1523: return;
1524: int y1 = Math.min(ya[1], yb[1]);
1525: int y2 = Math.max(ya[0], yb[0]);
1526: y = (y1 + y2) / 2;
1527: if ((ya[1] < yb[0]) || (yb[1] < ya[0])) {
1528: // no intersection
1529: if (dimension == HORIZONTAL) {
1530: g.drawLine(x1, ya[0], x1, y);
1531: g.drawLine(x1, ya[0], x1, ya[1]);
1532: g.drawLine(x2, yb[0], x2, y);
1533: g.drawLine(x2, yb[0], x2, yb[1]);
1534: } else {
1535: g.drawLine(ya[0], x1, y, x1);
1536: g.drawLine(ya[0], x1, ya[1], x1);
1537: g.drawLine(yb[0], x2, y, x2);
1538: g.drawLine(yb[0], x2, yb[1], x2);
1539: }
1540: } else {
1541: if (dimension == HORIZONTAL) {
1542: if (x1group)
1543: g.drawLine(x1, ya[0], x1, ya[1]);
1544: if (x2group)
1545: g.drawLine(x2, yb[0], x2, yb[1]);
1546: } else {
1547: if (x1group)
1548: g.drawLine(ya[0], x1, ya[1], x1);
1549: if (x2group)
1550: g.drawLine(yb[0], x2, yb[1], x2);
1551: }
1552: }
1553: } else {
1554: LayoutRegion child = interval.getCurrentSpace();
1555: if ((alignment == LEADING) || (alignment == TRAILING)) {
1556: x1 = group.positions[dimension][baseline ? BASELINE
1557: : alignment];
1558: if (interval.isParallel()
1559: && (interval.getAlignment() == BASELINE)) {
1560: x2 = child.positions[dimension][BASELINE];
1561: } else {
1562: x2 = child.positions[dimension][alignment];
1563: }
1564: } else {
1565: return;
1566: }
1567: if ((x1 == LayoutRegion.UNKNOWN)
1568: || (x2 == LayoutRegion.UNKNOWN))
1569: return;
1570: int[] pos = visualIntervalPosition(parent, opposite,
1571: alignment);
1572: y = (pos[0] + pos[1]) / 2;
1573: int xa = group.positions[dimension][LEADING];
1574: int xb = group.positions[dimension][TRAILING];
1575: if (parent.getParent() != null) {
1576: if (dimension == HORIZONTAL) {
1577: if (alignment == LEADING) {
1578: g.drawLine(xa, pos[0], xa, pos[1]);
1579: } else if (alignment == TRAILING) {
1580: g.drawLine(xb, pos[0], xb, pos[1]);
1581: }
1582: } else {
1583: if (alignment == LEADING) {
1584: g.drawLine(pos[0], xa, pos[1], xa);
1585: } else if (alignment == TRAILING) {
1586: g.drawLine(pos[0], xb, pos[1], xb);
1587: }
1588: }
1589: }
1590: }
1591: // Avoid overload of EQ when current space is incorrectly calculated.
1592: if ((x2 - x1 > 1) && (Math.abs(y) <= Short.MAX_VALUE)
1593: && (Math.abs(x1) <= Short.MAX_VALUE)
1594: && (Math.abs(x2) <= Short.MAX_VALUE)) {
1595: int x, angle;
1596: if (alignment == LEADING) {
1597: x = x1;
1598: angle = 180;
1599: } else {
1600: x = x2;
1601: angle = 0;
1602: }
1603: x2--;
1604: int diam = Math.min(4, x2 - x1);
1605: Stroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT,
1606: BasicStroke.JOIN_BEVEL, 0, new float[] { 1, 1 }, 0);
1607: Stroke oldStroke = g.getStroke();
1608: g.setStroke(stroke);
1609: if (dimension == HORIZONTAL) {
1610: g.drawLine(x1, y, x2, y);
1611: angle += 90;
1612: } else {
1613: g.drawLine(y, x1, y, x2);
1614: int temp = x;
1615: x = y;
1616: y = temp;
1617: }
1618: g.setStroke(oldStroke);
1619: if ((alignment == LEADING) || (alignment == TRAILING)) {
1620: Object hint = g
1621: .getRenderingHint(RenderingHints.KEY_ANTIALIASING);
1622: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1623: RenderingHints.VALUE_ANTIALIAS_ON);
1624: g.fillArc(x - diam, y - diam, 2 * diam, 2 * diam,
1625: angle, 180);
1626: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1627: hint);
1628: }
1629: }
1630: }
1631:
1632: private int[] visualIntervalPosition(LayoutInterval interval,
1633: int dimension, int alignment) {
1634: int min = Short.MAX_VALUE;
1635: int max = Short.MIN_VALUE;
1636: if (interval.isParallel()
1637: && (interval.getGroupAlignment() != BASELINE)) {
1638: Iterator iter = interval.getSubIntervals();
1639: while (iter.hasNext()) {
1640: LayoutInterval subInterval = (LayoutInterval) iter
1641: .next();
1642: int imin, imax;
1643: int oppDim = (dimension == HORIZONTAL) ? VERTICAL
1644: : HORIZONTAL;
1645: if (LayoutInterval.isPlacedAtBorder(subInterval,
1646: oppDim, alignment)) {
1647: if (subInterval.isParallel()) {
1648: int[] ipos = visualIntervalPosition(
1649: subInterval, dimension, alignment);
1650: imin = ipos[0];
1651: imax = ipos[1];
1652: } else if (!subInterval.isEmptySpace()) {
1653: LayoutRegion region = subInterval
1654: .getCurrentSpace();
1655: imin = region.positions[dimension][LEADING];
1656: imax = region.positions[dimension][TRAILING];
1657: } else {
1658: imin = min;
1659: imax = max;
1660: }
1661: } else {
1662: imin = min;
1663: imax = max;
1664: }
1665: if (min > imin)
1666: min = imin;
1667: if (max < imax)
1668: max = imax;
1669: }
1670: }
1671: if (!interval.isParallel() || (min == Short.MAX_VALUE)) {
1672: LayoutRegion region = interval.getCurrentSpace();
1673: min = region.positions[dimension][LEADING];
1674: max = region.positions[dimension][TRAILING];
1675: }
1676: return new int[] { min, max };
1677: }
1678:
1679: // -----
1680: // copying
1681:
1682: /**
1683: * Copy components with layout specified by sourceToTargetId map to
1684: * the given target container. Assuming all components come from one
1685: * container and one layer (layout roots pair), i.e. forming one piece of
1686: * layout.
1687: * If copying all components of one container to an empty container then
1688: * exact 1:1 copy is created, otherwise the layout copy is placed into a
1689: * separate layer not to interact with the existing layout.
1690: * If copying within the same container, the copied components are placed
1691: * slightly shifted from the original ones. Otherwise placed in the center.
1692: * This method requires Ids for the copied components provided
1693: * (LayoutComponent instances are cretated automatically).
1694: * @param sourceModel the source LayoutModel
1695: * @param sourceToTargetId components mapping between the original and the copied
1696: * components' Ids; same Ids can be used - then the components are
1697: * moved
1698: * @param targetContainerId the Id of the target container
1699: */
1700: public void copyLayout(LayoutModel sourceModel,
1701: Map<String, String> sourceToTargetId,
1702: String targetContainerId) {
1703: if (sourceToTargetId.size() == 0) {
1704: return;
1705: }
1706: if (sourceModel == null) {
1707: sourceModel = layoutModel;
1708: }
1709:
1710: Map.Entry<String, String> firstEntry = sourceToTargetId
1711: .entrySet().iterator().next();
1712: LayoutComponent sourceContainer = sourceModel
1713: .getLayoutComponent(firstEntry.getKey()).getParent();
1714: LayoutComponent targetContainer = layoutModel
1715: .getLayoutComponent(targetContainerId);
1716: if (sourceContainer != targetContainer
1717: && sourceContainer.getSubComponentCount() == sourceToTargetId
1718: .size()
1719: && (targetContainer == null || targetContainer
1720: .getSubComponentCount() == 0)) {
1721: // copying/moving entire content of a container into an empty target container
1722: if (sourceModel != layoutModel
1723: || !firstEntry.getKey().equals(
1724: firstEntry.getValue())) {
1725: layoutModel.copyContainerLayout(sourceContainer,
1726: sourceToTargetId, targetContainer);
1727: } else { // same source and target component - don't copy, just move
1728: layoutModel.moveContainerLayout(sourceContainer,
1729: targetContainer);
1730: }
1731: } else { // copying part of the layout
1732: // collect the components, create new if needed, compute bounds, ...
1733: Map<LayoutComponent, LayoutComponent> sourceToTargetComp = new HashMap<LayoutComponent, LayoutComponent>();
1734: LayoutComponent[] sourceComponents = new LayoutComponent[sourceToTargetId
1735: .size()];
1736: LayoutComponent[] targetComponents = new LayoutComponent[sourceToTargetId
1737: .size()];
1738: Rectangle[] bounds = new Rectangle[sourceToTargetId.size()];
1739: LayoutRegion overallSpace = new LayoutRegion();
1740: LayoutInterval[] commonParents = new LayoutInterval[DIM_COUNT];
1741: int i = 0;
1742: for (Map.Entry<String, String> entry : sourceToTargetId
1743: .entrySet()) {
1744: String sourceId = entry.getKey();
1745: LayoutComponent sourceLC = sourceModel
1746: .getLayoutComponent(sourceId);
1747: String targetId = entry.getValue();
1748: LayoutComponent targetLC = layoutModel
1749: .getLayoutComponent(targetId);
1750: if (targetLC == null) {
1751: targetLC = new LayoutComponent(targetId, false);
1752: } else if (targetLC.getParent() == targetContainer) {
1753: throw new IllegalArgumentException(
1754: "The component is already placed in the target layout container"); // NOI18N
1755: }
1756: sourceToTargetComp.put(sourceLC, targetLC);
1757: targetComponents[i] = targetLC;
1758:
1759: LayoutRegion space = sourceLC.getLayoutInterval(0)
1760: .getCurrentSpace();
1761: overallSpace.expand(space);
1762: bounds[i] = space.toRectangle(new Rectangle());
1763: sourceComponents[i] = sourceLC;
1764: i++;
1765:
1766: for (int dim = 0; dim < DIM_COUNT; dim++) {
1767: if (commonParents[dim] == null) {
1768: commonParents[dim] = sourceLC
1769: .getLayoutInterval(dim);
1770: } else {
1771: commonParents[dim] = LayoutInterval
1772: .getCommonParent(commonParents[dim],
1773: sourceLC.getLayoutInterval(dim));
1774: }
1775: }
1776: }
1777:
1778: // copy the intervals
1779: LayoutInterval[] addingInts = new LayoutInterval[DIM_COUNT];
1780: for (int dim = 0; dim < DIM_COUNT; dim++) {
1781: addingInts[dim] = restrictedCopy(commonParents[dim],
1782: sourceToTargetComp, overallSpace, dim, null);
1783: }
1784: // in case components are moved (not copied) make sure both the
1785: // components and intervals are correctly removed from the original place
1786: if (targetComponents.length > 1) {
1787: // for multiple components the intervals are extracted in restrictedCopy
1788: // (their common parent is removed), so now also remove the components
1789: // so addComponents does not try to remove the intervals later
1790: for (LayoutComponent comp : targetComponents) {
1791: if (comp.getParent() != null) {
1792: layoutModel.removeComponent(comp, false);
1793: }
1794: }
1795: } // for single component both the component and intervals will be removed in addComponents
1796:
1797: // place the copied intervals
1798: int[] shift = getCopyShift(sourceComponents,
1799: targetContainer, overallSpace,
1800: sourceContainer == targetContainer);
1801: if (shift != null) {
1802: prepareDragger(targetComponents, bounds,
1803: new Point(0, 0), LayoutDragger.ALL_EDGES);
1804: dragger.setTargetContainer(targetContainer,
1805: getTargetRootsForCopy(targetContainer));
1806: dragger.move(shift, false, false);
1807: addComponents(targetComponents, targetContainer,
1808: addingInts, false);
1809: dragger = null;
1810: } else {
1811: addUnspecified(targetComponents, targetContainer,
1812: addingInts);
1813: }
1814: }
1815: visualStateUpToDate = false;
1816: }
1817:
1818: /**
1819: * Copy components without layout to the given target container. The layout
1820: * is determined from the visual bounds of the components. Can be used for
1821: * copying external components (from different layout) to a container, or
1822: * for converting container's layout to this layout model. New
1823: * LayoutComponent instances are created as needed, added all to the
1824: * specified target container (centered, in separate layer of layout roots).
1825: * @param idToBounds mapping component Ids to the visual bounds
1826: * @param targetContainerId the Id of the target container
1827: * @param relative if true then the position of the entire formation
1828: * is set to 0, 0 before the conversion starts
1829: */
1830: public void copyLayoutFromOutside(
1831: Map<String, Rectangle> idToBounds,
1832: String targetContainerId, boolean relative) {
1833: LayoutComponent targetContainer = layoutModel
1834: .getLayoutComponent(targetContainerId);
1835: if (targetContainer.getSubComponentCount() > 0) {
1836: relative = true;
1837: }
1838: Map<LayoutComponent, Rectangle> compToBounds = new HashMap<LayoutComponent, Rectangle>();
1839: LayoutComponent[] components = new LayoutComponent[idToBounds
1840: .size()];
1841: int minX = Integer.MAX_VALUE;
1842: int minY = Integer.MAX_VALUE;
1843: int i = 0;
1844: for (Map.Entry<String, Rectangle> entry : idToBounds.entrySet()) {
1845: String targetId = entry.getKey();
1846: LayoutComponent targetLC = layoutModel
1847: .getLayoutComponent(targetId);
1848: if (targetLC == null) {
1849: targetLC = new LayoutComponent(targetId, false);
1850: } else if (targetLC.getParent() != null) {
1851: throw new IllegalArgumentException(
1852: "Target component already exists and is placed in the layout"); // NOI18N
1853: }
1854: Rectangle r = new Rectangle(entry.getValue());
1855: compToBounds.put(targetLC, r);
1856: components[i] = targetLC;
1857: LayoutRegion compSpace = new LayoutRegion(r,
1858: LayoutRegion.UNKNOWN);
1859: Dimension preferred = visualMapper
1860: .getComponentPreferredSize(targetId);
1861: for (int dim = 0; dim < DIM_COUNT; dim++) {
1862: int size = compSpace.size(dim);
1863: if (preferred == null
1864: || size != ((dim == HORIZONTAL) ? preferred.width
1865: : preferred.height)) {
1866: targetLC.getLayoutInterval(dim).setPreferredSize(
1867: compSpace.size(dim));
1868: }
1869: }
1870: if (relative) {
1871: minX = Math.min(minX,
1872: compSpace.positions[HORIZONTAL][LEADING]);
1873: minY = Math.min(minY,
1874: compSpace.positions[VERTICAL][LEADING]);
1875: }
1876: i++;
1877: }
1878: if (relative) {
1879: // need the overall bounds enclosure based on 0, 0
1880: for (Rectangle r : compToBounds.values()) {
1881: r.x -= minX;
1882: r.y -= minY;
1883: }
1884: }
1885: LayoutInterval[] addingInts = LayoutModel
1886: .createIntervalsFromBounds(compToBounds);
1887: if (relative) {
1888: addUnspecified(components, targetContainer, addingInts);
1889: } else {
1890: addToEmpty(components, targetContainer, addingInts);
1891: }
1892: visualStateUpToDate = false;
1893: }
1894:
1895: /**
1896: * Duplicates layout of given components. Duplicated components are added
1897: * sequentially along given axis (dimension), in parallel in the orthogonal
1898: * dimension.
1899: * @param sourceIds Ids of the source components
1900: * @param targetIds Ids for the duplicates (non-existing layout components
1901: * are created automatically with the given Ids)
1902: * @param dimension the dimension in which the layout should be duplicated
1903: * (extended sequentially); HORIZONTAL, VERTICAL, or < 0
1904: * @param direction the direction of addition - LEADING or TRAILING
1905: */
1906: public void duplicateLayout(String[] sourceIds, String[] targetIds,
1907: int dimension, int direction) {
1908: LayoutComponent[] sourceComps = new LayoutComponent[sourceIds.length];
1909: LayoutInterval[][] sourceIntervals = new LayoutInterval[DIM_COUNT][sourceIds.length];
1910: LayoutComponent[] targetComps = new LayoutComponent[targetIds.length];
1911: Map<LayoutComponent, LayoutComponent> compMap = new HashMap<LayoutComponent, LayoutComponent>();
1912: LayoutComponent container = null;
1913: for (int i = 0; i < sourceComps.length; i++) {
1914: LayoutComponent sourceLC = layoutModel
1915: .getLayoutComponent(sourceIds[i]);
1916: LayoutComponent parent = sourceLC.getParent();
1917: if (i == 0) {
1918: container = parent;
1919: } else if (parent != container) {
1920: throw new IllegalArgumentException(
1921: "Duplicated components must be in the same container."); // NOI18N
1922: }
1923: sourceComps[i] = sourceLC;
1924:
1925: LayoutComponent targetLC = layoutModel
1926: .getLayoutComponent(targetIds[i]);
1927: if (targetLC == null) {
1928: targetLC = new LayoutComponent(targetIds[i], false);
1929: } else if (targetLC.getParent() != null) {
1930: throw new IllegalArgumentException(
1931: "Target component already exists and is placed in the layout"); // NOI18N
1932: }
1933: layoutModel.addComponent(targetLC, container, -1);
1934: compMap.put(sourceLC, targetLC);
1935: targetComps[i] = targetLC;
1936: for (int dim = 0; dim < DIM_COUNT; dim++) {
1937: LayoutInterval li = sourceLC.getLayoutInterval(dim);
1938: sourceIntervals[dim][i] = li;
1939: }
1940: }
1941:
1942: int seqDim = dimension < 0 ? getSeqDuplicatingDimension(sourceComps)
1943: : dimension;
1944: int seqDir = direction < 0 ? getSeqDuplicatingDirection(
1945: sourceComps, seqDim) : direction;
1946: int parDim = seqDim ^ 1;
1947: try {
1948: modelListener.deactivate(); // do not react on model changes during adding
1949:
1950: duplicateSequentially(sourceIntervals[seqDim], compMap,
1951: seqDim, seqDir);
1952: duplicateInParallel(sourceIntervals[parDim], compMap,
1953: parDim);
1954:
1955: visualStateUpToDate = false;
1956: optimizeStructure = true;
1957: updateDesignModifications(container);
1958: } finally {
1959: if (!modelListener.isActive()) {
1960: modelListener.activate();
1961: }
1962: }
1963: }
1964:
1965: private static int getSeqDuplicatingDimension(
1966: LayoutComponent[] components) {
1967: return VERTICAL;
1968: }
1969:
1970: private static int getSeqDuplicatingDirection(
1971: LayoutComponent[] components, int dimension) {
1972: return TRAILING;
1973: }
1974:
1975: private void duplicateSequentially(LayoutInterval[] intervals,
1976: Map<LayoutComponent, LayoutComponent> componentMap,
1977: int dimension, int direction) {
1978: // get the root groups/components to duplicate, prepare parent sequences
1979: Set<LayoutInterval> dupRoots = new HashSet<LayoutInterval>();
1980: Set<LayoutInterval> dupParents = new HashSet<LayoutInterval>();
1981: for (LayoutInterval li : intervals) {
1982: LayoutInterval parent = li.getParent();
1983: while (parent != null) {
1984: if (dupRoots.contains(parent)) {
1985: break;
1986: } else {
1987: parent = parent.getParent();
1988: }
1989: }
1990: if (parent == null) { // interval not known yet
1991: parent = li.getParent();
1992: while (parent != null) {
1993: if (shouldDuplicateWholeGroup(parent, li, intervals)) {
1994: li = parent;
1995: parent = li.getParent();
1996: } else {
1997: dupRoots.add(li);
1998: LayoutInterval targetSeq;
1999: if (li.isSequential()) {
2000: targetSeq = li;
2001: } else if (parent.isSequential()) {
2002: targetSeq = parent;
2003: } else {
2004: LayoutInterval seq = new LayoutInterval(
2005: SEQUENTIAL);
2006: layoutModel.addInterval(seq, parent,
2007: layoutModel.removeInterval(li));
2008: layoutModel.addInterval(li, seq, -1);
2009: targetSeq = seq;
2010: }
2011: dupParents.add(targetSeq);
2012: break;
2013: }
2014: }
2015: if (parent == null) { // the root duplicated
2016: parent = li;
2017: LayoutInterval group = new LayoutInterval(PARALLEL);
2018: group.setGroupAlignment(parent.getGroupAlignment());
2019: while (parent.getSubIntervalCount() > 0) {
2020: layoutModel.addInterval(layoutModel
2021: .removeInterval(parent, 0), group, -1);
2022: }
2023: LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
2024: layoutModel.addInterval(seq, parent, -1);
2025: layoutModel.addInterval(group, seq, -1);
2026: dupParents.add(seq);
2027: }
2028: }
2029: }
2030:
2031: // duplicate...
2032: for (LayoutInterval seq : dupParents) {
2033: int start = -1;
2034: boolean wholeSeq = dupRoots.contains(seq);
2035: LayoutRegion space = seq.getParent().getCurrentSpace();
2036: for (int i = 0; i < seq.getSubIntervalCount(); i++) {
2037: LayoutInterval sub = seq.getSubInterval(i);
2038: boolean duplicate = !sub.isEmptySpace()
2039: && (wholeSeq || dupRoots.contains(sub));
2040: boolean last = (i + 1 == seq.getSubIntervalCount());
2041: if (duplicate && start < 0) {
2042: start = i;
2043: }
2044: if (start >= 0
2045: && ((!duplicate && !sub.isEmptySpace()) || last)) {
2046: int count = i - start;
2047: if (last) {
2048: if (!sub.isEmptySpace()) {
2049: count++;
2050: }
2051: } else if (seq.getSubInterval(i - 1).isEmptySpace()) {
2052: count--;
2053: }
2054: if (count > 0) { // copy the continuous section within the sequence
2055: for (int j = start; j < start + count; j++) {
2056: LayoutInterval li = seq.getSubInterval(j);
2057: LayoutInterval copy = restrictedCopy(li,
2058: componentMap, space, dimension,
2059: null);
2060: if (direction == LEADING) {
2061: layoutModel.addInterval(copy, seq,
2062: start);
2063: start++;
2064: i++;
2065: j++;
2066: } else { // TRAILING
2067: layoutModel.addInterval(copy, seq, j
2068: + count);
2069: i++;
2070: }
2071: }
2072: // need a gap between the original and duplicated section
2073: int gapIndex;
2074: LayoutInterval gap = null;
2075: if (direction == LEADING) {
2076: gapIndex = start + count; // here should be the original gap to use
2077: if (gapIndex < seq.getSubIntervalCount()) {
2078: gap = seq.getSubInterval(gapIndex);
2079: }
2080: gapIndex = start; // here it should be placed
2081: } else { // TRAILING
2082: gapIndex = start - 1; // here should be the original gap to use
2083: if (gapIndex >= 0) {
2084: gap = seq.getSubInterval(gapIndex);
2085: }
2086: gapIndex = start + count; // here it should be placed
2087: }
2088: LayoutInterval newGap = new LayoutInterval(
2089: SINGLE);
2090: if (gap != null && gap.isEmptySpace()) {
2091: LayoutInterval.cloneInterval(gap, newGap);
2092: }
2093: layoutModel.addInterval(newGap, seq, gapIndex);
2094: i++;
2095: }
2096: start = -1;
2097: }
2098: }
2099: }
2100: }
2101:
2102: private static boolean shouldDuplicateWholeGroup(
2103: LayoutInterval group, LayoutInterval knownSub,
2104: LayoutInterval[] dupIntervals) {
2105: assert group.isGroup();
2106: Iterator it = group.getSubIntervals();
2107: while (it.hasNext()) {
2108: LayoutInterval sub = (LayoutInterval) it.next();
2109: if (sub == knownSub || sub.isEmptySpace()) {
2110: continue;
2111: }
2112: boolean included;
2113: if (sub.isGroup()) {
2114: included = shouldDuplicateWholeGroup(sub, null,
2115: dupIntervals);
2116: } else {
2117: assert sub.isComponent();
2118: included = false;
2119: for (LayoutInterval li : dupIntervals) {
2120: if (li == sub) {
2121: included = true;
2122: break;
2123: }
2124: }
2125: }
2126: if (included && group.isParallel()) {
2127: return true; // one is enough in parallel group
2128: }
2129: if (!included && group.isSequential()) {
2130: return false; // all required in a sequence
2131: }
2132: }
2133: return group.isSequential();
2134: }
2135:
2136: private void duplicateInParallel(LayoutInterval[] intervals,
2137: Map<LayoutComponent, LayoutComponent> componentMap,
2138: int dimension) {
2139: Map<LayoutInterval, LayoutInterval> intMap = new HashMap<LayoutInterval, LayoutInterval>();
2140: for (LayoutInterval li : intervals) {
2141: intMap.put(li, componentMap.get(li.getComponent())
2142: .getLayoutInterval(dimension));
2143: }
2144: for (LayoutInterval li : intervals) {
2145: LayoutInterval copy = intMap.get(li);
2146: if (copy == null) {
2147: continue;
2148: }
2149: LayoutInterval parent = li.getParent();
2150: if (parent.isParallel()) {
2151: LayoutInterval.cloneInterval(li, copy);
2152: layoutModel.setIntervalAlignment(copy, li
2153: .getRawAlignment());
2154: layoutModel.addInterval(copy, parent, -1);
2155: } else { // in sequence
2156: int index = parent.indexOf(li);
2157: int start = getDuplicationBoundary(parent, index,
2158: intMap.keySet(), LEADING);
2159: int end = getDuplicationBoundary(parent, index, intMap
2160: .keySet(), TRAILING);
2161: int gapStart = LayoutUtils.getVisualPosition(parent
2162: .getSubInterval(start), dimension, LEADING);
2163: LayoutInterval normalGap = null;
2164: boolean substGap = false;
2165: LayoutInterval parSeq = new LayoutInterval(SEQUENTIAL);
2166: for (int i = start; i <= end; i++) {
2167: LayoutInterval sub = parent.getSubInterval(i);
2168: copy = intMap.remove(sub);
2169: if (copy != null) {
2170: LayoutInterval.cloneInterval(sub, copy);
2171: if (normalGap != null) {
2172: LayoutInterval copyGap = new LayoutInterval(
2173: SINGLE);
2174: LayoutInterval.cloneInterval(normalGap,
2175: copyGap);
2176: layoutModel
2177: .addInterval(copyGap, parSeq, -1);
2178: normalGap = null;
2179: } else if (substGap) {
2180: LayoutInterval gap = new LayoutInterval(
2181: SINGLE);
2182: int gapEnd = sub.getCurrentSpace().positions[dimension][LEADING];
2183: gap.setSize(gapEnd - gapStart);
2184: layoutModel.addInterval(gap, parSeq, -1);
2185: substGap = false;
2186: }
2187: layoutModel.addInterval(copy, parSeq, -1);
2188: gapStart = sub.getCurrentSpace().positions[dimension][TRAILING];
2189: } else if (!sub.isEmptySpace()) { // skipped component
2190: normalGap = null;
2191: substGap = true;
2192: } else if (!substGap) { // normal gap
2193: normalGap = sub;
2194: }
2195: }
2196: if (normalGap != null) {
2197: LayoutInterval copyGap = new LayoutInterval(SINGLE);
2198: LayoutInterval.cloneInterval(normalGap, copyGap);
2199: layoutModel.addInterval(copyGap, parSeq, -1);
2200: } else if (substGap) {
2201: LayoutInterval gap = new LayoutInterval(SINGLE);
2202: int gapEnd = LayoutUtils.getVisualPosition(parent
2203: .getSubInterval(end), dimension, TRAILING);
2204: gap.setSize(gapEnd - gapStart);
2205: layoutModel.addInterval(gap, parSeq, -1);
2206: }
2207: operations.addParallelWithSequence(parSeq, parent,
2208: start, end, dimension);
2209: }
2210: }
2211: }
2212:
2213: private static int getDuplicationBoundary(LayoutInterval seq,
2214: int index, Set<LayoutInterval> dupIntervals, int direction) {
2215: assert seq.isSequential();
2216: int d = (direction == LEADING ? -1 : 1);
2217: index += d;
2218: while (index >= 0 && index < seq.getSubIntervalCount()) {
2219: LayoutInterval sub = seq.getSubInterval(index);
2220: if (sub.isParallel()) {
2221: break; // [This forces enclosing the component in a closed group
2222: // which might be unnecessary (at least in horizontal dimension, in
2223: // vertical it is most probably ok). Maybe we should parallelize with
2224: // parallel members if they don't contain another duplicated component.]
2225: }
2226: index += d;
2227: }
2228: return index - d;
2229: }
2230:
2231: public void encloseInContainer(String[] compIds, String contId) {
2232: LayoutComponent enclosingCont = layoutModel
2233: .getLayoutComponent(contId);
2234: if (enclosingCont == null) {
2235: enclosingCont = new LayoutComponent(contId, true);
2236: } else if (enclosingCont.getParent() != null) {
2237: throw new IllegalArgumentException(
2238: "Target container already exists and is placed in the layout"); // NOI18N
2239: } else if (enclosingCont.getSubComponentCount() > 0) {
2240: throw new IllegalArgumentException(
2241: "Target container is not empty."); // NOI18N
2242: }
2243: LayoutComponent parentCont = null;
2244: LayoutComponent[] components = new LayoutComponent[compIds.length];
2245: // LayoutComponent[][] borderComps = new LayoutComponent[DIM_COUNT][2];
2246: LayoutInterval[] commonParents = new LayoutInterval[DIM_COUNT];
2247: boolean[] resizing = new boolean[DIM_COUNT];
2248: Map<LayoutComponent, LayoutComponent> compMap = new HashMap<LayoutComponent, LayoutComponent>();
2249: LayoutRegion overallSpace = new LayoutRegion();
2250: int i = 0;
2251: for (String id : compIds) {
2252: LayoutComponent comp = layoutModel.getLayoutComponent(id);
2253: components[i++] = comp;
2254: compMap.put(comp, comp);
2255:
2256: if (parentCont == null) {
2257: parentCont = comp.getParent();
2258: }
2259: LayoutRegion space = comp.getLayoutInterval(0)
2260: .getCurrentSpace();
2261:
2262: for (int dim = 0; dim < DIM_COUNT; dim++) {
2263: // if (!overallSpace.isSet(dim) || space.positions[dim][LEADING] < overallSpace.positions[dim][LEADING]) {
2264: // borderComps[dim][LEADING] = comp;
2265: // } else if (!overallSpace.isSet(dim) || space.positions[dim][TRAILING] > overallSpace.positions[dim][TRAILING]) {
2266: // borderComps[dim][TRAILING] = comp;
2267: // }
2268: overallSpace.expand(space, dim);
2269:
2270: if (commonParents[dim] == null) {
2271: commonParents[dim] = comp.getLayoutInterval(dim);
2272: } else {
2273: commonParents[dim] = LayoutInterval
2274: .getCommonParent(commonParents[dim], comp
2275: .getLayoutInterval(dim));
2276: }
2277: }
2278: }
2279: LayoutInterval[] parentRoots = new LayoutInterval[DIM_COUNT];
2280: for (int dim = 0; dim < DIM_COUNT; dim++) {
2281: parentRoots[dim] = LayoutInterval.getRoot(components[0]
2282: .getLayoutInterval(dim));
2283: resizing[dim] = LayoutInterval
2284: .wantResize(commonParents[dim]);
2285: }
2286: if (enclosingCont.isLayoutContainer()) {
2287: LayoutInterval[] extractedInts = new LayoutInterval[DIM_COUNT];
2288: for (int dim = 0; dim < DIM_COUNT; dim++) {
2289: LayoutInterval extract = commonParents[dim];
2290: if (extract.isComponent()) { // just one component being enclosed
2291: layoutModel.removeInterval(extract);
2292: extractedInts[dim] = extract;
2293: } else {
2294: extractedInts[dim] = restrictedCopy(extract,
2295: compMap, overallSpace, dim, null);
2296: }
2297: }
2298: for (LayoutComponent comp : components) {
2299: layoutModel.removeComponent(comp, false);
2300: layoutModel.addComponent(comp, enclosingCont, -1);
2301: }
2302: for (int dim = 0; dim < DIM_COUNT; dim++) {
2303: // make sure the root is empty (clear possible "offset" gap)
2304: LayoutInterval root = enclosingCont
2305: .getDefaultLayoutRoot(dim);
2306: for (int n = root.getSubIntervalCount(); n > 0; n--) {
2307: root.remove(n - 1);
2308: }
2309: assert root.isParallel();
2310: LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
2311: seq.add(new LayoutInterval(SINGLE), -1);
2312: layoutModel.addInterval(extractedInts[dim], seq, -1);
2313: seq.add(new LayoutInterval(SINGLE), -1);
2314: layoutModel.addInterval(seq, root, -1);
2315: }
2316: } else {
2317: for (LayoutComponent comp : components) {
2318: layoutModel.removeComponentAndIntervals(comp, !comp
2319: .isLayoutContainer());
2320: }
2321: }
2322: LayoutInterval[] addingInts = new LayoutInterval[DIM_COUNT];
2323: for (int dim = 0; dim < DIM_COUNT; dim++) {
2324: LayoutInterval interval = enclosingCont
2325: .getLayoutInterval(dim);
2326: addingInts[dim] = interval;
2327: interval.setSizes(USE_PREFERRED_SIZE, DEFAULT,
2328: resizing[dim] ? Short.MAX_VALUE
2329: : USE_PREFERRED_SIZE);
2330: }
2331: // provisionally use dragger to position the container; maybe could be done better
2332: prepareDragger(new LayoutComponent[] { enclosingCont },
2333: new Rectangle[] { overallSpace
2334: .toRectangle(new Rectangle()) },
2335: new Point(0, 0), LayoutDragger.ALL_EDGES);
2336: dragger.setTargetContainer(parentCont, parentRoots);
2337: dragger.move(new int[] { 10, 10 }, true, false);
2338: dragger.move(new int[] { 0, 0 }, true, false);
2339: addComponents(new LayoutComponent[] { enclosingCont },
2340: parentCont, addingInts, false);
2341: dragger = null;
2342: visualStateUpToDate = false;
2343: }
2344:
2345: /**
2346: * Add a single new component (targetId) to given target container. No layout
2347: * information is provided, so the component is placed on a default location
2348: * in the layout. This means centered within the container, in a separate
2349: * layer of layout roots (not to interact with the rest of the layout).
2350: * The size of the component can be determined either by a "source"
2351: * component (typically if adding a copy of another component), or by
2352: * provided Dimension object (compSize).
2353: * @param targetId Id of the added component; may already be in the model,
2354: * but not in a container; created automatically if needed
2355: * @param sourceId Id of the source component - optional - if provided, the
2356: * size definition of this component is copied to the added component
2357: * @param compSize the size of the component - used if the source component
2358: * is not provided to determine the size from; can be null
2359: * @param targetContainerId the Id of the target container (must exist in the model)
2360: */
2361: public void addUnspecifiedComponent(String targetId,
2362: String sourceId, Dimension compSize,
2363: String targetContainerId) {
2364: LayoutComponent targetContainer = layoutModel
2365: .getLayoutComponent(targetContainerId);
2366: LayoutComponent targetLC = layoutModel
2367: .getLayoutComponent(targetId);
2368: if (targetLC == null) {
2369: targetLC = new LayoutComponent(targetId, false);
2370: } else if (targetLC.getParent() != null) {
2371: throw new IllegalArgumentException(
2372: "Target component already exists and is placed in the layout"); // NOI18N
2373: }
2374: LayoutComponent sourceLC = layoutModel
2375: .getLayoutComponent(sourceId);
2376: LayoutInterval[] addingInts = new LayoutInterval[DIM_COUNT];
2377: if (sourceLC != null) {
2378: for (int dim = 0; dim < DIM_COUNT; dim++) {
2379: LayoutInterval li = sourceLC.getLayoutInterval(dim);
2380: addingInts[dim] = LayoutInterval.cloneInterval(li,
2381: targetLC.getLayoutInterval(dim));
2382: }
2383: } else {
2384: Dimension preferred = compSize != null ? visualMapper
2385: .getComponentPreferredSize(targetId) : null;
2386: for (int dim = 0; dim < DIM_COUNT; dim++) {
2387: LayoutInterval li = targetLC.getLayoutInterval(dim);
2388: addingInts[dim] = li;
2389: if (preferred != null) {
2390: int size = (dim == HORIZONTAL) ? compSize.width
2391: : compSize.height;
2392: int pref = (dim == HORIZONTAL) ? preferred.width
2393: : preferred.height;
2394: if (size != pref) {
2395: li.setPreferredSize(size);
2396: }
2397: }
2398: }
2399: }
2400: addUnspecified(new LayoutComponent[] { targetLC },
2401: targetContainer, /*getTargetRootsForCopy(targetContainer),*/
2402: addingInts);
2403: visualStateUpToDate = false;
2404: }
2405:
2406: /**
2407: * Add a new component to the target layout. No layout information is
2408: * provided, so the component is added to a default location. (This means
2409: * centered within the container, in a separate layer of layout roots - not
2410: * to interact with the rest of the layout.)
2411: * @param component the component to be added; may already exist in the model,
2412: * but not placed in a container
2413: * @param targetContainerId Id of the target container; can be null
2414: */
2415: public void addUnspecifiedComponent(LayoutComponent component,
2416: String targetContainerId) {
2417: if (component.getParent() != null) {
2418: throw new IllegalArgumentException(
2419: "The component already exists and is placed in the layout"); // NOI18N
2420: }
2421: LayoutComponent targetContainer = layoutModel
2422: .getLayoutComponent(targetContainerId);
2423: if (targetContainer == null) {
2424: layoutModel.addRootComponent(component);
2425: } else {
2426: LayoutInterval[] addingInts = new LayoutInterval[DIM_COUNT];
2427: for (int dim = 0; dim < DIM_COUNT; dim++) {
2428: addingInts[dim] = component.getLayoutInterval(dim);
2429: }
2430: addUnspecified(new LayoutComponent[] { component },
2431: targetContainer, addingInts);
2432: visualStateUpToDate = false;
2433: }
2434: }
2435:
2436: private LayoutInterval[] getTargetRootsForCopy(
2437: LayoutComponent targetContainer) {
2438: LayoutInterval[] roots = layoutModel
2439: .addNewLayoutRoots(targetContainer);
2440: LayoutRegion space = getContainerSpace(targetContainer);
2441: for (int i = 0; i < DIM_COUNT; i++) {
2442: roots[i].setCurrentSpace(space);
2443: }
2444: return roots;
2445: }
2446:
2447: private static int[] getCopyShift(
2448: LayoutComponent[] sourceComponents,
2449: LayoutComponent targetContainer, LayoutRegion compSpace,
2450: boolean relative) {
2451: LayoutRegion contSpace = getContainerSpace(targetContainer);
2452: if (!compSpace.isSet() || !contSpace.isSet()) {
2453: return null;
2454: }
2455: int[] move = new int[DIM_COUNT];
2456: if (relative) {
2457: for (int dim = 0; dim < DIM_COUNT; dim++) {
2458: move[dim] = suggestCopyShift(sourceComponents, dim);
2459: }
2460: if (move[HORIZONTAL] == 0 && move[VERTICAL] == 0) {
2461: move[HORIZONTAL] = move[VERTICAL] = 10;
2462: }
2463: } else {
2464: for (int dim = 0; dim < DIM_COUNT; dim++) {
2465: move[dim] = (contSpace.size(dim) - compSpace.size(dim))
2466: / 2 - compSpace.positions[dim][LEADING]
2467: + contSpace.positions[dim][LEADING];
2468: }
2469:
2470: }
2471: return move;
2472: }
2473:
2474: private static int suggestCopyShift(
2475: LayoutComponent[] sourceComponents, int dimension) {
2476: if (!isAnyComponentSnappedToRoot(sourceComponents, dimension,
2477: TRAILING)) {
2478: return 10;
2479: } else if (!isAnyComponentSnappedToRoot(sourceComponents,
2480: dimension, LEADING)) {
2481: return -10;
2482: } else {
2483: return 0; // snapped to root border on both sides
2484: }
2485: }
2486:
2487: private static boolean isAnyComponentSnappedToRoot(
2488: LayoutComponent[] components, int dimension, int alignment) {
2489: LayoutInterval root = null;
2490: for (LayoutComponent comp : components) {
2491: LayoutInterval compInt = comp.getLayoutInterval(dimension);
2492: if (root == null) {
2493: root = LayoutInterval.getRoot(compInt);
2494: }
2495: if (LayoutInterval.isPlacedAtBorder(compInt, root,
2496: dimension, alignment)
2497: || isSnappedNextToInParent(compInt, root,
2498: dimension, alignment)) {
2499: return true;
2500: }
2501: }
2502: return false;
2503: }
2504:
2505: // [move this to LayoutInterval?]
2506: private static boolean isSnappedNextToInParent(
2507: LayoutInterval interval, LayoutInterval parent,
2508: int dimension, int alignment) {
2509: LayoutInterval gap = LayoutInterval.getNeighbor(interval,
2510: alignment, false, true, false);
2511: if (gap != null && LayoutInterval.isFixedDefaultPadding(gap)
2512: && parent.isParentOf(gap)) {
2513: LayoutInterval back = LayoutInterval.getDirectNeighbor(gap,
2514: alignment ^ 1, true);
2515: if ((back == interval || LayoutInterval.isPlacedAtBorder(
2516: interval, back, dimension, alignment))
2517: && LayoutInterval.getNeighbor(gap, alignment, true,
2518: true, false) == null
2519: && LayoutInterval.isPlacedAtBorder(gap.getParent(),
2520: parent, dimension, alignment)) {
2521: return true;
2522: }
2523: }
2524: return false;
2525: }
2526:
2527: private static LayoutRegion getContainerSpace(
2528: LayoutComponent container) {
2529: return container.getDefaultLayoutRoot(HORIZONTAL)
2530: .getCurrentSpace();
2531: }
2532:
2533: private LayoutInterval[] getActiveLayoutRoots(
2534: LayoutComponent container) {
2535: return container.getLayoutRoots().get(0);
2536: // [in future we may keep the default "layer" for each container stored somewhere]
2537: }
2538:
2539: // -----
2540: // LayoutModel.Listener implementation & related
2541:
2542: class Listener implements LayoutModel.Listener {
2543: private boolean active = false;
2544:
2545: public void layoutChanged(LayoutEvent ev) {
2546: if (!layoutModel.isUndoRedoInProgress()) {
2547: deactivate();
2548: LayoutDesigner.this .layoutChanged(ev);
2549: activate();
2550: }
2551: }
2552:
2553: void activate() {
2554: layoutModel.addListener(this );
2555: active = true;
2556: }
2557:
2558: void deactivate() {
2559: layoutModel.removeListener(this );
2560: active = false;
2561: }
2562:
2563: boolean isActive() {
2564: return active;
2565: }
2566: }
2567:
2568: private void layoutChanged(LayoutEvent e) {
2569: if (e.getType() == LayoutEvent.INTERVAL_REMOVED) {
2570: // component interval was removed - need to clear neighbor gaps etc.
2571: LayoutEvent.Interval ev = (LayoutEvent.Interval) e;
2572: LayoutInterval interval = ev.getInterval();
2573: LayoutComponent comp = interval.getComponent();
2574: if (comp != null) {
2575: int dim = -1;
2576: for (int i = 0; i < DIM_COUNT; i++) {
2577: if (comp.getLayoutInterval(i) == interval) {
2578: dim = i;
2579: break;
2580: }
2581: }
2582: assert dim > -1;
2583: LayoutInterval root = LayoutInterval.getRoot(ev
2584: .getParentInterval());
2585: intervalRemoved(ev.getParentInterval(), ev.getIndex(),
2586: true, LayoutInterval.wantResize(interval), dim);
2587: LayoutComponent container = comp.getParent();
2588: if (container != null
2589: && root.getSubIntervalCount() == 0) {
2590: // Empty root - eliminate if it is an additional layer or
2591: // default layer with just one additional (which then
2592: // becomes default).
2593: // Hack #127988: don't remove layout roots during resizing
2594: // (resized component stays in additional layer - unlike moved).
2595: boolean resizing = (dragger != null && dragger
2596: .isResizing());
2597: if (root == getActiveLayoutRoots(container)[dim]
2598: && (container.getLayoutRootCount() != 2 || resizing)) {
2599: propEmptyContainer(root, dim);
2600: } else if (!resizing) {
2601: layoutModel.removeLayoutRoots(container, root);
2602: }
2603: }
2604: }
2605: }
2606: }
2607:
2608: // -----
2609:
2610: private boolean isComponentResizable(LayoutComponent comp,
2611: int dimension) {
2612: boolean[] res = comp.getResizability();
2613: if (res == null) {
2614: res = visualMapper.getComponentResizability(comp.getId(),
2615: new boolean[DIM_COUNT]);
2616: comp.setResizability(res);
2617: }
2618: return res[dimension];
2619: }
2620:
2621: /**
2622: * Changes global alignment of the layout component.
2623: *
2624: * @param comp component whose alignment should be changed.
2625: * @param dimension dimension the alignment should be applied in.
2626: * @param alignment desired alignment.
2627: */
2628: public void adjustComponentAlignment(LayoutComponent comp,
2629: int dimension, int alignment) {
2630: if (logTestCode()) {
2631: testCode.add("// > ADJUST COMPONENT ALIGNMENT"); //NOI18N
2632: testCode.add("{"); //NOI18N
2633: testCode
2634: .add("LayoutComponent comp = model.getLayoutComponent(\""
2635: + comp.getId() + "\");"); //NOI18N
2636: testCode.add("int dimension = " + dimension); //NOI18N
2637: testCode.add("int alignment = " + alignment); //NOI18N
2638: testCode
2639: .add("ld.adjustComponentAlignment(comp, dimension, alignment);"); //NOI18N
2640: testCode.add("}"); //NOI18N
2641: }
2642: modelListener.deactivate();
2643: LayoutInterval interval = comp.getLayoutInterval(dimension);
2644:
2645: // Skip non-resizable groups
2646: LayoutInterval parent = interval.getParent();
2647: while (parent != null) {
2648: if (!LayoutInterval.canResize(parent)) {
2649: interval = parent;
2650: }
2651: parent = parent.getParent();
2652: }
2653: assert !LayoutInterval.wantResize(interval);
2654:
2655: boolean changed = false;
2656: parent = interval.getParent();
2657: while (parent != null) {
2658: if (parent.isParallel()) {
2659: if (LayoutInterval.wantResize(parent)
2660: && !LayoutInterval.wantResize(interval)) {
2661: int alg = interval.getAlignment();
2662: if (alg != alignment) {
2663: // Add fixed gap and change alignment
2664: int size = LayoutInterval
2665: .getIntervalCurrentSize(parent,
2666: dimension)
2667: - LayoutInterval
2668: .getIntervalCurrentSize(
2669: interval, dimension);
2670: if (size > 0) {
2671: if (!interval.isSequential()) {
2672: LayoutInterval seq = new LayoutInterval(
2673: SEQUENTIAL);
2674: layoutModel.setIntervalAlignment(
2675: interval, DEFAULT);
2676: int i = layoutModel
2677: .removeInterval(interval);
2678: layoutModel.addInterval(interval, seq,
2679: -1);
2680: layoutModel.addInterval(seq, parent, i);
2681: interval = seq;
2682: }
2683: int index = (alg == LEADING) ? -1 : 0;
2684: LayoutInterval gap = new LayoutInterval(
2685: SINGLE);
2686: gap.setSize(size);
2687: layoutModel.addInterval(gap, interval,
2688: index);
2689: }
2690: layoutModel.setIntervalAlignment(interval,
2691: alignment);
2692: }
2693: changed = true;
2694: }
2695: } else {
2696: boolean before = true;
2697: boolean seqChanged = false;
2698: for (int i = 0; i < parent.getSubIntervalCount(); i++) {
2699: LayoutInterval li = parent.getSubInterval(i);
2700: if (li == interval) {
2701: before = false;
2702: } else if (LayoutInterval.wantResize(li)) {
2703: if ((before && (alignment == LEADING))
2704: || (!before && (alignment == TRAILING))) {
2705: assert li.isEmptySpace();
2706: setIntervalResizing(li, false);
2707: if (li.getPreferredSize() == 0) {
2708: layoutModel.removeInterval(li);
2709: i--;
2710: }
2711: seqChanged = true;
2712: }
2713: }
2714: }
2715: if (!changed && seqChanged) {
2716: boolean insertGap = false;
2717: int index = parent.indexOf(interval);
2718: if (alignment == LEADING) {
2719: if (parent.getSubIntervalCount() <= index + 1) {
2720: insertGap = true;
2721: index = -1;
2722: } else {
2723: index++;
2724: LayoutInterval candidate = parent
2725: .getSubInterval(index);
2726: if (candidate.isEmptySpace()) {
2727: setIntervalResizing(candidate, true);
2728: } else {
2729: insertGap = true;
2730: }
2731: }
2732: } else {
2733: assert (alignment == TRAILING);
2734: if (index == 0) {
2735: insertGap = true;
2736: } else {
2737: LayoutInterval candidate = parent
2738: .getSubInterval(index - 1);
2739: if (candidate.isEmptySpace()) {
2740: setIntervalResizing(candidate, true);
2741: } else {
2742: insertGap = true;
2743: }
2744: }
2745: }
2746: if (insertGap) {
2747: LayoutInterval gap = new LayoutInterval(SINGLE);
2748: setIntervalResizing(gap, true);
2749: layoutModel.setIntervalSize(gap, 0, 0, gap
2750: .getMaximumSize());
2751: layoutModel.addInterval(gap, parent, index);
2752: }
2753: changed = true;
2754: }
2755: }
2756: interval = parent;
2757: parent = parent.getParent();
2758: }
2759: updateDesignModifications(interval, dimension);
2760: modelListener.activate();
2761: visualStateUpToDate = false;
2762: if (logTestCode()) {
2763: testCode.add("// < ADJUST COMPONENT ALIGNMENT"); //NOI18N
2764: }
2765: }
2766:
2767: /**
2768: * Returns alignment of the component as the first item of the array.
2769: * The second item of the array indicates whether the alignment can
2770: * be changed to leading or trailing (e.g. if the current value is not
2771: * enforced by other resizable components). The returned alignment is
2772: * global e.g. it shows which edge of the container the component will track.
2773: *
2774: * @param comp component whose alignment should be determined.
2775: * @param dimension dimension in which the alignment should be determined.
2776: * @return alignment (or -1 if the component doesn't have a global alignment)
2777: * as the first item of the array and (canBeChangedToLeading ? 1 : 0) +
2778: * (canBeChangedToTrailing ? 2 : 0) as the second item.
2779: */
2780: public int[] getAdjustableComponentAlignment(LayoutComponent comp,
2781: int dimension) {
2782: LayoutInterval interval = comp.getLayoutInterval(dimension);
2783: boolean leadingFixed = true;
2784: boolean trailingFixed = true;
2785: boolean leadingAdjustable = true;
2786: boolean trailingAdjustable = true;
2787:
2788: if (LayoutInterval.wantResize(interval)) {
2789: leadingFixed = trailingFixed = leadingAdjustable = trailingAdjustable = false;
2790: }
2791: LayoutInterval parent = interval.getParent();
2792: while (parent != null) {
2793: if (!LayoutInterval.canResize(parent)) {
2794: leadingFixed = trailingFixed = leadingAdjustable = trailingAdjustable = true;
2795: } else if (parent.isParallel()) {
2796: if (LayoutInterval.wantResize(parent)
2797: && !LayoutInterval.wantResize(interval)) {
2798: int alignment = interval.getAlignment();
2799: if (alignment == LEADING) {
2800: trailingFixed = false;
2801: } else if (alignment == TRAILING) {
2802: leadingFixed = false;
2803: }
2804: }
2805: } else {
2806: boolean before = true;
2807: Iterator iter = parent.getSubIntervals();
2808: while (iter.hasNext()) {
2809: LayoutInterval li = (LayoutInterval) iter.next();
2810: if (li == interval) {
2811: before = false;
2812: } else if (LayoutInterval.wantResize(li)) {
2813: boolean space = li.isEmptySpace();
2814: if (before) {
2815: leadingFixed = false;
2816: if (!space) {
2817: leadingAdjustable = false;
2818: }
2819: } else {
2820: trailingFixed = false;
2821: if (!space) {
2822: trailingAdjustable = false;
2823: }
2824: }
2825: }
2826: }
2827: }
2828: interval = parent;
2829: parent = parent.getParent();
2830: }
2831: int adjustable = (leadingAdjustable ? 1 << LEADING : 0)
2832: + (trailingAdjustable ? 1 << TRAILING : 0);
2833: if (leadingFixed && trailingFixed) {
2834: // As if top level group wantResize()
2835: if (LEADING == interval.getGroupAlignment()) {
2836: trailingFixed = false;
2837: } else {
2838: leadingFixed = false;
2839: }
2840: }
2841: int alignment;
2842: if (leadingFixed) {
2843: // !trailingFixed
2844: alignment = LEADING;
2845: } else {
2846: if (trailingFixed) {
2847: alignment = TRAILING;
2848: } else {
2849: alignment = -1;
2850: }
2851: }
2852: return new int[] { alignment, adjustable };
2853: }
2854:
2855: /**
2856: * Determines whether the component is resizing in the given direction.
2857: *
2858: * @param comp component whose resizability should be determined.
2859: * @param dimension dimension in which the resizability should be determined.
2860: * @return <code>true</code> if the component is resizing, returns
2861: * <code>false</code> otherwise.
2862: */
2863: public boolean isComponentResizing(LayoutComponent comp,
2864: int dimension) {
2865: LayoutInterval interval = comp.getLayoutInterval(dimension);
2866: boolean fill = interval
2867: .hasAttribute(LayoutInterval.ATTRIBUTE_FILL);
2868: return fill ? false : LayoutInterval
2869: .wantResizeInLayout(interval);
2870: }
2871:
2872: /**
2873: * Returns preferred size of the given interval (in pixels).
2874: *
2875: * @param interval interval whose preferred size should be determined.
2876: * @return preferred size of the given interval.
2877: */
2878: private int prefSizeOfInterval(LayoutInterval interval) {
2879: int dimension = -1;
2880: if (interval.isComponent()) {
2881: LayoutComponent comp = interval.getComponent();
2882: dimension = (interval == comp.getLayoutInterval(HORIZONTAL)) ? HORIZONTAL
2883: : VERTICAL;
2884: if (comp.isLinkSized(dimension)) {
2885: Collection linked = (Collection) layoutModel
2886: .getLinkSizeGroups(dimension).get(
2887: new Integer(comp
2888: .getLinkSizeId(dimension)));
2889: Iterator iter = linked.iterator();
2890: int prefSize = 0;
2891: while (iter.hasNext()) {
2892: String compId = (String) iter.next();
2893: LayoutComponent component = layoutModel
2894: .getLayoutComponent(compId);
2895: LayoutInterval intr = component
2896: .getLayoutInterval(dimension);
2897: int pref = intr.getPreferredSize();
2898: if (pref == NOT_EXPLICITLY_DEFINED) {
2899: Dimension prefDim = visualMapper
2900: .getComponentPreferredSize(compId);
2901: pref = (dimension == HORIZONTAL) ? prefDim.width
2902: : prefDim.height;
2903: }
2904: prefSize = Math.max(pref, prefSize);
2905: }
2906: return prefSize;
2907: }
2908: }
2909: int prefSize = interval.getPreferredSize();
2910: if (prefSize == NOT_EXPLICITLY_DEFINED) {
2911: if (interval.isComponent()) {
2912: LayoutComponent comp = interval.getComponent();
2913: Dimension pref = visualMapper
2914: .getComponentPreferredSize(comp.getId());
2915: return (dimension == HORIZONTAL) ? pref.width
2916: : pref.height;
2917: } else if (interval.isEmptySpace()) {
2918: return sizeOfEmptySpace(interval);
2919: } else {
2920: assert interval.isGroup();
2921: prefSize = 0;
2922: Iterator iter = interval.getSubIntervals();
2923: if (interval.isSequential()) {
2924: while (iter.hasNext()) {
2925: LayoutInterval subInterval = (LayoutInterval) iter
2926: .next();
2927: prefSize += prefSizeOfInterval(subInterval);
2928: }
2929: } else {
2930: while (iter.hasNext()) {
2931: LayoutInterval subInterval = (LayoutInterval) iter
2932: .next();
2933: prefSize = Math.max(prefSize,
2934: prefSizeOfInterval(subInterval));
2935: }
2936: }
2937: }
2938: }
2939: return prefSize;
2940: }
2941:
2942: /**
2943: * Returns size of the empty space represented by the given layout interval.
2944: *
2945: * @param interval layout interval that represents padding.
2946: * @return size of the padding.
2947: */
2948: private int sizeOfEmptySpace(LayoutInterval interval) {
2949: return LayoutUtils.getSizeOfDefaultGap(interval, visualMapper);
2950: }
2951:
2952: /**
2953: * Sets component resizability. Makes the component resizing or fixed.
2954: *
2955: * @param comp component whose resizability should be set.
2956: * @param dimension dimension in which the resizability should be changed.
2957: * @param resizable determines whether the component should be made
2958: * resizable in the given dimension.
2959: */
2960: public void setComponentResizing(LayoutComponent comp,
2961: int dimension, boolean resizing) {
2962: if (logTestCode()) {
2963: testCode.add("// > SET COMPONENT RESIZING"); //NOI18N
2964: testCode.add("{"); //NOI18N
2965: testCode
2966: .add("LayoutComponent comp = lm.getLayoutComponent(\""
2967: + comp.getId() + "\");"); //NOI18N
2968: testCode.add("int dimension = " + dimension + ";"); //NOI18N
2969: testCode.add("boolean resizing = " + resizing + ";"); //NOI18N
2970: testCode
2971: .add("ld.setComponentResizing(comp, dimension, resizing);"); //NOI18N
2972: testCode.add("}"); //NOI18N
2973: }
2974: modelListener.deactivate();
2975: LayoutInterval interval = comp.getLayoutInterval(dimension);
2976:
2977: // Unset the same-size if we are making the component resizable
2978: if (resizing && comp.isLinkSized(dimension)) {
2979: Collection linked = (Collection) layoutModel
2980: .getLinkSizeGroups(dimension).get(
2981: new Integer(comp.getLinkSizeId(dimension)));
2982: Collection toChange;
2983: if (linked.size() == 2) { // The second component will be unlinked, too.
2984: toChange = linked;
2985: } else {
2986: toChange = Collections.singletonList(comp.getId());
2987: }
2988: Iterator iter = toChange.iterator();
2989: while (iter.hasNext()) {
2990: String compId = (String) iter.next();
2991: LayoutComponent component = layoutModel
2992: .getLayoutComponent(compId);
2993: LayoutInterval intr = component
2994: .getLayoutInterval(dimension);
2995: Dimension prefDim = visualMapper
2996: .getComponentPreferredSize(compId);
2997: int prefSize = (dimension == HORIZONTAL) ? prefDim.width
2998: : prefDim.height;
2999: int currSize = intr.getCurrentSpace().size(dimension);
3000: if (currSize == prefSize) {
3001: currSize = NOT_EXPLICITLY_DEFINED;
3002: }
3003: layoutModel.setIntervalSize(intr,
3004: intr.getMinimumSize(), currSize, intr
3005: .getMaximumSize());
3006: }
3007: }
3008:
3009: LayoutInterval parent = interval.getParent();
3010: boolean fill = interval
3011: .hasAttribute(LayoutInterval.ATTRIBUTE_FILL);
3012: boolean formerFill = interval
3013: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL);
3014: if (fill || formerFill) {
3015: switchFillAttribute(interval, resizing);
3016: } else {
3017: setIntervalResizing(interval, resizing);
3018: }
3019: int delta = 0;
3020: if (!resizing) {
3021: int currSize = LayoutInterval.getIntervalCurrentSize(
3022: interval, dimension);
3023: int prefSize = prefSizeOfInterval(interval);
3024: delta = currSize - prefSize;
3025: if (delta != 0) {
3026: layoutModel.setIntervalSize(interval, interval
3027: .getMinimumSize(), currSize, interval
3028: .getMaximumSize());
3029: }
3030: }
3031: LayoutInterval intr = interval;
3032: LayoutInterval par = parent;
3033: while (par != null) {
3034: if (par.isParallel() && resizing) {
3035: int groupCurrSize = LayoutInterval
3036: .getIntervalCurrentSize(par, dimension);
3037: int currSize = LayoutInterval.getIntervalCurrentSize(
3038: intr, dimension);
3039: // PENDING currSize could change if groupPrefSize != groupCurrSize
3040: if (groupCurrSize != currSize) {
3041: LayoutInterval seqGroup = intr;
3042: LayoutInterval space = new LayoutInterval(SINGLE);
3043: space.setSize(groupCurrSize - currSize);
3044: int alignment = intr.getAlignment();
3045: int index = (alignment == LEADING) ? -1 : 0;
3046: if (intr.isSequential()) {
3047: int spaceIndex = (alignment == LEADING) ? intr
3048: .getSubIntervalCount() - 1 : 0;
3049: LayoutInterval adjacentSpace = intr
3050: .getSubInterval(spaceIndex);
3051: if (adjacentSpace.isEmptySpace()) {
3052: int spaceSize = LayoutInterval
3053: .getIntervalCurrentSize(
3054: adjacentSpace, dimension);
3055: layoutModel.removeInterval(adjacentSpace);
3056: space.setSize(groupCurrSize - currSize
3057: + spaceSize);
3058: }
3059: } else {
3060: seqGroup = new LayoutInterval(SEQUENTIAL);
3061: layoutModel.setIntervalAlignment(intr, DEFAULT);
3062: seqGroup.setAlignment(alignment);
3063: int i = layoutModel.removeInterval(intr);
3064: layoutModel.addInterval(intr, seqGroup, -1);
3065: layoutModel.addInterval(seqGroup, par, i);
3066: }
3067: layoutModel.addInterval(space, seqGroup, index);
3068: seqGroup.getCurrentSpace().set(dimension,
3069: par.getCurrentSpace());
3070: }
3071: } else if (par.isSequential()) {
3072: // Change resizability of gaps
3073: boolean parentSeq = (parent == par);
3074: List<LayoutInterval> resizableList = new LinkedList<LayoutInterval>();
3075: int alignment = parentSeq ? LayoutInterval
3076: .getEffectiveAlignment(interval) : 0;
3077: LayoutInterval leadingGap = null;
3078: LayoutInterval trailingGap = null;
3079: boolean afterDefining = false;
3080: Iterator iter = par.getSubIntervals();
3081: while (iter.hasNext()) {
3082: LayoutInterval candidate = (LayoutInterval) iter
3083: .next();
3084: if (candidate == interval) {
3085: afterDefining = true;
3086: }
3087: if (candidate.isEmptySpace()) {
3088: if (resizing) {
3089: setIntervalResizing(candidate, false);
3090: int currSize = LayoutInterval
3091: .getIntervalCurrentSize(candidate,
3092: dimension);
3093: int prefSize = prefSizeOfInterval(candidate);
3094: if (currSize != prefSize) {
3095: layoutModel.setIntervalSize(candidate,
3096: candidate.getMinimumSize(),
3097: currSize, candidate
3098: .getMaximumSize());
3099: delta += currSize - prefSize;
3100: }
3101: } else if (parentSeq) {
3102: boolean wasFill = candidate
3103: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL);
3104: boolean glue = (candidate
3105: .getPreferredSize() != NOT_EXPLICITLY_DEFINED);
3106: if (wasFill) {
3107: trailingGap = candidate;
3108: } else if ((trailingGap == null)
3109: || (!trailingGap
3110: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL))) {
3111: if (glue) {
3112: trailingGap = candidate;
3113: } else {
3114: if (afterDefining
3115: && ((trailingGap == null) || (trailingGap
3116: .getPreferredSize() == NOT_EXPLICITLY_DEFINED))) {
3117: trailingGap = candidate;
3118: }
3119: }
3120: }
3121: if ((leadingGap == null) && !afterDefining) {
3122: leadingGap = candidate;
3123: } else {
3124: if ((wasFill && ((leadingGap == null) || (!leadingGap
3125: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL))))
3126: || glue
3127: && ((leadingGap == null) || (!leadingGap
3128: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL) && (leadingGap
3129: .getPreferredSize() == NOT_EXPLICITLY_DEFINED)))) {
3130: leadingGap = candidate;
3131: }
3132: }
3133: }
3134: } else {
3135: if (candidate.getMaximumSize() == Short.MAX_VALUE) {
3136: resizableList.add(candidate);
3137: }
3138: }
3139: }
3140: if (resizableList.size() > 0) {
3141: iter = resizableList.iterator();
3142: delta = (LayoutInterval.getIntervalCurrentSize(par,
3143: dimension)
3144: - prefSizeOfInterval(par) + delta)
3145: / resizableList.size();
3146: while (iter.hasNext()) {
3147: LayoutInterval candidate = (LayoutInterval) iter
3148: .next();
3149: if (candidate.isGroup()) {
3150: // PENDING currSize could change - we can't modify prefSize of group directly
3151: } else {
3152: if (candidate == interval) {
3153: if (delta != 0) {
3154: int prefSize = prefSizeOfInterval(candidate);
3155: layoutModel.setIntervalSize(
3156: candidate, candidate
3157: .getMinimumSize(),
3158: Math.max(0, prefSize
3159: - delta), candidate
3160: .getMaximumSize());
3161: }
3162: } else {
3163: int currSize = LayoutInterval
3164: .getIntervalCurrentSize(
3165: candidate, dimension);
3166: layoutModel.setIntervalSize(candidate,
3167: candidate.getMinimumSize(),
3168: Math.max(0, currSize - delta),
3169: candidate.getMaximumSize());
3170: }
3171: }
3172: }
3173: }
3174: if (parentSeq) {
3175: if (!LayoutInterval.wantResize(par)) {
3176: LayoutInterval gap = null;
3177: if ((alignment == TRAILING)
3178: && (leadingGap != null)) {
3179: gap = leadingGap;
3180: setIntervalResizing(leadingGap, !resizing);
3181: layoutModel
3182: .changeIntervalAttribute(
3183: leadingGap,
3184: LayoutInterval.ATTRIBUTE_FILL,
3185: true);
3186: }
3187: if ((alignment == LEADING)
3188: && (trailingGap != null)) {
3189: gap = trailingGap;
3190: setIntervalResizing(trailingGap, !resizing);
3191: layoutModel
3192: .changeIntervalAttribute(
3193: trailingGap,
3194: LayoutInterval.ATTRIBUTE_FILL,
3195: true);
3196: }
3197: if ((gap != null)
3198: && (delta != 0)
3199: && (gap.getPreferredSize() != NOT_EXPLICITLY_DEFINED)) {
3200: layoutModel.setIntervalSize(gap, gap
3201: .getMinimumSize(), Math.max(0, gap
3202: .getPreferredSize()
3203: - delta), gap.getMaximumSize());
3204: }
3205: }
3206: parent = par.getParent(); // use parallel parent for group resizing check
3207: }
3208: }
3209: intr = par;
3210: par = par.getParent();
3211: }
3212:
3213: // Unset the same size once all changes in gap sizes are done
3214: if (resizing) {
3215: layoutModel.unsetSameSize(Collections.singletonList(comp
3216: .getId()), dimension);
3217: }
3218: modelListener.activate();
3219:
3220: if (resizing) {
3221: // cancel possible suppressed resizing
3222: while (parent != null) {
3223: if (!LayoutInterval.canResize(parent)) {
3224: operations.enableGroupResizing(parent);
3225: }
3226: parent = parent.getParent();
3227: }
3228: } else {
3229: // check if we should suppress resizing
3230: while (parent != null) {
3231: if (fillResizable(parent)) {
3232: operations.suppressGroupResizing(parent);
3233: parent = parent.getParent();
3234: } else {
3235: break;
3236: }
3237: }
3238: }
3239:
3240: updateDesignModifications(comp.getParent());
3241: visualStateUpToDate = false;
3242: if (logTestCode()) {
3243: testCode.add("// < SET COMPONENT RESIZING"); //NOI18N
3244: }
3245: }
3246:
3247: private boolean fillResizable(LayoutInterval interval) {
3248: if (!LayoutInterval.canResize(interval)) {
3249: return false;
3250: }
3251: if (interval.isGroup()) {
3252: boolean subres = true;
3253: Iterator it = interval.getSubIntervals();
3254: while (it.hasNext()) {
3255: LayoutInterval li = (LayoutInterval) it.next();
3256: if (LayoutInterval.wantResize(li) && !fillResizable(li)) {
3257: subres = false;
3258: break;
3259: }
3260: }
3261: return subres;
3262: } else {
3263: return interval.hasAttribute(LayoutInterval.ATTRIBUTE_FILL);
3264: }
3265: }
3266:
3267: /**
3268: * Aligns given components in the specified direction.
3269: *
3270: * @param componentIds IDs of components that should be aligned.
3271: * @param closed determines if closed group should be created.
3272: * @param dimension dimension to align in.
3273: * @param alignment requested alignment.
3274: */
3275: public void align(Collection componentIds, boolean closed,
3276: int dimension, int alignment) {
3277: if (logTestCode()) {
3278: testCode.add("// > ALIGN"); //NOI18N
3279: testCode.add("{"); //NOI18N
3280: LayoutTestUtils.writeCollection(testCode, "componentIds",
3281: componentIds); //NOI18N
3282: testCode.add("boolean closed = " + closed + ";"); //NOI18N
3283: testCode.add("int dimension = " + dimension + ";"); //NOI18N
3284: testCode.add("int alignment = " + alignment + ";"); //NOI18N
3285: testCode
3286: .add("ld.align(componentIds, closed, dimension, alignment);"); //NOI18N
3287: testCode.add("}"); //NOI18N
3288: }
3289: LayoutInterval[] intervals = new LayoutInterval[componentIds
3290: .size()];
3291: int counter = 0;
3292: Iterator iter = componentIds.iterator();
3293: while (iter.hasNext()) {
3294: String id = (String) iter.next();
3295: LayoutComponent component = layoutModel
3296: .getLayoutComponent(id);
3297: intervals[counter++] = component
3298: .getLayoutInterval(dimension);
3299: }
3300: modelListener.deactivate();
3301: try {
3302: new LayoutAligner(this , layoutModel, operations)
3303: .alignIntervals(intervals, closed, dimension,
3304: alignment);
3305: } finally {
3306: modelListener.activate();
3307: }
3308: requireStructureOptimization();
3309: if (logTestCode()) {
3310: testCode.add("// < ALIGN"); //NOI18N
3311: }
3312: }
3313:
3314: private void destroyRedundantGroups(
3315: Set<LayoutComponent> updatedContainers) {
3316: Iterator it = layoutModel.getAllComponents();
3317: while (it.hasNext()) {
3318: LayoutComponent comp = (LayoutComponent) it.next();
3319: if (!comp.isLayoutContainer())
3320: continue;
3321:
3322: boolean updated = false;
3323: for (LayoutInterval[] roots : comp.getLayoutRoots()) {
3324: for (int dim = 0; dim < DIM_COUNT; dim++) {
3325: updated = destroyRedundantGroups(roots[dim])
3326: || updated;
3327: }
3328: }
3329: if (updated) {
3330: updatedContainers.add(comp);
3331: }
3332: }
3333: }
3334:
3335: private boolean destroyRedundantGroups(LayoutInterval interval) {
3336: boolean updated = false;
3337: for (int i = interval.getSubIntervalCount() - 1; i >= 0; i--) {
3338: if (i >= interval.getSubIntervalCount())
3339: continue;
3340: LayoutInterval subInterval = interval.getSubInterval(i);
3341: if (subInterval.isGroup()) {
3342: destroyRedundantGroups(subInterval);
3343: destroyGroupIfRedundant(subInterval, interval);
3344: updated |= (subInterval.getParent() == null);
3345: }
3346: }
3347: return updated;
3348: }
3349:
3350: /**
3351: * Destroys the given group if it is redundant in the layout model.
3352: *
3353: * @param group group whose necessity should be checked.
3354: * @param boundary parent of the group that limits the changes that
3355: * should be made e.g. no changes outside of this group even if it were
3356: * itself redundant. Can be <code>null</code> if there's no boundary.
3357: */
3358: void destroyGroupIfRedundant(LayoutInterval group,
3359: LayoutInterval boundary) {
3360: if ((group == null) || (!group.isGroup())
3361: || (group == boundary))
3362: return;
3363: LayoutInterval parent = group.getParent();
3364: // Don't destroy root intervals
3365: if (parent == null)
3366: return;
3367:
3368: // Remove empty groups
3369: if (LayoutInterval.getCount(group, LayoutRegion.ALL_POINTS,
3370: true) == 0) {
3371: takeOutInterval(group, boundary);
3372: return;
3373: }
3374:
3375: if (operations.dissolveRedundantGroup(group)) {
3376: destroyGroupIfRedundant(parent, boundary);
3377: }
3378: }
3379:
3380: /**
3381: * Removes the given interval from the layout model. Consolidates
3382: * parent groups if necessary.
3383: *
3384: * @param interval interval that should be removed.
3385: * @param boundary parent of the group that limits the changes that
3386: * should be made e.g. no changes outside of this group even if it were
3387: * itself redundant. Can be <code>null</code> if there's no boundary.
3388: */
3389: void takeOutInterval(LayoutInterval interval,
3390: LayoutInterval boundary) {
3391: LayoutInterval parent = interval.getParent();
3392: int index = parent.indexOf(interval);
3393: List<LayoutInterval> toRemove = new LinkedList<LayoutInterval>();
3394: toRemove.add(interval);
3395: if (parent.isSequential()) {
3396: // Remove leading gap
3397: if (index > 0) {
3398: LayoutInterval li = parent.getSubInterval(index - 1);
3399: if (li.isEmptySpace()) {
3400: toRemove.add(li);
3401: }
3402: }
3403: // Remove trailing gap
3404: if (index + 1 < parent.getSubIntervalCount()) {
3405: LayoutInterval li = parent.getSubInterval(index + 1);
3406: if (li.isEmptySpace()) {
3407: toRemove.add(li);
3408: }
3409: }
3410: // Add dummy gap if necessary
3411: if ((toRemove.size() == 3)
3412: && (parent.getSubIntervalCount() > 3)) {
3413: LayoutInterval gap = new LayoutInterval(SINGLE);
3414: if (interval.isComponent()
3415: && (interval.getComponent().getLayoutInterval(
3416: VERTICAL) == interval)) {
3417: int alignment = LayoutInterval
3418: .getEffectiveAlignment(interval);
3419: int size = 0;
3420: for (int i = 0; i < 3; i++) {
3421: size += LayoutInterval.getIntervalCurrentSize(
3422: toRemove.get(i), VERTICAL);
3423: }
3424: gap.setSizes(NOT_EXPLICITLY_DEFINED, size,
3425: (alignment == TRAILING) ? Short.MAX_VALUE
3426: : USE_PREFERRED_SIZE);
3427: }
3428: layoutModel.addInterval(gap, parent, index);
3429: }
3430: }
3431: Iterator iter = toRemove.iterator();
3432: while (iter.hasNext()) {
3433: LayoutInterval remove = (LayoutInterval) iter.next();
3434: layoutModel.removeInterval(remove);
3435: }
3436: // Consolidate parent
3437: destroyGroupIfRedundant(parent, boundary);
3438: }
3439:
3440: // -----
3441:
3442: // [should change to operations.addGroupContent]
3443: /**
3444: * Creates a remainder parallel group (remainder to a main group of
3445: * aligned intervals).
3446: * @param list the content of the group, output from 'extract' method
3447: * @param seq a sequential group where to add to
3448: * @param index the index of the main group in the sequence
3449: * @param position the position of the remainder group relative to the main
3450: * group (LEADING or TRAILING)
3451: * @param mainAlignment effective alignment of the main group (LEADING or
3452: * TRAILING or something else meaning not aligned)
3453: * @param dimension dimension the remainder group is created in.
3454: */
3455: void createRemainderGroup(List list, LayoutInterval seq, int index,
3456: int position, int mainAlignment, int dimension) {
3457: assert seq.isSequential()
3458: && (position == LEADING || position == TRAILING);
3459: if (position == TRAILING) {
3460: index++;
3461: }
3462: // [revisit the way how spaces are handled - in accordance to optimizeGaps]
3463:
3464: LayoutInterval gap = null;
3465: LayoutInterval leadingGap = null;
3466: LayoutInterval trailingGap = null;
3467: boolean onlyGaps = true;
3468: boolean gapLeads = true;
3469: boolean gapTrails = true;
3470:
3471: // Remove sequences just with one gap
3472: for (int i = list.size() - 1; i >= 0; i--) {
3473: List subList = (List) list.get(i);
3474: if (subList.size() == 2) { // there is just one interval
3475: int alignment = ((Integer) subList.get(0)).intValue();
3476: LayoutInterval li = (LayoutInterval) subList.get(1);
3477: if (li.isEmptySpace()) {
3478: if (gap == null
3479: || li.getMaximumSize() > gap
3480: .getMaximumSize()) {
3481: gap = li;
3482: }
3483: if (isFixedPadding(li)) {
3484: if (alignment == LEADING) {
3485: leadingGap = li;
3486: gapTrails = false;
3487: } else if (alignment == TRAILING) {
3488: trailingGap = li;
3489: gapLeads = false;
3490: }
3491: } else {
3492: gapLeads = false;
3493: gapTrails = false;
3494: }
3495: list.remove(i);
3496: } else {
3497: onlyGaps = false;
3498: }
3499: }
3500: }
3501:
3502: if (list.size() == 1) { // just one sequence, need not a group
3503: List subList = (List) list.get(0);
3504: Iterator itr = subList.iterator();
3505: itr.next(); // skip alignment
3506: do {
3507: LayoutInterval li = (LayoutInterval) itr.next();
3508: layoutModel.addInterval(li, seq, index++);
3509: } while (itr.hasNext());
3510: return;
3511: }
3512:
3513: // find common ending gaps, possibility to eliminate some...
3514: for (Iterator it = list.iterator(); it.hasNext();) {
3515: List subList = (List) it.next();
3516: if (subList.size() != 2) { // there are more intervals (will form a sequential group)
3517: onlyGaps = false;
3518:
3519: boolean first = true;
3520: Iterator itr = subList.iterator();
3521: itr.next(); // skip seq. alignment
3522: do {
3523: LayoutInterval li = (LayoutInterval) itr.next();
3524: if (first) {
3525: first = false;
3526: if (isFixedPadding(li))
3527: leadingGap = li;
3528: else
3529: gapLeads = false;
3530: } else if (!itr.hasNext()) {
3531: if (isFixedPadding(li))
3532: trailingGap = li;
3533: else
3534: gapTrails = false;
3535: }
3536: } while (itr.hasNext());
3537: }
3538: }
3539:
3540: if (onlyGaps) {
3541: operations
3542: .insertGapIntoSequence(gap, seq, index, dimension);
3543: return;
3544: }
3545:
3546: // create group
3547: LayoutInterval group = new LayoutInterval(PARALLEL);
3548: if (position == mainAlignment) {
3549: // [but this should eliminate resizability only for gaps...]
3550: group.setMinimumSize(USE_PREFERRED_SIZE);
3551: group.setMaximumSize(USE_PREFERRED_SIZE);
3552: }
3553: // group.setGroupAlignment(alignment);
3554:
3555: // fill the group
3556: for (Iterator it = list.iterator(); it.hasNext();) {
3557: List subList = (List) it.next();
3558:
3559: if (gapLeads) {
3560: subList.remove(1);
3561: }
3562: if (gapTrails) {
3563: subList.remove(subList.size() - 1);
3564: }
3565:
3566: LayoutInterval interval;
3567: if (subList.size() == 2) { // there is just one interval - use it directly
3568: int alignment = ((Integer) subList.get(0)).intValue();
3569: interval = (LayoutInterval) subList.get(1);
3570: if (alignment == LEADING || alignment == TRAILING) {
3571: layoutModel.setIntervalAlignment(interval,
3572: alignment);
3573: }
3574: } else { // there are more intervals - group them in a sequence
3575: interval = new LayoutInterval(SEQUENTIAL);
3576: Iterator itr = subList.iterator();
3577: int alignment = ((Integer) itr.next()).intValue();
3578: if (alignment == LEADING || alignment == TRAILING) {
3579: interval.setAlignment(alignment);
3580: }
3581: do {
3582: LayoutInterval li = (LayoutInterval) itr.next();
3583: layoutModel.addInterval(li, interval, -1);
3584: } while (itr.hasNext());
3585: }
3586: layoutModel.addInterval(interval, group, -1);
3587: }
3588:
3589: // add the group to the sequence
3590: if (gapLeads) {
3591: layoutModel.addInterval(leadingGap, seq, index++);
3592: }
3593: layoutModel.addInterval(group, seq, index++);
3594: if (gapTrails) {
3595: layoutModel.addInterval(trailingGap, seq, index);
3596: }
3597: }
3598:
3599: static boolean isFixedPadding(LayoutInterval interval) {
3600: return interval.isEmptySpace()
3601: && (interval.getMinimumSize() == NOT_EXPLICITLY_DEFINED || interval
3602: .getMinimumSize() == USE_PREFERRED_SIZE)
3603: && interval.getPreferredSize() == NOT_EXPLICITLY_DEFINED
3604: && (interval.getMaximumSize() == NOT_EXPLICITLY_DEFINED || interval
3605: .getMaximumSize() == USE_PREFERRED_SIZE);
3606: }
3607:
3608: // -----
3609:
3610: // requires the layout image up-to-date (all positions known)
3611: // requires the group contains some component (at least indirectly)
3612: private int optimizeGaps(LayoutInterval group, int dimension,
3613: boolean recursive) {
3614: assert group.isParallel();
3615:
3616: // sub-groups first (not using iterator, intervals may change)
3617: if (recursive) {
3618: for (int i = 0; i < group.getSubIntervalCount(); i++) {
3619: LayoutInterval li = group.getSubInterval(i);
3620: if (li.isParallel()) {
3621: optimizeGaps(li, dimension, recursive);
3622: } else if (li.isSequential()) {
3623: for (int ii = 0; ii < li.getSubIntervalCount(); ii++) {
3624: LayoutInterval llii = li.getSubInterval(ii);
3625: if (llii.isParallel()) {
3626: int idx = optimizeGaps(llii, dimension,
3627: recursive);
3628: if (idx >= 0) // position in sequence changed (a gap inserted)
3629: ii = idx;
3630: }
3631: }
3632: }
3633: }
3634: }
3635:
3636: if (group.getGroupAlignment() == CENTER
3637: || group.getGroupAlignment() == BASELINE) {
3638: return -1;
3639: }
3640: int nonEmptyCount = LayoutInterval.getCount(group,
3641: LayoutRegion.ALL_POINTS, true);
3642: if (nonEmptyCount <= 1) {
3643: if (group.getParent() == null) {
3644: if (group.getSubIntervalCount() > 1) {
3645: // [removing container supporting gap]
3646: for (int i = group.getSubIntervalCount() - 1; i >= 0; i--) {
3647: if (group.getSubInterval(i).isEmptySpace()) {
3648: layoutModel.removeInterval(group, i);
3649: break;
3650: }
3651: }
3652: } else if (group.getSubIntervalCount() == 0) {
3653: // [sort of hack - would be nice the filling gap is ensured somewhere else]
3654: propEmptyContainer(group, dimension);
3655: }
3656: } else { // [dissolving one-member group should not be here]
3657: assert (nonEmptyCount == 1);
3658: assert (group.getSubIntervalCount() == 1);
3659: LayoutInterval interval = group.getSubInterval(0);
3660: //int alignment = interval.getAlignment();
3661: layoutModel.removeInterval(interval);
3662: layoutModel.setIntervalAlignment(interval, group
3663: .getAlignment());
3664: LayoutInterval parent = group.getParent();
3665: int index = layoutModel.removeInterval(group);
3666: if (parent.isSequential() && interval.isSequential()) {
3667: // dissolve the sequential group in its parent
3668: for (int i = interval.getSubIntervalCount() - 1; i >= 0; i--) {
3669: LayoutInterval subInterval = interval
3670: .getSubInterval(i);
3671: layoutModel.removeInterval(subInterval);
3672: layoutModel.addInterval(subInterval, parent,
3673: index);
3674: }
3675: eliminateConsecutiveGaps(parent, 0, dimension);
3676: } else {
3677: layoutModel.addInterval(interval, parent, index);
3678: }
3679: /* if (offsetGap != null) {
3680: // Reinsert/simulate behaviour of the removed offset gap.
3681: int size;
3682: if (parent.isSequential()) {
3683: size = LayoutInterval.getIntervalCurrentSize(group, dimension)
3684: - LayoutInterval.getIntervalCurrentSize(interval, dimension);
3685: if (alignment == LEADING) index++;
3686: } else {
3687: size = LayoutInterval.getIntervalCurrentSize(parent, dimension);
3688: index = -1;
3689: }
3690: layoutModel.setIntervalSize(offsetGap, offsetGap.getMinimumSize(), size, offsetGap.getMaximumSize());
3691: layoutModel.addInterval(offsetGap, parent, index);
3692: eliminateConsecutiveGaps(parent, 0, dimension);
3693: } */
3694: }
3695: return -1;
3696: }
3697:
3698: return operations.optimizeGaps(group, dimension);
3699: }
3700:
3701: // -----
3702:
3703: /**
3704: * Sets interval resizability. Makes it resizing or fixed.
3705: *
3706: * @param interval layout interval whose resizability should be set.
3707: * @param resizable determines whether the passed interval should be made resizable.
3708: */
3709: void setIntervalResizing(LayoutInterval interval, boolean resizable) {
3710: switchFillAttribute(interval, resizable);
3711: layoutModel
3712: .setIntervalSize(interval,
3713: resizable ? NOT_EXPLICITLY_DEFINED
3714: : USE_PREFERRED_SIZE, interval
3715: .getPreferredSize(),
3716: resizable ? Short.MAX_VALUE
3717: : USE_PREFERRED_SIZE);
3718: }
3719:
3720: // Changes fill attributes when interval becomes (non-)resizable.
3721: private void switchFillAttribute(LayoutInterval interval,
3722: boolean resizable) {
3723: if (resizable) {
3724: if (interval.hasAttribute(LayoutInterval.ATTRIBUTE_FILL)) {
3725: layoutModel.changeIntervalAttribute(interval,
3726: LayoutInterval.ATTRIBUTE_FORMER_FILL, true);
3727: layoutModel.changeIntervalAttribute(interval,
3728: LayoutInterval.ATTRIBUTE_FILL, false);
3729: }
3730: } else {
3731: if (interval
3732: .hasAttribute(LayoutInterval.ATTRIBUTE_FORMER_FILL)) {
3733: layoutModel.changeIntervalAttribute(interval,
3734: LayoutInterval.ATTRIBUTE_FORMER_FILL, false);
3735: layoutModel.changeIntervalAttribute(interval,
3736: LayoutInterval.ATTRIBUTE_FILL, true);
3737: }
3738: }
3739: }
3740:
3741: // -----
3742:
3743: public void setDefaultSize(String compId) {
3744: if (logTestCode()) {
3745: testCode.add("// > SET DEFAULT SIZE"); //NOI18N
3746: testCode.add("{"); //NOI18N
3747: testCode.add("String compId = \"${" + compId + "}\";"); //NOI18N
3748: testCode.add("ld.setDefaultSize(compId);"); //NOI18N
3749: testCode.add("}"); //NOI18N
3750: }
3751: LayoutComponent component = layoutModel
3752: .getLayoutComponent(compId);
3753: if (component != null)
3754: setDefaultSize(component);
3755: if (logTestCode()) {
3756: testCode.add("// < SET DEFAULT SIZE"); //NOI18N
3757: }
3758: }
3759:
3760: private void setDefaultSize(LayoutComponent component) {
3761: imposeSize = true;
3762: if (component.isLayoutContainer()) {
3763: for (LayoutComponent comp : component.getSubcomponents()) {
3764: if (comp.isLayoutContainer())
3765: setDefaultSize(comp);
3766: }
3767: for (LayoutInterval[] roots : component.getLayoutRoots()) {
3768: for (int dim = 0; dim < DIM_COUNT; dim++) {
3769: setDefaultSizeInContainer(roots[dim]);
3770: }
3771: }
3772: updateDesignModifications(component);
3773: } else {
3774: operations.resizeInterval(component
3775: .getLayoutInterval(HORIZONTAL),
3776: NOT_EXPLICITLY_DEFINED);
3777: operations.resizeInterval(component
3778: .getLayoutInterval(VERTICAL),
3779: NOT_EXPLICITLY_DEFINED);
3780: }
3781: }
3782:
3783: private void setDefaultSizeInContainer(LayoutInterval interval) {
3784: if (!interval.isGroup()) {
3785: if (LayoutInterval.canResize(interval))
3786: operations
3787: .resizeInterval(
3788: interval,
3789: interval.getMinimumSize() != USE_PREFERRED_SIZE ? interval
3790: .getMinimumSize()
3791: : NOT_EXPLICITLY_DEFINED);
3792: } else {
3793: for (Iterator it = interval.getSubIntervals(); it.hasNext();) {
3794: setDefaultSizeInContainer((LayoutInterval) it.next());
3795: }
3796: }
3797: }
3798:
3799: private void updateDesignModifications(LayoutComponent container) {
3800: if (imposeSize || optimizeStructure) {
3801: // additional update is going to happen after building the layout, so now
3802: // just clean the design attrs, container resizing gap will be found later
3803: LayoutInterval[] roots = getActiveLayoutRoots(container);
3804: for (int dim = 0; dim < DIM_COUNT; dim++) {
3805: cleanDesignAttrs(roots[dim]);
3806: }
3807: } else {
3808: LayoutInterval[] roots = getActiveLayoutRoots(container);
3809: for (int dim = 0; dim < DIM_COUNT; dim++) {
3810: updateDesignModifications(roots[dim], dim);
3811: }
3812: }
3813: }
3814:
3815: private void updateDesignModifications(LayoutInterval root,
3816: int dimension) {
3817: cleanDesignAttrs(root);
3818: findContainerResizingGap(root, dimension);
3819: }
3820:
3821: private static void cleanDesignAttrs(LayoutInterval group) {
3822: group.unsetAttribute(LayoutInterval.DESIGN_ATTRS);
3823: for (int i = 0, n = group.getSubIntervalCount(); i < n; i++) {
3824: LayoutInterval li = group.getSubInterval(i);
3825: if (li.isGroup())
3826: cleanDesignAttrs(li);
3827: else
3828: li.unsetAttribute(LayoutInterval.DESIGN_ATTRS);
3829: }
3830: }
3831:
3832: private void findContainerResizingGap(LayoutInterval rootInterval,
3833: int dimension) {
3834: if (!LayoutInterval.wantResize(rootInterval) && // See issue 66849
3835: (LayoutInterval.getIntervalCurrentSize(rootInterval,
3836: dimension) != prefSizeOfInterval(rootInterval))) {
3837: // Resizing gap would change the layout
3838: return;
3839: }
3840: // find gap for container resizing
3841: int gapPosition = TRAILING;
3842: LayoutInterval resGap = findContainerResizingGap(rootInterval,
3843: dimension, gapPosition);
3844: if (resGap == null) {
3845: gapPosition = LEADING;
3846: resGap = findContainerResizingGap(rootInterval, dimension,
3847: gapPosition);
3848: if (resGap == null) {
3849: gapPosition = -1;
3850: resGap = findContainerResizingGap(rootInterval,
3851: dimension, gapPosition);
3852: if (resGap == null) {
3853: return;
3854: }
3855: }
3856: } else if (!LayoutInterval.canResize(resGap)) { // we prefer resizing gaps
3857: LayoutInterval gap = findContainerResizingGap(rootInterval,
3858: dimension, LEADING);
3859: if (gap != null && LayoutInterval.canResize(gap)) {
3860: resGap = gap;
3861: gapPosition = LEADING;
3862: } else {
3863: gap = findContainerResizingGap(rootInterval, dimension,
3864: -1);
3865: if (gap != null && LayoutInterval.canResize(gap)) {
3866: resGap = gap;
3867: gapPosition = -1;
3868: }
3869: }
3870: }
3871:
3872: // mark the gap and all surrounding intervals
3873: resGap.setAttribute(LayoutInterval.ATTR_DESIGN_CONTAINER_GAP
3874: | LayoutInterval.ATTR_DESIGN_RESIZING);
3875:
3876: LayoutInterval sub = resGap;
3877: LayoutInterval parent = resGap.getParent();
3878: do {
3879: if (parent.isSequential()) {
3880: for (Iterator it = parent.getSubIntervals(); it
3881: .hasNext();) {
3882: LayoutInterval li = (LayoutInterval) it.next();
3883: if (li != sub) {
3884: li
3885: .setAttribute(LayoutInterval.ATTR_DESIGN_SUPPRESSED_RESIZING);
3886: }
3887: }
3888: } else { // parallel parent
3889: for (Iterator it = parent.getSubIntervals(); it
3890: .hasNext();) {
3891: LayoutInterval interval = (LayoutInterval) it
3892: .next();
3893: if (interval != sub) {
3894: assert interval.isSequential();
3895: if (interval.isSequential()) {
3896: for (int i = 0, n = interval
3897: .getSubIntervalCount(); i < n; i++) {
3898: LayoutInterval li = interval
3899: .getSubInterval(i);
3900: if (((i == 0 && gapPosition == LEADING) || (i + 1 == n && gapPosition == TRAILING))
3901: && canBeContainerResizingGap(li)) { // parallel resizing gap
3902: li
3903: .setAttribute(LayoutInterval.ATTR_DESIGN_RESIZING);
3904: } else
3905: li
3906: .setAttribute(LayoutInterval.ATTR_DESIGN_SUPPRESSED_RESIZING);
3907: }
3908: } else {
3909: interval
3910: .setAttribute(LayoutInterval.ATTR_DESIGN_SUPPRESSED_RESIZING);
3911: }
3912: }
3913: }
3914: }
3915: sub = parent;
3916: parent = sub.getParent();
3917: } while (parent != null);
3918: }
3919:
3920: private static LayoutInterval findContainerResizingGap(
3921: LayoutInterval group, int dimension, int alignment) {
3922: assert group.isParallel();
3923:
3924: LayoutInterval theGap = null;
3925: int gapSize = Integer.MAX_VALUE;
3926:
3927: for (Iterator it = group.getSubIntervals(); it.hasNext();) {
3928: LayoutInterval seq = (LayoutInterval) it.next();
3929: if (!seq.isSequential()) {
3930: return null;
3931: }
3932:
3933: int n = seq.getSubIntervalCount();
3934: if (alignment == LEADING || alignment == TRAILING) {
3935: // [check for the same resizability of the ending gaps]
3936: LayoutInterval li = seq
3937: .getSubInterval(alignment == LEADING ? 0
3938: : n - 1);
3939: LayoutInterval gap;
3940: if (canBeContainerResizingGap(li)
3941: && (LayoutInterval.wantResize(seq) || LayoutInterval
3942: .getEffectiveAlignment(li) == (alignment ^ 1))) { // making this gap resizing won't change the visual appearance of the sequence
3943: gap = li;
3944: } else if (li.isParallel()) {
3945: gap = findContainerResizingGap(li, dimension,
3946: alignment);
3947: } else
3948: gap = null; // not an ending gap
3949:
3950: if (gap == null) {
3951: return null;
3952: }
3953:
3954: LayoutInterval neighbor = LayoutInterval
3955: .getDirectNeighbor(gap, alignment ^ 1, false);
3956: int p1 = neighbor.getCurrentSpace().positions[dimension][alignment];
3957: int p2 = group.getCurrentSpace().positions[dimension][alignment];
3958: int size = Math.abs(p2 - p1);
3959:
3960: if (theGap == null || size < gapSize) {
3961: theGap = gap;
3962: gapSize = size;
3963: }
3964: // if (LayoutInterval.canResize(gap) != LayoutInterval.canResize(theGap)) {
3965: // return null;
3966: // }
3967: } else { // somewhere in the middle - can be just one
3968: for (int i = n - 2; i > 0; i--) {
3969: LayoutInterval li = seq.getSubInterval(i);
3970: if (canBeContainerResizingGap(li)
3971: && LayoutInterval.canResize(li)) {
3972: return group.getSubIntervalCount() == 1 ? li
3973: : null;
3974: }
3975: }
3976: return null;
3977: }
3978: }
3979:
3980: return theGap;
3981: }
3982:
3983: private static boolean canBeContainerResizingGap(LayoutInterval li) {
3984: return li.isEmptySpace()
3985: && (li.getPreferredSize() != NOT_EXPLICITLY_DEFINED || li
3986: .getMaximumSize() >= Short.MAX_VALUE);
3987: }
3988:
3989: /**
3990: * @param resizingDef if provided the size change is caused "internally"
3991: * (mouse operation driven by LayoutDragger)
3992: */
3993: private boolean imposeCurrentContainerSize(
3994: LayoutComponent component,
3995: LayoutDragger.SizeDef[] resizingDef, boolean recursive) {
3996: assert component.isLayoutContainer();
3997:
3998: Rectangle interior = visualMapper
3999: .getContainerInterior(component.getId());
4000: if (interior == null)
4001: return false; // this container is not built
4002: component.setCurrentInterior(interior);
4003:
4004: if (component.getParent() != null) {
4005: Rectangle bounds = visualMapper
4006: .getComponentBounds(component.getId());
4007: component.setCurrentBounds(bounds, visualMapper
4008: .getBaselinePosition(component.getId(),
4009: bounds.width, bounds.height));
4010: }
4011: for (LayoutComponent subComp : component.getSubcomponents()) {
4012: Rectangle bounds = visualMapper.getComponentBounds(subComp
4013: .getId());
4014: subComp.setCurrentBounds(bounds, visualMapper
4015: .getBaselinePosition(subComp.getId(), bounds.width,
4016: bounds.height));
4017: if (subComp.isLayoutContainer()) {
4018: if (recursive)
4019: imposeCurrentContainerSize(subComp, null, true);
4020: } else
4021: imposeCurrentComponentSize(subComp);
4022: }
4023: Dimension minimum = null;
4024: Dimension preferred = null;
4025: for (int i = 0; i < DIM_COUNT; i++) {
4026: LayoutInterval outer = component.getLayoutInterval(i);
4027: int currentSize = outer.getCurrentSpace().size(i);
4028: LayoutInterval defaultRoot = getActiveLayoutRoots(component)[i];
4029: for (LayoutInterval[] roots : component.getLayoutRoots()) {
4030: LayoutInterval root = roots[i];
4031: boolean empty = root.getSubIntervalCount() == 0;
4032: if (root == defaultRoot) {
4033: if (empty) { // empty root group - add a filling gap
4034: propEmptyContainer(root, i);
4035: } else if (resizingDef != null
4036: && resizingDef[i] != null) {
4037: // not empty, there can be a resizing gap inside the container
4038: LayoutInterval resGap = resizingDef[i]
4039: .getResizingGap();
4040: if (resGap != null) { // this is it (special gap for design time resizing)
4041: int size = resizingDef[i]
4042: .getResizingGapSize(currentSize);
4043: if (size == 0) { // remove the gap
4044: LayoutInterval gapParent = resGap
4045: .getParent();
4046: assert gapParent.isSequential();
4047: int index = layoutModel
4048: .removeInterval(resGap);
4049: assert index == 0
4050: || index == gapParent
4051: .getSubIntervalCount();
4052: if (gapParent.getSubIntervalCount() == 1) {
4053: LayoutInterval last = layoutModel
4054: .removeInterval(gapParent,
4055: 0);
4056: operations
4057: .addContent(
4058: last,
4059: gapParent
4060: .getParent(),
4061: layoutModel
4062: .removeInterval(gapParent));
4063: } else if (LayoutInterval
4064: .canResize(resGap)
4065: && !LayoutInterval
4066: .wantResize(root)) {
4067: // don't lose resizability of the layout
4068: index = index == 0 ? gapParent
4069: .getSubIntervalCount() - 1
4070: : 0;
4071: LayoutInterval otherGap = gapParent
4072: .getSubInterval(index);
4073: if (otherGap.isEmptySpace()) { // the gap should be resizing
4074: layoutModel
4075: .setIntervalSize(
4076: otherGap,
4077: NOT_EXPLICITLY_DEFINED,
4078: otherGap
4079: .getPreferredSize(),
4080: Short.MAX_VALUE);
4081: }
4082: }
4083: } else { // set gap size
4084: operations.resizeInterval(resGap, size);
4085: if (size == NOT_EXPLICITLY_DEFINED
4086: && LayoutInterval
4087: .canResize(resGap)) {
4088: // hack: eliminate unnecessary resizing of default padding gap
4089: resGap
4090: .setMaximumSize(USE_PREFERRED_SIZE);
4091: boolean layoutResizing = LayoutInterval
4092: .wantResize(root);
4093: resGap
4094: .setMaximumSize(Short.MAX_VALUE);
4095: if (layoutResizing) { // the gap should be fixed
4096: layoutModel.setIntervalSize(
4097: resGap,
4098: NOT_EXPLICITLY_DEFINED,
4099: NOT_EXPLICITLY_DEFINED,
4100: USE_PREFERRED_SIZE);
4101: }
4102: }
4103: }
4104: } else if (!LayoutInterval.wantResize(root)) {
4105: // no resizing gap in fixed layout of resizing container
4106: int minLayoutSize = computeMinimumDesignSize(root);
4107: int growth = root.getCurrentSpace().size(i)
4108: - minLayoutSize;
4109: if (growth > 0) { // add new resizing gap at the end to hold the new extra space
4110: LayoutInterval endGap = new LayoutInterval(
4111: SINGLE);
4112: endGap.setSizes(NOT_EXPLICITLY_DEFINED,
4113: growth, Short.MAX_VALUE);
4114: operations.insertGap(endGap, root,
4115: minLayoutSize, i, TRAILING);
4116: }
4117: }
4118: }
4119: }
4120: if (!empty) {
4121: updateLayoutStructure(root, i, true);
4122: }
4123: }
4124:
4125: if (component.getParent() != null) {
4126: if (minimum == null) {
4127: minimum = visualMapper
4128: .getComponentMinimumSize(component.getId());
4129: preferred = visualMapper
4130: .getComponentPreferredSize(component
4131: .getId());
4132: }
4133: int min = i == HORIZONTAL ? minimum.width
4134: : minimum.height;
4135: boolean externalSize = (visualMapper
4136: .hasExplicitPreferredSize(component.getId()) && currentSize != (i == HORIZONTAL ? preferred.width
4137: : preferred.height))
4138: || currentSize < min
4139: || (currentSize > min && !LayoutInterval
4140: .wantResize(defaultRoot));
4141: operations.resizeInterval(outer,
4142: externalSize ? currentSize
4143: : NOT_EXPLICITLY_DEFINED);
4144: }
4145: }
4146:
4147: return true;
4148: }
4149:
4150: private void imposeCurrentComponentSize(LayoutComponent component) {
4151: Dimension preferred = visualMapper
4152: .getComponentPreferredSize(component.getId());
4153: for (int i = 0; i < DIM_COUNT; i++) {
4154: LayoutInterval li = component.getLayoutInterval(i);
4155: int defPref = li.getPreferredSize();
4156: if (LayoutInterval.wantResizeInLayout(li) && defPref != 0) { // == 0 subordinate component (filling)
4157: // resizing component with size-defining role in parent
4158: int current = li.getCurrentSpace().size(i);
4159: int pref = i == HORIZONTAL ? preferred.width
4160: : preferred.height;
4161: if (defPref == NOT_EXPLICITLY_DEFINED)
4162: defPref = pref;
4163: if (defPref != current)
4164: operations.resizeInterval(li,
4165: current != pref ? current
4166: : NOT_EXPLICITLY_DEFINED);
4167: }
4168: }
4169: }
4170:
4171: private void imposeCurrentGapSize(LayoutInterval gap,
4172: int currentSize, int dimension) {
4173: int pad = -1;
4174: int min = gap.getMinimumSize();
4175: int pref = gap.getPreferredSize();
4176: if (pref == NOT_EXPLICITLY_DEFINED) {
4177: if (!LayoutInterval.wantResizeInLayout(gap))
4178: return; // don't change default gap if not resizing
4179: pad = LayoutUtils.getSizeOfDefaultGap(gap, visualMapper);
4180: pref = pad;
4181: }
4182: if (currentSize != pref) { // [check for canResize?]
4183: if (min == NOT_EXPLICITLY_DEFINED) {
4184: if (pad < 0) {
4185: pad = LayoutUtils.getSizeOfDefaultGap(gap,
4186: visualMapper);
4187: }
4188: min = pad;
4189: } else if (min == USE_PREFERRED_SIZE) {
4190: min = pref;
4191: }
4192: if (currentSize < min) {
4193: currentSize = min;
4194: }
4195: operations.resizeInterval(gap,
4196: currentSize == pad ? NOT_EXPLICITLY_DEFINED
4197: : currentSize);
4198: }
4199: }
4200:
4201: private void propEmptyContainer(LayoutInterval root, int dimension) {
4202: assert root.getParent() == null
4203: && root.getSubIntervalCount() == 0;
4204: LayoutInterval gap = new LayoutInterval(SINGLE);
4205: gap.setSizes(0, root.getCurrentSpace().size(dimension),
4206: Short.MAX_VALUE);
4207: layoutModel.addInterval(gap, root, 0);
4208: }
4209:
4210: private int computeMinimumDesignSize(LayoutInterval interval) {
4211: int size = 0;
4212: if (interval.isSingle()) {
4213: int min = interval.getMinimumSize(true);
4214: size = min == USE_PREFERRED_SIZE ? interval
4215: .getPreferredSize(true) : min;
4216: if (size == NOT_EXPLICITLY_DEFINED) {
4217: if (interval.isComponent()) {
4218: LayoutComponent comp = interval.getComponent();
4219: Dimension dim = min == USE_PREFERRED_SIZE ? visualMapper
4220: .getComponentPreferredSize(comp.getId())
4221: : visualMapper.getComponentMinimumSize(comp
4222: .getId());
4223: size = interval == comp
4224: .getLayoutInterval(HORIZONTAL) ? dim.width
4225: : dim.height;
4226: } else { // gap
4227: size = LayoutUtils.getSizeOfDefaultGap(interval,
4228: visualMapper);
4229: }
4230: }
4231: } else if (interval.isSequential()) {
4232: for (int i = 0, n = interval.getSubIntervalCount(); i < n; i++) {
4233: size += computeMinimumDesignSize(interval
4234: .getSubInterval(i));
4235: }
4236: } else { // parallel group
4237: for (int i = 0, n = interval.getSubIntervalCount(); i < n; i++) {
4238: size = Math.max(size, computeMinimumDesignSize(interval
4239: .getSubInterval(i)));
4240: }
4241: }
4242: return size;
4243: }
4244:
4245: // -----
4246:
4247: // recursive
4248: private void intervalRemoved(LayoutInterval parent, int index,
4249: boolean primary, boolean wasResizing, int dimension) {
4250: if (parent.isSequential()) {
4251: LayoutInterval leadingGap;
4252: LayoutInterval leadingNeighbor;
4253: if (index > 0) {
4254: LayoutInterval li = parent.getSubInterval(index - 1);
4255: if (li.isEmptySpace()) {
4256: leadingGap = li;
4257: layoutModel.removeInterval(li);
4258: index--;
4259: leadingNeighbor = index > 0 ? parent
4260: .getSubInterval(index - 1) : null;
4261: } else {
4262: leadingGap = null;
4263: leadingNeighbor = li;
4264: }
4265: } else {
4266: leadingGap = null;
4267: leadingNeighbor = null;
4268: }
4269:
4270: LayoutInterval trailingGap;
4271: LayoutInterval trailingNeighbor;
4272: if (index < parent.getSubIntervalCount()) {
4273: LayoutInterval li = parent.getSubInterval(index);
4274: if (li.isEmptySpace()) {
4275: trailingGap = li;
4276: layoutModel.removeInterval(li);
4277: trailingNeighbor = index < parent
4278: .getSubIntervalCount() ? parent
4279: .getSubInterval(index) : null;
4280: } else {
4281: trailingGap = null;
4282: trailingNeighbor = li;
4283: }
4284: } else {
4285: trailingGap = null;
4286: trailingNeighbor = null;
4287: }
4288:
4289: if (!wasResizing
4290: && ((leadingGap != null && LayoutInterval
4291: .canResize(leadingGap)) || (trailingGap != null && LayoutInterval
4292: .canResize(trailingGap))))
4293: wasResizing = true;
4294:
4295: LayoutInterval super Parent = parent.getParent();
4296:
4297: // [check for last interval (count==1), if parallel superParent try to re-add the interval]
4298: if (parent.getSubIntervalCount() == 0) { // nothing remained
4299: int idx = layoutModel.removeInterval(parent);
4300: if (super Parent.getParent() != null) {
4301: intervalRemoved(super Parent, idx, false,
4302: wasResizing, dimension);
4303: }
4304: // else if (superParent.getSubIntervalCount() == 0) { // empty root group - add a filling gap
4305: // propEmptyContainer(superParent, dimension);
4306: // }
4307: } else { // the sequence remains
4308: boolean restResizing = LayoutInterval
4309: .contentWantResize(parent);
4310: if (wasResizing && !restResizing) {
4311: if (leadingNeighbor == null
4312: && parent.getAlignment() == LEADING) {
4313: layoutModel.setIntervalAlignment(parent,
4314: TRAILING);
4315: }
4316: if (trailingNeighbor == null
4317: && parent.getAlignment() == TRAILING) {
4318: layoutModel.setIntervalAlignment(parent,
4319: LEADING);
4320: }
4321: }
4322:
4323: int cutSize = LayoutRegion.distance(
4324: (leadingNeighbor != null ? leadingNeighbor
4325: : parent).getCurrentSpace(),
4326: (trailingNeighbor != null ? trailingNeighbor
4327: : parent).getCurrentSpace(), dimension,
4328: leadingNeighbor != null ? TRAILING : LEADING,
4329: trailingNeighbor != null ? LEADING : TRAILING);
4330:
4331: if ((leadingNeighbor != null && trailingNeighbor != null) // inside a sequence
4332: || super Parent.getParent() == null // in root parallel group
4333: || (leadingNeighbor != null && LayoutInterval
4334: .getEffectiveAlignment(leadingNeighbor,
4335: TRAILING) == TRAILING)
4336: || (trailingNeighbor != null && LayoutInterval
4337: .getEffectiveAlignment(
4338: trailingNeighbor, LEADING) == LEADING)) { // create a placeholder gap
4339: int min, max;
4340: if (wasResizing && !restResizing) { // the gap should be resizing
4341: min = NOT_EXPLICITLY_DEFINED;
4342: max = Short.MAX_VALUE;
4343: } else {
4344: min = max = USE_PREFERRED_SIZE;
4345: }
4346: LayoutInterval gap = new LayoutInterval(SINGLE);
4347: gap.setSizes(min, cutSize, max);
4348: layoutModel.addInterval(gap, parent, index);
4349: } else { // this is an "open" end - compensate the size in the parent
4350: if (parent.getSubIntervalCount() == 1) {
4351: LayoutInterval last = layoutModel
4352: .removeInterval(parent, 0);
4353: layoutModel.addInterval(last, super Parent,
4354: layoutModel.removeInterval(parent));
4355: layoutModel.setIntervalAlignment(last, parent
4356: .getRawAlignment());
4357: } else { // adjust current space of the parent sequence
4358: // (border interval at the open end removed)
4359: int l = (trailingNeighbor != null
4360: && leadingNeighbor == null ? trailingNeighbor
4361: : parent).getCurrentSpace().positions[dimension][LEADING];
4362: int t = (leadingNeighbor != null
4363: && trailingNeighbor == null ? leadingNeighbor
4364: : parent).getCurrentSpace().positions[dimension][TRAILING];
4365: parent.getCurrentSpace().set(dimension, l, t);
4366: }
4367: maintainSize(super Parent, wasResizing
4368: || restResizing, dimension, parent, parent
4369: .getCurrentSpace().size(dimension)
4370: - cutSize);
4371: }
4372:
4373: if (wasResizing && !restResizing) {
4374: operations.enableGroupResizing(super Parent); // in case it was disabled
4375: }
4376: }
4377: } else {
4378: if (parent.getParent() == null)
4379: return; // Component placed directly in the root interval
4380: assert parent.isParallel()
4381: && parent.getSubIntervalCount() > 0;
4382:
4383: int groupAlign = parent.getGroupAlignment();
4384: if (primary
4385: && (groupAlign == LEADING || groupAlign == TRAILING)) {
4386: maintainSize(parent, wasResizing, dimension, null, 0);
4387: }
4388:
4389: if (parent.getSubIntervalCount() == 1
4390: && parent.getParent() != null) { // last interval in parallel group
4391: // cancel the group and move the interval up
4392: LayoutInterval remaining = parent.getSubInterval(0);
4393: layoutModel.removeInterval(remaining);
4394: layoutModel.setIntervalAlignment(remaining, parent
4395: .getAlignment());
4396: if (LayoutInterval.wantResize(remaining)
4397: && !LayoutInterval.canResize(parent)) {
4398: // resizing interval in fixed group - make it fixed
4399: if (remaining.isGroup())
4400: operations.suppressGroupResizing(remaining);
4401: else
4402: layoutModel.setIntervalSize(remaining,
4403: USE_PREFERRED_SIZE, remaining
4404: .getPreferredSize(),
4405: USE_PREFERRED_SIZE);
4406: }
4407: LayoutInterval super Parent = parent.getParent();
4408: int i = layoutModel.removeInterval(parent);
4409: operations.addContent(remaining, super Parent, i);
4410: if (remaining.isSequential()
4411: && super Parent.isSequential()) {
4412: // eliminate possible directly consecutive gaps
4413: // [this could be done by the addContent method directly]
4414: eliminateConsecutiveGaps(super Parent, i, dimension);
4415: }
4416: // [TODO if parallel superParent try to re-add the interval]
4417: } else if (wasResizing
4418: && !LayoutInterval.contentWantResize(parent)) {
4419: operations.enableGroupResizing(parent);
4420: }
4421: }
4422: }
4423:
4424: private void eliminateConsecutiveGaps(LayoutInterval group,
4425: int index, int dimension) {
4426: assert group.isSequential();
4427: if (index > 0)
4428: index--;
4429: while (index < group.getSubIntervalCount() - 1) {
4430: LayoutInterval current = group.getSubInterval(index);
4431: LayoutInterval next = group.getSubInterval(index + 1);
4432: if (current.isEmptySpace() && next.isEmptySpace()) {
4433: int la;
4434: LayoutRegion lr;
4435: if (index > 0) {
4436: la = TRAILING;
4437: lr = group.getSubInterval(index - 1)
4438: .getCurrentSpace();
4439: } else {
4440: la = LEADING;
4441: lr = group.getCurrentSpace();
4442: }
4443: int ta;
4444: LayoutRegion tr;
4445: if (index + 2 < group.getSubIntervalCount()) {
4446: ta = LEADING;
4447: tr = group.getSubInterval(index + 2)
4448: .getCurrentSpace();
4449: } else {
4450: ta = TRAILING;
4451: tr = group.getCurrentSpace();
4452: }
4453: operations.eatGap(current, next, LayoutRegion.distance(
4454: lr, tr, dimension, la, ta));
4455: } else
4456: index++;
4457: }
4458: }
4459:
4460: private void maintainSize(LayoutInterval group,
4461: boolean wasResizing, int dimension,
4462: LayoutInterval excluded, int excludedSize) {
4463: assert group.isParallel(); // [also not used for center or baseline groups]
4464:
4465: int groupSize = group.getCurrentSpace().size(dimension);
4466: int[] groupPos = group.getCurrentSpace().positions[dimension];
4467:
4468: boolean leadAlign = false;
4469: boolean trailAlign = false;
4470: int leadCompPos = Integer.MAX_VALUE;
4471: int trailCompPos = Integer.MIN_VALUE;
4472: int subSize = Integer.MIN_VALUE;
4473:
4474: Iterator it = group.getSubIntervals();
4475: while (it.hasNext()) {
4476: LayoutInterval li = (LayoutInterval) it.next();
4477: int align = li.getAlignment();
4478: int l, t; // leading and trailing position of first and last component
4479: if (li != excluded) {
4480: int size = li.getCurrentSpace().size(dimension);
4481: if (size >= groupSize) {
4482: return;
4483: }
4484: if (size > subSize) {
4485: subSize = size;
4486: }
4487: l = LayoutUtils.getOutermostComponent(li, dimension,
4488: LEADING).getCurrentSpace().positions[dimension][LEADING];
4489: t = LayoutUtils.getOutermostComponent(li, dimension,
4490: TRAILING).getCurrentSpace().positions[dimension][TRAILING];
4491: } else {
4492: if (excludedSize > subSize) {
4493: subSize = excludedSize;
4494: }
4495: if (align == LEADING) {
4496: l = groupPos[LEADING];
4497: t = groupPos[LEADING] + excludedSize;
4498: } else {// TRAILING
4499: l = groupPos[TRAILING] - excludedSize;
4500: t = groupPos[TRAILING];
4501: }
4502: }
4503: if (l < leadCompPos)
4504: leadCompPos = l;
4505: if (t > trailCompPos)
4506: trailCompPos = t;
4507:
4508: if (align == LEADING)
4509: leadAlign = true;
4510: else
4511: // TRAILING
4512: trailAlign = true;
4513: }
4514:
4515: if (leadAlign && trailAlign) {
4516: optimizeGaps(group, dimension, false);
4517: } else { // one open edge to compensate
4518: if (!LayoutInterval.canResize(group)) { // resizing disabled on the group
4519: wasResizing = false;
4520: }
4521: boolean resizing = LayoutInterval.wantResize(group);
4522: LayoutInterval parent = group.getParent();
4523: if (parent != null
4524: && parent.isParallel()
4525: && group.getAlignment() == (leadAlign ? LEADING
4526: : TRAILING)) { // the group can shrink and the parent compensate
4527: maintainSize(parent, wasResizing && !resizing,
4528: dimension, group, subSize);
4529: if (leadAlign) {
4530: groupPos[TRAILING] = trailCompPos;
4531: }
4532: if (trailAlign) {
4533: groupPos[LEADING] = leadCompPos;
4534: }
4535: groupPos[CENTER] = (groupPos[LEADING] + groupPos[LEADING]) / 2;
4536: } else {
4537: int increment = groupSize - subSize;
4538: assert increment > 0;
4539: LayoutInterval gap = new LayoutInterval(SINGLE);
4540: int min, max;
4541: if (!resizing && (wasResizing || parent == null)) {
4542: min = NOT_EXPLICITLY_DEFINED;
4543: max = Short.MAX_VALUE;
4544: } else {
4545: min = max = USE_PREFERRED_SIZE;
4546: }
4547: gap.setSizes(min, increment, max);
4548:
4549: operations.insertGap(gap, group,
4550: leadAlign ? trailCompPos : leadCompPos,
4551: dimension, leadAlign ? TRAILING : LEADING);
4552:
4553: if (leadAlign) {
4554: groupPos[TRAILING] = trailCompPos;
4555: }
4556: if (trailAlign) {
4557: groupPos[LEADING] = leadCompPos;
4558: }
4559: groupPos[CENTER] = (groupPos[LEADING] + groupPos[LEADING]) / 2;
4560:
4561: if (parent != null) {
4562: if (parent.isSequential()) {
4563: parent = parent.getParent();
4564: }
4565: optimizeGaps(parent, dimension, false);
4566: }
4567: }
4568: }
4569: }
4570:
4571: public String[] positionCode() {
4572: return dragger != null ? dragger.positionCode() : new String[2];
4573: }
4574:
4575: // -----
4576: // auxiliary fields holding temporary objects used frequently
4577:
4578: // converted cursor position used during moving/resizing
4579: private int[] cursorPos = { 0, 0 };
4580:
4581: // -----
4582: // test generation support
4583:
4584: static final String TEST_SWITCH = "netbeans.form.layout_test"; // NOI18N
4585:
4586: /* stores test code lines */
4587: public List<String> testCode = new ArrayList<String>();
4588:
4589: // these below are used for removing unwanted move entries, otherwise the code can exceed 10000 lines in a few seconds of form editor work ;O)
4590: private List<String> testCode0 = new ArrayList<String>();
4591: private List<String> beforeMove = new ArrayList<String>();
4592: private List<String> move1 = new ArrayList<String>();
4593: private List<String> move2 = new ArrayList<String>();
4594: private boolean isMoving = false;
4595:
4596: private int modelCounter = -1;
4597:
4598: private Point lastMovePoint = new Point(0, 0);
4599:
4600: public int getModelCounter() {
4601: return modelCounter;
4602: }
4603:
4604: public void setModelCounter(int modelCounter) {
4605: this .modelCounter = modelCounter;
4606: }
4607:
4608: public static boolean testMode() {
4609: return Boolean.getBoolean(TEST_SWITCH);
4610: }
4611:
4612: public boolean logTestCode() {
4613: return modelCounter > -1 && Boolean.getBoolean(TEST_SWITCH);
4614: }
4615: }
|