KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > form > layoutdesign > LayoutFeeder


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.form.layoutdesign;
21
22 import java.util.*;
23
24 /**
25  * This class is responsible for adding layout intervals to model based on
26  * mouse actions done by the user (input provided from LayoutDragger). When an
27  * instance is created, it analyzes the original positions - before the adding
28  * operation is performed (this is needed in case of resizing). Then 'add'
29  * method is called to add the intervals on desired place. It is responsibility
30  * of the caller to remove the intervals/components from original locations
31  * before calling 'add'.
32  * Note this class does not add LayoutComponent instances to model.
33  *
34  * @author Tomas Pavek
35  */

36
37 class LayoutFeeder implements LayoutConstants {
38
39     boolean imposeSize;
40     boolean optimizeStructure;
41
42     private LayoutModel layoutModel;
43     private LayoutOperations operations;
44
45     private LayoutDragger dragger;
46     private IncludeDesc[] originalPositions1 = new IncludeDesc[DIM_COUNT];
47     private IncludeDesc[] originalPositions2 = new IncludeDesc[DIM_COUNT];
48     private boolean[] originalLPositionsFixed = new boolean[DIM_COUNT];
49     private boolean[] originalTPositionsFixed = new boolean[DIM_COUNT];
50     private LayoutDragger.PositionDef[] newPositions = new LayoutDragger.PositionDef[DIM_COUNT];
51     private LayoutInterval[] addingIntervals; // horizontal, vertical
52
private boolean[] becomeResizing = new boolean[DIM_COUNT];
53
54     // working context (actual dimension)
55
private int dimension;
56     private LayoutInterval addingInterval;
57     private LayoutRegion addingSpace;
58     private boolean solveOverlap;
59     private boolean originalLPosFixed;
60     private boolean originalTPosFixed;
61
62     // params used when searching for the right place (inclusion)
63
private int aEdge;
64     private LayoutInterval aSnappedParallel;
65     private LayoutInterval aSnappedNextTo;
66
67     private static class IncludeDesc {
68         LayoutInterval parent;
69         int index = -1; // if adding next to
70
boolean newSubGroup; // can be true if parent is sequential (parallel subgroup for part of the sequence is to be created)
71
LayoutInterval neighbor; // if included in a sequence with single interval (which is not in sequence yet)
72
LayoutInterval snappedParallel; // not null if aligning in parallel
73
LayoutInterval snappedNextTo; // not null if snapped next to (can but need not be 'neighbor')
74
int alignment; // the edge this object defines (leading or trailing or default)
75
boolean fixedPosition; // whether distance from the neighbor is definitely fixed
76
int distance = Integer.MAX_VALUE;
77         int ortDistance = Integer.MAX_VALUE;
78
79         boolean snapped() {
80             return snappedNextTo != null || snappedParallel != null;
81         }
82     }
83
84     // -----
85

86     LayoutFeeder(LayoutOperations operations, LayoutDragger dragger, LayoutInterval[] addingIntervals) {
87         this.layoutModel = operations.getModel();
88         this.operations = operations;
89         this.dragger = dragger;
90         this.addingIntervals = addingIntervals;
91
92         for (int dim=0; dim < DIM_COUNT; dim++) {
93             dimension = dim;
94             if (dragger.isResizing()) {
95                 LayoutInterval adding = addingIntervals[dim];
96                 if (dragger.isResizing(dim)) {
97                     IncludeDesc pos = findOutCurrentPosition(
98                             adding, dim, dragger.getResizingEdge(dim)^1);
99                     LayoutDragger.PositionDef newPos = dragger.getPositions()[dim];
100                     if ((newPos == null || !newPos.snapped) && !pos.snapped()) {
101                         pos.alignment = LayoutInterval.getEffectiveAlignment(adding);
102                     }
103                     originalPositions1[dim] = pos;
104                     newPositions[dim] = newPos;
105                     becomeResizing[dim] = checkResizing(); // if to make the interval resizing
106
}
107                 else { // this dimension has not been resized
108
int alignment = DEFAULT;
109                     IncludeDesc pos1 = findOutCurrentPosition(adding, dim, alignment);
110                     originalPositions1[dim] = pos1;
111                     alignment = pos1.alignment;
112                     if (alignment == LEADING || alignment == TRAILING) {
113                         IncludeDesc pos2 = findOutCurrentPosition(adding, dim, alignment^1);
114                         if (pos2.snapped()) {
115                             originalPositions2[dim] = pos2;
116                         } // don't remember second position if not snapped, one is enough
117
}
118                 }
119                 originalLPositionsFixed[dim] = isFixedRelativePosition(adding, LEADING);
120                 originalTPositionsFixed[dim] = isFixedRelativePosition(adding, TRAILING);
121             }
122             else newPositions[dim] = dragger.getPositions()[dim];
123         }
124     }
125
126     void add() {
127         int overlapDim = getDimensionSolvingOverlap(newPositions);
128
129         for (int dim=overlapDim, dc=0; dc < DIM_COUNT; dim^=1, dc++) {
130             dimension = dim;
131             addingInterval = addingIntervals[dim];
132             addingSpace = dragger.getMovingSpace();
133             addingInterval.setCurrentSpace(addingSpace);
134             solveOverlap = overlapDim == dim;
135             IncludeDesc originalPos1 = originalPositions1[dim];
136             IncludeDesc originalPos2 = originalPositions2[dim];
137             correctNeighborInSequence(originalPos1);
138             correctNeighborInSequence(originalPos2);
139
140             if (dragger.isResizing()) {
141                 originalLPosFixed = originalLPositionsFixed[dim];
142                 originalTPosFixed = originalTPositionsFixed[dim];
143                 if (dragger.isResizing(dim)) {
144                     layoutModel.setIntervalSize(addingInterval,
145                         becomeResizing[dim] ? NOT_EXPLICITLY_DEFINED : USE_PREFERRED_SIZE,
146                         addingSpace.size(dim),
147                         becomeResizing[dim] ? Short.MAX_VALUE : USE_PREFERRED_SIZE);
148                 }
149             }
150
151             LayoutDragger.PositionDef newPos = newPositions[dim];
152             if (newPos != null && (newPos.alignment == CENTER || newPos.alignment == BASELINE)) {
153                 // hack: simplified adding to a closed group
154
aEdge = newPos.alignment;
155                 aSnappedParallel = newPos.interval;
156                 addSimplyAligned();
157                 continue;
158             }
159             if (dragger.isResizing() && (originalPos1.alignment == CENTER || originalPos1.alignment == BASELINE)) {
160                 aEdge = originalPos1.alignment;
161                 aSnappedParallel = originalPos1.snappedParallel;
162                 addSimplyAligned();
163                 continue;
164             }
165
166             // prepare task for searching the position
167
IncludeDesc inclusion1 = null;
168             IncludeDesc inclusion2 = null;
169
170             List inclusions = new LinkedList();
171             boolean preserveOriginal = false;
172
173             // if resizing in the other dimension then renew the original position
174
if (dragger.isResizing(dim^1)) {
175                 aEdge = originalPos1.alignment;
176                 aSnappedParallel = originalPos1.snappedParallel;
177                 aSnappedNextTo = originalPos1.snappedNextTo;
178             }
179             // if snapped in dragger then always find the position
180
else if (newPos != null) {
181                 aEdge = newPos.alignment;
182                 aSnappedParallel = !newPos.nextTo ? newPos.interval : null;
183                 aSnappedNextTo = newPos.snapped && newPos.nextTo ? newPos.interval : null;
184                 // if resizing only in this dimension then preserve the original position
185
preserveOriginal = dragger.isResizing(dim);
186             }
187             // if resizing only in this dimension and without snap then check for
188
// possible growing in parallel with part of its own parent sequence
189
else if (dragger.isResizing(dim)) {
190                 aEdge = originalPos1.alignment;
191                 aSnappedParallel = originalPos1.snappedParallel;
192                 aSnappedNextTo = originalPos1.snappedNextTo;
193                 preserveOriginal = true;
194             }
195             // otherwise plain moving without snap
196
else {
197                 aEdge = DEFAULT;
198                 aSnappedParallel = aSnappedNextTo = null;
199             }
200
201             LayoutInterval root = dragger.getTargetContainer().getLayoutRoot(dim);
202             analyzeParallel(root, inclusions);
203
204             // make sure an inclusion for parallel aligning is considered, choose best inclusion
205
if (inclusions.isEmpty()) { // inclusion for parallel aligning not found
206
assert aSnappedParallel != null;
207                 if (originalPos1 != null && originalPos1.alignment == aEdge)
208                     inclusions.add(originalPos1);
209                 else
210                     addAligningInclusion(inclusions);
211             }
212             else {
213                 IncludeDesc preferred = addAligningInclusion(inclusions); // make sure it is there...
214
if (inclusions.size() > 1) {
215                     if (preferred == null || (preserveOriginal && originalPos1.alignment == aEdge))
216                         preferred = originalPos1;
217                     mergeParallelInclusions(inclusions, preferred, preserveOriginal);
218                     assert inclusions.size() == 1;
219                 }
220             }
221
222             IncludeDesc found = (IncludeDesc) inclusions.get(0);
223             inclusions.clear();
224             if (preserveOriginal) { // resized in this dimension only
225
inclusion1 = originalPos1;
226                 if (found != originalPos1) {
227                     if (newPos != null)
228                         inclusion2 = found;
229                     if (found.parent == originalPos1.parent && found.newSubGroup)
230                         originalPos1.newSubGroup = true;
231                 }
232             }
233             else {
234                 inclusion1 = found;
235                 // second search needed if resizing in the other dimension
236
// and the second edge snapped in current dimension
237
if (dragger.isResizing(dim^1) && (newPos != null || originalPos2 != null)) {
238                     if (newPos != null) { // find inclusion based on position from dragger
239
assert dragger.isResizing(dim);
240                         aEdge = newPos.alignment;
241                         aSnappedParallel = !newPos.nextTo ? newPos.interval : null;
242                         aSnappedNextTo = newPos.snapped && newPos.nextTo ? newPos.interval : null;
243                     }
244                     else { // need to renew the original position
245
assert !dragger.isResizing(dim);
246                         aEdge = originalPos2.alignment;
247                         aSnappedParallel = originalPos2.snappedParallel;
248                         aSnappedNextTo = originalPos2.snappedNextTo;
249                     }
250                     // second round searching
251
analyzeParallel(root, inclusions);
252
253                     if (inclusions.isEmpty()) { // inclusion for parallel aligning not found
254
assert aSnappedParallel != null;
255                         if (originalPos2 != null && originalPos2.alignment == aEdge)
256                             inclusions.add(originalPos2);
257                         else
258                             addAligningInclusion(inclusions);
259                     }
260                     else {
261                         IncludeDesc preferred = addAligningInclusion(inclusions);
262                         if (inclusions.size() > 1) {
263                             if (preferred == null)
264                                 preferred = originalPos2 != null ? originalPos2 : originalPos1;
265                             mergeParallelInclusions(inclusions, preferred, false);
266                             assert inclusions.size() == 1;
267                         }
268                     }
269                     inclusion2 = (IncludeDesc) inclusions.get(0);
270                     inclusions.clear();
271                 }
272             }
273
274             if (!mergeSequentialInclusions(inclusion1, inclusion2))
275                 inclusion2 = null;
276
277             addInterval(inclusion1, inclusion2);
278         }
279     }
280
281     private static IncludeDesc findOutCurrentPosition(LayoutInterval interval, int dimension, int alignment) {
282         LayoutInterval parent = interval.getParent();
283         int nonEmptyCount = LayoutInterval.getCount(parent, LayoutRegion.ALL_POINTS, true);
284
285         IncludeDesc iDesc = new IncludeDesc();
286
287         if (parent.isSequential() && nonEmptyCount > 1) {
288             if (alignment < 0)
289                 alignment = LEADING;
290             if (nonEmptyCount == 2) { // the sequence may not survive when the interval is removed
291
// (if it survives, the inclusion gets corrected by 'correctNeighborInSequence' method)
292
iDesc.parent = parent.getParent();
293                 int index = 0;
294                 for (int i=parent.getSubIntervalCount()-1; i >= 0; i--) {
295                     LayoutInterval li = parent.getSubInterval(i);
296                     if (li == interval) {
297                         index = i;
298                     }
299                     else if (!li.isEmptySpace()) {
300                         iDesc.neighbor = li; // next to a single interval in parallel group
301
iDesc.index = index;
302                         break;
303                     }
304                 }
305             }
306             else { // simply goes to the sequence
307
iDesc.parent = parent;
308                 iDesc.index = parent.indexOf(interval);
309             }
310         }
311         else { // parallel parent
312
if (parent.isSequential()) {
313                 parent = parent.getParent(); // alone in sequence, take parent
314
nonEmptyCount = LayoutInterval.getCount(parent, LayoutRegion.ALL_POINTS, true);
315                 if (alignment < 0)
316                     alignment = LEADING;
317             }
318             else {
319                 int currentAlign = interval.getAlignment();
320                 if (alignment < 0 || (currentAlign != LEADING && currentAlign != TRAILING))
321                     alignment = currentAlign;
322             }
323             if (nonEmptyCount <= 2 && parent.getParent() != null) {
324                 // parallel group will not survive when the interval is removed
325
LayoutInterval subGroup = parent;
326                 parent = parent.getParent();
327                 if (parent.isSequential()) {
328                     boolean ortOverlap = false;
329                     for (Iterator it=parent.getSubIntervals(); it.hasNext(); ) {
330                         LayoutInterval li = (LayoutInterval) it.next();
331                         if (!li.isEmptySpace() && !li.isParentOf(interval)
332                             && LayoutRegion.overlap(interval.getCurrentSpace(), li.getCurrentSpace(), dimension^1, 0))
333                         { // orthogonal overlap - need to stay within the sequence
334
ortOverlap = true;
335                             break;
336                         }
337                     }
338                     if (ortOverlap) { // parallel with part of the sequence
339
iDesc.newSubGroup = true;
340                         iDesc.index = parent.indexOf(subGroup);
341                     }
342                     else // parallel with whole sequence
343
parent = parent.getParent();
344                 }
345                 iDesc.parent = parent;
346             }
347             else iDesc.parent = parent; // simply goes to the parallel group
348
}
349
350         if (alignment == LEADING || alignment == TRAILING) {
351             iDesc.fixedPosition = isFixedRelativePosition(interval, alignment);
352         }
353
354         iDesc.snappedParallel = findAlignedInterval(interval, dimension, alignment);
355
356         // check for next to aligning
357
if (iDesc.snappedParallel == null && (alignment == LEADING || alignment == TRAILING)) {
358             LayoutInterval gap = LayoutInterval.getNeighbor(interval, alignment, false, true, false);
359             if (gap != null && LayoutInterval.isFixedDefaultPadding(gap)) {
360                 LayoutInterval prev = LayoutInterval.getDirectNeighbor(gap, alignment^1, true);
361                 if (prev == interval || LayoutInterval.isPlacedAtBorder(interval, prev, dimension, alignment)) {
362                     LayoutInterval next = LayoutInterval.getNeighbor(gap, alignment, true, true, false);
363                     if (next != null) {
364                         if (next.getParent() == gap.getParent()
365                             || next.getCurrentSpace().positions[dimension][alignment^1]
366                                == gap.getParent().getCurrentSpace().positions[dimension][alignment])
367                         { // the next interval is really at preferred distance
368
iDesc.snappedNextTo = next;
369                         }
370                     }
371                     else { // likely next to the root group border
372
next = LayoutInterval.getRoot(interval);
373                         if (LayoutInterval.isPlacedAtBorder(gap.getParent(), next, dimension, alignment))
374                             iDesc.snappedNextTo = next;
375                     }
376                 }
377             }
378         }
379
380         iDesc.alignment = alignment;
381         return iDesc;
382     }
383
384     private static boolean isFixedRelativePosition(LayoutInterval interval, int edge) {
385         assert edge == LEADING || edge == TRAILING;
386         LayoutInterval parent = interval.getParent();
387         if (parent == null)
388             return true;
389         if (parent.isSequential()) {
390             LayoutInterval li = LayoutInterval.getDirectNeighbor(interval, edge, false);
391             if (li != null)
392                 return !LayoutInterval.wantResize(li);
393             else {
394                 interval = parent;
395                 parent = interval.getParent();
396             }
397         }
398         if (!LayoutInterval.isAlignedAtBorder(interval, parent, edge)
399                 && LayoutInterval.contentWantResize(parent))
400             return false;
401
402         return isFixedRelativePosition(parent, edge);
403     }
404
405     private static LayoutInterval findAlignedInterval(LayoutInterval interval,
406                                                       int dimension,
407                                                       int alignment)
408     {
409         LayoutInterval parent;
410         LayoutInterval alignedInterval = null;
411         // need to force parallel alignment in case of indented position whose
412
// parent parallel group won't survive removing of the resizing component
413
// (the resizing interval will target a higher group where the resulting
414
// indent gap might be different)
415
boolean indent = false;
416         parent = interval.getParent();
417         if ((alignment == LEADING || alignment == TRAILING)
418             && parent.isSequential()
419             && LayoutInterval.getCount(parent, -1, true) == 1)
420         { // alone in sequence
421
LayoutInterval neighbor = LayoutInterval.getDirectNeighbor(interval, alignment, false);
422             if (neighbor != null && neighbor.isEmptySpace() && !LayoutInterval.canResize(neighbor)
423                 && LayoutInterval.getCount(parent.getParent(), LayoutRegion.ALL_POINTS, true) == 2)
424             { // otherwise only one sibling in parallel group - candidate for aligned interval
425
indent = true;
426             }
427         }
428
429         do {
430             parent = LayoutInterval.getFirstParent(interval, PARALLEL);
431             if (!indent) {
432                 boolean aligned = alignment == LEADING || alignment == TRAILING ?
433                     LayoutInterval.isAlignedAtBorder(interval, parent, alignment) :
434                     interval.getParent() == parent && interval.getAlignment() == alignment;
435                 if (!aligned)
436                     return null;
437                 if (parent.getParent() == null)
438                     return parent; // aligned with root group
439
}
440
441             for (Iterator it=parent.getSubIntervals(); it.hasNext(); ) {
442                 LayoutInterval sub = (LayoutInterval) it.next();
443                 if (!sub.isEmptySpace() && sub != interval && !sub.isParentOf(interval)) {
444                     if (alignment == LEADING || alignment == TRAILING) {
445                         LayoutInterval li = LayoutUtils.getOutermostComponent(sub, dimension, alignment);
446                         if (LayoutInterval.isAlignedAtBorder(li, parent, alignment)
447                             || LayoutInterval.isPlacedAtBorder(li, parent, dimension, alignment))
448                         { // here we have an aligned component (let's see if it is not nested)
449
LayoutInterval p = LayoutInterval.getFirstParent(li, PARALLEL);
450                             while (p != parent) {
451                                 li = p;
452                                 p = LayoutInterval.getFirstParent(li, PARALLEL);
453                             }
454                             alignedInterval = li;
455                         }
456                         else continue; // not aligned subinterval
457
}
458                     else alignedInterval = sub;
459                     break;
460                 }
461             }
462
463             if (indent)
464                 return alignedInterval;
465
466             interval = parent;
467         }
468         while (alignedInterval == null);
469
470         return parent.getSubIntervalCount() > 2 ? parent : alignedInterval;
471     }
472
473     /**
474      * For inclusion derived from existing position (findOutCurrentPosition)
475      * checks if it does not refer to 'neighbor' being in sequential group.
476      * In such case the inclusion is changed to refer to the sequence (single
477      * neighbor is supposed to be in parallel group). This may happen if the
478      * sequence survived removal of the interval even with just one component
479      * remaining (because of gaps around it).
480      */

481     private static void correctNeighborInSequence(IncludeDesc iDesc) {
482         if (iDesc != null && iDesc.neighbor != null && iDesc.neighbor.getParent().isSequential()) {
483             assert iDesc.parent == iDesc.neighbor.getParent().getParent();
484             iDesc.parent = iDesc.neighbor.getParent();
485             iDesc.neighbor = null;
486         }
487     }
488
489     // -----
490
// overlap analysis
491

492     private int getDimensionSolvingOverlap(LayoutDragger.PositionDef[] positions) {
493         if (dragger.isResizing(HORIZONTAL) && !dragger.isResizing(VERTICAL)) {
494             return HORIZONTAL;
495         }
496         if ((dragger.isResizing(VERTICAL) && !dragger.isResizing(HORIZONTAL))
497             || (positions[HORIZONTAL] != null && positions[HORIZONTAL].snapped && (positions[VERTICAL] == null || !positions[VERTICAL].snapped))
498             || (positions[VERTICAL] != null && !positions[VERTICAL].nextTo && positions[VERTICAL].snapped
499                 && (positions[VERTICAL].interval.getParent() == null)
500                 && !existsComponentPlacedAtBorder(positions[VERTICAL].interval, VERTICAL, positions[VERTICAL].alignment))) {
501             return VERTICAL;
502         }
503         if (positions[VERTICAL] != null && positions[VERTICAL].nextTo && positions[VERTICAL].snapped
504             && (positions[VERTICAL].interval.getParent() == null)) {
505             int alignment = positions[VERTICAL].alignment;
506             int[][] overlapSides = overlappingGapSides(dragger.getTargetContainer().getLayoutRoot(HORIZONTAL),
507                                                        dragger.getMovingSpace());
508             if (((alignment == LEADING) || (alignment == TRAILING))
509                 && (overlapSides[VERTICAL][1-alignment] != 0)
510                 && (overlapSides[VERTICAL][alignment] == 0)) {
511                 return VERTICAL;
512             }
513         }
514         if ((positions[HORIZONTAL] == null || !positions[HORIZONTAL].snapped)
515             && (positions[VERTICAL] == null || !positions[VERTICAL].snapped)) {
516             boolean[] overlapDim = overlappingGapDimensions(dragger.getTargetContainer().getLayoutRoot(HORIZONTAL),
517                                                             dragger.getMovingSpace());
518             if (overlapDim[VERTICAL] && !overlapDim[HORIZONTAL]) {
519                 return VERTICAL;
520             }
521         }
522         return HORIZONTAL;
523     }
524     
525     /**
526      * Checks whether there is a component placed at the border
527      * of the specified interval.
528      *
529      * @param interval interval to check.
530      * @param dimension dimension that should be considered.
531      * @param alignment alignment that should be considered.
532      */

533     private static boolean existsComponentPlacedAtBorder(LayoutInterval interval, int dimension, int alignment) {
534         Iterator iter = interval.getSubIntervals();
535         while (iter.hasNext()) {
536             LayoutInterval subInterval = (LayoutInterval)iter.next();
537             if (LayoutInterval.isPlacedAtBorder(interval, dimension, alignment)) {
538                 if (subInterval.isComponent()) {
539                     return true;
540                 } else if (subInterval.isGroup()) {
541                     if (existsComponentPlacedAtBorder(subInterval, dimension, alignment)) {
542                         return true;
543                     }
544                 }
545             }
546         }
547         return false;
548     }
549
550     /**
551      * Fills the given list by components that overlap with the <code>region</code>.
552      *
553      * @param overlaps list that should be filled by overlapping components.
554      * @param group layout group that is scanned by this method.
555      * @param region region to check.
556      */

557     private static void fillOverlappingComponents(List overlaps, LayoutInterval group, LayoutRegion region) {
558         Iterator iter = group.getSubIntervals();
559         while (iter.hasNext()) {
560             LayoutInterval subInterval = (LayoutInterval)iter.next();
561             if (subInterval.isGroup()) {
562                 fillOverlappingComponents(overlaps, subInterval, region);
563             } else if (subInterval.isComponent()) {
564                 LayoutComponent component = subInterval.getComponent();
565                 LayoutRegion compRegion = subInterval.getCurrentSpace();
566                 if (LayoutRegion.overlap(compRegion, region, HORIZONTAL, 0)
567                     && LayoutRegion.overlap(compRegion, region, VERTICAL, 0)) {
568                     overlaps.add(component);
569                 }
570             }
571         }
572     }
573
574     // Helper method for getDimensionSolvingOverlap() method
575
private static boolean[] overlappingGapDimensions(LayoutInterval layoutRoot, LayoutRegion region) {
576         boolean[] result = new boolean[2];
577         int[][] overlapSides = overlappingGapSides(layoutRoot, region);
578         for (int i=0; i<DIM_COUNT; i++) {
579             result[i] = (overlapSides[i][0] == 1) && (overlapSides[i][1] == 1);
580         }
581         return result;
582     }
583
584     // Helper method for getDimensionSolvingOverlap() method
585
private static int[][] overlappingGapSides(LayoutInterval layoutRoot, LayoutRegion region) {
586         int[][] overlapSides = new int[][] {{0,0},{0,0}};
587         List overlaps = new LinkedList();
588 // LayoutInterval layoutRoot = LayoutInterval.getRoot(positions[HORIZONTAL].interval);
589
fillOverlappingComponents(overlaps, layoutRoot, region);
590         Iterator iter = overlaps.iterator();
591         while (iter.hasNext()) {
592             LayoutComponent component = (LayoutComponent)iter.next();
593             LayoutRegion compRegion = component.getLayoutInterval(HORIZONTAL).getCurrentSpace();
594             for (int i=0; i<DIM_COUNT; i++) {
595                 int[] edges = overlappingSides(compRegion, region, i);
596                 for (int j=0; j<2; j++) {
597                     if (edges[j] == 1) {
598                         overlapSides[i][j] = 1;
599                     } else if (edges[j] == -1) {
600                         if (overlapSides[i][j] == -1) {
601                             overlapSides[i][j] = 1;
602                         } else if (overlapSides[i][j] == 0) {
603                             overlapSides[i][j] = -1;
604                         }
605                     }
606                 }
607             }
608         }
609         return overlapSides;
610     }
611
612     // Helper method for overlappingGapSides() method
613
private static int[] overlappingSides(LayoutRegion compRegion, LayoutRegion region, int dimension) {
614         int[] sides = new int[2];
615         int compLeading = compRegion.positions[dimension][LEADING];
616         int compTrailing = compRegion.positions[dimension][TRAILING];
617         int regLeading = region.positions[dimension][LEADING];
618         int regTrailing = region.positions[dimension][TRAILING];
619         if ((regLeading < compTrailing) && (compTrailing < regTrailing)) {
620             sides[0] = 1;
621         }
622         if ((regLeading < compLeading) && (compLeading < regTrailing)) {
623             sides[1] = 1;
624         }
625         if ((sides[0] == 1) && (sides[1] == 1)) {
626             sides[0] = sides[1] = -1;
627         }
628         return sides;
629     }
630
631     // -----
632
// the following methods work in context of adding to actual dimension
633

634     private boolean checkResizing() {
635         LayoutInterval interval = addingIntervals[dimension];
636         int resizingEdge = dragger.getResizingEdge(dimension);
637         int fixedEdge = resizingEdge^1;
638         LayoutDragger.PositionDef newPos = newPositions[dimension];
639         boolean resizing = false;
640
641         if (newPos != null && newPos.snapped && newPos.interval != null) {
642             int align1, align2;
643             if (newPos.interval.isParentOf(interval)) {
644                 LayoutInterval parent = LayoutInterval.getFirstParent(interval, PARALLEL);
645                 if (!LayoutRegion.pointInside(dragger.getMovingSpace(), resizingEdge, parent.getCurrentSpace(), dimension))
646                     parent = newPos.interval;
647                 align1 = LayoutInterval.getEffectiveAlignmentInParent(interval, parent, fixedEdge);
648                 align2 = resizingEdge;
649             }
650             else {
651                 LayoutInterval parent = LayoutInterval.getCommonParent(interval, newPos.interval);
652                 align1 = LayoutInterval.getEffectiveAlignmentInParent(interval, parent, fixedEdge);
653                 align2 = newPos.nextTo ?
654                     LayoutInterval.getEffectiveAlignmentInParent(newPos.interval, parent, newPos.alignment^1) :
655                     resizingEdge;
656             }
657             if (align1 != align2 && (align1 == LEADING || align1 == TRAILING) && (align2 == LEADING || align2 == TRAILING))
658                 resizing = true;
659         }
660         // [perhaps we should consider also potential resizability of the component,
661
// not only on resizing operation - the condition should be:
662
// isComponentResizable(interval.getComponent(), dimension) ]
663
return resizing;
664 /* if (pos2 != null) {
665             resizing = pos1.snapped && pos2.snapped;
666         }
667         else if (pos1.snapped) {
668             LayoutInterval parent = interval.getParent();
669             if (parent == null || parent.isParallel()) {
670                 resizing = false;
671                 // cannot decide this as we can't be sure about the actual visual position of the group
672                 // [unless LayoutDragger provides two positions also for moving,
673                 // the resizing flag is always reset when the interval is moved]
674             }
675             else { // in sequence
676                 int align = pos1.alignment ^ 1;
677                 LayoutInterval gap = LayoutInterval.getNeighbor(interval, align, false, true, true);
678                 resizing = gap != null && gap.isDefaultPadding() && !LayoutInterval.canResize(gap);
679                 // [not catching situation when touching the root without gap]
680             }
681         }
682         else {
683             resizing = false;
684         } */

685     }
686
687     /**
688      * Adds aligned with an interval to existing group, or creates new.
689      * (Now used only to a limited extent for closed groups only.)
690      */

691     private void addSimplyAligned() {
692         int alignment = aEdge;
693         assert alignment == CENTER || alignment == BASELINE;
694         layoutModel.setIntervalAlignment(addingInterval, alignment);
695
696         if (aSnappedParallel.isParallel() && aSnappedParallel.getGroupAlignment() == alignment) {
697             layoutModel.addInterval(addingInterval, aSnappedParallel, -1);
698             return;
699         }
700         LayoutInterval parent = aSnappedParallel.getParent();
701         if (parent.isParallel() && parent.getGroupAlignment() == alignment) {
702             layoutModel.addInterval(addingInterval, parent, -1);
703             return;
704         }
705
706         int alignIndex = layoutModel.removeInterval(aSnappedParallel);
707         LayoutInterval subGroup = new LayoutInterval(PARALLEL);
708         subGroup.setGroupAlignment(alignment);
709         if (parent.isParallel()) {
710             subGroup.setAlignment(aSnappedParallel.getAlignment());
711         }
712         layoutModel.setIntervalAlignment(aSnappedParallel, alignment);
713         layoutModel.addInterval(aSnappedParallel, subGroup, -1);
714         layoutModel.addInterval(addingInterval, subGroup, -1);
715         layoutModel.addInterval(subGroup, parent, alignIndex);
716     }
717
718     void addInterval(IncludeDesc iDesc1, IncludeDesc iDesc2) {
719         addToGroup(iDesc1, iDesc2, true);
720
721         // align in parallel if required
722
if (iDesc1.snappedParallel != null || (iDesc2 != null && iDesc2.snappedParallel != null)) {
723             if (iDesc2 != null && iDesc2.snappedParallel != null) {
724                 alignInParallel(addingInterval, iDesc2.snappedParallel, iDesc2.alignment);
725             }
726             if (iDesc1.snappedParallel != null) {
727                 alignInParallel(addingInterval, iDesc1.snappedParallel, iDesc1.alignment);
728             }
729         }
730         checkParallelResizing(addingInterval, iDesc1, iDesc2);
731
732         // post processing
733
LayoutInterval parent = addingInterval.getParent();
734         int accAlign = DEFAULT;
735         if (parent.isSequential()) {
736             int tryAlign = parent.getAlignment() != TRAILING ? TRAILING : LEADING;
737             if (LayoutInterval.getDirectNeighbor(addingInterval, tryAlign, true) == null) {
738                 accAlign = tryAlign;
739             }
740             else {
741                 tryAlign ^= 1;
742                 if (LayoutInterval.getDirectNeighbor(addingInterval, tryAlign, true) == null)
743                     accAlign = tryAlign;
744             }
745         }
746         else {
747             accAlign = addingInterval.getAlignment() ^ 1;
748         }
749         if (accAlign != DEFAULT) {
750             accommodateOutPosition(addingInterval, accAlign); // adapt size of parent/neighbor
751
}
752
753         if (dragger.isResizing(dimension) && LayoutInterval.wantResize(addingInterval))
754             operations.suppressResizingOfSurroundingGaps(addingInterval);
755
756         // optimize gaps (may eliminate the sequence, also removes supporting gap in container)
757
operations.optimizeGaps(LayoutInterval.getFirstParent(addingInterval, PARALLEL), dimension);
758
759         parent = addingInterval.getParent();
760         if (parent.isSequential()) {// && !alignedInParallel)
761
int nonEmptyCount = LayoutInterval.getCount(parent, LayoutRegion.ALL_POINTS, true);
762             if (nonEmptyCount > 1 && dimension == HORIZONTAL) {
763                 // check whether the added interval could not be rather placed
764
// in a neighbor parallel group
765
operations.moveInsideSequential(parent, dimension);
766             }
767         }
768
769         // avoid unnecessary parallel group nesting
770
operations.mergeParallelGroups(LayoutInterval.getFirstParent(addingInterval, PARALLEL));
771     }
772
773     private void addToGroup(IncludeDesc iDesc1, IncludeDesc iDesc2, boolean definite) {
774         assert iDesc2 == null || (iDesc1.parent == iDesc2.parent
775                                   && iDesc1.newSubGroup == iDesc2.newSubGroup
776                                   && iDesc1.neighbor == iDesc2.neighbor);
777
778         LayoutInterval parent = iDesc1.parent;
779         LayoutInterval seq = null;
780         int index = 0;
781         if (parent.isSequential()) {
782             if (iDesc1.newSubGroup) {
783                 LayoutRegion space = addingSpace;
784 // if (dimension == VERTICAL) { // count in a margin in vertical direction
785
// // [because analyzeAdding uses it - maybe we should get rid of it completely]
786
// space = new LayoutRegion(space);
787
// space.reshape(VERTICAL, LEADING, -4);
788
// space.reshape(VERTICAL, TRAILING, 4);
789
// }
790
LayoutInterval subgroup = extractParallelSequence(
791                         parent, space, false, iDesc1.alignment); // dimension == VERTICAL
792
if (subgroup != null) { // just for robustness - null only if something got screwed up
793
seq = new LayoutInterval(SEQUENTIAL);
794                     parent = subgroup;
795                 }
796             }
797             if (seq == null) {
798                 seq = parent;
799                 parent = seq.getParent();
800                 index = iDesc1.index;
801             }
802         }
803         else { // parallel parent
804
LayoutInterval neighbor = iDesc1.neighbor;
805             if (neighbor != null) {
806                 assert neighbor.getParent() == parent;
807                 seq = new LayoutInterval(SEQUENTIAL);
808                 layoutModel.addInterval(seq, parent, layoutModel.removeInterval(neighbor));
809                 seq.setAlignment(neighbor.getAlignment());
810                 layoutModel.setIntervalAlignment(neighbor, DEFAULT);
811                 layoutModel.addInterval(neighbor, seq, 0);
812                 index = iDesc1.index;
813             }
814             else {
815                 seq = new LayoutInterval(SEQUENTIAL);
816                 seq.setAlignment(iDesc1.alignment);
817             }
818         }
819
820         assert iDesc1.alignment >= 0 || iDesc2 == null;
821         assert iDesc2 == null || iDesc2.alignment == (iDesc1.alignment^1);
822         assert parent.isParallel();
823
824         LayoutInterval[] neighbors = new LayoutInterval[2]; // LEADING, TRAILING
825
LayoutInterval[] gaps = new LayoutInterval[2]; // LEADING, TRAILING
826
LayoutInterval originalGap = null;
827         int[] centerDst = new int[2]; // LEADING, TRAILING
828

829         // find the neighbors for the adding interval
830
int count = seq.getSubIntervalCount();
831         if (index > count)
832             index = count;
833         for (int i = LEADING; i <= TRAILING; i++) {
834             int idx1 = i == LEADING ? index - 1 : index;
835             int idx2 = i == LEADING ? index - 2 : index + 1;
836             if (idx1 >= 0 && idx1 < count) {
837                 LayoutInterval li = seq.getSubInterval(idx1);
838                 if (li.isEmptySpace()) {
839                     originalGap = li;
840                     if (idx2 >= 0 && idx2 < count)
841                         neighbors[i] = seq.getSubInterval(idx2);
842                 }
843                 else neighbors[i] = li;
844             }
845             if (iDesc1.alignment < 0) { // no alignment known
846
centerDst[i] = addingSpace.positions[dimension][CENTER] -
847                     (neighbors[i] != null ?
848                         getPerceivedNeighborPosition(neighbors[i], addingSpace, dimension, i^1) :
849                         getPerceivedParentPosition(seq, parent, addingSpace, dimension, i));
850                 if (i == TRAILING)
851                     centerDst[i] *= -1;
852             }
853         }
854
855         // compute leading and trailing gaps
856
int edges = 2;
857         for (int i=LEADING; edges > 0; i^=1, edges--) {
858             gaps[i] = null;
859             LayoutInterval outerNeighbor = neighbors[i] == null ?
860                     LayoutInterval.getNeighbor(parent, i, false, true, false) : null;
861             IncludeDesc iiDesc = iDesc1.alignment < 0 || iDesc1.alignment == i ? iDesc1 : iDesc2;
862
863             if (neighbors[i] == null && iiDesc != null) { // at the start/end of the sequence
864
if (iiDesc.snappedNextTo != null
865                     && outerNeighbor != null && LayoutInterval.isDefaultPadding(outerNeighbor))
866                 { // the padding is outside of the parent already
867
continue;
868                 }
869                 if (iiDesc.snappedParallel != null
870                     && (!seq.isParentOf(iiDesc.snappedParallel) || originalGap == null))
871                 { // starting/ending edge aligned in parallel - does not need a gap
872
continue;
873                 }
874             }
875
876             boolean aligned;
877             if (iDesc1.alignment < 0) { // no specific alignment - decide based on distance
878
aligned = centerDst[i] < centerDst[i^1]
879                           || (centerDst[i] == centerDst[i^1] && i == LEADING);
880             }
881             else if (iDesc2 != null) { // both positions defined
882
aligned = iiDesc.fixedPosition
883                           || (i == LEADING && originalLPosFixed)
884                           || (i == TRAILING && originalTPosFixed);
885             }
886             else { // single position only (either next to or parallel)
887
if (iDesc1.snappedParallel == null || !seq.isParentOf(iDesc1.snappedParallel))
888                     aligned = i == iDesc1.alignment;
889                 else // special case - aligning with interval in the same sequence - to subst. its position
890
aligned = i == (iDesc1.alignment^1);
891             }
892
893             boolean minorGap = false;
894             if (!aligned && neighbors[i] == null && originalGap == null) {
895                 IncludeDesc otherDesc = iiDesc == iDesc1 ? iDesc2 : iDesc1;
896                 LayoutInterval parallel = otherDesc != null ? otherDesc.snappedParallel : null;
897                 // make sure new sequence has appropriate explicit alignment
898
if (parallel == null && seq.getSubIntervalCount() == 0 && seq.getAlignment() != (i^1)) {
899                     layoutModel.setIntervalAlignment(seq, i^1);
900                 }
901                 if (outerNeighbor != null && outerNeighbor.isEmptySpace()) {
902                     continue; // unaligned ending gap not needed - there's a gap outside the parent
903
}
904                 else { // minor gap if it does not need to define the parent size
905
minorGap = (parallel != null && parallel.getParent() != null)
906                                || (parent.getParent() != null && LayoutInterval.getCount(parent, i^1, true) > 0);
907                 }
908             }
909
910             boolean fixedGap = aligned;
911
912             if (!fixedGap) {
913                 if (minorGap || LayoutInterval.wantResize(addingInterval)) {
914                     fixedGap = true;
915                 }
916                 else if (originalGap != null && !LayoutInterval.canResize(originalGap)) {
917                     IncludeDesc otherDesc = iiDesc == iDesc1 ? iDesc2 : iDesc1;
918                     if (otherDesc == null || otherDesc.snappedParallel == null
919                         || neighbors[i] == null || LayoutInterval.getEffectiveAlignment(neighbors[i], i^1) == (i^1))
920                         fixedGap = true;
921                 }
922                 else if (originalGap == null) {
923                     if ((neighbors[i] != null && LayoutInterval.getEffectiveAlignment(neighbors[i], i^1) == (i^1))
924                          || LayoutInterval.wantResize(seq))
925 // || (neighbors[i] == null && !LayoutInterval.contentWantResize(parent)))
926
fixedGap = true;
927                 }
928             }
929
930             LayoutInterval gap = new LayoutInterval(SINGLE);
931             if (!minorGap && (iiDesc == null || iiDesc.snappedNextTo == null)) {
932                 // the gap possibly needs an explicit size
933
LayoutRegion space = iiDesc != null && iiDesc.snappedParallel != null ?
934                                      iiDesc.snappedParallel.getCurrentSpace() : addingSpace;
935                 int distance = neighbors[i] != null ?
936                     LayoutRegion.distance(neighbors[i].getCurrentSpace(), space, dimension, i^1, i) :
937                     LayoutRegion.distance(parent.getCurrentSpace(), space, dimension, i, i);
938                 if (i == TRAILING)
939                     distance *= -1;
940
941                 if (distance > 0) {
942                     int pad = neighbors[i] != null
943                               || LayoutInterval.getNeighbor(parent, i, false, true, false) == null ?
944                         determineExpectingPadding(addingInterval, neighbors[i], seq, i) :
945                         Short.MIN_VALUE; // has no neighbor, but is not related to container border
946
if (distance > pad || (fixedGap && distance != pad)) {
947                         gap.setPreferredSize(distance);
948                         if (fixedGap) {
949                             gap.setMinimumSize(USE_PREFERRED_SIZE);
950                             gap.setMaximumSize(USE_PREFERRED_SIZE);
951                         }
952                     }
953                 }
954             }
955             if (!fixedGap) {
956                 gap.setMaximumSize(Short.MAX_VALUE);
957                 // resizing gap may close the open parent group
958
IncludeDesc otherDesc = iiDesc == iDesc1 ? iDesc2 : iDesc1;
959                 if (definite && neighbors[i] != null && parent.getParent() != null
960                     && (otherDesc == null || otherDesc.alignment == DEFAULT)
961                     && !isSignificantGroupEdge(seq, i^1))
962                 { // the aligned edge needs to be anchored out of parent (independently)
963
parent = separateSequence(seq, i^1);
964                     if (i == TRAILING)
965                         edges++; // we need to revisit the LEADING gap
966
}
967             }
968
969             gaps[i] = gap;
970         }
971
972         if (seq.getParent() == null) { // newly created sequence
973
assert seq.getSubIntervalCount() == 0;
974             if (gaps[LEADING] == null && gaps[TRAILING] == null) { // after all, the sequence is not needed
975
layoutModel.setIntervalAlignment(addingInterval, seq.getAlignment());
976                 layoutModel.addInterval(addingInterval, parent, -1);
977                 return;
978             }
979             else layoutModel.addInterval(seq, parent, -1);
980         }
981
982         // aligning in parallel with interval in the same sequence was resolved
983
// by substituting its position
984
if (iDesc1.snappedParallel != null && seq.isParentOf(iDesc1.snappedParallel)) {
985             iDesc1.snappedParallel = null; // set to null not to try alignInParallel later
986
}
987
988         // finally add the surrounding gaps and the interval
989
if (originalGap != null) {
990             index = layoutModel.removeInterval(originalGap);
991         }
992         else if (neighbors[TRAILING] != null) {
993             index = seq.indexOf(neighbors[TRAILING]);
994         }
995         else if (neighbors[LEADING] != null) {
996             index = seq.getSubIntervalCount();
997         }
998         else index = 0;
999
1000        if (gaps[LEADING] != null) {
1001            layoutModel.addInterval(gaps[LEADING], seq, index++);
1002        }
1003        layoutModel.setIntervalAlignment(addingInterval, DEFAULT);
1004        layoutModel.addInterval(addingInterval, seq, index++);
1005        if (gaps[TRAILING] != null) {
1006            layoutModel.addInterval(gaps[TRAILING], seq, index);
1007        }
1008    }
1009
1010    private LayoutInterval extractParallelSequence(LayoutInterval seq,
1011                                                   LayoutRegion space,
1012                                                   boolean close,
1013                                                   int alignment)
1014    {
1015        int count = seq.getSubIntervalCount();
1016        int startIndex = 0;
1017        int endIndex = count - 1;
1018        int startPos = seq.getCurrentSpace().positions[dimension][LEADING];
1019        int endPos = seq.getCurrentSpace().positions[dimension][TRAILING];
1020        int point = alignment < 0 ? CENTER : alignment;
1021
1022        for (int i=0; i < count; i++) {
1023            LayoutInterval li = seq.getSubInterval(i);
1024            if (li.isEmptySpace())
1025                continue;
1026
1027            LayoutRegion subSpace = li.getCurrentSpace();
1028            boolean forcedParallel = !solveOverlap && LayoutUtils.contentOverlap(space, li, dimension);
1029            if (!forcedParallel && LayoutUtils.contentOverlap(space, li, dimension^1)) { // orthogonal overlap
1030
// this interval makes a hard boundary
1031
if (getAddDirection(space, subSpace, dimension, point) == LEADING) {
1032                    // given interval is positioned before this subinterval (trailing boundary)
1033
endIndex = i - 1;
1034                    endPos = subSpace.positions[dimension][LEADING];
1035                    break;
1036                }
1037                else { // given interval points behind this one (leading boundary)
1038
startIndex = i + 1;
1039                    startPos = subSpace.positions[dimension][TRAILING];
1040                }
1041            }
1042            else if (close) { // go for smallest parallel part possible
1043
int[] detPos = space.positions[dimension];
1044                int[] subPos = subSpace.positions[dimension];
1045                if (detPos[LEADING] >= subPos[TRAILING]) {
1046                    startIndex = i + 1;
1047                    startPos = subPos[TRAILING];
1048                }
1049                else if (detPos[LEADING] >= subPos[LEADING]) {
1050                    startIndex = i;
1051                    startPos = subPos[LEADING];
1052                }
1053                else if (detPos[TRAILING] <= subPos[TRAILING]) {
1054                    if (detPos[TRAILING] > subPos[LEADING]) {
1055                        endIndex = i;
1056                        endPos = subPos[TRAILING];
1057                        break;
1058                    }
1059                    else { // detPos[TRAILING] <= subPos[LEADING]
1060
endIndex = i - 1;
1061                        endPos = subPos[LEADING];
1062                        break;
1063                    }
1064                }
1065            }
1066        }
1067
1068        if (startIndex > endIndex) {
1069            return null; // no part of the sequence can be parallel to the given space
1070
}
1071        if (startIndex == 0 && endIndex == count-1) { // whole sequence is parallel
1072
return seq.getParent();
1073        }
1074
1075        LayoutInterval group = new LayoutInterval(PARALLEL);
1076// int effAlign1 = getEffectiveAlignment(seq.getSubInterval(startIndex));
1077
// int effAlign2 = getEffectiveAlignment(seq.getSubInterval(endIndex));
1078
// int groupAlign = (effAlign1 == effAlign2 || effAlign2 < 0) ? effAlign1 : effAlign2;
1079
if (alignment != DEFAULT) {
1080            group.setGroupAlignment(/*groupAlign == LEADING || groupAlign == TRAILING ?
1081                                groupAlign :*/
alignment);
1082        }
1083        if (startIndex == endIndex) {
1084            LayoutInterval li = layoutModel.removeInterval(seq, startIndex);
1085            layoutModel.addInterval(li, group, 0);
1086        }
1087        else {
1088            LayoutInterval interSeq = new LayoutInterval(SEQUENTIAL);
1089            group.add(interSeq, 0);
1090            int i = startIndex;
1091            while (i <= endIndex) {
1092                LayoutInterval li = layoutModel.removeInterval(seq, i);
1093                endIndex--;
1094                layoutModel.addInterval(li, interSeq, -1);
1095            }
1096        }
1097        layoutModel.addInterval(group, seq, startIndex);
1098
1099        group.getCurrentSpace().set(dimension, startPos, endPos);
1100
1101        return group;
1102    }
1103
1104    private static int getPerceivedParentPosition(LayoutInterval interval, LayoutInterval parent,
1105                                                  LayoutRegion space, int dimension, int alignment)
1106    {
1107        int position = Integer.MIN_VALUE;
1108        do {
1109            if (parent.isSequential()) {
1110                interval = parent;
1111                parent = interval.getParent();
1112            }
1113
1114            LayoutInterval neighbor = null;
1115            while (neighbor == null && parent.getParent() != null) {
1116                boolean significantEdge = interval.getParent() != null ?
1117                            isSignificantGroupEdge(interval, alignment) :
1118                            LayoutInterval.isClosedGroup(parent, alignment);
1119                if (significantEdge)
1120                    break;
1121
1122                neighbor = LayoutInterval.getDirectNeighbor(parent, alignment, true);
1123                if (neighbor == null) {
1124                    interval = parent;
1125                    parent = interval.getParent();
1126                    if (parent.isSequential()) {
1127                        interval = parent;
1128                        parent = interval.getParent();
1129                    }
1130                }
1131            }
1132
1133            if (neighbor == null) {
1134                position = parent.getCurrentSpace().positions[dimension][alignment];
1135            }
1136            else { // look for neighbor of the parent that has orthogonal overlap with the given space
1137
do {
1138                    position = getPerceivedNeighborPosition(neighbor, space, dimension, alignment^1);
1139                    if (position != Integer.MIN_VALUE)
1140                        break;
1141                    else // otherwise the space can "go through" this neighbor
1142
neighbor = LayoutInterval.getDirectNeighbor(neighbor, alignment, true);
1143                }
1144                while (neighbor != null);
1145                if (neighbor == null) {
1146                    interval = parent;
1147                    parent = interval.getParent();
1148                }
1149            }
1150        }
1151        while (position == Integer.MIN_VALUE);
1152        return position;
1153    }
1154
1155    private static boolean isSignificantGroupEdge(LayoutInterval interval, int alignment) {
1156        LayoutInterval group = interval.getParent();
1157        assert group.isParallel();
1158        if (interval.getAlignment() == alignment || LayoutInterval.wantResize(interval))
1159            return true;
1160
1161        if (!LayoutInterval.isClosedGroup(group, alignment))
1162            return false;
1163
1164        if (!LayoutInterval.isExplicitlyClosedGroup(group)) { // naturally closed group
1165
LayoutInterval neighborGap = LayoutInterval.getNeighbor(group, alignment, false, true, true);
1166            if (neighborGap != null && LayoutInterval.isDefaultPadding(neighborGap)) {
1167                // default padding means the group can be considered open at this edge
1168
return false;
1169            }
1170        }
1171        return true;
1172    }
1173
1174    private static int getPerceivedNeighborPosition(LayoutInterval neighbor, LayoutRegion space, int dimension, int alignment) {
1175        assert !neighbor.isEmptySpace();
1176
1177        if (neighbor.isComponent())
1178// || (neighbor.isParallel() && LayoutInterval.isClosedGroup(neighbor, alignment)
1179
// && !LayoutRegion.overlap(neighbor.getCurrentSpace(), space, dimension, 0))
1180
return neighbor.getCurrentSpace().positions[dimension][alignment];
1181
1182        int neighborPos = Integer.MIN_VALUE;
1183        int n = neighbor.getSubIntervalCount();
1184        int i, d;
1185        if (neighbor.isParallel() || alignment == LEADING) {
1186            d = 1;
1187            i = 0;
1188        }
1189        else {
1190            d = -1;
1191            i = n - 1;
1192        }
1193
1194        while (i >=0 && i < n) {
1195            LayoutInterval sub = neighbor.getSubInterval(i);
1196            i += d;
1197
1198            if (sub.isEmptySpace()
1199                || (sub.isComponent()
1200                    && !LayoutRegion.overlap(space, sub.getCurrentSpace(), dimension^1, 0)))
1201                continue;
1202
1203            int pos = getPerceivedNeighborPosition(sub, space, dimension, alignment);
1204            if (pos != Integer.MIN_VALUE) {
1205                if (neighbor.isSequential()) {
1206                    neighborPos = pos;
1207                    break;
1208                }
1209                else if (neighborPos == Integer.MIN_VALUE || pos*d < neighborPos*d) {
1210                    neighborPos = pos;
1211                    // continue, there can still be a closer position
1212
}
1213            }
1214        }
1215        return neighborPos;
1216    }
1217
1218    private LayoutInterval separateSequence(LayoutInterval seq, int alignment) {
1219        LayoutInterval parentPar = seq.getParent();
1220        assert parentPar.isParallel();
1221        while (!parentPar.getParent().isSequential()) {
1222            parentPar = parentPar.getParent();
1223        }
1224        LayoutInterval parentSeq = parentPar.getParent(); // sequential
1225

1226        int d = alignment == LEADING ? -1 : 1;
1227        int n = parentSeq.getSubIntervalCount();
1228        int end = parentSeq.indexOf(parentPar) + d;
1229        while (end >= 0 && end < n) {
1230            LayoutInterval sub = parentSeq.getSubInterval(end);
1231            if (!sub.isEmptySpace()) {
1232// LayoutRegion subSpace = sub.getCurrentSpace();
1233
// if (sub.isParallel()
1234
// && subSpace.positions[dimension][alignment^1]*d > addingSpace.positions[dimension][alignment]*d
1235
// && LayoutInterval.isClosedGroup(sub, alignment))
1236
if (LayoutUtils.contentOverlap(addingSpace, sub, dimension^1)) {
1237                    break;
1238                }
1239            }
1240            end += d;
1241        }
1242
1243        int endPos = end >= 0 && end < n ?
1244                     parentSeq.getSubInterval(end).getCurrentSpace().positions[dimension][alignment^1] :
1245                     parentSeq.getParent().getCurrentSpace().positions[dimension][alignment];
1246        end -= d;
1247        operations.parallelizeWithParentSequence(seq, end, dimension);
1248        parentPar = seq.getParent();
1249        parentPar.getCurrentSpace().positions[dimension][alignment] = endPos;
1250        return parentPar;
1251    }
1252
1253    /**
1254     * When an interval is added or resized out of current boundaries of its
1255     * parent, this method tries to accommodate the size increment in the parent
1256     * (and its parents). It acts according to the current visual position of
1257     * the interval - how it exceeds the current parent border. In the simplest
1258     * form the method tries to shorten the nearest gap in the parent sequence.
1259     */

1260    private void accommodateOutPosition(LayoutInterval interval, /*int dimension,*/ int alignment/*, boolean snapped*/) {
1261        if (alignment == CENTER || alignment == BASELINE) {
1262            return; // [but should consider these too...]
1263
}
1264
1265        int pos = interval.getCurrentSpace().positions[dimension][alignment];
1266        assert pos != LayoutRegion.UNKNOWN;
1267        int sizeIncrement = Integer.MIN_VALUE;
1268        int d = alignment == LEADING ? -1 : 1;
1269        int[] groupPos = null;
1270        LayoutInterval parent = interval.getParent();
1271        LayoutInterval prev = null;
1272
1273        do {
1274            if (parent.isSequential()) {
1275                if (sizeIncrement > 0) {
1276                    int accommodated = accommodateSizeInSequence(interval, prev, sizeIncrement, alignment);
1277                    sizeIncrement -= accommodated;
1278                    if (groupPos != null) {
1279                        groupPos[alignment] += accommodated * d;
1280                    }
1281                }
1282                LayoutInterval neighbor = LayoutInterval.getDirectNeighbor(interval, alignment, false);
1283                if (neighbor != null && (!neighbor.isEmptySpace() || LayoutInterval.canResize(neighbor))) {
1284                    // not a border interval in the sequence, can't go up
1285
return;
1286                }
1287                prev = interval;
1288            }
1289            else {
1290                groupPos = parent.getCurrentSpace().positions[dimension];
1291                if (groupPos[alignment] != LayoutRegion.UNKNOWN) {
1292                    sizeIncrement = (pos - groupPos[alignment]) * d;
1293                    if (sizeIncrement > 0) {
1294                        int subPos[] = interval.getCurrentSpace().positions[dimension];
1295                        if (!interval.getCurrentSpace().isSet(dimension)
1296                            || subPos[alignment]*d < groupPos[alignment]*d)
1297                        { // update space of subgroup according to parent (needed if subgroup is also parallel)
1298
subPos[alignment] = groupPos[alignment];
1299                        }
1300                    }
1301                }
1302                else groupPos = null;
1303                if (!interval.isSequential())
1304                    prev = interval;
1305            }
1306            interval = parent;
1307            parent = interval.getParent();
1308        }
1309        while ((sizeIncrement > 0 || sizeIncrement == Integer.MIN_VALUE)
1310               && parent != null
1311               && (!parent.isParallel() || interval.getAlignment() != alignment));
1312               // can't accommodate at the aligned side [but could probably turn to other side - update 'pos', etc]
1313
}
1314
1315    private int accommodateSizeInSequence(LayoutInterval interval, LayoutInterval lower, int sizeIncrement, int alignment) {
1316        LayoutInterval parent = interval.getParent();
1317        assert parent.isSequential();
1318        LayoutRegion space = lower.getCurrentSpace();
1319        int increment = sizeIncrement;
1320        int pos = interval.getCurrentSpace().positions[dimension][alignment];
1321        int outPos = parent.getParent().getCurrentSpace().positions[dimension][alignment];
1322
1323        boolean parallel = false;
1324        int d = alignment == LEADING ? -1 : 1;
1325        int start = parent.indexOf(interval);
1326        int end = lower.isComponent() ? start : -1;
1327        int n = parent.getSubIntervalCount();
1328
1329        for (int i=start+d; i >= 0 && i < n; i+=d) {
1330            LayoutInterval li = parent.getSubInterval(i);
1331            if (end != -1) { // consider parallel expansion of the sequence out
1332
// of its parent (similar to what separateSequence does)
1333
int endPos = Integer.MIN_VALUE;
1334                if (!li.isEmptySpace()) {
1335                    if (LayoutUtils.contentOverlap(space, li, dimension^1)) {
1336                        if (end != start) {
1337                            end = i - d;
1338                            endPos = li.getCurrentSpace().positions[dimension][alignment^1];
1339                        }
1340                        else end = -1; // there was only a gap before
1341
}
1342                    else { // no orthogonal overlap, count this in
1343
end = i;
1344                        if (!parallel && LayoutUtils.contentOverlap(space, li, dimension))
1345                            parallel = true;
1346                    }
1347                }
1348                if ((i == 0 || i+d == n) && endPos == Integer.MIN_VALUE && end != -1) { // last gap or non-overlapping
1349
if (end != start && (parallel || dimension == HORIZONTAL)) {
1350                        end = i;
1351                        endPos = outPos;
1352                    }
1353                    else end = -1; // only gap or not in parallel with anything (in vertical dimension)
1354
}
1355                if (endPos != Integer.MIN_VALUE) {
1356                    LayoutInterval toPar = lower.getParent().isSequential() ? lower.getParent() : lower;
1357                    LayoutInterval endGap = LayoutInterval.getDirectNeighbor(lower, alignment, false);
1358                    if (endGap == null && !LayoutInterval.isAlignedAtBorder(toPar, alignment)) {
1359                        endGap = new LayoutInterval(SINGLE);
1360                        if (!toPar.isSequential()) {
1361                            toPar = new LayoutInterval(SEQUENTIAL);
1362                            layoutModel.addInterval(toPar, lower.getParent(), layoutModel.removeInterval(lower));
1363                            layoutModel.setIntervalAlignment(toPar, lower.getRawAlignment());
1364                            layoutModel.setIntervalAlignment(lower, DEFAULT);
1365                            layoutModel.addInterval(lower, toPar, 0);
1366                        }
1367                        layoutModel.addInterval(endGap, toPar, alignment==LEADING ? 0 : -1);
1368                    }
1369                    else assert endGap == null || endGap.isEmptySpace();
1370
1371                    operations.parallelizeWithParentSequence(toPar, end, dimension);
1372
1373                    if (alignment == TRAILING) // adjust index
1374
i -= n - parent.getSubIntervalCount();
1375                    n = parent.getSubIntervalCount(); // adjust count
1376
end = -1; // don't try anymore
1377

1378                    increment -= Math.abs(endPos - pos);
1379                    if (increment < 0)
1380                        increment = 0;
1381                    continue;
1382                }
1383                else if (end == -1) { // no parallel expansion possible
1384
i = start; // restart
1385
continue;
1386                }
1387            }
1388            // otherwise look for a gap to reduce
1389
else if (li.isEmptySpace() && li.getPreferredSize() != NOT_EXPLICITLY_DEFINED) {
1390                int pad = determinePadding(interval, dimension, alignment);
1391                int currentSize = LayoutInterval.getIntervalCurrentSize(li, dimension);
1392
1393                int size = currentSize - increment;
1394                if (size <= pad) {
1395                    size = NOT_EXPLICITLY_DEFINED;
1396                    increment -= currentSize - pad;
1397                }
1398                else increment = 0;
1399
1400                operations.resizeInterval(li, size);
1401                if (LayoutInterval.wantResize(li) && LayoutInterval.wantResize(interval)) {
1402                    // cancel gap resizing if the neighbor is also resizing
1403
layoutModel.setIntervalSize(li, li.getMinimumSize(), li.getPreferredSize(), USE_PREFERRED_SIZE);
1404                }
1405                break;
1406            }
1407            else {
1408                interval = li;
1409            }
1410        }
1411        return sizeIncrement - increment;
1412    }
1413
1414    /**
1415     * This method aligns an interval (just simply added to the layout - so it
1416     * is already placed correctly where it should appear) in parallel with
1417     * another interval.
1418     * @return parallel group with aligned intervals if some aligning changes happened,
1419     * null if addingInterval has already been aligned or could not be aligned
1420     */

1421    private LayoutInterval alignInParallel(LayoutInterval interval, LayoutInterval toAlignWith, int alignment) {
1422        assert alignment == LEADING || alignment == TRAILING;
1423
1424        if (toAlignWith.isParentOf(interval) // already aligned to parent
1425
|| interval.isParentOf(toAlignWith)) // can't align with own subinterval
1426
{ // contained intervals can't be aligned
1427
return null;
1428        }
1429        else {
1430            LayoutInterval commonParent = LayoutInterval.getCommonParent(interval, toAlignWith);
1431            if (commonParent == null || commonParent.isSequential()) // can't align with interval in the same sequence
1432
return null;
1433        }
1434
1435        // if not in same parallel group try to substitute interval with parent
1436
boolean resizing = LayoutInterval.wantResize(interval);
1437        LayoutInterval aligning = interval; // may be substituted with parent
1438
LayoutInterval parParent = LayoutInterval.getFirstParent(interval, PARALLEL);
1439        while (!parParent.isParentOf(toAlignWith)) {
1440            if (LayoutInterval.isAlignedAtBorder(aligning, parParent, alignment)) { // substitute with parent
1441
// // make sure parent space is up-to-date
1442
// parParent.getCurrentSpace().positions[dimension][alignment]
1443
// = aligning.getCurrentSpace().positions[dimension][alignment];
1444
// allow parent resizing if substituting for resizing interval
1445
if (resizing && !LayoutInterval.canResize(parParent))
1446                    operations.enableGroupResizing(parParent);
1447                aligning = parParent;
1448                parParent = LayoutInterval.getFirstParent(aligning, PARALLEL);
1449            }
1450            else parParent = null;
1451            if (parParent == null) // not parent of toAlignWith
1452
return null; // can't align with interval from different branch
1453
}
1454
1455        boolean resizingOp = dragger.isResizing(dimension);
1456
1457        // hack: remove aligning interval temporarily not to influence next analysis
1458
LayoutInterval tempRemoved = aligning;
1459        while (tempRemoved.getParent() != parParent)
1460            tempRemoved = tempRemoved.getParent();
1461        int removedIndex = parParent.remove(tempRemoved);
1462
1463        // check if we shouldn't rather align with a whole group (parent of toAlignWith)
1464
boolean alignWithParent = false;
1465        LayoutInterval alignParent;
1466        do {
1467            alignParent = LayoutInterval.getFirstParent(toAlignWith, PARALLEL);
1468            if (alignParent == null)
1469                return null; // aligning with parent (the interval must be already aligned)
1470
if (canSubstAlignWithParent(toAlignWith, dimension, alignment, resizingOp)) {
1471                // toAlignWith is at border so we can perhaps use the parent instead
1472
if (alignParent == parParent) {
1473// if (LayoutInterval.getDirectNeighbor(aligning, alignment, false) == null)
1474
alignWithParent = true;
1475                }
1476                else toAlignWith = alignParent;
1477            }
1478        }
1479        while (toAlignWith == alignParent);
1480
1481        parParent.add(tempRemoved, removedIndex); // add back temporarily removed
1482

1483        if (alignParent != parParent)
1484            return null; // can't align (toAlignWith is too deep)
1485

1486        if (aligning != interval) {
1487            if (!LayoutInterval.isAlignedAtBorder(toAlignWith, alignment)) {
1488                // may have problems with S-layout
1489
int dst = LayoutRegion.distance(aligning.getCurrentSpace(),
1490                                                toAlignWith.getCurrentSpace(),
1491                                                dimension, alignment, alignment)
1492                          * (alignment == TRAILING ? -1 : 1);
1493                if (dst > 0) { // try to eliminate effect of avoiding S-layout
1494
// need to exclude 'interval' - remove it temporarily
1495
tempRemoved = interval;
1496                    while (tempRemoved.getParent() != aligning)
1497                        tempRemoved = tempRemoved.getParent();
1498                    removedIndex = aligning.remove(tempRemoved);
1499
1500                    operations.cutStartingGap(aligning, dst, dimension, alignment);
1501
1502                    aligning.add(tempRemoved, removedIndex); // add back temporarily removed
1503
}
1504            }
1505            optimizeStructure = true;
1506        }
1507
1508        // check congruence of effective alignment
1509
int effAlign1 = LayoutInterval.getEffectiveAlignment(toAlignWith, alignment);
1510// int effAlign2 = LayoutInterval.getEffectiveAlignment(aligning, alignment);
1511
// if (effAlign1 == (alignment^1) /*&& effAlign2 != effAlign1*/) {
1512
// LayoutInterval gap = LayoutInterval.getDirectNeighbor(aligning, alignment, false);
1513
// if (gap != null && gap.isEmptySpace()) {
1514
// layoutModel.setIntervalSize(gap, NOT_EXPLICITLY_DEFINED, gap.getPreferredSize(), Short.MAX_VALUE);
1515
// }
1516
// gap = LayoutInterval.getDirectNeighbor(aligning, alignment^1, false);
1517
// if (gap != null && gap.isEmptySpace() && LayoutInterval.getDirectNeighbor(gap, alignment^1, true) == null) {
1518
// layoutModel.setIntervalSize(gap, USE_PREFERRED_SIZE, gap.getPreferredSize(), USE_PREFERRED_SIZE);
1519
// }
1520
// }
1521

1522        // separate content out of the emerging group
1523
List alignedList = new ArrayList(2);
1524        List remainder = new ArrayList(2);
1525        int originalCount = parParent.getSubIntervalCount();
1526
1527        int extAlign1 = extract(toAlignWith, alignedList, remainder, alignment);
1528        extract(aligning, alignedList, remainder, alignment);
1529
1530        assert !alignWithParent || remainder.isEmpty();
1531
1532        // add indent if needed
1533
int indent = LayoutRegion.distance(toAlignWith.getCurrentSpace(), interval.getCurrentSpace(),
1534                                           dimension, alignment, alignment);
1535        assert indent == 0 || alignment == LEADING; // currently support indent only at the LEADING side
1536
if (indent != 0) {
1537            LayoutInterval indentGap = new LayoutInterval(SINGLE);
1538            indentGap.setSize(Math.abs(indent));
1539            // [need to use default padding for indent gap]
1540
LayoutInterval parent = interval.getParent();
1541            if (parent == null || !parent.isSequential()) {
1542                LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
1543                if (parent != null) {
1544                    layoutModel.addInterval(seq, parent, layoutModel.removeInterval(interval));
1545                }
1546                layoutModel.setIntervalAlignment(interval, DEFAULT);
1547                layoutModel.addInterval(interval, seq, 0);
1548                parent = seq;
1549            }
1550            layoutModel.addInterval(indentGap, parent, alignment == LEADING ? 0 : -1);
1551            if (interval == aligning)
1552                alignedList.set(alignedList.size()-1, parent);
1553        }
1554
1555        // prepare the group where the aligned intervals will be placed
1556
LayoutInterval group;
1557        LayoutInterval commonSeq;
1558        if (alignWithParent || (originalCount == 2 && parParent.getParent() != null)) {
1559            // reuse the original group - avoid unnecessary nesting
1560
group = parParent;
1561            if (!remainder.isEmpty()) { // need a sequence for the remainder group
1562
LayoutInterval groupParent = group.getParent();
1563                if (groupParent.isSequential()) {
1564                    commonSeq = groupParent;
1565                }
1566                else { // insert a new one
1567
int index = layoutModel.removeInterval(group);
1568                    commonSeq = new LayoutInterval(SEQUENTIAL);
1569                    commonSeq.setAlignment(group.getAlignment());
1570                    layoutModel.addInterval(commonSeq, groupParent, index);
1571// commonSeq.getCurrentSpace().set(dimension, groupParent.getCurrentSpace());
1572
layoutModel.setIntervalAlignment(group, DEFAULT);
1573                    layoutModel.addInterval(group, commonSeq, -1);
1574                }
1575            }
1576            else commonSeq = null;
1577        }
1578        else { // need to create a new group
1579
group = new LayoutInterval(PARALLEL);
1580            group.setGroupAlignment(alignment);
1581            if (!remainder.isEmpty()) { // need a new sequence for the remainder group
1582
commonSeq = new LayoutInterval(SEQUENTIAL);
1583                commonSeq.add(group, 0);
1584                if (effAlign1 == LEADING || effAlign1 == TRAILING) {
1585                    commonSeq.setAlignment(effAlign1);
1586                }
1587                layoutModel.addInterval(commonSeq, parParent, -1);
1588// commonSeq.getCurrentSpace().set(dimension, parParent.getCurrentSpace());
1589
}
1590            else {
1591                commonSeq = null;
1592                if (effAlign1 == LEADING || effAlign1 == TRAILING) {
1593                    group.setAlignment(effAlign1);
1594                }
1595                layoutModel.addInterval(group, parParent, -1);
1596            }
1597            if (alignment == LEADING || alignment == TRAILING) {
1598                int alignPos = toAlignWith.getCurrentSpace().positions[dimension][alignment];
1599                int outerPos = parParent.getCurrentSpace().positions[dimension][alignment^1];
1600                group.getCurrentSpace().set(dimension,
1601                                            alignment == LEADING ? alignPos : outerPos,
1602                                            alignment == LEADING ? outerPos : alignPos);
1603            }
1604        }
1605
1606        // add the intervals and their separated neighbors to the aligned group
1607
LayoutInterval aligning2 = (LayoutInterval) alignedList.get(1);
1608        if (aligning2.getParent() != group) {
1609            if (aligning2.getParent() != null) {
1610                layoutModel.removeInterval(aligning2);
1611            }
1612            layoutModel.addInterval(aligning2, group, -1);
1613        }
1614        if (!LayoutInterval.isAlignedAtBorder(aligning2, alignment)) {
1615            layoutModel.setIntervalAlignment(aligning2, alignment);
1616        }
1617
1618        LayoutInterval aligning1 = (LayoutInterval) alignedList.get(0);
1619        if (aligning1.getParent() != group) {
1620            if (aligning1.getParent() != null) {
1621                layoutModel.setIntervalAlignment(aligning1, extAlign1); //aligning1.getAlignment()); // remember explicit alignment
1622
layoutModel.removeInterval(aligning1);
1623            }
1624            layoutModel.addInterval(aligning1, group, -1);
1625        }
1626        if (!resizingOp && group.getSubIntervalCount() == 2
1627                && !LayoutInterval.isAlignedAtBorder(aligning1, alignment)
1628                && !LayoutInterval.isAlignedAtBorder(aligning2, alignment^1)) {
1629            layoutModel.setIntervalAlignment(aligning1, alignment);
1630        }
1631
1632        // create the remainder group next to the aligned group
1633
if (!remainder.isEmpty()) {
1634            int index = commonSeq.indexOf(group);
1635            if (alignment == TRAILING)
1636                index++;
1637            LayoutInterval sideGroup = operations.addGroupContent(
1638                    remainder, commonSeq, index, dimension, alignment/*, effAlign*/);
1639            if (sideGroup != null) {
1640                int pos1 = parParent.getCurrentSpace().positions[dimension][alignment];
1641                int pos2 = toAlignWith.getCurrentSpace().positions[dimension][alignment];
1642                sideGroup.getCurrentSpace().set(dimension,
1643                                                alignment == LEADING ? pos1 : pos2,
1644                                                alignment == LEADING ? pos2 : pos1);
1645                operations.optimizeGaps(sideGroup, dimension);
1646                operations.mergeParallelGroups(sideGroup);
1647            }
1648        }
1649
1650        return group;
1651    }
1652
1653    private int extract(LayoutInterval interval, List toAlign, List toRemain, int alignment) {
1654        int effAlign = LayoutInterval.getEffectiveAlignment(interval, alignment);
1655        LayoutInterval parent = interval.getParent();
1656        if (parent.isSequential()) {
1657            int extractCount = operations.extract(interval, alignment, false,
1658                                                  alignment == LEADING ? toRemain : null,
1659                                                  alignment == LEADING ? null : toRemain);
1660            if (extractCount == 1) { // the parent won't be reused
1661
if (effAlign == LEADING || effAlign == TRAILING)
1662                    layoutModel.setIntervalAlignment(interval, effAlign);
1663                layoutModel.removeInterval(parent);
1664                toAlign.add(interval);
1665            }
1666            else { // we'll reuse the parent sequence in the new group
1667
toAlign.add(parent);
1668            }
1669        }
1670        else {
1671            toAlign.add(interval);
1672        }
1673        return effAlign;
1674    }
1675
1676    private void checkParallelResizing(LayoutInterval interval, IncludeDesc iDesc1, IncludeDesc iDesc2) {
1677        LayoutInterval parallelInt;
1678        LayoutInterval group = interval.getParent();
1679        if (group.isSequential()) {
1680            parallelInt = group;
1681            group = group.getParent();
1682        }
1683        else parallelInt = interval;
1684
1685        // do nothing in root and in parallel group tied closely to root on both edges
1686
if (group.getParent() == null)
1687            return;
1688        // a bit of "heuristics" follows...
1689
int rootAlign = DEFAULT;
1690        if (iDesc1.snappedNextTo != null && iDesc1.snappedNextTo.getParent() == null)
1691            rootAlign = iDesc1.alignment;
1692        if (iDesc2 != null && iDesc2.snappedNextTo != null && iDesc2.snappedNextTo.getParent() == null)
1693            rootAlign = rootAlign == DEFAULT ? iDesc2.alignment : LayoutRegion.ALL_POINTS;
1694        if (rootAlign == LEADING || rootAlign == TRAILING) {
1695            // one edge snapped to root - check the other one for full span
1696
int remIdx = group.remove(parallelInt); // temporarily
1697
LayoutInterval neighbor = LayoutInterval.getNeighbor(group, rootAlign^1, false, true, true);
1698            if ((neighbor != null
1699                 && neighbor.getPreferredSize() == NOT_EXPLICITLY_DEFINED
1700                 && LayoutInterval.isAlignedAtBorder(neighbor.getParent(), LayoutInterval.getRoot(neighbor), rootAlign^1))
1701                 ||
1702                (neighbor == null
1703                 && LayoutInterval.isAlignedAtBorder(group, LayoutInterval.getRoot(group), rootAlign^1)))
1704            { // the other group edge tied closely to root
1705
rootAlign = LayoutRegion.ALL_POINTS;
1706            }
1707            group.add(parallelInt, remIdx);
1708        }
1709        if (rootAlign == LayoutRegion.ALL_POINTS)
1710            return;
1711
1712        // find resizing neighbor gap of interval
1713
LayoutInterval neighborGap = null;
1714        if (interval != parallelInt) {
1715            assert parallelInt.isSequential();
1716            for (int i=LEADING; i <= TRAILING; i++) {
1717                LayoutInterval gap = LayoutInterval.getDirectNeighbor(interval, i, false);
1718                if (gap != null && gap.isEmptySpace() && LayoutInterval.canResize(gap)) {
1719                    neighborGap = gap;
1720                    break;
1721                }
1722            }
1723        }
1724
1725        // interval or its neighbor gap must be resizing
1726
if (LayoutInterval.wantResize(interval)) {
1727            if (!dragger.isResizing(dimension))
1728                return;
1729        }
1730        else if (neighborGap == null)
1731            return;
1732
1733        if (!LayoutInterval.canResize(group)
1734            && ((iDesc1.snappedNextTo != null && !group.isParentOf(iDesc1.snappedNextTo))
1735                 || (iDesc2 != null && iDesc2.snappedNextTo != null && !group.isParentOf(iDesc2.snappedNextTo))))
1736        { // snapped out of the group - it might not want to be suppressed (will check right away)
1737
operations.enableGroupResizing(group);
1738        }
1739
1740        if (LayoutInterval.canResize(group) && group.getParent() != null) {
1741            // suppress par. group resizing if it is otherwise fixed
1742
boolean contentResizing = false;
1743            boolean samePosition = false;
1744            for (Iterator it=group.getSubIntervals(); it.hasNext(); ) {
1745                LayoutInterval li = (LayoutInterval) it.next();
1746                if (li != parallelInt) {
1747                    if (LayoutInterval.wantResize(li)) {
1748                        contentResizing = true;
1749                        break;
1750                    }
1751                    if (!samePosition) {
1752                        int align = li.getAlignment();
1753                        if (align == LEADING || align == TRAILING)
1754                            samePosition = getExpectedBorderPosition(parallelInt, dimension, align^1)
1755                                           == getExpectedBorderPosition(li, dimension, align^1);
1756                    }
1757                }
1758            }
1759            if (!contentResizing && samePosition) {
1760                operations.suppressGroupResizing(group);
1761            }
1762        }
1763
1764        if (!LayoutInterval.canResize(group)) {
1765            // reset explicit size of interval or gap - subordinate to fixed content
1766
layoutModel.changeIntervalAttribute(parallelInt, LayoutInterval.ATTRIBUTE_FILL, true);
1767            if (neighborGap != null) {
1768                layoutModel.setIntervalSize(neighborGap, NOT_EXPLICITLY_DEFINED, NOT_EXPLICITLY_DEFINED, Short.MAX_VALUE);
1769            }
1770            else if (interval.isComponent()) {
1771                java.awt.Dimension JavaDoc sizeLimit;
1772                LayoutComponent lc = interval.getComponent();
1773                sizeLimit = lc.isLayoutContainer() ?
1774                    operations.getMapper().getComponentMinimumSize(lc.getId()) :
1775                    operations.getMapper().getComponentPreferredSize(lc.getId());
1776                int pref = dimension == HORIZONTAL ? sizeLimit.width : sizeLimit.height;
1777                if (interval.getPreferredSize() < pref)
1778                    layoutModel.setIntervalSize(interval, 0, 0, interval.getMaximumSize());
1779                else
1780                    layoutModel.setIntervalSize(interval,
1781                            interval.getMinimumSize() != USE_PREFERRED_SIZE ? interval.getMinimumSize() : NOT_EXPLICITLY_DEFINED,
1782                            NOT_EXPLICITLY_DEFINED,
1783                            interval.getMaximumSize());
1784            }
1785        }
1786
1787        if (interval.isComponent() && neighborGap == null
1788            && (parallelInt == interval || LayoutInterval.getCount(parallelInt, DEFAULT, true) == 1))
1789        { // look for same sized components
1790
setParallelSameSize(group, parallelInt, dimension);
1791        }
1792    }
1793
1794    private void setParallelSameSize(LayoutInterval group, LayoutInterval aligned, int dimension) {
1795        LayoutInterval alignedComp = getOneNonEmpty(aligned);
1796
1797        for (Iterator it=group.getSubIntervals(); it.hasNext(); ) {
1798            LayoutInterval li = (LayoutInterval) it.next();
1799            if (li != aligned) {
1800                if (li.isParallel()) {
1801                    setParallelSameSize(li, alignedComp, dimension);
1802                }
1803                else {
1804                    LayoutInterval sub = getOneNonEmpty(li);
1805                    if (sub != null
1806                        && LayoutRegion.sameSpace(alignedComp.getCurrentSpace(), sub.getCurrentSpace(), dimension)
1807                        && !LayoutInterval.wantResize(li))
1808                    { // viusally aligned subinterval
1809
if (sub.isParallel()) {
1810                            setParallelSameSize(sub, alignedComp, dimension);
1811                        }
1812                        else { // make this component filling the group - effectively keeping same size
1813
layoutModel.setIntervalAlignment(li, aligned.getAlignment());
1814                            int min = sub.getMinimumSize();
1815                            layoutModel.setIntervalSize(sub,
1816                                    min != USE_PREFERRED_SIZE ? min : NOT_EXPLICITLY_DEFINED,
1817                                    sub.getPreferredSize(),
1818                                    Short.MAX_VALUE);
1819                            layoutModel.changeIntervalAttribute(sub, LayoutInterval.ATTRIBUTE_FILL, true);
1820                        }
1821                    }
1822                }
1823            }
1824        }
1825    }
1826
1827    private static LayoutInterval getOneNonEmpty(LayoutInterval interval) {
1828        if (!interval.isSequential())
1829            return interval;
1830
1831        LayoutInterval nonEmpty = null;
1832        for (Iterator it=interval.getSubIntervals(); it.hasNext(); ) {
1833            LayoutInterval li = (LayoutInterval) it.next();
1834            if (!li.isEmptySpace()) {
1835                if (nonEmpty == null)
1836                    nonEmpty = li;
1837                else
1838                    return null;
1839            }
1840        }
1841        return nonEmpty;
1842    }
1843
1844    private int getExpectedBorderPosition(LayoutInterval interval, int dimension, int alignment) {
1845        LayoutInterval comp = LayoutUtils.getOutermostComponent(interval, dimension, alignment);
1846        int pos = comp.getCurrentSpace().positions[dimension][alignment];
1847        LayoutInterval neighbor = LayoutInterval.getNeighbor(comp, alignment, false, true, false);
1848        if (neighbor != null && neighbor.isEmptySpace() && interval.isParentOf(neighbor)) {
1849            int diff = neighbor.getPreferredSize();
1850            if (diff == NOT_EXPLICITLY_DEFINED)
1851                diff = LayoutUtils.getSizeOfDefaultGap(neighbor, operations.getMapper());
1852            if (alignment == LEADING)
1853                diff *= -1;
1854            pos += diff;
1855        }
1856        return pos;
1857    }
1858
1859    private int determinePadding(LayoutInterval interval, int dimension, int alignment) {
1860        LayoutInterval neighbor = LayoutInterval.getNeighbor(interval, alignment, true, true, false);
1861        return dragger.findPadding(neighbor, interval, dimension, alignment);
1862        // need to go through dragger as the component of 'interval' is not in model yet
1863
}
1864
1865    /**
1866     * Finds padding for an interval (yet to be added) in relation to a base
1867     * interval or a parent interval border (base interval null)
1868     * @param alignment LEADING or TRAILING point of addingInt
1869     */

1870    private int determineExpectingPadding(LayoutInterval addingInt,
1871                                          LayoutInterval baseInt,
1872                                          LayoutInterval baseParent,
1873                                          int alignment)
1874    {
1875        if (baseInt == null) {
1876            baseInt = LayoutInterval.getNeighbor(baseParent, SEQUENTIAL, alignment);
1877        }
1878        return dragger.findPadding(baseInt, addingInt, dimension, alignment);
1879    }
1880
1881    // -----
1882

1883    private void analyzeParallel(LayoutInterval group, List inclusions) {
1884        Iterator it = group.getSubIntervals();
1885        while (it.hasNext()) {
1886            LayoutInterval sub = (LayoutInterval) it.next();
1887            if (sub.isEmptySpace())
1888                continue;
1889
1890            LayoutRegion subSpace = sub.getCurrentSpace();
1891
1892            if (sub.isParallel()
1893                && pointInside(addingSpace, aEdge, sub, dimension)
1894                && shouldEnterGroup(sub))
1895            { // group space contains significant edge
1896
analyzeParallel(sub, inclusions);
1897            }
1898            else if (sub.isSequential()) {
1899                // always analyze sequence - it may be valid even if there is no
1900
// overlap (not required in vertical dimension)
1901
analyzeSequential(sub, inclusions);
1902            }
1903            else if (orthogonalOverlap(sub)) {
1904                boolean dimOverlap = LayoutRegion.overlap(addingSpace, subSpace, dimension, 0);
1905                if (dimOverlap && !solveOverlap) {
1906                    IncludeDesc origPos = originalPositions1[dimension];
1907                    if (origPos == null || (aEdge^1) != origPos.alignment
1908                        || (origPos.parent != sub && !sub.isParentOf(origPos.parent)))
1909                    { // don't want to deal with the overlap here
1910
continue;
1911                    }
1912                    // otherwise overlapping enclosing space of 'sub' does not
1913
// matter as the other edge of adding component resides
1914
// inside 'sub', so this position should be in sequence
1915
}
1916                if (dimOverlap)
1917                    imposeSize = true;
1918
1919                int distance = LayoutRegion.UNKNOWN;
1920                if (aSnappedNextTo != null) {
1921                    // check if aSnappedNextTo is related to this position with 'sub' as neighbor
1922
LayoutInterval neighbor;
1923                    if (sub == aSnappedNextTo
1924                        || sub.isParentOf(aSnappedNextTo)
1925                        || aSnappedNextTo.getParent() == null
1926                        || (neighbor = LayoutInterval.getNeighbor(sub, aEdge, true, true, false)) == aSnappedNextTo
1927                        || (neighbor != null && neighbor.isParentOf(aSnappedNextTo)))
1928                    { // nextTo snap is relevant to this position
1929
distance = -1; // IncludeDesc.snappedNextTo will be set if distance == -1
1930
}
1931                }
1932                if (distance != -1) {
1933                    if (!dimOverlap) { // determine distance from 'sub'
1934
int dstL = LayoutRegion.distance(subSpace, addingSpace,
1935                                                         dimension, TRAILING, LEADING);
1936                        int dstT = LayoutRegion.distance(addingSpace, subSpace,
1937                                                         dimension, TRAILING, LEADING);
1938                        distance = dstL >= 0 ? dstL : dstT;
1939                    }
1940                    else distance = 0; // overlapping
1941
}
1942
1943                IncludeDesc iDesc = addInclusion(group, false, distance, 0, inclusions);
1944                if (iDesc != null) {
1945                    iDesc.neighbor = sub;
1946                    int point = aEdge < 0 ? CENTER : aEdge;
1947                    iDesc.index = getAddDirection(addingSpace, subSpace, dimension, point) == LEADING ? 0 : 1;
1948                }
1949            }
1950        }
1951
1952        if (inclusions.isEmpty()) { // no inclusion found yet
1953
if (group.getParent() == null
1954                && (aSnappedParallel == null || canAlignWith(aSnappedParallel, group, aEdge)))
1955            { // this is the last (top) valid group
1956
int distance = aSnappedNextTo == group ? -1 : Integer.MAX_VALUE;
1957                addInclusion(group, false, distance, Integer.MAX_VALUE, inclusions);
1958            }
1959        }
1960    }
1961
1962    private void analyzeSequential(LayoutInterval group, List inclusions) {
1963        boolean inSequence = false;
1964        boolean parallelWithSequence = false;
1965        int index = -1;
1966        int distance = Integer.MAX_VALUE;
1967        int ortDistance = Integer.MAX_VALUE;
1968
1969        for (int i=0,n=group.getSubIntervalCount(); i < n; i++) {
1970            LayoutInterval sub = group.getSubInterval(i);
1971            if (sub.isEmptySpace()) {
1972                if (index == i)
1973                    index++;
1974                continue;
1975            }
1976
1977            LayoutRegion subSpace = sub.getCurrentSpace();
1978
1979            // first analyze the interval as a possible sub-group
1980
if (sub.isParallel()
1981                && pointInside(addingSpace, aEdge, sub, dimension)
1982                && shouldEnterGroup(sub))
1983            { // group space contains significant edge
1984
int count = inclusions.size();
1985                analyzeParallel(sub, inclusions);
1986                if (inclusions.size() > count)
1987                    return;
1988            }
1989
1990            // second analyze the interval as a single element for "next to" placement
1991
boolean ortOverlap = orthogonalOverlap(sub);
1992            int margin = (dimension == VERTICAL && !ortOverlap ? 4 : 0);
1993            boolean dimOverlap = LayoutRegion.overlap(addingSpace, subSpace, dimension, margin);
1994            // in vertical dimension always pretend orthogonal overlap if there
1995
// is no overlap in horizontal dimension (i.e. force inserting into sequence)
1996
if (ortOverlap || (dimension == VERTICAL && !dimOverlap && !parallelWithSequence)) {
1997                if (dimOverlap) { // overlaps in both dimensions
1998
if (!solveOverlap) { // don't want to solve the overlap in this sequence
1999
IncludeDesc origPos = originalPositions1[dimension];
2000                        if (origPos == null || (aEdge^1) != origPos.alignment
2001                            || (origPos.parent != sub && !sub.isParentOf(origPos.parent)))
2002                        { // overlap will be solved in the other dimension
2003
return;
2004                        }
2005                        // otherwise overlapping enclosing space of 'sub' does not
2006
// matter as the other edge of adding component resides
2007
// inside 'sub', so this position should be in sequence
2008
}
2009                    if (ortOverlap)
2010                        imposeSize = true;
2011
2012                    inSequence = true;
2013                    distance = ortDistance = 0;
2014                }
2015                else { // determine distance from the interval
2016
int dstL = LayoutRegion.distance(subSpace, addingSpace,
2017                                                     dimension, TRAILING, LEADING);
2018                    int dstT = LayoutRegion.distance(addingSpace, subSpace,
2019                                                     dimension, TRAILING, LEADING);
2020                    if (dstL >= 0 && dstL < distance)
2021                        distance = dstL;
2022                    if (dstT >= 0 && dstT < distance)
2023                        distance = dstT;
2024
2025                    if (ortOverlap) {
2026                        ortDistance = 0;
2027                        inSequence = true;
2028                    }
2029                    else { // remember also the orthogonal distance
2030
dstL = LayoutRegion.distance(subSpace, addingSpace,
2031                                                     dimension^1, TRAILING, LEADING);
2032                        dstT = LayoutRegion.distance(addingSpace, subSpace,
2033                                                     dimension^1, TRAILING, LEADING);
2034                        if (dstL > 0 && dstL < ortDistance)
2035                            ortDistance = dstL;
2036                        if (dstT > 0 && dstT < ortDistance)
2037                            ortDistance = dstT;
2038                    }
2039                }
2040                int point = aEdge < 0 ? CENTER : aEdge;
2041                if (getAddDirection(addingSpace, subSpace, dimension, point) == LEADING) {
2042                    if (aEdge != LEADING)
2043                        index = i;
2044                    break; // this interval is already after the adding one, no need to continue
2045
}
2046                else { // intervals before this one are irrelevant
2047
parallelWithSequence = false;
2048                    if (aEdge == LEADING)
2049                        index = i + 1;
2050                }
2051            }
2052            else { // no orthogonal overlap, moreover in vertical dimension located parallelly
2053
parallelWithSequence = true;
2054            }
2055        }
2056
2057        if (inSequence || (dimension == VERTICAL && !parallelWithSequence)) {
2058            // so it make sense to add the interval to this sequence
2059
if (aSnappedNextTo != null
2060                && (group.isParentOf(aSnappedNextTo) || aSnappedNextTo.getParent() == null))
2061            { // snapped interval is in this sequence, or it is the root group
2062
distance = -1; // preferred distance
2063
}
2064            IncludeDesc iDesc = addInclusion(group, parallelWithSequence, distance, ortDistance, inclusions);
2065            if (iDesc != null) {
2066                if (index == -1)
2067                    index = aEdge == LEADING ? 0 : group.getSubIntervalCount();
2068                iDesc.index = index;
2069            }
2070        }
2071    }
2072
2073    private static boolean pointInside(LayoutRegion space, int alignment, LayoutInterval group, int dimension) {
2074        LayoutRegion groupSpace = group.getCurrentSpace();
2075        if (alignment != DEFAULT) {
2076            return LayoutRegion.pointInside(space, alignment, groupSpace, dimension);
2077        }
2078
2079        boolean leadingInside = LayoutRegion.pointInside(space, LEADING, groupSpace, dimension);
2080        boolean trailingInside = LayoutRegion.pointInside(space, TRAILING, groupSpace, dimension);
2081        return (leadingInside && trailingInside)
2082               || (leadingInside && !LayoutInterval.isClosedGroup(group, TRAILING))
2083               || (trailingInside && !LayoutInterval.isClosedGroup(group, LEADING));
2084/* int point = alignment < 0 ? CENTER : alignment;
2085        LayoutRegion groupSpace = group.getCurrentSpace();
2086        if (LayoutRegion.pointInside(space, point, groupSpace, dimension))
2087            return true;
2088
2089        if (alignment < 0){
2090            return (LayoutInterval.getCount(group, TRAILING, true) == 0
2091                    && LayoutRegion.pointInside(space, LEADING, groupSpace, dimension))
2092                ||
2093                   (LayoutInterval.getCount(group, LEADING, true) == 0
2094                    && LayoutRegion.pointInside(space, TRAILING, groupSpace, dimension));
2095        }
2096        return false; */

2097    }
2098
2099    private boolean orthogonalOverlap(LayoutInterval interval) {
2100        boolean ortOverlap;
2101        if (solveOverlap || !LayoutUtils.isOverlapPreventedInOtherDimension(addingInterval, interval, dimension)) {
2102            // we are interested in the orthogonal overlap (i.e. overlap in the other dimension)
2103
ortOverlap = LayoutUtils.contentOverlap(addingSpace, interval, dimension^1);
2104            if (ortOverlap
2105                && dragger.isResizing(dimension) && !dragger.isResizing(dimension^1)
2106                && originalPositions1[dimension] != null)
2107            { // there is overlap, but in case of resizing in one dimension
2108
// only we should not consider overlap that was not cared of
2109
// already before the resizing started (i.e. the resizing
2110
// interval was not in sequence with the interval in question)
2111
IncludeDesc original = originalPositions1[dimension];
2112                LayoutInterval parent = original.parent;
2113                if (parent.isParentOf(interval)) {
2114                    if (parent.isParallel()
2115                        && (original.neighbor == null
2116                            || (original.neighbor != interval && !original.neighbor.isParentOf(interval))))
2117                        ortOverlap = false;
2118                }
2119                else if (parent == interval) {
2120                    if (parent.isParallel() && original.neighbor == null)
2121                        ortOverlap = false;
2122                }
2123                else if (!interval.isParentOf(parent)) {
2124                    parent = LayoutInterval.getCommonParent(parent, interval);
2125                    if (parent != null && parent.isParallel())
2126                        ortOverlap = false;
2127                }
2128            }
2129        }
2130        // otherwise the overlap is prevented in the other dimension so we should
2131
// not consider it (though the actual visual appearance might look so)
2132
else ortOverlap = false;
2133        return ortOverlap;
2134    }
2135
2136    private IncludeDesc addInclusion(LayoutInterval parent,
2137                                     boolean subgroup,
2138                                     int distance,
2139                                     int ortDistance,
2140                                     List inclusions)
2141    {
2142        if (!inclusions.isEmpty()) {
2143            int index = inclusions.size() - 1;
2144            IncludeDesc last = (IncludeDesc) inclusions.get(index);
2145            boolean useLast = false;
2146            boolean useNew = false;
2147
2148            boolean ortOverlap1 = last.ortDistance == 0;
2149            boolean ortOverlap2 = ortDistance == 0;
2150            if (ortOverlap1 != ortOverlap2) {
2151                useLast = ortOverlap1;
2152                useNew = ortOverlap2;
2153            }
2154            else if (ortOverlap1) { // both having orthogonal overlap
2155
useLast = useNew = true;
2156            }
2157            else { // none having orthogonal overlap (could happen in vertical dimension)
2158
if (last.ortDistance != ortDistance) {
2159                    useLast = last.ortDistance < ortDistance;
2160                    useNew = ortDistance < last.ortDistance;
2161                }
2162                else if (last.distance != distance) {
2163                    useLast = last.distance < distance;
2164                    useNew = distance < last.distance;
2165                }
2166            }
2167            if (!useLast && !useNew) { // could not choose according to distance, so prefer deeper position
2168
LayoutInterval parParent = last.parent.isParallel() ?
2169                                           last.parent : last.parent.getParent();
2170                useNew = parParent.isParentOf(parent);
2171                useLast = !useNew;
2172            }
2173
2174            if (!useLast)
2175                inclusions.remove(index);
2176            if (!useNew)
2177                return null;
2178        }
2179
2180        IncludeDesc iDesc = new IncludeDesc();
2181        iDesc.parent = parent;
2182        iDesc.newSubGroup = subgroup;
2183        iDesc.alignment = aEdge;
2184        iDesc.snappedParallel = aSnappedParallel;
2185        if (distance == -1) {
2186            iDesc.snappedNextTo = aSnappedNextTo;
2187            iDesc.fixedPosition = true;
2188        }
2189        iDesc.distance = distance;
2190        iDesc.ortDistance = ortDistance;
2191        inclusions.add(iDesc);
2192
2193        return iDesc;
2194    }
2195
2196    /**
2197     * Adds an inclusion for parallel aligning if none of found non-overlapping
2198     * inclusions is compatible with the required aligning.
2199     * Later mergeParallelInclusions may still unify the inclusions, but if not
2200     * then the inclusion created here is used - because requested parallel
2201     * aligning needs to be preserved even if overlapping can't be avoided.
2202     */

2203    private IncludeDesc addAligningInclusion(List inclusions) {
2204        if (aSnappedParallel == null)
2205            return null;
2206
2207        boolean compatibleFound = false;
2208        for (Iterator it=inclusions.iterator(); it.hasNext(); ) {
2209            IncludeDesc iDesc = (IncludeDesc) it.next();
2210            if (canAlignWith(aSnappedParallel, iDesc.parent, aEdge)) {
2211                compatibleFound = true;
2212                break;
2213            }
2214        }
2215        if (!compatibleFound) {
2216            IncludeDesc iDesc = new IncludeDesc();
2217            iDesc.parent = aSnappedParallel.getParent() != null ?
2218                           LayoutInterval.getFirstParent(aSnappedParallel, PARALLEL) :
2219                           aSnappedParallel;
2220            iDesc.snappedParallel = aSnappedParallel;
2221            iDesc.alignment = aEdge;
2222            inclusions.add(0, iDesc);
2223            return iDesc;
2224        }
2225        else return null;
2226    }
2227
2228    /**
2229     * @param preserveOriginal if true, original inclusion needs to be preserved,
2230     * will be merged with new inclusion sequentially; if false, original
2231     * inclusion is just consulted when choosing best inclusion
2232     */

2233    private void mergeParallelInclusions(List inclusions, IncludeDesc original, boolean preserveOriginal) {
2234        // 1st step - find representative (best) inclusion
2235
IncludeDesc best = null;
2236        boolean bestOriginal = false;
2237        for (Iterator it=inclusions.iterator(); it.hasNext(); ) {
2238            IncludeDesc iDesc = (IncludeDesc) it.next();
2239            if (original == null || !preserveOriginal || canCombine(iDesc, original)) {
2240                if (best != null) {
2241                    boolean originalCompatible = original != null && !preserveOriginal
2242                                                 && iDesc.parent == original.parent;
2243                    if (!bestOriginal && originalCompatible) {
2244                        best = iDesc;
2245                        bestOriginal = true;
2246                    }
2247                    else if (bestOriginal == originalCompatible) {
2248                        LayoutInterval group1 = best.parent.isSequential() ?
2249                                                best.parent.getParent() : best.parent;
2250                        LayoutInterval group2 = iDesc.parent.isSequential() ?
2251                                                iDesc.parent.getParent() : iDesc.parent;
2252                        if (group1.isParentOf(group2)) {
2253                            best = iDesc; // deeper is better
2254
}
2255                        else if (!group2.isParentOf(group1) && iDesc.distance < best.distance) {
2256                            best = iDesc;
2257                        }
2258                    }
2259                }
2260                else {
2261                    best = iDesc;
2262                    bestOriginal = original != null && !preserveOriginal && iDesc.parent == original.parent;
2263                }
2264            }
2265        }
2266
2267        if (best == null) { // nothing compatible with original position
2268
assert preserveOriginal;
2269            inclusions.clear();
2270            inclusions.add(original);
2271            return;
2272        }
2273
2274        LayoutInterval commonGroup = best.parent.isSequential() ? best.parent.getParent() : best.parent;
2275
2276        // 2nd remove incompatible inclusions, move compatible ones to same level
2277
for (Iterator it=inclusions.iterator(); it.hasNext(); ) {
2278            IncludeDesc iDesc = (IncludeDesc) it.next();
2279            if (iDesc != best) {
2280                if (!compatibleInclusions(iDesc, best, dimension)) {
2281                    it.remove();
2282                }
2283                else {
2284                    LayoutInterval group = iDesc.parent.isSequential() ?
2285                                           iDesc.parent.getParent() : iDesc.parent;
2286                    if (group.isParentOf(commonGroup)) {
2287                        LayoutInterval neighbor = iDesc.parent.isSequential() ?
2288                                                  iDesc.parent : iDesc.neighbor;
2289                        layoutModel.removeInterval(neighbor);
2290                        // [what about the alignment?]
2291
layoutModel.addInterval(neighbor, commonGroup, -1);
2292                        if (group.getSubIntervalCount() == 1) {
2293                            LayoutInterval parent = group.getParent();
2294                            LayoutInterval last = layoutModel.removeInterval(group, 0);
2295                            operations.addContent(last, parent, layoutModel.removeInterval(group));
2296                            if (commonGroup == last && commonGroup.getParent() == null) // commonGroup dissolved in parent
2297
commonGroup = parent;
2298                            updateReplacedOriginalGroup(commonGroup, null);
2299                        }
2300                        if (iDesc.parent == group)
2301                            iDesc.parent = commonGroup;
2302                    }
2303                }
2304            }
2305        }
2306
2307        if (best.parent.isParallel() && best.snappedParallel != null && best.ortDistance != 0
2308            && inclusions.size() > 1)
2309        { // forced inclusion by addAlignedInclusion
2310
inclusions.remove(best);
2311        }
2312        if (inclusions.size() == 1)
2313            return;
2314
2315        // 3rd analyse inclusions requiring a subgroup (parallel with part of sequence)
2316
LayoutInterval subGroup = null;
2317        LayoutInterval nextTo = null;
2318        List separatedLeading = new LinkedList();
2319        List separatedTrailing = new LinkedList();
2320
2321        for (Iterator it=inclusions.iterator(); it.hasNext(); ) {
2322            IncludeDesc iDesc = (IncludeDesc) it.next();
2323            if (iDesc.parent.isSequential() && iDesc.newSubGroup) {
2324                LayoutInterval parSeq = extractParallelSequence(iDesc.parent, addingSpace, false, iDesc.alignment);
2325                assert parSeq.isParallel(); // parallel group with part of the original sequence
2326
if (subGroup == null) {
2327                    subGroup = parSeq;
2328                }
2329                else {
2330                    LayoutInterval sub = layoutModel.removeInterval(parSeq, 0);
2331                    layoutModel.addInterval(sub, subGroup, -1);
2332                }
2333                // extract surroundings of the group in the sequence
2334
operations.extract(parSeq, DEFAULT, true, separatedLeading, separatedTrailing);
2335                layoutModel.removeInterval(parSeq);
2336                layoutModel.removeInterval(iDesc.parent);
2337            }
2338        }
2339
2340        int extractAlign = DEFAULT;
2341        if (subGroup != null) {
2342            if (separatedLeading.isEmpty())
2343                extractAlign = TRAILING;
2344            if (separatedTrailing.isEmpty())
2345                extractAlign = LEADING;
2346        }
2347
2348        // 4th collect surroundings of adding interval
2349
// (the intervals will go into a side group in step 5, or into subgroup
2350
// of 'subGroup' next to the adding interval if in previous step some
2351
// content was separated into a parallel subgroup of a sequence)
2352
LayoutInterval subsubGroup = null;
2353        for (Iterator it=inclusions.iterator(); it.hasNext(); ) {
2354            IncludeDesc iDesc = (IncludeDesc) it.next();
2355            if (iDesc.parent.isParallel() || !iDesc.newSubGroup) {
2356                addToGroup(iDesc, null, false);
2357// mainEffectiveAlign = getEffectiveAlignment(interval);
2358
operations.extract(addingInterval, extractAlign, extractAlign == DEFAULT,
2359                                   separatedLeading, separatedTrailing);
2360                LayoutInterval parent = addingInterval.getParent();
2361                layoutModel.removeInterval(addingInterval);
2362                layoutModel.removeInterval(parent);
2363                if (extractAlign != DEFAULT && LayoutInterval.getCount(parent, DEFAULT, true) >= 1) {
2364                    if (subsubGroup == null) {
2365                        subsubGroup = new LayoutInterval(PARALLEL);
2366                        subsubGroup.setGroupAlignment(extractAlign);
2367                    }
2368                    operations.addContent(parent, subsubGroup, -1);
2369                }
2370            }
2371            if (iDesc.snappedNextTo != null)
2372                nextTo = iDesc.snappedNextTo;
2373            if (iDesc != best)
2374                it.remove();
2375        }
2376
2377        // prepare the common group for merged content
2378
int[] borderPos = commonGroup.getCurrentSpace().positions[dimension];
2379        int[] neighborPos = (subGroup != null ? subGroup : addingInterval).getCurrentSpace().positions[dimension];
2380        LayoutInterval commonSeq;
2381        int index;
2382        if (commonGroup.getSubIntervalCount() == 0 && commonGroup.getParent() != null) {
2383            // the common group got empty - eliminate it to avoid unncessary nesting
2384
LayoutInterval parent = commonGroup.getParent();
2385            index = layoutModel.removeInterval(commonGroup);
2386            if (parent.isSequential()) {
2387                commonSeq = parent;
2388                commonGroup = parent.getParent();
2389            }
2390            else { // parallel parent
2391
commonSeq = new LayoutInterval(SEQUENTIAL);
2392                commonSeq.setAlignment(commonGroup.getAlignment());
2393                layoutModel.addInterval(commonSeq, parent, index);
2394                commonGroup = parent;
2395                index = 0;
2396            }
2397        }
2398        else {
2399            commonSeq = new LayoutInterval(SEQUENTIAL);
2400            layoutModel.addInterval(commonSeq, commonGroup, -1);
2401            index = 0;
2402        }
2403        if (commonSeq.getSubIntervalCount() == 0) {
2404            commonSeq.getCurrentSpace().set(dimension, commonGroup.getCurrentSpace());
2405        }
2406        updateReplacedOriginalGroup(commonGroup, commonSeq);
2407
2408        // 5th create groups of merged content around the adding component
2409
LayoutInterval sideGroupLeading = null;
2410        LayoutInterval sideGroupTrailing = null;
2411        if (!separatedLeading.isEmpty()) {
2412            int checkCount = commonSeq.getSubIntervalCount(); // remember ...
2413
sideGroupLeading = operations.addGroupContent(
2414                    separatedLeading, commonSeq, index, dimension, LEADING); //, mainEffectiveAlign
2415
index += commonSeq.getSubIntervalCount() - checkCount;
2416        }
2417        if (!separatedTrailing.isEmpty()) {
2418            sideGroupTrailing = operations.addGroupContent(
2419                    separatedTrailing, commonSeq, index, dimension, TRAILING); //, mainEffectiveAlign
2420
}
2421        if (sideGroupLeading != null) {
2422            int checkCount = commonSeq.getSubIntervalCount(); // remember ...
2423
sideGroupLeading.getCurrentSpace().set(dimension, borderPos[LEADING], neighborPos[LEADING]);
2424            operations.optimizeGaps(sideGroupLeading, dimension);
2425            index += commonSeq.getSubIntervalCount() - checkCount;
2426        }
2427        if (sideGroupTrailing != null) {
2428            sideGroupTrailing.getCurrentSpace().set(dimension, neighborPos[TRAILING], borderPos[TRAILING]);
2429            operations.optimizeGaps(sideGroupTrailing, dimension);
2430        }
2431
2432        // 6th adjust the final inclusion
2433
best.parent = commonSeq;
2434        best.newSubGroup = false;
2435        best.neighbor = null;
2436
2437        LayoutInterval separatingGap;
2438        int gapIdx = index;
2439        if (gapIdx == commonSeq.getSubIntervalCount()) {
2440            gapIdx--;
2441            separatingGap = commonSeq.getSubInterval(gapIdx);
2442        }
2443        else {
2444            separatingGap = commonSeq.getSubInterval(gapIdx);
2445            if (!separatingGap.isEmptySpace()) {
2446                gapIdx--;
2447                if (gapIdx > 0)
2448                    separatingGap = commonSeq.getSubInterval(gapIdx);
2449            }
2450        }
2451        if (!separatingGap.isEmptySpace())
2452            separatingGap = null;
2453        else if (subGroup == null) {
2454            index = gapIdx;
2455            // eliminate the gap if caused by addToGroup called to separate adding
2456
// interval's surroundings to side groups; the gap will be created
2457
// again when addToGroup is called definitively (for merged inclusions)
2458
if (index == 0 && !LayoutInterval.isAlignedAtBorder(commonSeq, LEADING)) {
2459                layoutModel.removeInterval(separatingGap);
2460                separatingGap = null;
2461            }
2462            else if (index == commonSeq.getSubIntervalCount()-1 && !LayoutInterval.isAlignedAtBorder(commonSeq, TRAILING)) {
2463                layoutModel.removeInterval(separatingGap);
2464                separatingGap = null;
2465            }
2466        }
2467
2468        best.snappedNextTo = nextTo;
2469        if (nextTo != null)
2470            best.fixedPosition = true;
2471
2472        // 7th resolve subgroup
2473
if (subGroup != null) {
2474            if (separatingGap != null
2475                && (extractAlign == DEFAULT
2476                    || (extractAlign == LEADING && index > gapIdx)
2477                    || (extractAlign == TRAILING && index <= gapIdx)))
2478            { // subGroup goes next to a separating gap - which is likely superflous
2479
// (the extracted parallel sequence in subGroup has its own gap)
2480
layoutModel.removeInterval(separatingGap);
2481                if (index >= gapIdx && index > 0)
2482                    index--;
2483            }
2484            int subIdx = index;
2485            if (subsubGroup != null && subsubGroup.getSubIntervalCount() > 0) {
2486                LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
2487                seq.setAlignment(best.alignment);
2488                operations.addContent(subsubGroup, seq, 0);
2489                layoutModel.addInterval(seq, subGroup, -1);
2490                // [should run optimizeGaps on subsubGroup?]
2491
best.parent = seq;
2492                index = extractAlign == LEADING ? 0 : seq.getSubIntervalCount();
2493            }
2494            else {
2495                best.newSubGroup = true;
2496            }
2497            operations.addContent(subGroup, commonSeq, subIdx);
2498
2499            updateMovedOriginalNeighbor();
2500        }
2501
2502        best.index = index;
2503    }
2504
2505    private static boolean compatibleInclusions(IncludeDesc iDesc1, IncludeDesc iDesc2, int dimension) {
2506        LayoutInterval group1 = iDesc1.parent.isSequential() ?
2507                                iDesc1.parent.getParent() : iDesc1.parent;
2508        LayoutInterval group2 = iDesc2.parent.isSequential() ?
2509                                iDesc2.parent.getParent() : iDesc2.parent;
2510        if (group1 == group2)
2511            return true;
2512
2513        if (group1.isParentOf(group2)) {
2514            LayoutInterval temp = group1;
2515            group1 = group2;
2516            //group2 = temp;
2517
IncludeDesc itemp = iDesc1;
2518            //iDesc1 = iDesc2;
2519
iDesc2 = itemp;
2520        }
2521        else if (!group2.isParentOf(group1)) {
2522            return false;
2523        }
2524
2525        // group2 is parent of group1
2526
LayoutInterval neighbor = iDesc2.parent.isSequential() ? iDesc2.parent : iDesc2.neighbor;
2527        if (neighbor == null)
2528            return false;
2529        LayoutRegion spaceToHold = neighbor.getCurrentSpace();
2530        LayoutRegion spaceAvailable = group1.getCurrentSpace();
2531        return LayoutRegion.pointInside(spaceToHold, LEADING, spaceAvailable, dimension)
2532               && LayoutRegion.pointInside(spaceToHold, TRAILING, spaceAvailable, dimension);
2533    }
2534
2535    private void updateReplacedOriginalGroup(LayoutInterval newGroup, LayoutInterval newSeq) {
2536        updateReplacedOriginalGroup(originalPositions1[dimension], newGroup, newSeq);
2537        updateReplacedOriginalGroup(originalPositions2[dimension], newGroup, newSeq);
2538    }
2539
2540    private static void updateReplacedOriginalGroup(IncludeDesc iDesc, LayoutInterval newGroup, LayoutInterval newSeq)
2541    {
2542        if (iDesc != null && LayoutInterval.getRoot(newGroup) != LayoutInterval.getRoot(iDesc.parent)) {
2543            if (iDesc.parent.isParallel())
2544                iDesc.parent = newGroup;
2545            else if (newSeq != null)
2546                iDesc.parent = newSeq;
2547        }
2548    }
2549
2550    private void updateMovedOriginalNeighbor() {
2551        updateMovedOriginalNeighbor(originalPositions1[dimension]);
2552        updateMovedOriginalNeighbor(originalPositions2[dimension]);
2553    }
2554
2555    private static void updateMovedOriginalNeighbor(IncludeDesc iDesc) {
2556        if (iDesc != null && iDesc.neighbor != null) {
2557            iDesc.parent = LayoutInterval.getFirstParent(iDesc.neighbor, PARALLEL);
2558            correctNeighborInSequence(iDesc);
2559        }
2560    }
2561
2562    private boolean mergeSequentialInclusions(IncludeDesc iDesc1, IncludeDesc iDesc2) {
2563        if (iDesc2 == null || !canCombine(iDesc1, iDesc2))
2564            return false;
2565
2566        assert (iDesc1.alignment == LEADING || iDesc1.alignment == TRAILING)
2567                && (iDesc2.alignment == LEADING || iDesc2.alignment == TRAILING)
2568                && iDesc1.alignment == (iDesc2.alignment^1);
2569
2570        if (iDesc1.parent == iDesc2.parent)
2571            return true;
2572
2573        LayoutInterval commonGroup;
2574        boolean nextTo;
2575        if (iDesc1.parent.isParentOf(iDesc2.parent)) {
2576            commonGroup = iDesc1.parent;
2577            nextTo = iDesc1.neighbor != null || iDesc2.snappedNextTo != null;
2578        }
2579        else if (iDesc2.parent.isParentOf(iDesc1.parent)) {
2580            commonGroup = iDesc2.parent;
2581            nextTo = iDesc2.neighbor != null || iDesc1.snappedNextTo != null;
2582        }
2583        else {
2584            commonGroup = LayoutInterval.getFirstParent(iDesc1.parent, SEQUENTIAL);
2585            nextTo = false;
2586        }
2587
2588        if (commonGroup.isSequential() || nextTo) {
2589            // inclusions in common sequence or the upper inclusion has the lower as neighbor
2590
if (iDesc1.alignment == TRAILING) {
2591                IncludeDesc temp = iDesc1;
2592                iDesc1 = iDesc2;
2593                iDesc2 = temp;
2594            } // so iDesc1 is leading and iDesc2 trailing
2595

2596            int startIndex = 0;
2597            LayoutInterval ext1 = null;
2598            boolean startGap = false;
2599            int endIndex = 0;
2600            LayoutInterval ext2 = null;
2601            boolean endGap = false;
2602
2603            if (commonGroup.isSequential()) {
2604                if (commonGroup.isParentOf(iDesc1.parent)) {
2605                    ext1 = iDesc1.parent.isSequential() ? iDesc1.parent : iDesc1.neighbor;
2606                    if (ext1 != null) {
2607                        while (ext1.getParent().getParent() != commonGroup) {
2608                            ext1 = ext1.getParent();
2609                        }
2610                        startIndex = commonGroup.indexOf(ext1.getParent());
2611                    }
2612                    else { // nothing to extract, just find out the index
2613
LayoutInterval inCommon = iDesc1.parent;
2614                        while (inCommon.getParent() != commonGroup) {
2615                            inCommon = inCommon.getParent();
2616                        }
2617                        startIndex = commonGroup.indexOf(inCommon);
2618                    }
2619                }
2620                else {
2621                    startIndex = iDesc1.index;
2622                    if (startIndex == commonGroup.getSubIntervalCount())
2623                        startIndex--;
2624                    startGap = commonGroup.getSubInterval(startIndex).isEmptySpace();
2625                }
2626
2627                if (commonGroup.isParentOf(iDesc2.parent)) {
2628                    ext2 = iDesc2.parent.isSequential() ? iDesc2.parent : iDesc2.neighbor;
2629                    if (ext2 != null) {
2630                        while (ext2.getParent().getParent() != commonGroup) {
2631                            ext2 = ext2.getParent();
2632                        }
2633                        endIndex = commonGroup.indexOf(ext2.getParent());
2634                    }
2635                    else {
2636                        LayoutInterval inCommon = iDesc2.parent;
2637                        while (inCommon.getParent() != commonGroup) {
2638                            inCommon = inCommon.getParent();
2639                        }
2640                        endIndex = commonGroup.indexOf(inCommon);
2641                    }
2642                }
2643                else {
2644                    endIndex = iDesc2.index;
2645                    if (iDesc2.snappedParallel == null || !commonGroup.isParentOf(iDesc2.snappedParallel))
2646                        endGap = commonGroup.getSubInterval(--endIndex).isEmptySpace();
2647                }
2648            }
2649
2650            if ((endIndex > startIndex + 1 || (endIndex == startIndex+1 && !startGap && !endGap))
2651                && ((ext1 != null && !iDesc1.newSubGroup) || (ext2 != null && !iDesc2.newSubGroup)))
2652            { // there is a significant part of the common sequence to be parallelized
2653
LayoutInterval parGroup;
2654                if (startIndex == 0 && endIndex == commonGroup.getSubIntervalCount()-1) {
2655                    // parallel with whole sequence
2656
parGroup = commonGroup.getParent();
2657                }
2658                else { // separate part of the original sequence
2659
parGroup = new LayoutInterval(PARALLEL);
2660                    LayoutInterval parSeq = new LayoutInterval(SEQUENTIAL);
2661                    layoutModel.addInterval(parSeq, parGroup, 0);
2662                    parGroup.getCurrentSpace().set(dimension,
2663                            LayoutUtils.getVisualPosition(commonGroup.getSubInterval(startIndex), dimension, LEADING),
2664                            LayoutUtils.getVisualPosition(commonGroup.getSubInterval(endIndex), dimension, TRAILING));
2665                    int i = startIndex;
2666                    while (i <= endIndex) {
2667                        LayoutInterval li = layoutModel.removeInterval(commonGroup, i);
2668                        endIndex--;
2669                        layoutModel.addInterval(li, parSeq, -1);
2670                    }
2671                    layoutModel.addInterval(parGroup, commonGroup, startIndex);
2672                }
2673                LayoutInterval extSeq = new LayoutInterval(SEQUENTIAL); // sequence for the extracted inclusion targets
2674
layoutModel.addInterval(extSeq, parGroup, -1);
2675                if (ext1 != null) {
2676                    LayoutInterval parent = ext1.getParent();
2677                    layoutModel.removeInterval(ext1);
2678                    if (parent.getSubIntervalCount() == 1) {
2679                        LayoutInterval last = layoutModel.removeInterval(parent, 0);
2680                        operations.addContent(last, parent.getParent(), layoutModel.removeInterval(parent));
2681                    }
2682                    operations.addContent(ext1, extSeq, 0);
2683                    if (ext2 != null) {
2684                        LayoutInterval gap = new LayoutInterval(SINGLE);
2685                        int size = LayoutRegion.distance(ext1.getCurrentSpace(), ext2.getCurrentSpace(), dimension, LEADING, TRAILING);
2686                        gap.setSize(size);
2687                        layoutModel.addInterval(gap, extSeq, -1);
2688
2689                        iDesc1.index = iDesc2.index = extSeq.indexOf(gap);
2690                    }
2691                    else iDesc2.index = iDesc1.index;
2692                }
2693                else iDesc1.index = iDesc2.index;
2694                if (ext2 != null) {
2695                    LayoutInterval parent = ext2.getParent();
2696                    layoutModel.removeInterval(ext2);
2697                    if (parent.getSubIntervalCount() == 1) {
2698                        LayoutInterval last = layoutModel.removeInterval(parent, 0);
2699                        operations.addContent(last, parent.getParent(), layoutModel.removeInterval(parent));
2700                    }
2701                    operations.addContent(ext2, extSeq, -1);
2702                }
2703
2704                iDesc1.parent = iDesc2.parent = extSeq;
2705                iDesc1.newSubGroup = iDesc2.newSubGroup = false;
2706                iDesc1.neighbor = iDesc2.neighbor = null;
2707            }
2708            else { // end position, stay in subgroup
2709
if (iDesc2.parent.isParentOf(iDesc1.parent)) {
2710                    iDesc2.parent = iDesc1.parent;
2711                    iDesc2.index = iDesc1.index;
2712                    iDesc2.newSubGroup = iDesc1.newSubGroup;
2713                    iDesc2.neighbor = iDesc1.neighbor;
2714                    if (endGap) // there's an outer gap
2715
iDesc2.fixedPosition = false;
2716                }
2717                else if (iDesc1.parent.isParentOf(iDesc2.parent)) {
2718                    iDesc1.parent = iDesc2.parent;
2719                    iDesc1.index = iDesc2.index;
2720                    iDesc1.newSubGroup = iDesc2.newSubGroup;
2721                    iDesc1.neighbor = iDesc2.neighbor;
2722                    if (startGap) // there's an outer gap
2723
iDesc1.fixedPosition = false;
2724                }
2725            }
2726        }
2727        else { // common group is parallel - there is nothing in sequence, so nothing to extract
2728
assert iDesc1.parent.isParallel() && iDesc2.parent.isParallel()
2729                   && (commonGroup == iDesc1.parent || commonGroup == iDesc2.parent)
2730                   && iDesc1.neighbor == null && iDesc2.neighbor == null;
2731
2732            if ((iDesc2.snappedNextTo == null && iDesc2.snappedParallel == null)
2733                || (iDesc2.snappedParallel != null && canAlignWith(iDesc2.snappedParallel, iDesc1.parent, iDesc2.alignment)))
2734            { // iDesc2 can adapt to iDesc1
2735
iDesc2.parent = iDesc1.parent;
2736                return true;
2737            }
2738
2739            if (iDesc2.parent == commonGroup) {
2740                IncludeDesc temp = iDesc1;
2741                iDesc1 = iDesc2;
2742                iDesc2 = temp;
2743            } // so iDesc1 is super-group and iDesc2 subgroup
2744
assert iDesc2.snappedNextTo == null;
2745
2746            if (iDesc2.snappedParallel == iDesc2.parent) {
2747                iDesc2.parent = LayoutInterval.getFirstParent(iDesc2.parent, PARALLEL);
2748                if (iDesc2.parent == iDesc1.parent)
2749                    return true;
2750            }
2751
2752            if (iDesc2.snappedParallel == null || canAlignWith(iDesc2.snappedParallel, iDesc1.parent, iDesc2.alignment)) {
2753                // subgroup is either not snapped at all, or can align also in parent group
2754
iDesc2.parent = iDesc1.parent;
2755                return true;
2756            }
2757
2758            if (LayoutInterval.isAlignedAtBorder(iDesc2.parent, iDesc1.parent, iDesc1.alignment)) {
2759                iDesc1.parent = iDesc2.parent;
2760                return true; // subgroup is aligned to parent group edge
2761
}
2762
2763            LayoutInterval seq = iDesc2.parent.getParent();
2764            if (seq.isSequential() && seq.getParent() == iDesc1.parent) {
2765                int index = seq.indexOf(iDesc2.parent) + (iDesc1.alignment == LEADING ? -1 : 1);
2766                LayoutInterval gap = (index == 0 || index == seq.getSubIntervalCount()-1) ?
2767                                     seq.getSubInterval(index) : null;
2768                if (gap != null
2769                    && LayoutInterval.isFixedDefaultPadding(gap)
2770                    && iDesc1.snappedNextTo == iDesc1.parent
2771                    && LayoutInterval.wantResize(seq))
2772                { // subgroup is at preferred gap from parent - corresponds to parent's snappedNextTo
2773
iDesc1.parent = iDesc2.parent;
2774                    iDesc1.snappedNextTo = null;
2775                    iDesc1.snappedParallel = iDesc2.parent;
2776                    return true;
2777                }
2778
2779                if (gap != null && gap.isEmptySpace() && iDesc1.snappedParallel == iDesc1.parent) {
2780                    // need to make the subgroup aligned to parent group
2781
int gapSize = LayoutInterval.getIntervalCurrentSize(gap, dimension);
2782                    copyGapInsideGroup(gap, gapSize, iDesc2.parent, iDesc1.alignment);
2783                    layoutModel.removeInterval(gap);
2784                    iDesc1.parent = iDesc2.parent;
2785                    return true;
2786                }
2787            }
2788
2789            iDesc2.parent = iDesc1.parent; // prefer super-group otherwise
2790
}
2791
2792        return true;
2793    }
2794
2795    /**
2796     * Moves a gap next to a parallel group into the parallel group - i.e. each
2797     * interval in the group gets extended by the gap. Sort of opposite to
2798     * LayoutOperations.optimizeGaps.
2799     * @param alignment which side of the group is extended
2800     */

2801    private void copyGapInsideGroup(LayoutInterval gap, int gapSize, LayoutInterval group, int alignment) {
2802        assert gap.isEmptySpace() && (alignment == LEADING || alignment == TRAILING);
2803
2804        if (alignment == LEADING)
2805            gapSize = -gapSize;
2806
2807        group.getCurrentSpace().positions[dimension][alignment] += gapSize;
2808
2809        for (Iterator it=group.getSubIntervals(); it.hasNext(); ) {
2810            LayoutInterval sub = (LayoutInterval) it.next();
2811            LayoutInterval gapClone = LayoutInterval.cloneInterval(gap, null);
2812            if (sub.isSequential()) {
2813                sub.getCurrentSpace().positions[dimension][alignment] += gapSize;
2814                int index = alignment == LEADING ? 0 : sub.getSubIntervalCount();
2815                operations.insertGapIntoSequence(gapClone, sub, index, dimension);
2816            }
2817            else {
2818                LayoutInterval seq = new LayoutInterval(SEQUENTIAL);
2819                seq.getCurrentSpace().set(dimension, sub.getCurrentSpace());
2820                seq.getCurrentSpace().positions[dimension][alignment] += gapSize;
2821                seq.setAlignment(sub.getRawAlignment());
2822                layoutModel.addInterval(seq, group, layoutModel.removeInterval(sub));
2823                layoutModel.setIntervalAlignment(sub, DEFAULT);
2824                layoutModel.addInterval(sub, seq, 0);
2825                layoutModel.addInterval(gapClone, seq, alignment == LEADING ? 0 : 1);
2826            }
2827        }
2828    }
2829
2830    private boolean shouldEnterGroup(LayoutInterval group) {
2831        assert group.isParallel();
2832
2833        int alignment = aEdge;
2834        int groupAlign = group.getGroupAlignment();
2835        if (groupAlign != alignment
2836            && ((groupAlign != LEADING && groupAlign != TRAILING)
2837                 || (alignment != LEADING && alignment != TRAILING && alignment != DEFAULT)))
2838            return false; // incompatible group alignment
2839

2840        if (aSnappedParallel != null && !allowsSubAlignWith(aSnappedParallel, group, alignment))
2841            return false; // could not align with position.interval
2842

2843        return true;
2844    }
2845
2846    /**
2847     * @return whether within or under 'group' one might align in parallel
2848     * with 'interval'; so it return false if content of 'group' is
2849     * in an incompatible branch
2850     */

2851    private boolean allowsSubAlignWith(LayoutInterval interval, LayoutInterval group, int alignment) {
2852        if (group == interval || group.isParentOf(interval))
2853            return true;
2854
2855        LayoutInterval parent = LayoutInterval.getFirstParent(interval, PARALLEL);
2856        while (parent != null) {
2857            if (LayoutInterval.isAlignedAtBorder(interval, parent, alignment)) {
2858                if (parent.isParentOf(group) && LayoutInterval.isAlignedAtBorder(group, parent, alignment)) {
2859                    return true;
2860                }
2861                interval = parent;
2862                parent = LayoutInterval.getFirstParent(interval, PARALLEL);
2863            }
2864            else parent = null;
2865        }
2866
2867        return false;
2868    }
2869
2870    /**
2871     * @return whether being in 'group' (having it as first parallel parent)
2872     * allows parallel align with 'interval'
2873     */

2874    private boolean canAlignWith(LayoutInterval interval, LayoutInterval group, int alignment) {
2875        if (group.isSequential())
2876            group = group.getParent();
2877
2878        if (interval == group)
2879            return true; // can align to group border from inside
2880

2881        LayoutInterval parent = interval.getParent();
2882        if (parent == null)
2883            parent = interval;
2884        else if (parent.isSequential())
2885            parent = parent.getParent();
2886
2887        while (parent != null && parent != group && !parent.isParentOf(group)) {
2888            if (canSubstAlignWithParent(interval, dimension, alignment, dragger.isResizing(dimension))) {
2889                interval = parent;
2890                parent = LayoutInterval.getFirstParent(interval, PARALLEL);
2891            }
2892            else parent = null;
2893        }
2894        if (parent == null)
2895            return false;
2896        if (parent == group)
2897            return true;
2898        // otherwise parent.isParentOf(group)
2899
return LayoutInterval.isAlignedAtBorder(group, parent, alignment);
2900        // we silently assume that addingInterval will end up aligned in 'group'
2901
}
2902
2903    private static boolean canSubstAlignWithParent(LayoutInterval interval, int dimension, int alignment, boolean placedAtBorderEnough) {
2904        LayoutInterval parent = LayoutInterval.getFirstParent(interval, PARALLEL);
2905        boolean aligned = LayoutInterval.isAlignedAtBorder(interval, parent, alignment);
2906        if (!aligned
2907            && LayoutInterval.getDirectNeighbor(interval, alignment, false) == null
2908            && LayoutInterval.isPlacedAtBorder(interval, parent, dimension, alignment))
2909        { // not aligned, but touching parallel group border
2910
aligned = placedAtBorderEnough
2911                      || LayoutInterval.getDirectNeighbor(parent, alignment, true) != null
2912                      || LayoutInterval.isClosedGroup(parent, alignment);
2913            if (!aligned) { // check if the group can be considered "closed" at alignment edge
2914
boolean allTouching = true;
2915                for (Iterator it=parent.getSubIntervals(); it.hasNext(); ) {
2916                    LayoutInterval li = (LayoutInterval) it.next();
2917                    if (li.getAlignment() == alignment || LayoutInterval.wantResize(li)) {
2918                        aligned = true;
2919                        break;
2920                    }
2921                    else if (allTouching && !LayoutInterval.isPlacedAtBorder(li, dimension, alignment)) {
2922                        allTouching = false;
2923                    }
2924                }
2925                if (allTouching)
2926                    aligned = true;
2927            }
2928        }
2929        return aligned;
2930    }
2931
2932    private boolean canCombine(IncludeDesc iDesc1, IncludeDesc iDesc2) {
2933        if (iDesc1.parent == iDesc2.parent)
2934            return true;
2935
2936        LayoutInterval commonGroup;
2937        if (iDesc1.parent.isParentOf(iDesc2.parent))
2938            return isBorderInclusion(iDesc2);
2939        else if (iDesc2.parent.isParentOf(iDesc1.parent))
2940            return isBorderInclusion(iDesc1);
2941        else {
2942            LayoutInterval parParent1 = iDesc1.parent.isParallel() ? iDesc1.parent : iDesc1.parent.getParent();
2943            LayoutInterval parParent2 = iDesc2.parent.isParallel() ? iDesc2.parent : iDesc2.parent.getParent();
2944            return parParent1.getParent() == parParent2.getParent()
2945                   && isBorderInclusion(iDesc1)
2946                   && isBorderInclusion(iDesc2)
2947                   && LayoutInterval.getDirectNeighbor(parParent1, iDesc1.alignment^1, true) == parParent2;
2948        }
2949    }
2950
2951    private boolean isBorderInclusion(IncludeDesc iDesc) {
2952        if (iDesc.alignment != LEADING && iDesc.alignment != TRAILING)
2953            return false;
2954
2955        if (iDesc.parent.isSequential()) {
2956            int startIndex = iDesc.alignment == LEADING ? iDesc.index : 0;
2957            int endIndex = iDesc.alignment == LEADING ? iDesc.parent.getSubIntervalCount() - 1 : iDesc.index - 1;
2958            return startIndex > endIndex
2959                   || !LayoutUtils.contentOverlap(addingSpace, iDesc.parent, startIndex, endIndex, dimension^1);
2960        }
2961
2962        return iDesc.neighbor == null
2963               || (iDesc.alignment == LEADING && iDesc.index >= 1)
2964               || (iDesc.alignment == TRAILING && iDesc.index == 0);
2965    }
2966
2967    private static int getAddDirection(LayoutRegion adding,
2968                                       LayoutRegion existing,
2969                                       int dimension,
2970                                       int alignment)
2971    {
2972        return LayoutRegion.distance(adding, existing, dimension, alignment, CENTER) > 0 ?
2973               LEADING : TRAILING;
2974    }
2975}
2976
Popular Tags