KickJava   Java API By Example, From Geeks To Geeks.

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


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.awt.Graphics2D JavaDoc;
23 import java.awt.Stroke JavaDoc;
24 import java.awt.BasicStroke JavaDoc;
25 import java.util.*;
26
27 /*
28 Finding position procedure [sort of out of date]:
29 - find vertical position first - preferring aligned position
30 - find horizontal position - preferring derived (next to) position
31
32 finding position in a dimension:
33 - find best derived positions for each alignment (L, T):
34   - go down from the root, looking for distances (opposite edges)
35   - exclude elements not overlapping in the other dimension
36   - if a distance is smaller than SNAP_DISTANCE, compare with the best position so far:
37     - better position is that with better alignment in the opposite dimension
38       - i.e. has smallest alignment distance (any)
39     - otherwise the distance must be closer
40     - otherwise the lower interval in hierarchy is preferred (given that
41       it is visited only if the position is inside higher)
42 - find best aligned positions for each alignment (L, T, C, B):
43   - go down from the root, looking for distances (same edges)
44   - check only component elements
45   - if a distance is smaller than SNAP_DISTANCE, compare with the best position so far:
46     - closer distance is better
47     - otherwise the distance in the other dimension must be closer
48 - choose the best position
49   - the unpreferred position must have twice smaller distance, and be better
50     by at least SNAP_DISTANCE/2
51 */

