KickJava   Java API By Example, From Geeks To Geeks.

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


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.*;
23 import java.util.*;
24 import java.util.List JavaDoc;
25 import javax.swing.undo.*;
26
27
28 /**
29  * This class manages layout data of a form. Specifically it:
30  * - provides entry points for exploring the layout,
31  * - allows to add/remove layout intervals and components,
32  * - allows to listen on changes,
33  * - manages an undo/redo queue for the layout, provides undo/redo marks,
34  * and allows to perform undo/redo to given mark.
35  *
36  * @author Tomas Pavek
37  */

38
39 public class LayoutModel implements LayoutConstants {
40
41     // map String component Id -> LayoutComponent instance
42
private Map idToComponents = new HashMap();
43
44     // list of listeners registered on LayoutModel
45
private ArrayList listeners;
46
47     // layout changes recording and undo/redo
48
private boolean recordingChanges = true;
49     private boolean undoRedoInProgress;
50     private int changeMark;
51     private int oldestMark;
52     private int changeCountHardLimit = 10000;
53     private Map undoMap = new HashMap(500);
54     private Map redoMap = new HashMap(100);
55     private LayoutUndoableEdit lastUndoableEdit;
56
57     // remembers whether the model was corrected/upgraded during loading
58
private boolean corrected;
59
60     // -----
61

62     /**
63      * Basic mapping method. Returns LayoutComponent for given Id.
64      * @return LayoutComponent of given Id, null if there is no such component
65      * registered in the model
66      */

67     public LayoutComponent getLayoutComponent(String JavaDoc compId) {
68         return (LayoutComponent) idToComponents.get(compId);
69     }
70
71     public void addRootComponent(LayoutComponent comp) {
72         addComponent(comp, null, -1);
73     }
74
75     public void removeComponent(String JavaDoc compId, boolean fromModel) {
76         LayoutComponent comp = getLayoutComponent(compId);
77         if (comp != null)
78             removeComponentAndIntervals(comp, fromModel);
79     }
80
81     /**
82      * @return false if the component does not exist in the layout model
83      */

84     public boolean changeComponentToContainer(String JavaDoc componentId) {
85         LayoutComponent component = getLayoutComponent(componentId);
86         if (component != null) {
87             setLayoutContainer(component, true);
88             return true;
89         }
90         return false;
91     }
92
93     /**
94      * Changes a container to a component (that cannot contain sub-components).
95      * All its current sub-components are removed. Those not being containers
96      * are also removed from the model - containers remain in model.
97      * @return false if the component does not exist in the layout model
98      */

99     public boolean changeContainerToComponent(String JavaDoc componentId) {
100         LayoutComponent component = getLayoutComponent(componentId);
101         if (component == null) {
102             return false;
103         }
104         for (int i=component.getSubComponentCount()-1; i>=0; i--) {
105             LayoutComponent sub = component.getSubComponent(i);
106             removeComponentAndIntervals(sub, !sub.isLayoutContainer());
107         }
108         if (component.getParent() == null) { // non-container without a parent
109
removeComponent(component, true);
110         }
111         setLayoutContainer(component, false); // this removes everything from the component
112
return true;
113     }
114
115     // -----
116

117     void registerComponent(LayoutComponent comp, boolean recursive) {
118         registerComponentImpl(comp);
119         if (recursive && comp.isLayoutContainer()) {
120             for (Iterator it=comp.getSubcomponents(); it.hasNext(); ) {
121                 registerComponent((LayoutComponent)it.next(), recursive);
122             }
123         }
124     }
125     
126     void registerComponentImpl(LayoutComponent comp) {
127         Object JavaDoc lc = idToComponents.put(comp.getId(), comp);
128
129         if (lc != comp) {
130             // record undo/redo and fire event
131
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.COMPONENT_REGISTERED);
132             ev.setComponent(comp);
133             addChange(ev);
134             fireEvent(ev);
135         } // else noop => don't need change event
136
}
137
138     void unregisterComponent(LayoutComponent comp, boolean recursive) {
139         if (recursive && comp.isLayoutContainer()) {
140             for (Iterator it=comp.getSubcomponents(); it.hasNext(); ) {
141                 unregisterComponent((LayoutComponent)it.next(), recursive);
142             }
143         }
144         removeComponentFromLinkSizedGroup(comp, HORIZONTAL);
145         removeComponentFromLinkSizedGroup(comp, VERTICAL);
146         unregisterComponentImpl(comp);
147     }
148
149     void unregisterComponentImpl(LayoutComponent comp) {
150         Object JavaDoc lc = idToComponents.remove(comp.getId());
151
152         if (lc != null) {
153             // record undo/redo and fire event
154
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.COMPONENT_UNREGISTERED);
155             ev.setComponent(comp);
156             addChange(ev);
157             fireEvent(ev);
158         } // else noop => don't need change event
159
}
160
161     void changeComponentId(LayoutComponent comp, String JavaDoc newId) {
162         unregisterComponentImpl(comp);
163         comp.setId(newId);
164         registerComponentImpl(comp);
165     }
166
167     void replaceComponent(LayoutComponent comp, LayoutComponent substComp) {
168         assert substComp.getParent() == null;
169         for (int i=0; i<DIM_COUNT; i++) {
170             LayoutInterval interval = comp.getLayoutInterval(i);
171             LayoutInterval substInt = substComp.getLayoutInterval(i);
172             assert substInt.getParent() == null;
173             setIntervalAlignment(substInt, interval.getRawAlignment());
174             setIntervalSize(substInt, interval.getMinimumSize(),
175                     interval.getPreferredSize(), interval.getMaximumSize());
176             LayoutInterval parentInt = interval.getParent();
177             if (parentInt != null) {
178                 int index = removeInterval(interval);
179                 addInterval(substInt, parentInt, index);
180             }
181         }
182
183         LayoutComponent parent = comp.getParent();
184         if (parent != null) {
185             int index = removeComponentImpl(comp);
186             addComponentImpl(substComp, parent, index);
187         }
188         unregisterComponentImpl(comp);
189         registerComponentImpl(substComp);
190     }
191
192     Iterator getAllComponents() {
193         return idToComponents.values().iterator();
194     }
195
196     public void addNewComponent(LayoutComponent component, LayoutComponent parent, LayoutComponent prototype) {
197         for (int i=0; i<DIM_COUNT; i++) {
198             LayoutInterval interval = component.getLayoutInterval(i);
199             if (parent != null) {
200                 addInterval(interval, parent.getLayoutRoot(i), -1);
201             }
202             setIntervalAlignment(interval, DEFAULT);
203 // setIntervalSize(interval, USE_PREFERRED_SIZE, interval.getPreferredSize(), USE_PREFERRED_SIZE);
204
if (prototype != null) {
205                 LayoutInterval pInt = prototype.getLayoutInterval(i);
206                 setIntervalSize(interval, interval.getMinimumSize(), pInt.getPreferredSize(), interval.getMaximumSize());
207             }
208         }
209         addComponent(component, parent, -1);
210     }
211
212     // Note this method does not care about adding the layout intervals of the
213
// component, it must be done in advance.
214
void addComponent(LayoutComponent component, LayoutComponent parent, int index) {
215         addComponentImpl(component, parent, index);
216         registerComponent(component, true);
217     }
218
219     void addComponentImpl(LayoutComponent component, LayoutComponent parent, int index) {
220         assert component.getParent() == null;
221
222         if (parent != null) {
223             assert getLayoutComponent(parent.getId()) == parent;
224             index = parent.add(component, index);
225         }
226         else {
227             assert component.isLayoutContainer();
228         }
229
230         // record undo/redo and fire event
231
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.COMPONENT_ADDED);
232         ev.setComponent(component, parent, index);
233         addChange(ev);
234         fireEvent(ev);
235     }
236
237     void removeComponent(LayoutComponent component) {
238         removeComponent(component, true);
239     }
240     
241     // Low level removal - removes the component from parent, unregisters it,
242
// records the change for undo/redo, and fires an event. Does nothing to
243
// the layout intervals of the component.
244
void removeComponent(LayoutComponent component, boolean fromModel) {
245         removeComponentImpl(component);
246         if (fromModel && (getLayoutComponent(component.getId()) != null)) {
247             unregisterComponent(component, true);
248         }
249     }
250
251     int removeComponentImpl(LayoutComponent component) {
252         int index;
253         LayoutComponent parent = component.getParent();
254         if (parent != null) {
255             index = parent.remove(component);
256         } else {
257             return -1; // the removal operation is "noop"
258
}
259
260         // record undo/redo and fire event
261
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.COMPONENT_REMOVED);
262         ev.setComponent(component, parent, index);
263         addChange(ev);
264         fireEvent(ev);
265
266         return index;
267     }
268
269     void removeComponentAndIntervals(LayoutComponent comp, boolean fromModel) {
270         boolean wasRoot = (comp.getParent() == null);
271         removeComponent(comp, fromModel);
272         if (!wasRoot) {
273             for (int i=0; i < DIM_COUNT; i++) {
274                 LayoutInterval interval = comp.getLayoutInterval(i);
275                 if (interval.getParent() != null)
276                     removeInterval(interval);
277             }
278         }
279     }
280
281     void addInterval(LayoutInterval interval, LayoutInterval parent, int index) {
282         assert interval.getParent() == null;
283
284         index = parent.add(interval, index);
285
286         // record undo/redo and fire event
287
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_ADDED);
288         ev.setInterval(interval, parent, index);
289         addChange(ev);
290         fireEvent(ev);
291     }
292
293     // Low level removal - removes the interval from parent, records the
294
// change for undo/redo, and fires an event.
295
int removeInterval(LayoutInterval interval) {
296         LayoutInterval parent = interval.getParent();
297         int index = parent.remove(interval);
298
299         // record undo/redo and fire event
300
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_REMOVED);
301         ev.setInterval(interval, parent, index);
302         addChange(ev);
303         fireEvent(ev);
304
305         return index;
306     }
307
308     LayoutInterval removeInterval(LayoutInterval parent, int index) {
309         LayoutInterval interval = parent.remove(index);
310
311         // record undo/redo and fire event
312
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_REMOVED);
313         ev.setInterval(interval, parent, index);
314         addChange(ev);
315         fireEvent(ev);
316
317         return interval;
318     }
319     
320     void changeIntervalAttribute(LayoutInterval interval, int attribute, boolean set) {
321         int oldAttributes = interval.getAttributes();
322         if (set) {
323             interval.setAttribute(attribute);
324         } else {
325             interval.unsetAttribute(attribute);
326         }
327         int newAttributes = interval.getAttributes();
328         
329         // record undo/redo (don't fire event)
330
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_ATTRIBUTES_CHANGED);
331         ev.setAttributes(interval, oldAttributes, newAttributes);
332         addChange(ev);
333     }
334
335     void setIntervalAlignment(LayoutInterval interval, int alignment) {
336         int oldAlignment = interval.getRawAlignment();
337         interval.setAlignment(alignment);
338
339         // record undo/redo (don't fire event)
340
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_ALIGNMENT_CHANGED);
341         ev.setAlignment(interval, oldAlignment, alignment);
342         addChange(ev);
343     }
344
345     void setGroupAlignment(LayoutInterval group, int alignment) {
346         int oldAlignment = group.getGroupAlignment();
347         if (alignment == oldAlignment) {
348             return;
349         }
350         group.setGroupAlignment(alignment);
351
352         // record undo/redo (don't fire event)
353
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.GROUP_ALIGNMENT_CHANGED);
354         ev.setAlignment(group, oldAlignment, alignment);
355         addChange(ev);
356     }
357
358     void setLayoutContainer(LayoutComponent component, boolean container) {
359         boolean oldContainer = component.isLayoutContainer();
360         if (oldContainer != container) {
361             LayoutInterval[] roots = component.getLayoutRoots();
362             component.setLayoutContainer(container, null);
363
364             // record undo/redo (don't fire event)
365
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.CONTAINER_ATTR_CHANGED);
366             ev.setContainer(component, roots);
367             addChange(ev);
368         }
369     }
370
371     public void setIntervalSize(LayoutInterval interval, int min, int pref, int max) {
372         int oldMin = interval.getMinimumSize();
373         int oldPref = interval.getPreferredSize();
374         int oldMax = interval.getMaximumSize();
375         if (min == oldMin && pref == oldPref && max == oldMax) {
376             return; // no change
377
}
378         interval.setSizes(min, pref, max);
379         if (interval.isComponent()) {
380             LayoutComponent comp = interval.getComponent();
381             boolean horizontal = (interval == comp.getLayoutInterval(HORIZONTAL));
382             if (oldMin != min) {
383                 comp.firePropertyChange(horizontal ? PROP_HORIZONTAL_MIN_SIZE : PROP_VERTICAL_MIN_SIZE,
384                     new Integer JavaDoc(oldMin), new Integer JavaDoc(min));
385             }
386             if (oldPref != pref) {
387                 comp.firePropertyChange(horizontal ? PROP_HORIZONTAL_PREF_SIZE : PROP_VERTICAL_PREF_SIZE,
388                     new Integer JavaDoc(oldPref), new Integer JavaDoc(pref));
389             }
390             if (oldMax != max) {
391                 comp.firePropertyChange(horizontal ? PROP_HORIZONTAL_MAX_SIZE : PROP_VERTICAL_MAX_SIZE,
392                     new Integer JavaDoc(oldMax), new Integer JavaDoc(max));
393             }
394         }
395
396         // record undo/redo (don't fire event)
397
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_SIZE_CHANGED);
398         ev.setSize(interval, oldMin, oldPref, oldMax, min, pref, max);
399         addChange(ev);
400     }
401     
402     public void copyModelFrom(LayoutModel sourceModel, Map/*<String,String>*/ sourceToTargetIds,
403             String JavaDoc sourceContainerId, String JavaDoc targetContainerId) {
404         LayoutComponent sourceContainer = sourceModel.getLayoutComponent(sourceContainerId);
405         LayoutComponent targetContainer = getLayoutComponent(targetContainerId);
406         if (targetContainer == null) {
407             targetContainer = new LayoutComponent(targetContainerId, true);
408             addRootComponent(targetContainer);
409         } else if (!targetContainer.isLayoutContainer()) {
410             changeComponentToContainer(targetContainerId);
411         }
412         // Create LayoutComponents
413
Iterator iter = sourceToTargetIds.entrySet().iterator();
414         while (iter.hasNext()) {
415             Map.Entry entry = (Map.Entry)iter.next();
416             String JavaDoc targetId = (String JavaDoc)entry.getValue();
417             LayoutComponent targetLC = getLayoutComponent(targetId);
418             if (targetLC == null) {
419                 String JavaDoc sourceId = (String JavaDoc)entry.getKey();
420                 LayoutComponent sourceLC = sourceModel.getLayoutComponent(sourceId);
421                 targetLC = new LayoutComponent(targetId, sourceLC.isLayoutContainer());
422             }
423             if (targetLC.getParent() == null) {
424                 addComponent(targetLC, targetContainer, -1);
425             }
426         }
427         // Copy LayoutIntervals
428
for (int dim=0; dim<DIM_COUNT; dim++) {
429             LayoutInterval sourceInterval = sourceContainer.getLayoutRoot(dim);
430             LayoutInterval targetInterval = targetContainer.getLayoutRoot(dim);
431             copyInterval(sourceInterval, targetInterval, sourceToTargetIds);
432         }
433     }
434     
435     private void copyInterval(LayoutInterval sourceInterval, LayoutInterval targetInterval, Map/*<String,String>*/ sourceToTargetIds) {
436         Iterator iter = sourceInterval.getSubIntervals();
437         while (iter.hasNext()) {
438             LayoutInterval sourceSub = (LayoutInterval)iter.next();
439             LayoutInterval clone = null;
440             if (sourceSub.isComponent()) {
441                 String JavaDoc compId = (String JavaDoc)sourceToTargetIds.get(sourceSub.getComponent().getId());
442                 LayoutComponent comp = getLayoutComponent(compId);
443                 int dimension = (sourceSub == sourceSub.getComponent().getLayoutInterval(HORIZONTAL)) ? HORIZONTAL : VERTICAL;
444                 clone = comp.getLayoutInterval(dimension);
445             }
446             LayoutInterval targetSub = LayoutInterval.cloneInterval(sourceSub, clone);
447             if (sourceSub.isGroup()) {
448                 copyInterval(sourceSub, targetSub, sourceToTargetIds);
449             }
450             addInterval(targetSub, targetInterval, -1);
451         }
452     }
453     
454     /**
455      * Creates layout model based on the current layout represented
456      * by bounds of given components.
457      *
458      * @param idToComponent maps component Id to <code>Component</code>.
459      */

