KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > gridsplit > GridSplitCell


1 /*
2  * AbstractHierarchyElement.java
3  *
4  * Created on 19 November 2004, 16:26
5  */

6
7 package org.netbeans.swing.gridsplit;
8
9 import java.awt.Component JavaDoc;
10 import java.awt.Dimension JavaDoc;
11 import java.awt.Point JavaDoc;
12 import java.awt.Rectangle JavaDoc;
13 import java.util.Iterator JavaDoc;
14 import java.util.List JavaDoc;
15 import java.util.ArrayList JavaDoc;
16 import org.apache.xpath.axes.IteratorPool;
17
18 /**
19  *
20  * @author aubrecht
21  */

22 public class GridSplitCell {
23     
24     public static final int NORTH = 1;
25     public static final int SOUTH = 2;
26     public static final int WEST = 4;
27     public static final int EAST = 8;
28     
29     public static final int NO_SPLITTER = 0;
30     public static final int HORIZONTAL_SPLITTER = NORTH + SOUTH;
31     public static final int VERTICAL_SPLITTER = WEST + EAST;
32     
33     
34     //the parent cell in the hierarchy or null if this is a root cell
35
private GridSplitCell parent;
36     //children cell, empty if this is a leaf cell (i.e. cell with an actual swing component)
37
private List JavaDoc children = new ArrayList JavaDoc();
38     //split orientation, HORIZONTAL means that the split bars are vertical
39
private int splitterOrientation = NO_SPLITTER;
40     //swing component displayed in this cell or null if this cell has children cells
41
private Component JavaDoc component;
42     //defines the portion of the resizing delta consumed by this cell
43
private double resizeWeight = 0.0;
44     //normalized resize weight, used internally only
45
private double normalizedResizeWeight = 0.0;
46     //true if this cell is collapsed (has zero size), splitter is shown for this cell
47
private boolean collapsed = false;
48     //true if this component is hidden in the hierarchy, no splitter is visible for this cell
49
private boolean isHidden = false;
50     //cells current dimensions
51
private Dimension JavaDoc dimension = new Dimension JavaDoc(0,0);
52     //cells location in the SplitPane
53
private Point JavaDoc location = new Point JavaDoc(0,0);
54     
55     GridSplitCell( Component JavaDoc c, double resizeWeight ) {
56         this( c, resizeWeight, new Dimension JavaDoc(0,0) );
57     }
58
59     GridSplitCell( Component JavaDoc c, double resizeWeight, Dimension JavaDoc initialDimension ) {
60         this.component = c;
61         this.resizeWeight = resizeWeight;
62         this.dimension.width = initialDimension.width;
63         this.dimension.height = initialDimension.height;
64     }
65     
66     /**
67      * Copy constructor
68      */

69     GridSplitCell( GridSplitCell src ) {
70         copy( src, this );
71         this.parent = src.parent;
72     }
73     
74     public GridSplitCell getParent() {
75         return parent;
76     }
77     
78     void setParent( GridSplitCell newParent ) {
79         this.parent = newParent;
80     }
81     
82     /**
83      * @return Number of children cells or zero if this cell is not split.
84      */

85     public int count() {
86         return children.size();
87     }
88     
89     /**
90      * @return Number of children cells that are not hidden (i.e. a split bar is shown for them)
91      */

92     public int countVisibleCells() {
93         //TODO refactor this method
94
return getVisibleCells().size();
95     }
96     
97     /**
98      * @return Children cell at given position.
99      */

100     public GridSplitCell cellAt( int index ) {
101         return (GridSplitCell) children.get( index );
102     }
103     
104     /**
105      * @return True if this is a root cell (i.e. the top-most cell in the hierarchy).
106      */

107     public boolean isRootCell() {
108         return null == getParent();
109     }
110     
111     /**
112      * Add a new cell with given compent to this component's side. This cell may split itself if needed.
113      * @param compToAdd Component to be added to split hierarchy.
114      * @param side This cell's side that will be shared with the new component.
115      * @param initialSize The percentage of this cell's parent's size that will be used as new component's initial dimension.
116      * @param resizeWeight New component's resize weight.
117      * @return Split cell encapsulating the new component.
118      */

119     public GridSplitCell addToSide( Component JavaDoc compToAdd, int side, double initialSize, double resizeWeight ) {
120         GridSplitCell newChild = new GridSplitCell( compToAdd, resizeWeight );
121         GridSplitCell owner;
122         GridSplitCell neighbor;
123         if( isRootCell() ) {
124             //adding a component to the root cell, i.e. the component will be attached
125
//to split pane's side
126
owner = this;
127             if( isSplitterSide( side ) ) {
128                 //the root cell is already split and the new component will share
129
//a split side
130
if( side == NORTH || side == WEST ) {
131                     neighbor = cellAt( 0 );
132                 } else {
133                     neighbor = cellAt( count()-1 );
134                 }
135             } else {
136                 //the root is either not split yet or the share side does not match split orientation
137
owner = this;
138                 neighbor = split();
139             }
140         } else {
141             owner = getParent();
142             if( owner.isSplitterSide( side ) ) {
143                 //the cell is already split and the shared side matches existing split orientation
144
neighbor = this;
145             } else {
146                 //the cell is either not split yet or the share side does not match split orientation
147
owner = this;
148                 neighbor = split();
149             }
150         }
151         owner.addToSide( neighbor, newChild, side, initialSize );
152         return newChild;
153     }
154     
155     /**
156      * Split this cell. Its current contents is copied to a new child cell.
157      */

158     private GridSplitCell split() {
159         GridSplitCell child = new GridSplitCell( this );
160         
161         children.clear();
162         children.add( child );
163         child.setParent( this );
164         component = null;
165         
166         return child;
167     }
168     
169     /**
170      * Add a new child cell and set its initial dimensions.
171      */

172     private void addToSide( GridSplitCell neighbor, GridSplitCell newChild, int side, double initialSize ) {
173         Dimension JavaDoc parentSize = getDimension( 0 );
174         splitterOrientation = (side == NORTH || side == SOUTH) ? VERTICAL_SPLITTER : HORIZONTAL_SPLITTER;
175         int insertIndex = children.indexOf( neighbor );
176         assert insertIndex >= 0;
177         if( side == SOUTH || side == EAST )
178             insertIndex++;
179         if( insertIndex > children.size() )
180             children.add( newChild );
181         else
182             children.add( insertIndex, newChild );
183         if( isHorizontalSplitter() ) {
184             newChild.dimension = new Dimension JavaDoc( (int)(parentSize.width*initialSize), parentSize.height );
185         } else {
186             newChild.dimension = new Dimension JavaDoc( parentSize.width, (int)(parentSize.height*initialSize) );
187         }
188         newChild.setParent( this );
189     }
190     
191     /**
192      * @return True if given side matches current splitter orientation.
193      */

194     boolean isSplitterSide( int side ) {
195         return (isHorizontalSplitter() && (side == WEST || side == EAST))
196             || (isVerticalSplitter() && (side == SOUTH || side == NORTH));
197     }
198
199     /**
200      * Remove given cell from the list of children cells. The hierarchy is rearranged
201      * if this cell has only one child left after the removal.
202      */

203     public void remove( GridSplitCell child ) {
204         assert children.contains( child );
205         
206         children.remove( child );
207         if( children.size() == 1 ) {
208             GridSplitCell orphan = cellAt( 0 );
209             
210             if( null != getParent() ) {
211                 //replace this cell with orphan's contents in the parent cell
212
getParent().replace( this, orphan );
213             } else {
214                 //this is a root cell, so just take over all properties from the orphan
215
copy( orphan, this );
216             }
217         }
218     }
219     
220     /**
221      * Replace the origCell with newCell.
222      */

223     private void replace( GridSplitCell origCell, GridSplitCell newCell ) {
224         assert children.contains( origCell );
225         int index = children.indexOf( origCell );
226         if( newCell.isSplit() && newCell.splitterOrientation == splitterOrientation ) {
227             children.addAll( index, newCell.children );
228             for( Iterator JavaDoc i=newCell.children.iterator(); i.hasNext(); ) {
229                 GridSplitCell c = (GridSplitCell)i.next();
230                 c.setParent( this );
231             }
232             children.remove( origCell );
233         } else {
234             children.set( index, newCell );
235             newCell.setParent( this );
236         }
237     }
238     
239     /**
240      * @return True if this cell is a splitter cell.
241      */

242     public boolean isSplit() {
243         return null == component || children.size() == 1;
244     }
245     
246     public int getSplitOrientation() {
247         return splitterOrientation;
248     }
249     
250     public boolean isHorizontalSplitter() {
251         return splitterOrientation == HORIZONTAL_SPLITTER;
252     }
253     
254     public boolean isVerticalSplitter() {
255         return splitterOrientation == VERTICAL_SPLITTER;
256     }
257
258     public Component JavaDoc getComponent() {
259         return component;
260     }
261
262     /**
263      * @param dividerSize The width of splitter bar.
264      * @return The minimum size of this cell. If this cell is a split cell then the
265      * result is a sum of minimum sizes of all children cells.
266      */

267     public Dimension JavaDoc getMinimumSize( int dividerSize ) {
268         if( isCollapsed() || isHidden() ) {
269             return new Dimension JavaDoc( 0, 0 );
270         } else {
271             if( isSplit() ) {
272                 return calculateMinimumSize( dividerSize );
273             } else {
274                 return component.getMinimumSize();
275             }
276         }
277     }
278     
279     /**
280      * @return Sum of minimum sizes of all children cells.
281      */

282     private Dimension JavaDoc calculateMinimumSize( int dividerSize ) {
283         Dimension JavaDoc minSize = new Dimension JavaDoc( 0, 0 );
284         ArrayList JavaDoc visibleCells = getVisibleCells();
285         for( Iterator JavaDoc i=visibleCells.iterator(); i.hasNext(); ) {
286             GridSplitCell child = (GridSplitCell)i.next();
287             
288             Dimension JavaDoc childMinSize = child.getMinimumSize( dividerSize );
289             
290             if( isHorizontalSplitter() ) {
291                 minSize.width += childMinSize.width;
292                 if( childMinSize.height > minSize.height )
293                     minSize.height = childMinSize.height;
294             } else {
295                 minSize.height += childMinSize.height;
296                 if( childMinSize.width > minSize.width )
297                     minSize.width = childMinSize.width;
298             }
299         }
300         if( isHorizontalSplitter() ) {
301             minSize.width += (visibleCells.size()-1)*dividerSize;
302         } else {
303             minSize.height += (visibleCells.size()-1)*dividerSize;
304         }
305         
306         return minSize;
307     }
308
309     /**
310      * @return List of children cells that are not hidden.
311      */

312     ArrayList JavaDoc getVisibleCells() {
313         ArrayList JavaDoc res = new ArrayList JavaDoc( children.size() );
314         for( Iterator JavaDoc i=children.iterator(); i.hasNext(); ) {
315             GridSplitCell child = (GridSplitCell)i.next();
316             if( child.isHidden() )
317                 continue;
318             res.add( child );
319         }
320         return res;
321     }
322     
323     /**
324      * @return List of children cells that can be resized (not hidden and not collapsed).
325      */

326     ArrayList JavaDoc getResizeableCells() {
327         ArrayList JavaDoc res = new ArrayList JavaDoc( children.size() );
328         for( Iterator JavaDoc i=children.iterator(); i.hasNext(); ) {
329             GridSplitCell child = (GridSplitCell)i.next();
330             if( child.isHidden() || child.isCollapsed() )
331                 continue;
332             res.add( child );
333         }
334         return res;
335     }
336     
337     /**
338      * @return List of children cells with non-zero resize weight.
339      */

340     ArrayList JavaDoc getResizeHungryCells() {
341         ArrayList JavaDoc res = new ArrayList JavaDoc( children.size() );
342         for( Iterator JavaDoc i=children.iterator(); i.hasNext(); ) {
343             GridSplitCell child = (GridSplitCell)i.next();
344             if( child.isHidden() || child.isCollapsed() || child.resizeWeight == 0.0 )
345                 continue;
346             res.add( child );
347         }
348         return res;
349     }
350     
351     /**
352      * Adjust cell's dimensions.
353      */

354     void resize( int width, int height, int dividerSize ) {
355         if( isSplit() ) {
356             
357             //find out what the delta is
358
int currentSize = getSize( this, dividerSize );
359             int newNetSize = (isHorizontalSplitter() ? width : height);
360             int delta = newNetSize - currentSize;
361             //System.out.println( "Delta is: " + delta );
362

363             if( delta > 0 ) {
364                 //the child cells will grow
365

366                 grow( delta, dividerSize );
367                 
368             } else if( delta < 0 ) {
369                 
370                 delta = shrink( delta, dividerSize );
371
372                 if( delta > 0 ) {
373                     //the complete delta couldn't be distributed because of minimum sizes
374
//System.out.println( "Remaining size not distributed: " + delta );
375
newNetSize -= delta;
376                     if( isHorizontalSplitter() )
377                         width -= delta;
378                     else
379                         height -= delta;
380                 }
381             }
382             
383             //resize all child cells
384
int totalSize = 0;
385             ArrayList JavaDoc visibleCells = getVisibleCells();
386             for( int i=0; i<visibleCells.size(); i++ ) {
387                 GridSplitCell child = (GridSplitCell)visibleCells.get( i );
388                 int childWidth = isHorizontalSplitter() ? child.getSize( this, dividerSize ) : width;
389                 int childHeight = isHorizontalSplitter() ? height : child.getSize( this, dividerSize );
390                 
391                 if( isHorizontalSplitter() ) {
392                     totalSize += childWidth;
393                     if( i == visibleCells.size()-1 && totalSize < newNetSize ) {
394                         //XXX this is probably overkill, this adjustment is already done in distributeDelta()
395
//adjust rounding errors (+/- one pixel) for the last cell
396
System.out.println( "Extra width: " + (newNetSize - totalSize));
397                         childWidth += newNetSize - totalSize;
398                         //child.dimension.width += newNetSize - totalSize;
399
} else {
400                         totalSize += dividerSize;
401                     }
402                 } else {
403                     totalSize += childHeight;
404                     if( i == visibleCells.size()-1 && totalSize < newNetSize ) {
405                         //adjust rounding errors (+/- one pixel) for the last cell
406
//XXX this is probably overkill, this adjustment is already done in distributeDelta()
407
System.out.println( "Extra height: " + (newNetSize - totalSize));
408                         childHeight += newNetSize - totalSize;
409                         //child.dimension.height += newNetSize - totalSize;
410
} else {
411                         totalSize += dividerSize;
412                     }
413                 }
414                 child.resize( childWidth, childHeight, dividerSize );
415             }
416             if( !(isCollapsed() || isHidden()) ) {
417                 if( isHorizontalSplitter() )
418                     setDimension( new Dimension JavaDoc( newNetSize, height ) );
419                 else
420                     setDimension( new Dimension JavaDoc( width, newNetSize ) );
421             }
422         } else {
423             setDimension( new Dimension JavaDoc( width, height ) );
424         }
425     }
426     
427     /**
428      * Grow children cell dimensions.
429      */

430     private void grow( int delta, int dividerSize ) {
431         //children with resize weight > 0 that are not collapsed
432
ArrayList JavaDoc hungryCells = getResizeHungryCells();
433
434         //grow some/all child windows
435
if( !hungryCells.isEmpty() ) {
436             //we have children with non-zero resize weight so let them consume the whole delta
437
normalizeResizeWeights( hungryCells );
438             distributeDelta( delta, hungryCells, dividerSize );
439         } else {
440             //resize all children proportionally
441
ArrayList JavaDoc resizeableCells = getResizeableCells();
442             normalizeResizeWeights( resizeableCells );
443             distributeDelta( delta, resizeableCells, dividerSize );
444         }
445     }
446     
447     /**
448      * Shrink children cell dimensions.
449      * The children cells will not shrink below their minimum sizes.
450      *
451      * @return The remaining resize delta that has not been distributed among children cells.
452      */

453     private int shrink( int negativeDelta, int dividerSize ) {
454         int delta = -negativeDelta;
455
456         //children with resize weight > 0 that are not collapsed
457
ArrayList JavaDoc hungryCells = getResizeHungryCells();
458
459         //first find out how much cells with non-zero resize weight can shrink
460
int resizeArea = calculateShrinkableArea( hungryCells, dividerSize );
461         if( resizeArea >= delta ) {
462             resizeArea = delta;
463             delta = 0;
464         } else {
465             delta -= resizeArea;
466         }
467         if( resizeArea > 0 ) {
468             //shrink cells with non-zero resize weight
469
distributeDelta( -resizeArea, hungryCells, dividerSize );
470         }
471
472         if( delta > 0 ) {
473             //hungry cells did not consume the complete delta,
474
//distribute the remaining delta among other resizeable cells
475
ArrayList JavaDoc resizeableCells = getResizeableCells();
476
477             resizeArea = calculateShrinkableArea( resizeableCells, dividerSize );
478             if( resizeArea >= delta ) {
479                 resizeArea = delta;
480                 delta = 0;
481             } else {
482                 delta -= resizeArea;
483             }
484             if( resizeArea > 0 ) {
485                 distributeDelta( -resizeArea, resizeableCells, dividerSize );
486             }
487         }
488         return delta;
489     }
490     
491     /**
492      * Sum up the available resize space of given cells. The resize space is the difference
493      * between child cell's current size and child cell's minimum size.
494      * Children cells that cannot be resized are removed from the given list and
495      * resize weights of remaining cells are normalized.
496      */

497     private int calculateShrinkableArea( ArrayList JavaDoc cells, int dividerSize ) {
498         int res = 0;
499         ArrayList JavaDoc nonShrinkable = new ArrayList JavaDoc( cells.size() );
500         for( int i=0; i<cells.size(); i++ ) {
501             GridSplitCell c = (GridSplitCell)cells.get( i );
502             if( c.isCollapsed() || c.isHidden() )
503                 continue;
504             int currentSize = c.getSize( c.getParent(), dividerSize );
505             int minSize = c.getMinSize( c.getParent(), dividerSize );
506             if( currentSize - minSize > 0 ) {
507                 res += currentSize - minSize;
508             } else {
509                 nonShrinkable.add( c );
510             }
511         }
512         
513         cells.removeAll( nonShrinkable );
514         for( int i=0; i<cells.size(); i++ ) {
515             GridSplitCell c = (GridSplitCell)cells.get( i );
516             int currentSize = c.getSize( c.getParent(), dividerSize );
517             int minSize = c.getMinSize( c.getParent(), dividerSize );
518             c.normalizedResizeWeight = 1.0*(currentSize-minSize)/res;
519         }
520         return res;
521     }
522     
523     /**
524      * Distribute the given delta among given cell dimensions using their normalized weights.
525      */

526     private void distributeDelta( int delta, ArrayList JavaDoc cells, int dividerSize ) {
527         int totalDistributed = 0;
528         for( int i=0; i<cells.size(); i++ ) {
529             GridSplitCell child = (GridSplitCell)cells.get( i );
530             int childDelta = (int)(child.normalizedResizeWeight*delta);
531             totalDistributed += childDelta;
532             if( i == cells.size()-1 ) //fix rounding errors
533
childDelta += delta - totalDistributed;
534             child.setSize( this, child.getSize( this, dividerSize ) + childDelta );
535         }
536     }
537     
538     /**
539      * Normalize resize weights so that their sum equals to 1.
540      */

541     private void normalizeResizeWeights( List JavaDoc cells ) {
542         if( cells.isEmpty() )
543             return;
544         
545         double totalWeight = 0.0;
546         for( Iterator JavaDoc i=cells.iterator(); i.hasNext(); ) {
547             GridSplitCell c = (GridSplitCell)i.next();
548             totalWeight += c.resizeWeight;
549         }
550         
551         double deltaWeight = (1.0 - totalWeight) / cells.size();
552
553         for( Iterator JavaDoc i=cells.iterator(); i.hasNext(); ) {
554             GridSplitCell c = (GridSplitCell)i.next();
555             c.normalizedResizeWeight = c.resizeWeight + deltaWeight;
556         }
557     }
558     
559     /**
560      * Set the collapsed flag for this cell and all its children.
561      */

562     public void setCollapsed( boolean collapsed ) {
563         if( this.collapsed == collapsed ) {
564             return;
565         }
566         this.collapsed = collapsed;
567         if( isSplit() ) {
568             for( Iterator JavaDoc i=children.iterator(); i.hasNext(); ) {
569                 GridSplitCell cell = (GridSplitCell)i.next();
570                 cell.setCollapsed( collapsed );
571             }
572         } else {
573             component.setVisible( !collapsed );
574         }
575     }
576     
577     public boolean isCollapsed() {
578         return collapsed;
579     }
580     
581     public void setHidden( boolean hidden ) {
582         this.isHidden = hidden;
583     }
584     
585     public boolean isHidden() {
586         return isHidden;
587     }
588     
589     private int getSize( GridSplitCell splitCell, int dividerSize ) {
590         if( splitCell.isHorizontalSplitter() )
591             return getDimension( dividerSize ).width;
592         return getDimension( dividerSize ).height;
593     }
594     
595     private int getMinSize( GridSplitCell splitCell, int dividerSize ) {
596         if( splitCell.isHorizontalSplitter() )
597             return getMinimumSize( dividerSize ).width;
598         return getMinimumSize( dividerSize ).height;
599     }
600     
601     private void setSize( GridSplitCell splitCell, int newSize ) {
602         if( isCollapsed() || isHidden() )
603             return;
604         
605         if( splitCell.isHorizontalSplitter() )
606             dimension.width = newSize;
607         else
608             dimension.height = newSize;
609     }
610     
611     private void setDimension( Dimension JavaDoc newDimension ) {
612         if( isCollapsed() || isHidden() )
613             return;
614         
615         this.dimension = newDimension;
616     }
617     
618     public Dimension JavaDoc getDimension( int dividerSize ) {
619         if( isHidden() || isCollapsed() )
620             return new Dimension JavaDoc( 0, 0 );
621         
622         if( isSplit() ) {
623             Dimension JavaDoc res = new Dimension JavaDoc( 0, 0 );
624             ArrayList JavaDoc visibleCells = getVisibleCells();
625             for( int i=0; i<visibleCells.size(); i++ ) {
626                 GridSplitCell child = (GridSplitCell)visibleCells.get( i );
627                 Dimension JavaDoc childDim = child.getDimension( dividerSize );
628                 if( isHorizontalSplitter() ) {
629                     res.height = this.dimension.height;
630                     res.width += childDim.width;
631                 } else {
632                     res.height += childDim.height;
633                     res.width = this.dimension.width;
634                 }
635             }
636             if( isHorizontalSplitter() ) {
637                 res.width += (visibleCells.size()-1)*dividerSize;
638             } else {
639                 res.height += (visibleCells.size()-1)*dividerSize;
640             }
641             return res;
642         }
643         return new Dimension JavaDoc( dimension );
644     }
645     
646     private static void copy( GridSplitCell source, GridSplitCell target ) {
647         target.component = source.component;
648         target.children = new ArrayList JavaDoc( source.children );
649         for( Iterator JavaDoc i=target.children.iterator(); i.hasNext(); ) {
650             GridSplitCell cell = (GridSplitCell)i.next();
651             cell.setParent( target );
652         }
653         //target.dividerSize = source.dividerSize;
654
target.resizeWeight = source.resizeWeight;
655         target.splitterOrientation = source.splitterOrientation;
656         target.collapsed = source.collapsed;
657         target.dimension = new Dimension JavaDoc( source.dimension );
658         target.isHidden = source.isHidden;
659         target.location = new Point JavaDoc( source.location );
660     }
661     
662     /**
663      * Set cell's new location. If the cell contains an actual swing component
664      * then its bounds and location are also set.
665      */

666     void setLocation( int x, int y, int dividerSize ) {
667         location.x = x;
668         location.y = y;
669         if( isSplit() ) {
670             ArrayList JavaDoc visibleCells = getVisibleCells();
671             int childX = x;
672             int childY = y;
673             for( int i=0; i<visibleCells.size(); i++ ) {
674                 GridSplitCell child = (GridSplitCell)visibleCells.get( i );
675                 Dimension JavaDoc d = child.getDimension( dividerSize );
676                 child.setLocation( childX, childY, dividerSize );
677
678                 if( isHorizontalSplitter() )
679                     childX += d.width + dividerSize;
680                 else
681                     childY += d.height + dividerSize;
682             }
683         } else {
684             component.setLocation( location );
685             component.setSize( dimension );
686         }
687     }
688     
689     Point JavaDoc getLocation() {
690         return new Point JavaDoc( location );
691     }
692 }
Popular Tags