52
53 class LayoutDragger implements LayoutConstants {
54
55     private VisualMapper visualMapper;
56
57     // fixed (initial) parameters of the operation ---
58

59     // type of the operation, one of ADDING, MOVING, RESIZING
60
private int operation;
61     private static final int ADDING = 0;
62     private static final int MOVING = 1;
63     private static final int RESIZING = 2;
64
65     // components being moved
66
private LayoutComponent[] movingComponents;
67
68     // for each dimension defines what component edges are to be moved:
69
// - LayoutRegion.ALL_POINTS means whole component is moved
70
// - LEADING or TRAILING means the component is resized
71
// - LayoutRegion.NO_POINT means the component is not changed in given dimension
72
private int movingEdges[]; // array according to dimension (HORIZONTAL or VERTICAL)
73

74     // indicates whether the operation is resizing - according to movingEdges
75
// private boolean resizing;
76

77     // initial components' positions and sizes (defines the formation to move)
78
private LayoutRegion[] movingFormation;
79
80     // initial mouse cursor position (when moving/resizing starts)
81
private int[] startCursorPosition;
82
83     private SizeDef[] sizing;
84
85     // parameters changed with each move step ---
86

87     // last mouse cursor position
88
private int[] lastCursorPosition = new int[] { LayoutRegion.UNKNOWN, LayoutRegion.UNKNOWN };
89
90     // direction of mouse move from last position
91
private int[] moveDirection = new int[] { LEADING, LEADING };
92
93     // dimension that is temporarily locked (kept unchanged)
94
private int lockedDimension = -1;
95
96     // container the components are being moved in/over
97
private LayoutComponent targetContainer;
98
99     // actual components' bounds of moving/resizing components
100
private LayoutRegion[] movingBounds;
101
102     // last found positions for the moving/resizing component
103
private PositionDef[] bestPositions = new PositionDef[DIM_COUNT];
104     
105     // determines whether the dragged component can snap to baseline
106
private boolean canSnapToBaseline; // used only for multiselection
107

108     // the following fields hold various parameters used during the procedure
109
// of finding a suitable position for the moving/resizing components
110
// (fields are used not to need to pass the parameters through all the
111
// methods again and again and to avoid repetitive creation of arrays) ---
112

113     private LayoutRegion movingSpace;
114     private int dimension;
115     private boolean snapping;
116
117     // arrays of position candidates for the moving/resizing component
118
private PositionDef[][] findingsNextTo;
119     private PositionDef[][] findingsAligned;
120
121     // constants ---
122

123     static final int[] ALL_EDGES = { LayoutRegion.ALL_POINTS,
124                                      LayoutRegion.ALL_POINTS };
125
126     // length of tips of painted guiding lines
127
private static final int GL_TIP = 8;
128
129     // distance in which components are drawn to guiding line
130
private static final int SNAP_DISTANCE = 8;
131
132     // max. orthogonal distance from a component to be still recognized as "next to"
133
private static final int ORT_DISTANCE = 8;
134     
135     private static BasicStroke JavaDoc dashedStroke = new BasicStroke JavaDoc(1.0f, BasicStroke.CAP_BUTT,
136         BasicStroke.JOIN_MITER, 5.0f, new float[]{5.0f, 2.0f}, 0.0f);
137
138     // -----
139
// setup
140

141     LayoutDragger(LayoutComponent[] comps,
142                   LayoutRegion[] compBounds,
143                   int[] initialCursorPos,
144                   int[] movingEdges,
145                   VisualMapper mapper)
146     {
147         for (int i=0; i < DIM_COUNT; i++) {
148             if (movingEdges[i] == LEADING || movingEdges[i] == TRAILING) {
149                 operation = RESIZING;
150                 break;
151             }
152         }
153         if (operation != RESIZING) {
154             operation = comps[0].getParent() == null ? ADDING : MOVING;
155         }
156
157         this.movingComponents = comps;
158         this.movingFormation = compBounds;
159         this.startCursorPosition = initialCursorPos;
160         this.movingEdges = movingEdges;
161         visualMapper = mapper;
162
163         movingBounds = new LayoutRegion[compBounds.length];
164         movingSpace = new LayoutRegion();
165         for (int i=0; i < compBounds.length; i++) {
166             movingBounds[i] = new LayoutRegion();
167             movingSpace.expand(compBounds[i]);
168         }
169         
170         // set canSnapToBaseline field - check that we are moving baseline group
171
LayoutInterval parent = comps[0].getLayoutInterval(VERTICAL).getParent();
172         for (int i=0; i < comps.length; i++) {
173             if (comps[i].getLayoutInterval(VERTICAL).getParent() != parent) {
174                 parent = null;
175                 break;
176             }
177         }
178         canSnapToBaseline = (comps.length == 1) || ((parent != null) && (parent.getGroupAlignment() == BASELINE));
179
180         findingsNextTo = new PositionDef[DIM_COUNT][];
181         findingsAligned = new PositionDef[DIM_COUNT][];
182         for (int i=0; i < DIM_COUNT; i++) {
183             int n = LayoutRegion.POINT_COUNT[i];
184             findingsNextTo[i] = new PositionDef[n];
185             findingsAligned[i] = new PositionDef[n];
186             for (int j=0; j < n; j++) {
187                 findingsNextTo[i][j] = new PositionDef();
188                 findingsAligned[i][j] = new PositionDef();
189             }
190         }
191
192         if (operation == RESIZING) {
193             prepareResizing();
194         }
195     }
196
197     private void prepareResizing() {
198         sizing = new SizeDef[DIM_COUNT];
199         LayoutComponent comp = movingComponents[0]; // [limitation: only one component can be resized]
200
LayoutRegion space = movingFormation[0];
201         java.awt.Dimension JavaDoc prefSize = null;
202         for (int i=0; i < DIM_COUNT; i++) {
203             if (isResizing(i)) {
204                 SizeDef sizeDef = new SizeDef();
205                 sizing[i] = sizeDef;
206                 sizeDef.originalSize = space.size(i);
207                 if (comp.isLayoutContainer()) {
208                     LayoutInterval resGap = findResizingGap(comp.getLayoutRoot(i));
209                     if (resGap != null) {
210                         sizeDef.resizingGap = resGap;
211                         sizeDef.originalGapSize = LayoutInterval.getIntervalCurrentSize(resGap, i);
212                         sizeDef.preferredGapSize = LayoutUtils.getSizeOfDefaultGap(resGap, visualMapper);
213                         sizeDef.preferredSize = sizeDef.originalSize
214                                 - sizeDef.originalGapSize + sizeDef.preferredGapSize;
215                         sizeDef.zeroPreferredSize = isZeroResizingGap(resGap) ?
216                                 sizeDef.originalSize - sizeDef.originalGapSize : Short.MIN_VALUE;
217                     }
218                     else {
219                         if (prefSize == null) {
220                             prefSize = visualMapper.getComponentMinimumSize(comp.getId());
221                         }
222                         sizeDef.preferredSize = i == HORIZONTAL ? prefSize.width : prefSize.height;
223                     }
224                 }
225                 else {
226                     if (prefSize == null) {
227                         prefSize = visualMapper.getComponentPreferredSize(comp.getId());
228                     }
229                     sizeDef.preferredSize = i == HORIZONTAL ? prefSize.width : prefSize.height;
230                 }
231             }
232         }
233     }
234
235     private LayoutInterval findResizingGap(LayoutInterval group) {
236         for (Iterator it=group.getSubIntervals(); it.hasNext(); ) {
237             LayoutInterval li = (LayoutInterval) it.next();
238             if (li.isEmptySpace() && li.hasAttribute(LayoutInterval.ATTR_DESIGN_CONTAINER_GAP)) {
239                 return li;
240             }
241             else if (li.isGroup()) {
242                 LayoutInterval gap = findResizingGap(li);
243                 if (gap != null) {
244                     return gap;
245                 }
246             }
247         }
248         return null;
249     }
250
251     private static boolean isZeroResizingGap(LayoutInterval gap) {
252         return LayoutInterval.getNeighbor(gap, LEADING, false, true, false) == null
253             || LayoutInterval.getNeighbor(gap, TRAILING, false, true, false) == null;
254     }
255
256     void setTargetContainer(LayoutComponent container) {
257         targetContainer = container;
258     }
259
260     LayoutComponent getTargetContainer() {
261         return targetContainer;
262     }
263
264     boolean isResizing() {
265         return operation == RESIZING;
266     }
267
268     boolean isResizing(int dim) {
269         return movingEdges[dim] == LEADING || movingEdges[dim] == TRAILING;
270     }
271
272     int getResizingEdge(int dim) {
273         return movingEdges[dim];
274     }
275
276     LayoutComponent[] getMovingComponents() {
277         return movingComponents;
278     }
279
280     VisualMapper getVisualMapper() {
281         return visualMapper;
282     }
283
284     // -----
285
// results
286

287     LayoutRegion[] getMovingBounds() {
288         return movingBounds;
289     }
290     
291     LayoutRegion getMovingSpace() {
292         return movingSpace;
293     }
294
295     PositionDef[] getPositions() {
296 /* for (dimension=0; dimension < DIM_COUNT; dimension++) {
297             if (movingEdges[dimension] != LayoutRegion.NO_POINT) {
298                 PositionDef best = bestPositions[dimension];
299                 if (best == null && !isResizing(dimension)) { // not found, retry without position restriction
300                     snapping = false;
301                     findBestPosition();
302                 }
303             }
304         } */

305         return bestPositions;
306     }
307
308     SizeDef[] getSizes() {
309         return sizing;
310     }
311
312     boolean snappedToDefaultSize(int dimension) {
313         if (isResizing(dimension) && bestPositions[dimension] == null) {
314             int size = movingSpace.size(dimension);
315             return size == sizing[dimension].preferredSize
316                 || size == sizing[dimension].zeroPreferredSize;
317         }
318         return false;
319     }
320
321     // -----
322
// moving & painting
323

324     void move(int[] cursorPos, boolean autoPositioning, boolean lockDimension) {
325         // translate mouse cursor position, compute move direction, ...
326
int lockCandidate = -1; // dimension that might be locked if there's aligned position
327
int minDelta = Integer.MAX_VALUE;
328         for (int i=0; i < DIM_COUNT; i++) {
329             cursorPos[i] -= startCursorPosition[i]; // translate to diff from the initial state
330
int currentPos = cursorPos[i];
331             int lastPos = lastCursorPosition[i];
332             lastCursorPosition[i] = currentPos;
333             if (lastPos == LayoutRegion.UNKNOWN) { // first move step, can't calc direction
334
lockDimension = false; // can't lock yet
335
}
336             else {
337                 int delta = currentPos - lastPos;
338                 if (delta != 0) { // position changed in this direction
339
moveDirection[i] = delta > 0 ? TRAILING : LEADING;
340                 }
341                 if (movingEdges[i] != LayoutRegion.ALL_POINTS) {
342                     lockDimension = false; // can't lock - this is not pure moving
343
}
344                 else if (lockedDimension < 0) { // not locked yet
345
PositionDef pos = bestPositions[i];
346                     if (pos != null && !pos.nextTo && delta < minDelta) {
347                         lockCandidate = i;
348                         minDelta = delta;
349                     }
350                 }
351             }
352         }
353
354         // check locked dimension
355
if (lockDimension) {
356             if (lockedDimension < 0) { // not set yet
357
lockedDimension = lockCandidate;
358             }
359         }
360         else lockedDimension = -1;
361
362         // compute actual position of the moving components
363
for (int i=0; i < movingBounds.length; i++) {
364             for (int j=0; j < DIM_COUNT; j++) {
365                 if (j != lockedDimension) {
366                     movingBounds[i].set(j, movingFormation[i]);
367                     movingBounds[i].reshape(j, movingEdges[j], cursorPos[j]);
368                 }
369                 // for locked dimension the space is already set and not changing
370
}
371         }
372         movingSpace = new LayoutRegion();
373         for (int i=0; i<movingBounds.length; i++) {
374             movingSpace.expand(movingBounds[i]);
375         }
376         if (canSnapToBaseline) { // awfull, but working
377
movingSpace.positions[VERTICAL][BASELINE] = movingBounds[0].positions[VERTICAL][BASELINE];
378         }
379
380         // reset finding results
381
for (int i=0; i < DIM_COUNT; i++) {
382             if (i != lockedDimension) {
383                 bestPositions[i] = null;
384                 for (int j=0; j < LayoutRegion.POINT_COUNT[i]; j++) {
385                     findingsNextTo[i][j].reset();
386                     findingsAligned[i][j].reset();
387                 }
388             }
389         }
390
391         // find position in the layout
392
snapping = autoPositioning;
393         if (autoPositioning) {
394             // important: looking for vertical position first
395
for (dimension=DIM_COUNT-1; dimension >= 0; dimension--) {
396                 if (dimension != lockedDimension
397                     && movingEdges[dimension] != LayoutRegion.NO_POINT)
398                 { // look for a suitable position in this dimension
399
int snapDistance = findBestPosition();
400                     // snap effect
401
if (snapDistance != LayoutRegion.UNKNOWN) {
402                         cursorPos[dimension] -= snapDistance;
403                         for (int i=0; i<movingBounds.length; i++) {
404                             movingBounds[i].reshape(dimension,
405                                             movingEdges[dimension],
406                                             -snapDistance);
407                         }
408                         movingSpace.reshape(dimension,
409                                             movingEdges[dimension],
410                                             -snapDistance);
411                     }
412                 }
413             }
414         }
415
416         // translate mouse cursor position back to absolute coordinates
417
for (int i=0; i < DIM_COUNT; i++) {
418             cursorPos[i] += startCursorPosition[i];
419         }
420     }
421
422     void paintMoveFeedback(Graphics2D JavaDoc g) {
423         final int OVERLAP = 10;
424         for (int i=0; i < DIM_COUNT; i++) {
425             LayoutDragger.PositionDef position = bestPositions[i];
426             if (position != null) {
427                 boolean inRoot = (position.interval.getParent() == null);
428                 int dir = 1-i; // opposite direction
429
int align = position.alignment;
430                 LayoutInterval interval = position.interval;
431                 LayoutInterval parent = interval.getParent();
432                 boolean parentUsed;
433                 do {
434                     parentUsed = false;
435                     if (parent != null && parent.isParallel()) {
436                         // check if the interval alignment coincides with parent
437
if (align == LEADING || align == TRAILING) {
438                             LayoutRegion parRegion = parent.getCurrentSpace();
439                             if (!position.nextTo
440                                 && LayoutRegion.distance(parRegion, movingSpace, i, align, align) == 0)
441                             {
442                                 parentUsed = true;
443                             }
444                         }
445                         else if (align == parent.getGroupAlignment()) {
446                             parentUsed = true;
447                         }
448                     }
449                     if (parentUsed) {
450                         interval = parent;
451                         parent = LayoutInterval.getFirstParent(parent, PARALLEL);
452                     }
453                 } while (parentUsed);
454                 LayoutRegion posRegion = interval.getCurrentSpace();
455                 LayoutRegion contRegion = targetContainer.getLayoutRoot(0).getCurrentSpace();
456                 int conty1 = contRegion.positions[dir][LEADING];
457                 int conty2 = contRegion.positions[dir][TRAILING];
458                 int posx = posRegion.positions[i][(inRoot || !position.nextTo) ? align : 1-align];
459                 int posy1 = posRegion.positions[dir][LEADING]-OVERLAP;
460                 posy1 = Math.max(posy1, conty1);
461                 int posy2 = posRegion.positions[dir][TRAILING]+OVERLAP;
462                 posy2 = Math.min(posy2, conty2);
463                 int x = movingSpace.positions[i][align];
464                 int y1 = movingSpace.positions[dir][LEADING]-OVERLAP;
465                 y1 = Math.max(y1, conty1);
466                 int y2 = movingSpace.positions[dir][TRAILING]+OVERLAP;
467                 y2 = Math.min(y2, conty2);
468                 Stroke JavaDoc oldStroke = g.getStroke();
469                 g.setStroke(dashedStroke);
470                 if (position.nextTo) { // adding next to
471
if (i == HORIZONTAL) {
472                         g.drawLine(x, Math.min(y1, posy1), x, Math.max(y2, posy2));
473                     } else {
474                         g.drawLine(Math.min(y1, posy1), x, Math.max(y2, posy2), x);
475                     }
476                 } else { // adding aligned
477
//int ay1 = Math.min(y1, posy1);
478
//int ay2 = Math.max(y2, posy2);
479
if (x == posx) {
480                         if (i == HORIZONTAL) {
481                             g.drawLine(posx, Math.min(y1, posy1), posx, Math.max(y2, posy2));
482                         } else {
483                             g.drawLine(Math.min(y1, posy1), posx, Math.max(y2, posy2), posx);
484                         }
485                     }
486                     else { // indented position
487
if (i == HORIZONTAL) {
488                             g.drawLine(posx, posy1, posx, posy2);
489                             g.drawLine(x, y1, x, y2);
490                         }
491                         else {
492                             g.drawLine(posy1, posx, posy2, posx);
493                             g.drawLine(y1, x, y2, x);
494                         }
495                     }
496                 }
497                 g.setStroke(oldStroke);
498             }
499             else if (snappedToDefaultSize(i)) { // resizing snapped to default preferred size
500
int align = movingEdges[i];
501                 int x1 = movingSpace.positions[i][align];
502                 int x2 = movingSpace.positions[i][align^1];
503                 int y = movingSpace.positions[i^1][CENTER];
504                 Stroke JavaDoc oldStroke = g.getStroke();
505                 g.setStroke(dashedStroke);
506                 if (i == HORIZONTAL) {
507                     g.drawLine(x1, y, x2, y);
508                 }
509                 else {
510                     g.drawLine(y, x1, y, x2);
511                 }
512                 g.setStroke(oldStroke);
513             }
514         }
515     }
516
517     String JavaDoc[] positionCode() {
518         String JavaDoc[] code = new String JavaDoc[DIM_COUNT];
519         for (int i=0; i < DIM_COUNT; i++) {
520             LayoutDragger.PositionDef position = bestPositions[i];
521             if (position != null) {
522                 int alignment = position.alignment;
523                 if (position.nextTo) { // adding next to
524
code[i] = "nextTo" + dimensionCode(i) + alignmentCode(alignment); // NOI18N
525
} else { // adding aligned
526
int x = movingSpace.positions[i][alignment];
527                     int posx = position.interval.getCurrentSpace().positions[i][alignment];
528                     if (x == posx) {
529                         code[i] = "align" + dimensionCode(i) + alignmentCode(alignment); // NOI18N
530
} else {
531                         code[i] = "indent"; // NOI18N
532
}
533                 }
534             } else if (snappedToDefaultSize(i)) {
535                 code[i] = "snappedToDefault" + dimensionCode(i); // NOI18N
536
}
537         }
538         if (code[0] == null) {
539             code[0] = code[1];
540             code[1] = null;
541         }
542         if (code[0] == null) {
543             code[0] = isResizing() ? "generalResizing" : "generalPosition"; // NOI18N
544
}
545         return code;
546     }
547
548     private static String JavaDoc dimensionCode(int dim) {
549         return (dim == HORIZONTAL) ? "Horizontal" : "Vertical"; // NOI18N
550
}
551
552     private static String JavaDoc alignmentCode(int alignment) {
553         String JavaDoc code = null;
554         switch (alignment) {
555             case LEADING: code = "Leading"; break; // NOI18N
556
case TRAILING: code = "Trailing"; break; // NOI18N
557
case BASELINE: code = "Baseline"; break; // NOI18N
558
}
559         return code;
560     }
561
562     // -----
563
// finding position in the layout
564

565     /**
566      * For the moving/resizing component represented by 'movingSpace' finds the
567      * most suitable position the component could be placed to. Works in the
568      * dimension defined by 'dimension' field.
569      */

570     private int findBestPosition() {
571         PositionDef best;
572         int snapDistance = LayoutRegion.UNKNOWN;
573
574         if (targetContainer != null) {
575             PositionDef bestNextTo;
576             PositionDef bestAligned;
577             LayoutInterval layoutRoot = targetContainer.getLayoutRoot(dimension);
578             int edges = movingEdges[dimension];
579
580             // [we could probably find the best position directly in scanning, not
581
// separately for each alignment point, choosing the best one additionally here
582
// (only issue is that BASELINE is preferred no matter the distance score)]
583
// 1st go through the layout and find position candidates
584
checkRootForNextTo(layoutRoot, edges);
585             scanLayoutForNextTo(layoutRoot, edges);
586             bestNextTo = chooseBestNextTo();
587
588             if (snapping) { // finding aligned position makes sense only if we can snap to it
589
checkRootForAligned(layoutRoot, edges);
590                 scanLayoutForAligned(layoutRoot, edges);
591                 bestAligned = chooseBestAligned();
592             }
593             else bestAligned = null;
594
595             // 2nd choose the best position
596
if (bestAligned == null) {
597                 best = bestNextTo;
598             }
599             else if (bestNextTo == null) {
600                 best = bestAligned;
601             }
602             else { // both available
603
boolean preferredNextTo = isPreferredNextTo(bestNextTo, bestAligned);
604                 int nextToDst = smallestDistance(findingsNextTo[dimension]);
605                 int alignedDst = smallestDistance(findingsAligned[dimension]);
606                 if (!relatedPositions(bestNextTo, bestAligned)) {
607                     // penalize the aligned position according to distance in the other dimension
608
int alignedOrtDst = Math.abs(LayoutRegion.nonOverlapDistance(
609                         bestAligned.interval.getCurrentSpace(), movingSpace, dimension ^ 1));
610                     alignedDst = getDistanceScore(alignedDst, alignedOrtDst);
611                 }
612                 if (preferredNextTo) {
613                     best = alignedDst*2 <= nextToDst && nextToDst - alignedDst >= SNAP_DISTANCE/2 ?
614                            bestAligned : bestNextTo;
615                 }
616                 else {
617                     best = nextToDst*2 <= alignedDst && alignedDst - nextToDst >= SNAP_DISTANCE/2 ?
618                            bestNextTo : bestAligned;
619                 }
620                 if (best == bestNextTo) {
621                     PositionDef equalAligned = getAlignedEqualToNextTo(bestNextTo);
622                     if (equalAligned != null)
623                         best = equalAligned;
624                 }
625             }
626         }
627         else {
628             best = null;
629         }
630
631         if (snapping) {
632             if (isResizing(dimension)) {
633                 int prefSizeDiff = movingSpace.size(dimension) - sizing[dimension].preferredSize;
634                 int zeroSizeDiff = movingSpace.size(dimension) - sizing[dimension].zeroPreferredSize;
635                 int sizeDiff = Math.abs(prefSizeDiff) <= Math.abs(zeroSizeDiff) ?
636                                prefSizeDiff : zeroSizeDiff;
637                 int absDiff = Math.abs(sizeDiff);
638                 if (absDiff < SNAP_DISTANCE && (best == null || absDiff < Math.abs(best.distance))) {
639                     best = null; // snapping to preferred size has precedence here
640
snapDistance = movingEdges[dimension] == LEADING ? -sizeDiff : sizeDiff;
641                 }
642             }
643             if (best != null) {
644                 snapDistance = best.distance;
645             }
646         }
647
648         bestPositions[dimension] = best;
649         return snapDistance;
650     }
651
652     /**
653      * Checks distance of the leading/trailing edges of the moving component
654      * to the padding positions in the root layout interval. (Distance in root
655      * interval is checked differently than scanLayoutForNextTo method does.)
656      * @param layoutRoot layout interval to be checked
657      * @param alignment determines which edges of the moving space should be
658      * checked - can be LEADING or TRAILING, or ALL_POINTS for both
659      */

660     private void checkRootForNextTo(LayoutInterval layoutRoot, int alignment) {
661         assert alignment == LayoutRegion.ALL_POINTS || alignment == LEADING || alignment == TRAILING;
662
663         if (operation == RESIZING && isValidNextToResizing(layoutRoot, alignment) != 1)
664             return;
665
666         LayoutRegion rootSpace = layoutRoot.getCurrentSpace();
667
668         for (int i = LEADING; i <= TRAILING; i++) {
669             if (alignment == LayoutRegion.ALL_POINTS || alignment == i) {
670                 int distance = LayoutRegion.distance(rootSpace, movingSpace,
671                                                      dimension, i, i);
672                 assert distance != LayoutRegion.UNKNOWN;
673                 if (snapping) {
674                     // PENDING consider the resulting interval when moving more components
675
int pad = findPadding(null, movingComponents[0].getLayoutInterval(dimension),
676                         dimension, i); // [limitation: only one component can be moved]
677
distance += (i == LEADING ? -pad : pad);
678                 }
679
680                 if (!snapping || Math.abs(distance) < SNAP_DISTANCE) {
681                     PositionDef bestSoFar = findingsNextTo[dimension][i];
682                     assert !bestSoFar.isSet();
683                     bestSoFar.interval = layoutRoot;
684                     bestSoFar.alignment = i;
685                     bestSoFar.distance = distance;
686                     bestSoFar.nextTo = true;
687                     bestSoFar.snapped = snapping && Math.abs(distance) < SNAP_DISTANCE;
688                 }
689             }
690         }
691     }
692
693     /**
694      * Recursively scans given interval for suitable sub-intervals next to
695      * which the moving component could be suitably positioned.
696      * @param interval group to scan
697      * @param alignment determines which edges of the moving space should be
698      * checked - can be LEADING or TRAILING, or ALL_POINTS for both
699      * @return position of the group as a whole to the moving space (corresponds
700      * to the edge by which the moving component is attached)
701      */

702     private int scanLayoutForNextTo(LayoutInterval interval, int alignment) {
703         assert alignment == LayoutRegion.ALL_POINTS || alignment == LEADING || alignment == TRAILING;
704
705         int groupOuterAlignment = DEFAULT;
706
707         for (int idx=0, count=interval.getSubIntervalCount(); idx < count; idx++) {
708             LayoutInterval sub = interval.getSubInterval(idx);
709             if (sub.isEmptySpace()) {
710                 continue;
711             }
712
713             if (!orthogonalOverlap(interval, idx))
714                 continue;
715
716             int nextToAlignment = DEFAULT;
717
718             if (sub.isComponent()) {
719                 if (isValidInterval(sub)
720                     && (operation != RESIZING || isValidNextToResizing(sub, alignment) == 1))
721                 { // sub is a component, not being moved/resized
722
nextToAlignment = checkNextToPosition(sub, alignment);
723                 }
724             }
725             else if (sub.isSequential()) {
726                 nextToAlignment = scanLayoutForNextTo(sub, alignment);
727             }
728             else { // parallel group
729
// check if the group is not going to be dissolved (contains moving interval)
730
boolean validForRef = isValidInterval(sub);
731                 int validResizing = validForRef && operation == RESIZING ?
732                                     isValidNextToResizing(sub, alignment) : 1;
733                 int subGroupOuterAlign;
734
735                 if (validResizing != -1 && canGoInsideForNextTo(sub, validForRef)) {
736                     int align = alignment;
737                     for (int i = LEADING; i <= TRAILING; i++) {
738                         if (alignment != LayoutRegion.ALL_POINTS && i != alignment) {
739                             continue; // skip irrelevant alignment
740
}
741                         int insideDst = LayoutRegion.distance(sub.getCurrentSpace(), movingSpace, dimension, i, i)
742                                         * (i == LEADING ? 1 : -1);
743                         if (insideDst < -SNAP_DISTANCE) {
744                             // out of the subgroup - there is nothing "next to" inside
745
if (align == LayoutRegion.ALL_POINTS)
746                                 align = i ^ 1;
747                             else
748                                 align = LayoutRegion.NO_POINT;
749                         }
750                     }
751                     if (align != LayoutRegion.NO_POINT) {
752                         subGroupOuterAlign = scanLayoutForNextTo(sub, align);
753                     }
754                     else subGroupOuterAlign = DEFAULT;
755                 }
756                 else subGroupOuterAlign = alignment;
757
758                 if (validForRef && validResizing == 1 && subGroupOuterAlign != DEFAULT) {
759                     nextToAlignment = checkNextToPosition(sub, subGroupOuterAlign);
760                 }
761             }
762
763             if (nextToAlignment != DEFAULT) {
764                 if (interval.isSequential()) {
765                     // for sequence only first and last intervals can be used for outer alignment
766
if (groupOuterAlignment == DEFAULT && (idx == 0 || idx+1 == count)) {
767                         if (idx != 0) {
768                             nextToAlignment = nextToAlignment == TRAILING ? DEFAULT : LEADING;
769                         }
770                         else if (idx+1 != count) {
771                             nextToAlignment = nextToAlignment == LEADING ? DEFAULT : TRAILING;
772                         }
773                         groupOuterAlignment = nextToAlignment;
774                     }
775                 }
776                 else {
777                     // check if 'sub' is aligned at the corresponding border of the
778
// group - to know if the whole group could not be next to
779
if (LayoutInterval.wantResize(sub)) {
780                         if (nextToAlignment == LayoutRegion.ALL_POINTS) {
781                             groupOuterAlignment = LayoutRegion.ALL_POINTS; // both L and T can happen
782
}
783                         else if (groupOuterAlignment == DEFAULT) {
784                             groupOuterAlignment = nextToAlignment; // "next to" side has 'sub' aligned
785
}
786                     }
787                     else if ((nextToAlignment == LayoutRegion.ALL_POINTS
788                               || (nextToAlignment^1) == sub.getAlignment())
789                              && groupOuterAlignment == DEFAULT)
790                     { // 'sub' aligned at the "next to" side
791
groupOuterAlignment = sub.getAlignment() ^ 1;
792                     }
793                 }
794             }
795         }
796
797         return groupOuterAlignment;
798     }
799
800     /**
801      * Checks if moving interval can be considered as overlapping in the
802      * orthogonal dimension (thus being relevant for next to snapping) with
803      * given interval's sub-interval.
804      */

805     private boolean orthogonalOverlap(LayoutInterval interval, int index) {
806         LayoutInterval sub = interval.getSubInterval(index);
807         LayoutRegion subSpace = sub.getCurrentSpace();
808         if (LayoutRegion.overlap(movingSpace, subSpace, dimension^1, 0))
809             return true;
810
811         if (dimension == VERTICAL) { // there may be some exceptions in vertical dimension
812
if (sub.isSequential()) // sub-sequence to be checked later
813
return true;
814
815             // [note: can do this reliably only for root vertical sequence - with
816
// more sequences LayoutFeeder might ignore it anyway]
817
if (interval.getParent() != null && interval.getParent().getSubIntervalCount() > 1)
818                 return false;
819
820             if (!LayoutRegion.overlap(movingSpace, interval.getCurrentSpace(), dimension, 0))
821                 return true; // not in parallel with any sibling
822
if (interval.isSequential()) {
823                 // check if it is before first or after last
824
if (LayoutRegion.distance(movingSpace, subSpace, dimension, TRAILING, LEADING) > 0) {
825                     // moving interval is located in front of 'sub''
826
while (--index >= 0) {
827                         LayoutInterval li = interval.getSubInterval(index);
828                         if (!li.isEmptySpace() && isValidInterval(li))
829                             break;
830                     }
831                     if (index < 0) // 'sub' is the first interval
832
return true;
833                 }
834                 else if (LayoutRegion.distance(subSpace, movingSpace, dimension, TRAILING, LEADING) > 0) {
835                     // moving interval is locate behind 'sub''
836
while (++index < interval.getSubIntervalCount()) {
837                         LayoutInterval li = interval.getSubInterval(index);
838                         if (!li.isEmptySpace() && isValidInterval(li))
839                             break;
840                     }
841                     if (index == interval.getSubIntervalCount()) // 'sub' is the last interval
842
return true;
843                 }
844             }
845         }
846
847         return false;
848     }
849
850     private int checkNextToPosition(LayoutInterval sub, int alignment) {
851         int nextToAlignment = DEFAULT;
852         LayoutRegion subSpace = sub.getCurrentSpace();
853         for (int i = LEADING; i <= TRAILING; i++) {
854             if (alignment != LayoutRegion.ALL_POINTS && i != alignment)
855                 continue; // skip irrelevant edge
856

857             boolean validDistance;
858             int distance = LayoutRegion.distance(subSpace, movingSpace,
859                                                  dimension, i^1, i);
860             if (snapping) {
861                 // PENDING consider the resulting interval when moving more components
862
int pad = findPadding(sub, movingComponents[0].getLayoutInterval(dimension),
863                     dimension, i);// [limitation: only one component can be moved]
864
distance += (i == LEADING ? -pad : pad);
865                 validDistance = Math.abs(distance) < SNAP_DISTANCE;
866             }
867             else {
868                 validDistance = (i == LEADING ? distance > 0 : distance < 0);
869             }
870             if (validDistance) {
871                 nextToAlignment = nextToAlignment == DEFAULT ? i : LayoutRegion.ALL_POINTS;
872
873                 PositionDef bestSoFar = findingsNextTo[dimension][i];
874                 if (!bestSoFar.isSet() || compareNextToPosition(sub, distance, bestSoFar) > 0) {
875                     bestSoFar.interval = sub;
876                     bestSoFar.alignment = i;
877                     bestSoFar.distance = distance;
878                     bestSoFar.nextTo = true;
879                     bestSoFar.snapped = snapping;
880                 }
881             }
882         }
883         return nextToAlignment;
884     }
885
886     /**
887      * Compares given "next to" position with the best position found so far.
888      * Cares about the visual aspect only, not the logical structure.
889      * @return int as result of comparison: 1 - new position is better
890      * -1 - old position is better
891      * 0 - the positions are equal
892      */

893     private int compareNextToPosition(LayoutInterval newInterval, int newDistance,
894                                       PositionDef bestSoFar)
895     {
896         if (!bestSoFar.isSet())
897             return 1; // best not set yet
898

899         LayoutRegion newSpace = newInterval.getCurrentSpace();
900         LayoutRegion oldSpace = bestSoFar.interval.getCurrentSpace();
901         int oldDistance = Math.abs(bestSoFar.distance);
902
903         // 1st compare the direct distance
904
if (newDistance < 0)
905             newDistance = -newDistance;
906         if (newDistance != oldDistance) {
907             return newDistance < oldDistance ? 1 : -1;
908         }
909
910         if (newInterval.isParentOf(bestSoFar.interval)) {
911             return 1;
912         }
913
914         // 2nd compare the orthogonal distance
915
int newOrtDst = Math.abs(
916                 LayoutRegion.minDistance(newSpace, movingSpace, dimension ^ 1));
917         int oldOrtDst = Math.abs(
918                 LayoutRegion.minDistance(oldSpace, movingSpace, dimension ^ 1));
919         if (newOrtDst != oldOrtDst) {
920             return newOrtDst < oldOrtDst ? 1 : -1;
921         }
922
923         return 0;
924     }
925
926     private static boolean canGoInsideForNextTo(LayoutInterval subGroup, boolean valid) {
927         // can't go inside a group if it has "closed" group alignment (center
928
// or baseline) - only can if the group is not valid (i.e. going to be
929
// removed so just one not-aligned interval might remain)
930
return subGroup.isSequential()
931                || (subGroup.isParallel()
932                    && (!valid
933                        || (subGroup.getGroupAlignment() != CENTER
934                            && subGroup.getGroupAlignment() != BASELINE)));
935     }
936
937     /**
938      * Checks distance of the leading/trailing edges of the moving component
939      * to the border positions in the root layout interval. (Distance in root
940      * interval is checked differently than scanLayoutForAligned method does.)
941      * @param layoutRoot layout interval to be checked
942      * @param alignment determines which edges of the moving space should be
943      * checked - can be LEADING or TRAILING, or ALL_POINTS for both
944      */

945     private void checkRootForAligned(LayoutInterval layoutRoot, int alignment) {
946         assert alignment == LayoutRegion.ALL_POINTS || alignment == LEADING || alignment == TRAILING;
947
948         if (operation == RESIZING && !isValidAlignedResizing(layoutRoot, alignment))
949             return;
950
951         LayoutRegion rootSpace = layoutRoot.getCurrentSpace();
952
953         for (int i = LEADING; i <= TRAILING; i++) {
954             if (alignment == LayoutRegion.ALL_POINTS || alignment == i) {
955                 int distance = LayoutRegion.distance(rootSpace, movingSpace,
956                                                      dimension, i, i);
957                 if (distance != LayoutRegion.UNKNOWN
958                     && Math.abs(distance) < SNAP_DISTANCE)
959                 { // compare the actual distance with the best one
960
PositionDef bestSoFar = findingsAligned[dimension][i];
961                     assert !bestSoFar.isSet();
962                     bestSoFar.interval = layoutRoot;
963                     bestSoFar.alignment = i;
964                     bestSoFar.distance = distance;
965                     bestSoFar.nextTo = false;
966                     bestSoFar.snapped = true;
967                 }
968             }
969         }
970     }
971
972     /**
973      * Recursively scans given interval for suitable sub-intervals which the
974      * moving component could be aligned with.
975      * @param alignment determines which edges of the moving space should be
976      * checked - can be LEADING or TRAILING, or ALL_POINTS for both
977      */

978     private void scanLayoutForAligned(LayoutInterval interval, int alignment) {
979         assert alignment == LayoutRegion.ALL_POINTS || alignment == LEADING || alignment == TRAILING;
980
981         Iterator it = interval.getSubIntervals();
982         while (it.hasNext()) {
983             LayoutInterval sub = (LayoutInterval) it.next();
984             if (sub.isEmptySpace())
985                 continue;
986
987             if (sub.isComponent()
988                 && isValidInterval(sub)
989                 && (operation != RESIZING || isValidAlignedResizing(sub, alignment)))
990             { // check distance of all alignment points of the moving
991
// component to the examined interval space
992
for (int i=0; i < LayoutRegion.POINT_COUNT[dimension]; i++) {
993                     if (alignment == LayoutRegion.ALL_POINTS || i == alignment) {
994                         int indentedDst = getIndentedDistance(sub, i);
995                         int directDst = getDirectDistance(sub, i);
996                         int distance = Math.abs(indentedDst) < Math.abs(directDst) ?
997                                        indentedDst : directDst;
998                         if (checkAlignedDistance(distance, sub.getCurrentSpace(), i)) {
999                             // compare the actual distance with the best one
1000
PositionDef bestSoFar = findingsAligned[dimension][i];
1001                            if (compareAlignedPosition(sub, distance, bestSoFar) >= 0) {
1002                                // >= 0 means we naturally prefer later components
1003
bestSoFar.interval = sub;
1004                                bestSoFar.alignment = i;
1005                                bestSoFar.distance = distance;
1006                                bestSoFar.nextTo = false;
1007                                bestSoFar.snapped = true;
1008                            }
1009                        }
1010                    }
1011                }
1012            }
1013
1014            if (sub.getSubIntervalCount() > 0
1015                && LayoutRegion.overlap(sub.getCurrentSpace(), movingSpace, dimension, SNAP_DISTANCE/2))
1016            { // the group overlaps with the moving space so it makes sense to dive into it
1017
scanLayoutForAligned(sub, alignment);
1018            }
1019        }
1020    }
1021
1022    private int getIndentedDistance(LayoutInterval interval, int alignment) {
1023        if (dimension == HORIZONTAL && alignment == LEADING) {
1024            // indented position is limited to horizontal dimension, left alignment
1025
LayoutRegion examinedSpace = interval.getCurrentSpace();
1026            int verticalDst = LayoutRegion.distance(examinedSpace, movingSpace,
1027                                                    VERTICAL, TRAILING, LEADING);
1028            if (verticalDst >= 0 && verticalDst < 2 * SNAP_DISTANCE) {
1029                // PENDING does it have a sense to generalize this for multiselection?
1030
int indent = findIndent(interval.getComponent(), movingComponents[0],
1031                                        dimension, alignment);
1032                if (indent > 0) {
1033                    return LayoutRegion.distance(examinedSpace, movingSpace,
1034                                                 dimension, alignment, alignment)
1035                           - indent;
1036                }
1037            }
1038        }
1039        return Integer.MAX_VALUE;
1040    }
1041
1042    private int getDirectDistance(LayoutInterval interval, int alignment) {
1043        return checkValidAlignment(interval, alignment) ?
1044               LayoutRegion.distance(interval.getCurrentSpace(), movingSpace,
1045                                     dimension, alignment, alignment) :
1046               Integer.MAX_VALUE;
1047    }
1048
1049    private boolean checkValidAlignment(LayoutInterval interval, int alignment) {
1050        int presentAlign = interval.getAlignment();
1051        // check if the interval is not the last one to remain in parallel group
1052
if (presentAlign != DEFAULT) {
1053            boolean lastOne = true;
1054            Iterator it = interval.getParent().getSubIntervals();
1055            while (it.hasNext()) {
1056                LayoutInterval li = (LayoutInterval) it.next();
1057                if (li != interval && isValidInterval(li)) {
1058                    lastOne = false;
1059                    break;
1060                }
1061            }
1062            if (lastOne) {
1063                presentAlign = DEFAULT;
1064            }
1065        }
1066
1067        if (alignment == LEADING || alignment == TRAILING) {
1068            // leading and trailing can't align with "closed" alignments
1069
if (presentAlign == CENTER || presentAlign == BASELINE)
1070                return false;
1071        }
1072        else if (alignment == CENTER) {
1073            // center alignment is allowed only with already centered intervals
1074
// (center alignment needs to be set up explicitly first)
1075
if (presentAlign != CENTER)
1076                return false;
1077        }
1078        else if (alignment == BASELINE) {
1079            // baseline can't go with other "closed" alignments
1080
if (presentAlign == CENTER)
1081                return false;
1082        }
1083        return true;
1084    }
1085
1086    private boolean checkAlignedDistance(int distance, LayoutRegion examinedSpace, int alignment) {
1087        if (distance != LayoutRegion.UNKNOWN && Math.abs(distance) < SNAP_DISTANCE) {
1088            // check if there is nothing in the way along the line of aligned edges
1089
int x1, x2, y1, y2;
1090            int indent = movingSpace.positions[dimension][alignment]
1091                         - examinedSpace.positions[dimension][alignment] - distance;
1092            if (indent == 0) {
1093                x1 = examinedSpace.positions[dimension][alignment] - SNAP_DISTANCE/2;
1094                x2 = examinedSpace.positions[dimension][alignment] + SNAP_DISTANCE/2;
1095                y2 = movingSpace.positions[dimension^1][LEADING];
1096            }
1097            else {
1098                x1 = examinedSpace.positions[dimension][alignment];
1099                x2 = x1 + indent + SNAP_DISTANCE/2;
1100                y2 = movingSpace.positions[dimension^1][TRAILING];
1101                // note indent is not offered at all by getIndentedDistance if
1102
// the vertical position "does not match"
1103
}
1104            y1 = examinedSpace.positions[dimension^1][TRAILING];
1105            if (y1 > y2) {
1106                y1 = movingSpace.positions[dimension^1][TRAILING];
1107                y2 = examinedSpace.positions[dimension^1][LEADING];
1108                if (y1 > y2) { // orthogonally overlaps - so can see it
1109
return true;
1110                }
1111            }
1112            return !contentOverlap(targetContainer.getLayoutRoot(dimension),
1113                                   x1, x2, y1, y2, dimension);
1114        }
1115        return false;
1116    }
1117
1118    private boolean contentOverlap(LayoutInterval group, int x1, int x2, int y1, int y2, int dim) {
1119        int[][] groupPos = group.getCurrentSpace().positions;
1120        for (int i=0, n=group.getSubIntervalCount(); i < n; i++) {
1121            LayoutInterval li = group.getSubInterval(i);
1122            int _x1, _x2, _y1, _y2;
1123            if (li.isEmptySpace()) {
1124                if (group.isParallel())
1125                    continue;
1126                _x1 = i == 0 ? groupPos[dim][LEADING] :
1127                               group.getSubInterval(i-1).getCurrentSpace().positions[dim][TRAILING];
1128                _x2 = i+1 == n ? groupPos[dim][TRAILING] :
1129                                 group.getSubInterval(i+1).getCurrentSpace().positions[dim][LEADING];
1130                _y1 = groupPos[dim^1][LEADING];
1131                _y2 = groupPos[dim^1][TRAILING];
1132                if (_y1 < y1) {
1133                    _y2 = _y1;
1134                }
1135                else if (_y2 > y2) {
1136                    _y1 = _y2;
1137                }
1138            }
1139            else {
1140                int[][] positions = li.getCurrentSpace().positions;
1141                _x1 = positions[dim][LEADING];
1142                _x2 = positions[dim][TRAILING];
1143                _y1 = positions[dim^1][LEADING];
1144                _y2 = positions[dim^1][TRAILING];
1145            }
1146
1147            if (_x1 < x2 && _x2 > x1 && _y1 < y2 && _y2 > y1) { // overlap
1148
if (li.isComponent()) {
1149                    if (isValidInterval(li))
1150                        return true;
1151                }
1152                else if (li.isEmptySpace()) {
1153                    if (i > 0 && i+1 < n // first/last space is not in the way
1154
&& (li.getMinimumSize() == NOT_EXPLICITLY_DEFINED || li.getMinimumSize() == USE_PREFERRED_SIZE)
1155                        && li.getPreferredSize() == NOT_EXPLICITLY_DEFINED
1156                        && (li.getMaximumSize() == NOT_EXPLICITLY_DEFINED || li.getMaximumSize() == USE_PREFERRED_SIZE))
1157                    { // preferred padding might be in the way
1158
LayoutInterval prev = group.getSubInterval(i-1);
1159                        LayoutInterval next = group.getSubInterval(i+1);
1160                        if ((!prev.isComponent() || isValidInterval(prev))
1161                            && (!next.isComponent() || isValidInterval(next)))
1162                        { // preferred padding between valid intervals (i.e. not next to the moving component itself)
1163
return true;
1164                        }
1165                    }
1166                    if (_x1 >= x1 && _x2 <= x2)
1167                        return false; // goes over a gap in a sequence - so no overlap
1168
}
1169                else if (li.isGroup() && contentOverlap(li, x1, x2, y1, y2, dim)) {
1170                    return true;
1171                }
1172            }
1173        }
1174        return false;
1175    }
1176
1177    /**
1178     * @return int as result of comparison: 1 - new position is better
1179     * -1 - old position is better
1180     * 0 - the positions are equal
1181     */

1182    private int compareAlignedPosition(LayoutInterval newInterval,
1183                                       int newDistance,
1184                                       PositionDef bestSoFar)
1185    {
1186        if (!bestSoFar.isSet())
1187            return 1; // best not set yet
1188

1189        // compute direct distance
1190
if (newDistance < 0)
1191            newDistance = -newDistance;
1192        int oldDistance = Math.abs(bestSoFar.distance);
1193
1194        if (newInterval.getParent() == null) {
1195            return newDistance < oldDistance ? 1 : -1;
1196        }
1197        if (bestSoFar.interval.getParent() == null) {
1198            return oldDistance < newDistance ? -1 : 1;
1199        }
1200
1201        // compute orthogonal distance
1202
LayoutRegion newSpace = newInterval.getCurrentSpace();
1203        LayoutRegion oldSpace = bestSoFar.interval.getCurrentSpace();
1204        int newOrtDst = Math.abs(
1205                LayoutRegion.nonOverlapDistance(newSpace, movingSpace, dimension ^ 1));
1206        int oldOrtDst = Math.abs(
1207                LayoutRegion.nonOverlapDistance(oldSpace, movingSpace, dimension ^ 1));
1208
1209        // compute score
1210
int newScore = getDistanceScore(newDistance, newOrtDst);
1211        int oldScore = getDistanceScore(oldDistance, oldOrtDst);
1212
1213        if (newScore != oldScore) {
1214            return newScore < oldScore ? 1 : -1;
1215        }
1216        return 0;
1217    }
1218
1219    private static int getDistanceScore(int directDistance, int ortDistance) {
1220        // orthogonal distance >= SNAP_DISTANCE is penalized
1221
return directDistance + ortDistance / SNAP_DISTANCE;
1222    }
1223
1224    private PositionDef chooseBestNextTo() {
1225        PositionDef[] positions = findingsNextTo[dimension];
1226        PositionDef bestPos = null;
1227        int bestDst = 0;
1228        for (int i=0; i < positions.length; i++) {
1229            PositionDef pos = positions[i];
1230            if (pos.isSet()) {
1231                int dst = Math.abs(pos.distance);
1232                if (bestPos == null || dst < bestDst
1233                        || (dst == bestDst && moveDirection[dimension] == i)) {
1234                    bestPos = pos;
1235                    bestDst = dst;
1236                }
1237            }
1238        }
1239        return bestPos;
1240    }
1241
1242    private PositionDef chooseBestAligned() {
1243        PositionDef[] positions = findingsAligned[dimension];
1244        PositionDef bestPos = null;
1245        for (int i=positions.length-1; i >= 0; i--) {
1246            PositionDef pos = positions[i];
1247            if (pos.isSet()) {
1248                if (i == BASELINE || i == CENTER) {
1249                    return pos;
1250                }
1251                if (bestPos == null) {
1252                    bestPos = pos;
1253                }
1254                else {
1255                    int c = compareAlignedPosition(pos.interval, pos.distance, bestPos);
1256                    if (c == 0) {
1257                        c = compareAlignedDirection(pos, bestPos);
1258                    }
1259                    if (c > 0) {// || (c == 0 && moveDirection[dimension] != bestPos.alignment)) {
1260
bestPos = pos;
1261                    }
1262                }
1263            }
1264        }
1265        return bestPos;
1266    }
1267
1268    private int compareAlignedDirection(PositionDef pos1, PositionDef pos2) {
1269        boolean p1 = isSuitableAlignment(pos1);
1270        boolean p2 = isSuitableAlignment(pos2);
1271        if (p1 == p2) {
1272            p1 = (pos1.alignment == moveDirection[dimension]);
1273            p2 = (pos2.alignment == moveDirection[dimension]);
1274            if (p1 == p2)
1275                return 0;
1276        }
1277        return p1 ? 1 : -1;
1278    }
1279
1280    private static boolean isSuitableAlignment(PositionDef pos) {
1281        assert pos.alignment == LEADING || pos.alignment == TRAILING;
1282        LayoutInterval parParent = LayoutInterval.getFirstParent(pos.interval, PARALLEL);
1283        return LayoutInterval.isAlignedAtBorder(pos.interval, parParent, pos.alignment)
1284               || !LayoutInterval.isAlignedAtBorder(pos.interval, parParent, pos.alignment^1);
1285    }
1286
1287    private static int smallestDistance(PositionDef[] positions) {
1288        int bestDst = -1;
1289        for (int i=0; i < positions.length; i++) {
1290            PositionDef pos = positions[i];
1291            if (pos.isSet()) {
1292                int dst = Math.abs(pos.distance);
1293                if (bestDst < 0 || dst < bestDst) {
1294                    bestDst = dst;
1295                }
1296            }
1297        }
1298        return bestDst;
1299    }
1300
1301    private boolean isPreferredNextTo(PositionDef bestNextTo, PositionDef bestAligned) {
1302        if (bestNextTo != null && bestAligned != null) {
1303            if (operation == RESIZING) {
1304                // prefer aligned resizing if already aligned at the other edge
1305
// otherwise prefer next to
1306
LayoutInterval resizing = movingComponents[0].getLayoutInterval(dimension);
1307                int fixedEdge = movingEdges[dimension] ^ 1;
1308                if (bestAligned.interval.isParentOf(resizing)) {
1309                    if (LayoutInterval.isAlignedAtBorder(resizing, bestAligned.interval, fixedEdge)) {
1310                        return false;
1311                    }
1312                }
1313                else {
1314                    LayoutInterval commonParent = LayoutInterval.getCommonParent(resizing, bestAligned.interval);
1315                    if (LayoutInterval.isAlignedAtBorder(resizing, commonParent, fixedEdge)
1316                        && LayoutInterval.isAlignedAtBorder(bestAligned.interval, commonParent, fixedEdge))
1317                        return false;
1318                }
1319                return true;
1320            }
1321        }
1322        return dimension == HORIZONTAL;
1323    }
1324
1325    private static boolean relatedPositions(PositionDef nextTo, PositionDef aligned) {
1326        if (nextTo.interval == null || aligned.interval == null)
1327            return false;
1328
1329        LayoutInterval neighbor = LayoutInterval.getNeighbor(
1330                aligned.interval, nextTo.alignment, true, true, false);
1331        return neighbor == nextTo.interval
1332               || (neighbor == null && nextTo.interval.getParent() == null);
1333    }
1334
1335    private PositionDef getAlignedEqualToNextTo(PositionDef bestNextTo) {
1336        if (operation == RESIZING || !bestNextTo.snapped)
1337            return null;
1338
1339        int alignment = bestNextTo.alignment;
1340        PositionDef alignedAlternative = findingsAligned[dimension][alignment];
1341        if (alignedAlternative != null && alignedAlternative.distance == bestNextTo.distance) {
1342            // choose aligned position if its interval is just next to
1343
// bestNextTo.interval (both positions would lead to same result)
1344
LayoutInterval neighbor = LayoutInterval.getNeighbor(
1345                             alignedAlternative.interval, alignment, true, true, false);
1346            if (neighbor != null
1347                    && (neighbor == bestNextTo.interval || neighbor.isParentOf(bestNextTo.interval)))
1348                return alignedAlternative;
1349        }
1350        return null;
1351    }
1352
1353    /**
1354     * Checks whether given interval can be used for the moving interval to
1355     * relate to. Returns false for other moving intervals, or for groups that
1356     * won't survive removal of the moving intervals from their original
1357     * positions.
1358     */

1359    private boolean isValidInterval(LayoutInterval interval) {
1360        if (operation == ADDING) {
1361            return true;
1362        }
1363
1364        if (interval.isGroup()) {
1365            // as the moving intervals are going to be removed first, a valid
1366
// group must contain at least two other intervals - otherwise it
1367
// is dissolved before the moving intervals are re-added
1368
int count = 0;
1369            Iterator it = interval.getSubIntervals();
1370            while (it.hasNext()) {
1371                LayoutInterval li = (LayoutInterval) it.next();
1372                if ((!li.isEmptySpace() || interval.isSequential())
1373                    && isValidInterval(li))
1374                { // filling gap (in parallel group) does not count
1375
count++;
1376                    if (count > 1)
1377                        return true;
1378                }
1379            }
1380            return false;
1381        }
1382        else {
1383            for (int i=0; i < movingComponents.length; i++) {
1384                if (movingComponents[i].getLayoutInterval(dimension) == interval) {
1385                    return false;
1386                }
1387            }
1388            return true;
1389        }
1390    }
1391
1392    /**
1393     * @return 1 - is valid for next to resizing
1394     * 0 - not valid, but some sub-interval could be
1395     * -1 - not valid, even no sub-interval
1396     */

1397    private int isValidNextToResizing(LayoutInterval interval, int alignment) {
1398        assert alignment == LEADING || alignment == TRAILING;
1399        LayoutInterval resizing = movingComponents[0].getLayoutInterval(dimension);
1400        if (interval.isParentOf(resizing)) {
1401            return interval.getParent() == null && clearWayToParent(resizing, interval, dimension, alignment)
1402                   && (!toDeepToMerge(resizing, interval, alignment)
1403                       || LayoutInterval.getNeighbor(resizing, alignment, true, true, false) == null) ?
1404                   1 : 0;
1405        }
1406
1407        LayoutInterval commonParent = LayoutInterval.getCommonParent(interval, resizing);
1408        if (commonParent.isSequential()) {
1409            if (toDeepToMerge(resizing, commonParent, alignment)
1410                && LayoutInterval.getNeighbor(resizing, alignment, true, true, false) != interval)
1411                return -1;
1412
1413            resizing = getClearWayToParent(resizing, commonParent, dimension, alignment);
1414            if (resizing == null)
1415                return -1;
1416
1417            while (interval.getParent() != commonParent) {
1418                interval = interval.getParent();
1419            }
1420
1421            int startIndex = commonParent.indexOf(alignment == LEADING ? interval : resizing) + 1;
1422            int endIndex = commonParent.indexOf(alignment == LEADING ? resizing : interval) - 1;
1423            return startIndex <= endIndex
1424                   && !LayoutUtils.contentOverlap(movingSpace, commonParent, startIndex, endIndex, dimension^1) ?
1425                   1 : -1;
1426        }
1427        return -1;
1428    }
1429
1430    private boolean isValidAlignedResizing(LayoutInterval interval, int alignment) {
1431        // the examined interval position must be reachable with positive size
1432
// of the resizing interval
1433
int dst = LayoutRegion.distance(movingSpace, interval.getCurrentSpace(),
1434                                        dimension, alignment^1, alignment);
1435        if ((alignment == LEADING && dst <= 0) || (alignment == TRAILING && dst >= 0)) {
1436            // now exclude resizing across an interval in the same sequence
1437
LayoutInterval resizing = movingComponents[0].getLayoutInterval(dimension);
1438            if (interval.isParentOf(resizing)) {
1439                if (!clearWayToParent(resizing, interval, dimension, alignment))
1440                    return false;
1441                if (toDeepToMerge(resizing, interval, alignment)) {
1442                    LayoutInterval neighbor = LayoutInterval.getNeighbor(resizing, alignment, true, true, false);
1443                    if (neighbor != null && interval.isParentOf(neighbor))
1444                        return false;
1445                }
1446                return true;
1447            }
1448            else {
1449                LayoutInterval commonParent = LayoutInterval.getCommonParent(interval, resizing);
1450                if (commonParent.isParallel())
1451                    return true;
1452
1453                if (toDeepToMerge(resizing, commonParent, alignment))
1454                    return false;
1455
1456                // if in a sequence, aligning is possible if the intervals don't overlap orthogonally
1457
resizing = getClearWayToParent(resizing, commonParent, dimension, alignment);
1458                if (resizing == null)
1459                    return false;
1460
1461                while (interval.getParent() != commonParent) {
1462                    interval = interval.getParent();
1463                }
1464
1465                int startIndex, endIndex;
1466                if (alignment == LEADING) {
1467                    startIndex = commonParent.indexOf(interval);
1468                    endIndex = commonParent.indexOf(resizing) - 1;
1469                }
1470                else { // TRAILING
1471
startIndex = commonParent.indexOf(resizing) + 1;
1472                    endIndex = commonParent.indexOf(interval);
1473                }
1474                return startIndex <= endIndex
1475                       && !LayoutUtils.contentOverlap(movingSpace, commonParent, startIndex, endIndex, dimension^1);
1476            }
1477        }
1478        return false;
1479    }
1480
1481    private static boolean clearWayToParent(LayoutInterval interval, LayoutInterval parent, int dimension, int alignment) {
1482        return getClearWayToParent(interval, parent, dimension, alignment) != null;
1483    }
1484
1485    private static LayoutInterval getClearWayToParent(LayoutInterval interval,
1486            LayoutInterval topParent, int dimension, int alignment)
1487    {
1488        LayoutRegion space = interval.getCurrentSpace();
1489        LayoutInterval parent = interval.getParent();
1490        while (parent != topParent) {
1491            if (parent.isSequential()) {
1492                int startIndex, endIndex;
1493                if (alignment == LEADING) {
1494                    startIndex = 0;
1495                    endIndex = parent.indexOf(interval) - 1;
1496                }
1497                else {
1498                    startIndex = parent.indexOf(interval) + 1;
1499                    endIndex = parent.getSubIntervalCount() - 1;
1500                }
1501                if (startIndex <= endIndex
1502                    && LayoutUtils.contentOverlap(space, parent, startIndex, endIndex, dimension^1))
1503                { // there is a sub-interval in the way
1504
return null;
1505                }
1506            }
1507            interval = parent;
1508            parent = interval.getParent();
1509        }
1510        return interval;
1511    }
1512
1513    // When resizing we can't attach the component edge to a component that is
1514
// more than one level of unaligned parallel groups away.
1515
// See LayoutFeeder.accommodateSizeInSequence.
1516
private static boolean toDeepToMerge(LayoutInterval interval, LayoutInterval parent, int alignment) {
1517        int level = 0;
1518        int a = DEFAULT;
1519        LayoutInterval prev = null;
1520        LayoutInterval p = interval.getParent();
1521        while (p != parent) {
1522            if (p.isParallel()) {
1523                if (a == DEFAULT) {
1524                    a = interval.getAlignment();
1525                    if (a != alignment)
1526                        level++;
1527                }
1528                else if (!LayoutInterval.isAlignedAtBorder(prev, p, a)) {
1529                    level++;
1530                    if (level > 1)
1531                        return true;
1532                }
1533                prev = p;
1534            }
1535            interval = p;
1536            p = interval.getParent();
1537        }
1538        return level >= 2;
1539    }
1540
1541    /**
1542     * Finds value of padding between a moving component and given layout
1543     * interval.
1544     * @param alignment edge of the component
1545     */

1546    int findPadding(LayoutInterval interval, LayoutInterval moving, int dimension, int alignment) {
1547        int oppAlignment = (alignment == LEADING) ? TRAILING : LEADING;
1548        List movingComps = LayoutUtils.edgeSubComponents(moving, alignment);
1549        List fixedComps = LayoutUtils.edgeSubComponents(interval, oppAlignment);
1550        List sources = (alignment == LEADING) ? fixedComps : movingComps;
1551        List targets = (alignment == LEADING) ? movingComps : fixedComps;
1552        Map map = new HashMap();
1553        for (int i=0; i<movingComponents.length; i++) {
1554            map.put(movingComponents[i].getId(), movingBounds[i]);
1555        }
1556        return LayoutUtils.getSizeOfDefaultGap(sources, targets, visualMapper,
1557                                               targetContainer.getId(), map);
1558    }
1559
1560    /**
1561     * @return <= 0 if no indentation is recommended for given component pair
1562     */

1563    int findIndent(LayoutComponent mainComp, LayoutComponent indentedComp,
1564                          int dimension, int alignment)
1565    {
1566        return visualMapper.getPreferredPadding(mainComp.getId(), indentedComp.getId(),
1567            dimension, alignment, VisualMapper.INDENT);
1568    }
1569
1570    // -----
1571
// innerclasses
1572

1573    static class PositionDef {
1574        private int distance = LayoutRegion.UNKNOWN;
1575
1576        LayoutInterval interval;
1577        int alignment = LayoutRegion.NO_POINT;
1578        boolean nextTo;
1579        boolean snapped;
1580// boolean padding;
1581

1582        private void reset() {
1583            distance = LayoutRegion.UNKNOWN;
1584            interval = null;
1585            alignment = LayoutRegion.NO_POINT;
1586        }
1587
1588        private boolean isSet() {
1589            return interval != null;
1590        }
1591        
1592        public String JavaDoc toString() {
1593            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1594            sb.append("distance=").append(distance); // NOI18N
1595
sb.append(",alignment=").append(alignment); // NOI18N
1596
sb.append(",nextTo=").append(nextTo); // NOI18N
1597
sb.append(",snapped=").append(snapped); // NOI18N
1598
return sb.toString();
1599        }
1600    }
1601
1602    static class SizeDef {
1603        private int originalSize;
1604        private int preferredSize;
1605        private int zeroPreferredSize; // size of container if resizing gap goes to zero
1606
private LayoutInterval resizingGap; // inside resizing container
1607
private int originalGapSize;
1608        private int preferredGapSize;
1609
1610        LayoutInterval getResizingGap() {
1611            return resizingGap;
1612        }
1613
1614        int getResizingGapSize(int currentSize) {
1615            if (resizingGap == null)
1616                return LayoutRegion.UNKNOWN;
1617            if (currentSize == zeroPreferredSize)
1618                return 0;
1619            int gapSize = originalGapSize - originalSize + currentSize;
1620            return currentSize == preferredSize || gapSize < 0 ? //preferredGapSize
1621
NOT_EXPLICITLY_DEFINED : gapSize;
1622        }
1623    }
1624}
1625
Popular Tags