460     public void createModel(String JavaDoc containerId, Container cont, Map idToComponent) {
461         if (idToComponent.isEmpty()) return;
462         LayoutComponent lCont = getLayoutComponent(containerId);
463         assert (lCont != null);
464         Insets insets = new Insets(0, 0, 0, 0);
465         if (cont instanceof javax.swing.JComponent JavaDoc) {
466             javax.swing.border.Border JavaDoc border = ((javax.swing.JComponent JavaDoc)cont).getBorder();
467             if (border != null) {
468                 insets = border.getBorderInsets(cont);
469             }
470         }
471         Map idToBounds = new HashMap();
472         Iterator iter = idToComponent.entrySet().iterator();
473         Rectangle notKnown = new Rectangle();
474         while (iter.hasNext()) {
475             Map.Entry entry = (Map.Entry)iter.next();
476             String JavaDoc id = (String JavaDoc)entry.getKey();
477             Component component = (Component)entry.getValue();
478             LayoutComponent lComp = getLayoutComponent(id);
479             if (lComp == null) {
480                 lComp = new LayoutComponent(id, false);
481             }
482             addComponent(lComp, lCont, -1);
483             Rectangle bounds = component.getBounds();
484             Dimension dim = component.getPreferredSize();
485             if (bounds.equals(notKnown)) { // Issue 65919
486
bounds.setSize(dim);
487             }
488             bounds = new Rectangle(bounds.x - insets.left, bounds.y - insets.top, bounds.width, bounds.height);
489             idToBounds.put(id, bounds);
490             if (dim.width != bounds.width) {
491                 LayoutInterval interval = lComp.getLayoutInterval(HORIZONTAL);
492                 setIntervalSize(interval, interval.getMinimumSize(), bounds.width, interval.getMaximumSize());
493             }
494             if (dim.height != bounds.height) {
495                 LayoutInterval interval = lComp.getLayoutInterval(VERTICAL);
496                 setIntervalSize(interval, interval.getMinimumSize(), bounds.height, interval.getMaximumSize());
497             }
498         }
499         RegionInfo region = new RegionInfo(idToBounds);
500         region.calculateIntervals();
501         // PENDING don't insert parallel group to parallel group
502
addInterval(region.getInterval(HORIZONTAL), lCont.getLayoutRoot(HORIZONTAL), -1);
503         addInterval(region.getInterval(VERTICAL), lCont.getLayoutRoot(VERTICAL), -1);
504     }
505
506     public LayoutInterval[] createIntervalsFromBounds(LayoutRegion space, LayoutComponent[] components, LayoutRegion[] bounds) {
507         Map idToBounds = new HashMap();
508         for (int i=0; i<components.length; i++) {
509             for (int dim=0; dim<DIM_COUNT; dim++) {
510                 LayoutInterval interval = components[i].getLayoutInterval(dim);
511                 setIntervalSize(interval, interval.getMinimumSize(), bounds[i].size(dim), interval.getMaximumSize());
512             }
513             Rectangle compBounds = new Rectangle(
514                 bounds[i].positions[HORIZONTAL][LEADING]-space.positions[HORIZONTAL][LEADING],
515                 bounds[i].positions[VERTICAL][LEADING]-space.positions[VERTICAL][LEADING],
516                 bounds[i].size(HORIZONTAL),
517                 bounds[i].size(VERTICAL));
518             idToBounds.put(components[i].getId(), compBounds);
519         }
520         RegionInfo region = new RegionInfo(idToBounds);
521         region.calculateIntervals();
522         LayoutInterval[] result = new LayoutInterval[DIM_COUNT];
523         for (int dim=0; dim<DIM_COUNT; dim++) {
524             result[dim] = region.getInterval(dim);
525         }
526         return result;
527     }
528
529     private class RegionInfo {
530         private LayoutInterval horizontal = null;
531         private LayoutInterval vertical = null;
532         private Map idToBounds;
533         private int minx;
534         private int maxx;
535         private int miny;
536         private int maxy;
537         private int dimension;
538
539         public RegionInfo(Map idToBounds) {
540             this.idToBounds = idToBounds;
541             this.dimension = -1;
542             minx = miny = 0;
543             updateRegionBounds();
544         }
545         
546         private RegionInfo(Map idToBounds, int dimension) {
547             this.idToBounds = idToBounds;
548             this.dimension = dimension;
549             minx = miny = Short.MAX_VALUE;
550             updateRegionBounds();
551         }
552         
553         private void updateRegionBounds() {
554             maxy = maxx = Short.MIN_VALUE;
555             Iterator iter = idToBounds.values().iterator();
556             while (iter.hasNext()) {
557                 Rectangle bounds = (Rectangle)iter.next();
558                 minx = Math.min(minx, bounds.x);
559                 miny = Math.min(miny, bounds.y);
560                 maxx = Math.max(maxx, bounds.x + bounds.width);
561                 maxy = Math.max(maxy, bounds.y + bounds.height);
562             }
563         }
564
565         public void calculateIntervals() {
566             if (idToBounds.size() == 1) {
567                 String JavaDoc id = (String JavaDoc)idToBounds.keySet().iterator().next();
568                 Rectangle bounds = (Rectangle)idToBounds.get(id);
569                 LayoutComponent comp = getLayoutComponent(id);
570                 horizontal = comp.getLayoutInterval(HORIZONTAL);
571                 horizontal = prefixByGap(horizontal, bounds.x - minx);
572                 vertical = comp.getLayoutInterval(VERTICAL);
573                 vertical = prefixByGap(vertical, bounds.y - miny);
574                 return;
575             }
576             int effDim = -1;
577             List JavaDoc parts = null;
578             Map removedIdToBounds = null;
579             do {
580                 boolean remove = ((dimension == -1) && (effDim == HORIZONTAL))
581                     || ((dimension != -1) && (effDim != -1));
582                 if (remove) {
583                     effDim = -1;
584                 }
585                 if (dimension == -1) {
586                     switch (effDim) {
587                         case -1: effDim = VERTICAL; break;
588                         case VERTICAL: effDim = HORIZONTAL; break;
589                         case HORIZONTAL: remove = true;
590                     }
591                 } else {
592                     effDim = dimension;
593                 }
594                 if (remove) { // no cut found, remove some component
595
String JavaDoc id = (String JavaDoc)idToBounds.keySet().iterator().next();
596                     Rectangle bounds = (Rectangle)idToBounds.remove(id);
597                     if (removedIdToBounds == null) {
598                         removedIdToBounds = new HashMap();
599                     }
600                     removedIdToBounds.put(id, bounds);
601                 }
602                 Set cutSet = createPossibleCuts(effDim);
603                 parts = cutIntoParts(cutSet, effDim);
604             } while (!idToBounds.isEmpty() && parts.isEmpty());
605             dimension = effDim;
606             List JavaDoc regions = new LinkedList();
607             Iterator iter = parts.iterator();
608             while (iter.hasNext()) {
609                 Map part = (Map)iter.next();
610                 RegionInfo region = new RegionInfo(part, (dimension == HORIZONTAL) ? VERTICAL : HORIZONTAL);
611                 region.calculateIntervals();
612                 regions.add(region);
613             }
614             mergeSubRegions(regions, dimension);
615             if (removedIdToBounds != null) {
616                 for (int dim = HORIZONTAL; dim <= VERTICAL; dim++) {
617                     iter = removedIdToBounds.entrySet().iterator();
618                     LayoutInterval parent = (dim == HORIZONTAL) ? horizontal : vertical;
619                     if (!parent.isParallel()) {
620                         LayoutInterval parGroup = new LayoutInterval(PARALLEL);
621                         addInterval(parent, parGroup, -1);
622                         if (dim == HORIZONTAL) {
623                             horizontal = parGroup;
624                         } else {
625                             vertical = parGroup;
626                         }
627                         parent = parGroup;
628                     }
629                     while (iter.hasNext()) {
630                         Map.Entry entry = (Map.Entry)iter.next();
631                         String JavaDoc id = (String JavaDoc)entry.getKey();
632                         Rectangle bounds = (Rectangle)entry.getValue();
633                         LayoutComponent comp = getLayoutComponent(id);
634                         LayoutInterval interval = comp.getLayoutInterval(dim);
635                         int gap = (dim == HORIZONTAL) ? bounds.x - minx : bounds.y - miny;
636                         interval = prefixByGap(interval, gap);
637                         addInterval(interval, parent, -1);
638                     }
639                 }
640             }
641         }
642         
643         private SortedSet createPossibleCuts(int dimension) {
644             SortedSet cutSet = new TreeSet();
645             Iterator iter = idToBounds.keySet().iterator();
646             while (iter.hasNext()) {
647                 String JavaDoc id = (String JavaDoc)iter.next();
648                 Rectangle bounds = (Rectangle)idToBounds.get(id);
649                 // Leading lines are sufficient
650
int leading = (dimension == HORIZONTAL) ? bounds.x : bounds.y;
651                 cutSet.add(new Integer JavaDoc(leading));
652             }
653             cutSet.add(new Integer JavaDoc((dimension == HORIZONTAL) ? maxx : maxy));
654             return cutSet;
655         }
656         
657         private List JavaDoc cutIntoParts(Set cutSet, int dimension) {
658             List JavaDoc parts = new LinkedList();
659             Iterator iter = cutSet.iterator();
660             while (iter.hasNext()) {
661                 Integer JavaDoc cutInt = (Integer JavaDoc)iter.next();
662                 int cut = cutInt.intValue();
663                 boolean isCut = true;
664                 Map preIdToBounds = new HashMap();
665                 Map postIdToBounds = new HashMap();
666                 Iterator it = idToBounds.entrySet().iterator();
667                 while (isCut && it.hasNext()) {
668                     Map.Entry entry = (Map.Entry)it.next();
669                     String JavaDoc id = (String JavaDoc)entry.getKey();
670                     Rectangle bounds = (Rectangle)entry.getValue();
671                     int leading = (dimension == HORIZONTAL) ? bounds.x : bounds.y;
672                     int trailing = leading + ((dimension == HORIZONTAL) ? bounds.width : bounds.height);
673                     if (leading >= cut) {
674                         postIdToBounds.put(id, bounds);
675                     } else if (trailing <= cut) {
676                         preIdToBounds.put(id, bounds);
677                     } else {
678                         isCut = false;
679                     }
680                 }
681                 if (isCut && !preIdToBounds.isEmpty()
682                     // the last cut candidate (end of the region) cannot be the first cut
683
&& (!parts.isEmpty() || (preIdToBounds.size() != idToBounds.size()))) {
684                     idToBounds.keySet().removeAll(preIdToBounds.keySet());
685                     parts.add(preIdToBounds);
686                 }
687             }
688             return parts;
689         }
690         
691         private void mergeSubRegions(List JavaDoc regions, int dimension) {
692             if (regions.size() == 0) {
693                 horizontal = new LayoutInterval(PARALLEL);
694                 vertical = new LayoutInterval(PARALLEL);
695                 return;
696             }
697             LayoutInterval seqGroup = new LayoutInterval(SEQUENTIAL);
698             LayoutInterval parGroup = new LayoutInterval(PARALLEL);
699             int lastSeqTrailing = (dimension == HORIZONTAL) ? minx : miny;
700             Iterator iter = regions.iterator();
701             while (iter.hasNext()) {
702                 RegionInfo region = (RegionInfo)iter.next();
703                 LayoutInterval seqInterval;
704                 LayoutInterval parInterval;
705                 int seqGap;
706                 int parGap;
707                 if (dimension == HORIZONTAL) {
708                     seqInterval = region.horizontal;
709                     parInterval = region.vertical;
710                     parGap = region.miny - miny;
711                     seqGap = region.minx - lastSeqTrailing;
712                     lastSeqTrailing = region.maxx;
713                 } else {
714                     seqInterval = region.vertical;
715                     parInterval = region.horizontal;
716                     parGap = region.minx - minx;
717                     seqGap = region.miny - lastSeqTrailing;
718                     lastSeqTrailing = region.maxy;
719                 }
720                 // PENDING optimization of the resulting layout model
721
if (seqGap > 0) {
722                     LayoutInterval gap = new LayoutInterval(SINGLE);
723                     gap.setSize(seqGap);
724                     addInterval(gap, seqGroup, -1);
725                 }
726                 addInterval(seqInterval, seqGroup, -1);
727                 parInterval = prefixByGap(parInterval, parGap);
728                 addInterval(parInterval, parGroup, -1);
729             }
730             if (dimension == HORIZONTAL) {
731                 horizontal = seqGroup;
732                 vertical = parGroup;
733             } else {
734                 horizontal = parGroup;
735                 vertical = seqGroup;
736             }
737         }
738         
739         private LayoutInterval prefixByGap(LayoutInterval interval, int size) {
740             if (size > 0) {
741                 LayoutInterval gap = new LayoutInterval(SINGLE);
742                 gap.setSize(size);
743                 if (interval.isSequential()) {
744                     addInterval(gap, interval, 0);
745                 } else {
746                     LayoutInterval group = new LayoutInterval(SEQUENTIAL);
747                     addInterval(gap, group, -1);
748                     addInterval(interval, group, -1);
749                     interval = group;
750                 }
751             }
752             return interval;
753         }
754         
755         public LayoutInterval getInterval(int dimension) {
756             return (dimension == HORIZONTAL) ? horizontal : vertical;
757         }
758
759     }
760
761     // -----
762
// listeners registration, firing methods (no synchronization)
763

764     void addListener(Listener l) {
765         if (listeners == null) {
766             listeners = new ArrayList();
767         }
768         else {
769             listeners.remove(l);
770         }
771         listeners.add(l);
772     }
773
774     void removeListener(Listener l) {
775         if (listeners != null) {
776             listeners.remove(l);
777         }
778     }
779
780     private void fireEvent(LayoutEvent event) {
781         if (listeners != null && listeners.size() > 0) {
782             Iterator it = ((List JavaDoc)listeners.clone()).iterator();
783             while (it.hasNext()) {
784                 ((Listener)it.next()).layoutChanged(event);
785             }
786         }
787     }
788
789     /**
790      * Listener interface for changes in the layout model.
791      */

792     interface Listener {
793         void layoutChanged(LayoutEvent ev);
794     }
795
796     // -----
797
// changes recording and undo/redo
798

799     public boolean isChangeRecording() {
800         return recordingChanges;
801     }
802
803     public void setChangeRecording(boolean record) {
804         recordingChanges = record;
805     }
806
807     boolean isUndoRedoInProgress() {
808         return undoRedoInProgress;
809     }
810
811     public Object JavaDoc getChangeMark() {
812         return new Integer JavaDoc(changeMark);
813     }
814     
815     public void endUndoableEdit() {
816         if (lastUndoableEdit != null) {
817             lastUndoableEdit.endMark = getChangeMark();
818             lastUndoableEdit = null;
819         }
820     }
821     
822     public boolean isUndoableEditInProgress() {
823         return (lastUndoableEdit != null);
824     }
825
826     public UndoableEdit getUndoableEdit() {
827         if (recordingChanges && !undoRedoInProgress) {
828             LayoutUndoableEdit undoEdit = new LayoutUndoableEdit();
829             undoEdit.startMark = getChangeMark();
830             endUndoableEdit();
831             lastUndoableEdit = undoEdit;
832             return undoEdit;
833         }
834         return null;
835     }
836
837     private void addChange(LayoutEvent change) {
838         if (recordingChanges && !undoRedoInProgress) {
839             redoMap.clear();
840             if (undoMap.size() == 0)
841                 oldestMark = changeMark;
842
843             undoMap.put(new Integer JavaDoc(changeMark++), change);
844
845             while (undoMap.size() > changeCountHardLimit) {
846                 undoMap.remove(new Integer JavaDoc(oldestMark++));
847             }
848         }
849     }
850
851     boolean undo(Object JavaDoc startMark, Object JavaDoc endMark) {
852         assert !undoRedoInProgress;
853         if (!undoMap.containsKey(startMark)) {
854             return false; // the mark is not present in the undo queue
855
}
856
857         int start = ((Integer JavaDoc)startMark).intValue();
858         int end = ((Integer JavaDoc)endMark).intValue();
859         undoRedoInProgress = true;
860
861         while (end > start) {
862             Object JavaDoc key = new Integer JavaDoc(--end);
863             LayoutEvent change = (LayoutEvent) undoMap.remove(key);
864             if (change != null) {
865                 change.undo();
866                 redoMap.put(key, change);
867             }
868         }
869
870         undoRedoInProgress = false;
871         return true;
872     }
873
874     boolean redo(Object JavaDoc startMark, Object JavaDoc endMark) {
875         assert !undoRedoInProgress;
876         if (!redoMap.containsKey(startMark)) {
877             return false; // the mark is not present in the redo queue
878
}
879
880         int start = ((Integer JavaDoc)startMark).intValue();
881         int end = ((Integer JavaDoc)endMark).intValue();
882         undoRedoInProgress = true;
883
884         while (start < end) {
885             Object JavaDoc key = new Integer JavaDoc(start++);
886             LayoutEvent change = (LayoutEvent) redoMap.remove(key);
887             if (change != null) {
888                 change.redo();
889                 undoMap.put(key, change);
890             }
891         }
892
893         undoRedoInProgress = false;
894         return true;
895     }
896
897     void releaseChanges(Object JavaDoc fromMark, Object JavaDoc toMark) {
898         int m1 = ((Integer JavaDoc)fromMark).intValue();
899         int m2 = ((Integer JavaDoc)toMark).intValue();
900
901         while (m1 < m2) {
902             Object JavaDoc m = new Integer JavaDoc(m1);
903             undoMap.remove(m);
904             redoMap.remove(m);
905             m1++;
906         }
907     }
908
909     /**
910      * UndoableEdit implementation for series of changes in layout model.
911      */

912     private class LayoutUndoableEdit extends AbstractUndoableEdit {
913         private Object JavaDoc startMark;
914         private Object JavaDoc endMark;
915
916         public void undo() throws CannotUndoException {
917             super.undo();
918             if (endMark == null) {
919                 assert lastUndoableEdit == this;
920                 endMark = getChangeMark();
921                 lastUndoableEdit = null;
922             }
923             LayoutModel.this.undo(startMark, endMark);
924         }
925
926         public void redo() throws CannotRedoException {
927             super.redo();
928             LayoutModel.this.redo(startMark, endMark);
929         }
930
931         public String JavaDoc getUndoPresentationName() {
932             return ""; // NOI18N
933
}
934         public String JavaDoc getRedoPresentationName() {
935             return ""; // NOI18N
936
}
937
938         public void die() {
939             releaseChanges(startMark, endMark != null ? endMark : getChangeMark());
940         }
941     }
942
943     /**
944      * Returns dump of the layout model. For debugging and testing purposes only.
945      *
946      * @return dump of the layout model.
947      */

948     public String JavaDoc dump(final Map idToNameMap) {
949         Set roots = new TreeSet(new Comparator() {
950             // comparator to ensure stable order of dump; according to tree
951
// hierarchy, order within container, name
952
public int compare(Object JavaDoc o1, Object JavaDoc o2) {
953                 if (o1 == o2)
954                     return 0;
955                 LayoutComponent lc1 = (LayoutComponent) o1;
956                 LayoutComponent lc2 = (LayoutComponent) o2;
957                 // parent always first
958
if (lc1.isParentOf(lc2))
959                     return -1;
960                 if (lc2.isParentOf(lc1))
961                     return 1;
962                 // get the same level under common parent
963
LayoutComponent parent = LayoutComponent.getCommonParent(lc1, lc2);
964                 while (lc1.getParent() != parent)
965                     lc1 = lc1.getParent();
966                 while (lc2.getParent() != parent)
967                     lc2 = lc2.getParent();
968                 if (parent != null) { // in the same tree
969
return parent.indexOf(lc1) < parent.indexOf(lc2) ? -1 : 1;
970                 }
971                 else { // in distinct trees
972
String JavaDoc id1 = lc1.getId();
973                     String JavaDoc id2 = lc2.getId();
974                     if (idToNameMap != null) {
975                         id1 = (String JavaDoc) idToNameMap.get(id1);
976                         id2 = (String JavaDoc) idToNameMap.get(id2);
977                         if (id1 == null) {
978                             return -1;
979                         }
980                         if (id2 == null) {
981                             return 1;
982                         }
983                     }
984                     return id1.compareTo(id2);
985                 }
986             }
987         });
988         Iterator iter = idToComponents.entrySet().iterator();
989         while (iter.hasNext()) {
990             Map.Entry entry = (Map.Entry)iter.next();
991             LayoutComponent comp = (LayoutComponent)entry.getValue();
992             if (comp.isLayoutContainer()) {
993                 roots.add(comp);
994             }
995         }
996         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
997         sb.append("<LayoutModel>\n"); // NOI18N
998
Iterator rootIter = roots.iterator();
999         while (rootIter.hasNext()) {
1000            LayoutComponent root = (LayoutComponent)rootIter.next();
1001            String JavaDoc rootId = root.getId();
1002            if (idToNameMap != null) {
1003                rootId = (String JavaDoc) idToNameMap.get(rootId);
1004            }
1005            if (rootId != null)
1006                sb.append(" <Root id=\""+rootId+"\">\n"); // NOI18N
1007
else
1008                sb.append(" <Root>\n"); // NOI18N
1009
sb.append(dumpLayout(2, root, idToNameMap, true));
1010            sb.append(" </Root>\n"); // NOI18N
1011
}
1012        sb.append("</LayoutModel>\n"); // NOI18N
1013
return sb.toString();
1014    }
1015    
1016    /**
1017     * Returns dump of the layout interval.
1018     *
1019     * @param interval interval whose dump should be returned.
1020     * @param dimension dimension in which the layout interval resides.
1021     */

1022    public String JavaDoc dump(LayoutInterval interval, int dimension) {
1023        return new LayoutPersistenceManager(this).saveIntervalLayout(2, interval, dimension);
1024    }
1025    
1026    /**
1027     * Returns dump of the layout model.
1028     *
1029     * @param indent determines size of indentation.
1030     * @param root container layout model should be dumped.
1031     * @param humanReadable determines whether constants should be replaced
1032     * by human readable expressions.
1033     * @return dump of the layout model.
1034     */

1035    public String JavaDoc dumpLayout(int indent, LayoutComponent root, Map idToNameMap, boolean humanReadable) {
1036        return new LayoutPersistenceManager(this).saveLayout(indent, root, idToNameMap, humanReadable);
1037    }
1038    
1039    /**
1040     * Loads the layout of the given container.
1041     *
1042     * @param rootId ID of the layout root (the container whose layout should be loaded).
1043     * @param dimLayoutList nodes holding the information about the layout.
1044     * @param nameToIdMap map from component names to component IDs.
1045     */

1046    public void loadModel(String JavaDoc rootId, org.w3c.dom.NodeList JavaDoc dimLayoutList, Map nameToIdMap)
1047        throws java.io.IOException JavaDoc
1048    {
1049        new LayoutPersistenceManager(this).loadModel(rootId, dimLayoutList, nameToIdMap);
1050    }
1051
1052    /**
1053     * Returns whether the model was repaired (because of some error found) or
1054     * upgraded automatically during loading. After loading, it might be a good
1055     * idea to save the corrected state, so to mark the loaded layout as modified.
1056     * @return whether the model was changed during loading or saving
1057     */

1058    public boolean wasCorrected() {
1059        return corrected;
1060    }
1061
1062    void setCorrected() {
1063        corrected = true;
1064    }
1065
1066    /*
1067     * LINKSIZE
1068     */

1069    
1070    // each object in the map is a List and contains list of components within the group
1071
private Map linkSizeGroupsH = new HashMap();
1072    private Map linkSizeGroupsV = new HashMap();
1073    
1074    private int maxLinkGroupId = 0;
1075
1076    void addComponentToLinkSizedGroup(int groupId, String JavaDoc compId, int dimension) {
1077                
1078        if (NOT_EXPLICITLY_DEFINED == groupId) { //
1079
return;
1080        }
1081        if (maxLinkGroupId < groupId) {
1082            maxLinkGroupId=groupId;
1083        }
1084        Integer JavaDoc groupIdInt = new Integer JavaDoc(groupId);
1085        Map linkSizeGroups = (dimension == HORIZONTAL) ? linkSizeGroupsH : linkSizeGroupsV;
1086        List JavaDoc l = (List JavaDoc)linkSizeGroups.get(groupIdInt);
1087        if ((l != null) && (l.contains(compId) || !sameContainer(compId, (String JavaDoc)l.get(0)))) {
1088            return;
1089        }
1090        addComponentToLinkSizedGroupImpl(groupId, compId, dimension);
1091    }
1092
1093    void addComponentToLinkSizedGroupImpl(int groupId, String JavaDoc compId, int dimension) {
1094        LayoutComponent lc = getLayoutComponent(compId);
1095        Integer JavaDoc groupIdInt = new Integer JavaDoc(groupId);
1096        Map linkSizeGroups = (dimension == HORIZONTAL) ? linkSizeGroupsH : linkSizeGroupsV;
1097        List JavaDoc l = (List JavaDoc)linkSizeGroups.get(groupIdInt);
1098        if (l != null) {
1099            l.add(lc.getId());
1100        } else {
1101            l = new ArrayList();
1102            l.add(lc.getId());
1103            linkSizeGroups.put(groupIdInt, l);
1104        }
1105
1106        int oldLinkSizeId = lc.getLinkSizeId(dimension);
1107        lc.setLinkSizeId(groupId, dimension);
1108        
1109        // record undo/redo and fire event
1110
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_LINKSIZE_CHANGED);
1111        ev.setLinkSizeGroup(lc, oldLinkSizeId, groupId, dimension);
1112        addChange(ev);
1113        fireEvent(ev);
1114    }
1115
1116    private boolean sameContainer(String JavaDoc compId1, String JavaDoc compId2) {
1117        LayoutComponent lc1 = getLayoutComponent(compId1);
1118        LayoutComponent lc2 = getLayoutComponent(compId2);
1119        return lc1.getParent().equals(lc2.getParent());
1120    }
1121    
1122    void removeComponentFromLinkSizedGroup(LayoutComponent comp, int dimension) {
1123
1124        if (comp == null) return;
1125        
1126        int linkId = comp.getLinkSizeId(dimension);
1127        if (linkId != NOT_EXPLICITLY_DEFINED) {
1128
1129            Map map = (dimension == HORIZONTAL) ? linkSizeGroupsH : linkSizeGroupsV;
1130            Integer JavaDoc linkIdInt = new Integer JavaDoc(linkId);
1131            
1132            List JavaDoc l = null;
1133            l = (List JavaDoc)map.get(linkIdInt);
1134            l.remove(comp.getId());
1135            comp.setLinkSizeId(NOT_EXPLICITLY_DEFINED, dimension);
1136            
1137            if (l.size() == 1) {
1138                LayoutComponent lc = getLayoutComponent((String JavaDoc)l.get(0));
1139                int oldLinkSizeId = lc.getLinkSizeId(dimension);
1140                lc.setLinkSizeId(NOT_EXPLICITLY_DEFINED, dimension);
1141                map.remove(linkIdInt);
1142                // record undo/redo and fire event
1143
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_LINKSIZE_CHANGED);
1144                ev.setLinkSizeGroup(lc, oldLinkSizeId, NOT_EXPLICITLY_DEFINED, dimension);
1145                addChange(ev);
1146                fireEvent(ev);
1147            }
1148            
1149            if (l.size() == 0) {
1150                map.remove(linkIdInt);
1151            }
1152
1153            // record undo/redo and fire event
1154
LayoutEvent ev = new LayoutEvent(this, LayoutEvent.INTERVAL_LINKSIZE_CHANGED);
1155            ev.setLinkSizeGroup(comp, linkId, NOT_EXPLICITLY_DEFINED, dimension);
1156            addChange(ev);
1157            fireEvent(ev);
1158        }
1159    }
1160    
1161    /**
1162     * @return returns FALSE if components are not linked, and if so, they are linked in the same group
1163     * returns TRUE if all components are in the same linksize group
1164     * returns INVALID if none of above is true
1165     */

1166    public int areComponentsLinkSized(List JavaDoc/*<String>*/ components, int dimension) {
1167
1168        if (components.size() == 1) {
1169            String JavaDoc id = (String JavaDoc)components.get(0);
1170            boolean retVal = (getLayoutComponent(id).isLinkSized(dimension));
1171            return retVal ? TRUE : FALSE;
1172        }
1173        
1174        Iterator i = components.iterator();
1175        boolean someUnlinkedPresent = false;
1176        List JavaDoc idsFound = new ArrayList();
1177
1178        while (i.hasNext()) {
1179            String JavaDoc cid = (String JavaDoc)i.next();
1180            LayoutComponent lc = getLayoutComponent(cid);
1181            Integer JavaDoc linkSizeId = new Integer JavaDoc(lc.getLinkSizeId(dimension));
1182            if (!idsFound.contains(linkSizeId)) {
1183                idsFound.add(linkSizeId);
1184            }
1185            if (idsFound.size() > 2) { // components are from at least two different groups
1186
return INVALID;
1187            }
1188        }
1189        if (idsFound.size() == 1) {
1190            if (idsFound.contains(new Integer JavaDoc(NOT_EXPLICITLY_DEFINED))) {
1191                return FALSE;
1192            }
1193            return TRUE;
1194        }
1195        if (idsFound.contains(new Integer JavaDoc(NOT_EXPLICITLY_DEFINED))) { // == 2 elements
1196
return FALSE;
1197        } else {
1198            return INVALID;
1199        }
1200    }
1201        
1202    Map getLinkSizeGroups(int dimension) {
1203        if (HORIZONTAL == dimension) {
1204            return linkSizeGroupsH;
1205        }
1206        if (VERTICAL == dimension) {
1207            return linkSizeGroupsV;
1208        }
1209        return null; // incorrect dimension passed
1210
}
1211    
1212    public void unsetSameSize(List JavaDoc/*<String>*/ components, int dimension) {
1213        Iterator i = components.iterator();
1214        while (i.hasNext()) {
1215            String JavaDoc cid = (String JavaDoc)i.next();
1216            LayoutComponent lc = getLayoutComponent(cid);
1217            removeComponentFromLinkSizedGroup(lc, dimension);
1218        }
1219    }
1220    
1221    public void setSameSize(List JavaDoc/*<String>*/ components, int dimension) {
1222        Iterator i = components.iterator();
1223        int groupId = findGroupId(components, dimension);
1224        
1225        while (i.hasNext()) {
1226            String JavaDoc cid = (String JavaDoc)i.next();
1227            LayoutComponent lc = getLayoutComponent(cid);
1228            addComponentToLinkSizedGroup(groupId, lc.getId(), dimension);
1229        }
1230    }
1231    
1232    private int findGroupId(List JavaDoc/*<String*/ components, int dimension) {
1233        Iterator i = components.iterator();
1234        while (i.hasNext()) {
1235            String JavaDoc cid = (String JavaDoc)i.next();
1236            LayoutComponent lc = getLayoutComponent(cid);
1237            if (lc.isLinkSized(dimension)) {
1238                return lc.getLinkSizeId(dimension);
1239            }
1240        }
1241        return ++maxLinkGroupId;
1242    }
1243}
1244
Popular Tags