KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > windows > view > ui > MultiSplitPane


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.core.windows.view.ui;
21
22 import java.awt.Component JavaDoc;
23 import java.awt.Container JavaDoc;
24 import java.awt.Cursor JavaDoc;
25 import java.awt.Dimension JavaDoc;
26 import java.awt.Graphics JavaDoc;
27 import java.awt.LayoutManager JavaDoc;
28 import java.awt.Point JavaDoc;
29 import java.awt.event.MouseEvent JavaDoc;
30 import java.awt.event.MouseListener JavaDoc;
31 import java.awt.event.MouseMotionListener JavaDoc;
32 import java.util.ArrayList JavaDoc;
33 import java.util.Iterator JavaDoc;
34 import java.util.List JavaDoc;
35 import javax.accessibility.Accessible JavaDoc;
36 import javax.accessibility.AccessibleContext JavaDoc;
37 import javax.accessibility.AccessibleRole JavaDoc;
38 import javax.accessibility.AccessibleState JavaDoc;
39 import javax.accessibility.AccessibleStateSet JavaDoc;
40 import javax.swing.JPanel JavaDoc;
41 import javax.swing.JSplitPane JavaDoc;
42 import javax.swing.UIManager JavaDoc;
43 import org.netbeans.core.windows.view.ViewElement;
44
45
46
47
48 /**
49  * A split pane that can display two or more resizeable components separated
50  * by draggable split bars. The child components can be arranged in a row
51  * (horizontal orientation) or in a column (vertical orientation).
52  *
53  * @author Stanislav Aubrecht
54  */

55 public class MultiSplitPane extends JPanel JavaDoc
56                 implements MouseMotionListener JavaDoc, MouseListener JavaDoc {
57     
58     //the divider that user is currently dragging with a mouse
59
private MultiSplitDivider draggingDivider;
60     //a list of draggable split dividers
61
private ArrayList JavaDoc<MultiSplitDivider> dividers = new ArrayList JavaDoc<MultiSplitDivider>();
62     //true if the list of children has been updated
63
private boolean dirty = true;
64     //a list of children cells (component wrappers)
65
private ArrayList JavaDoc<MultiSplitCell> cells = new ArrayList JavaDoc<MultiSplitCell>();
66     //split orientation JSplitPane.HORIZONTAL_SPLIT or JSplitPane.VERTICAL_SPLIT
67
private int orientation;
68     //the width or height of the divider bar
69
private int dividerSize;
70     
71     private boolean userMovedSplit = false;
72     
73     public MultiSplitPane() {
74         setLayout( new MultiSplitLayout() );
75         addMouseMotionListener( this );
76         addMouseListener( this );
77         
78         //get default divider size from SplitPane's UI
79
dividerSize = UIManager.getInt("SplitPane.dividerSize"); //NOI18N
80
if( 0 == dividerSize )
81             dividerSize = 7;
82     }
83     
84     /**
85      * Set new list of components to be displayed in the split and also their
86      * resize weights and initial split weights.
87      *
88      * @param orientation Use JSplitPane.HORIZONTAL_SPLIT for horizontal orientation
89      * (children components are arranged in a single row) or JSplitPane.VERTICAL_SPLIT
90      * for vertical orientation (children components are arranged in a single column).
91      * @param childrenComponents ViewElements to be displayed in the split pane.
92      * @param splitWeights Initial split positions, i.e. what portion of the split window
93      * the child components initially require.
94      */

95     public void setChildren( int orientation,
96                              ViewElement[] childrenViews,
97                              double[] splitWeights ) {
98         
99         assert childrenViews.length == splitWeights.length;
100
101         this.orientation = orientation;
102
103         //list of components currently displayed in the split
104
List JavaDoc<Component JavaDoc> currentComponents = collectComponents();
105         
106         cells.clear();
107         for( int i=0; i<childrenViews.length; i++ ) {
108             cells.add( new MultiSplitCell( childrenViews[i],
109                                            splitWeights[i],
110                                            isHorizontalSplit() ) );
111         }
112         List JavaDoc<Component JavaDoc> updatedComponents = collectComponents();
113         
114         ArrayList JavaDoc<Component JavaDoc> removed = new ArrayList JavaDoc<Component JavaDoc>( currentComponents );
115         removed.removeAll( updatedComponents ); //componets that were removed from the split
116
ArrayList JavaDoc<Component JavaDoc> added = new ArrayList JavaDoc<Component JavaDoc>( updatedComponents );
117         added.removeAll( currentComponents ); //components that were added to the split
118

119         for(Component JavaDoc c: removed) {
120             remove( c );
121         }
122         
123         for(Component JavaDoc c: added) {
124             add( c );
125         }
126
127         dirty = true;
128     }
129     
130     int getCellCount() {
131         return cells.size();
132     }
133     
134     MultiSplitCell cellAt( int index ) {
135         assert index >= 0;
136         assert index < cells.size();
137         return (MultiSplitCell)cells.get( index );
138     }
139     
140     /**
141      * Remove child component at the given position from the split.
142      */

143     public void removeViewElementAt( int index ) {
144         if( index < 0 || index >= cells.size() )
145             return;
146         MultiSplitCell cellToRemove = (MultiSplitCell)cells.remove( index );
147         remove( cellToRemove.getComponent() );
148         dirty = true;
149     }
150     
151     public int getOrientation() {
152         return orientation;
153     }
154     
155     public boolean isVerticalSplit() {
156         return orientation == JSplitPane.VERTICAL_SPLIT;
157     }
158     
159     public boolean isHorizontalSplit() {
160         return orientation == JSplitPane.HORIZONTAL_SPLIT;
161     }
162     
163     private List JavaDoc<Component JavaDoc> collectComponents() {
164         ArrayList JavaDoc<Component JavaDoc> res = new ArrayList JavaDoc<Component JavaDoc>( getCellCount() );
165         for( int i=0; i<getCellCount(); i++ ) {
166             MultiSplitCell cell = cellAt( i );
167             Component JavaDoc c = cell.getComponent();
168             assert null != c;
169             res.add( c );
170         }
171         return res;
172     }
173     
174     /**
175      * Calculate split weights for all children components according to split's current dimensions.
176      */

177     public void calculateSplitWeights( List JavaDoc<ViewElement> visibleViews, List JavaDoc<Double JavaDoc> splitWeights ) {
178         double size = isHorizontalSplit() ? getSize().width : getSize().height;
179         if( size <= 0.0 )
180             return;
181         for( int i=0; i<getCellCount(); i++ ) {
182             MultiSplitCell cell = cellAt( i );
183             double weight = cell.getSize() / size;
184             splitWeights.add( Double.valueOf( weight ) );
185             visibleViews.add( cell.getViewElement() );
186         }
187     }
188     
189     public int getDividerSize() {
190         return dividerSize;
191     }
192     
193     public void setDividerSize( int newDividerSize ) {
194         dirty |= newDividerSize != dividerSize;
195         this.dividerSize = newDividerSize;
196     }
197     
198     public Dimension JavaDoc getMinimumSize() {
199         //the minimum size is a sum of minimum sizes of all children components
200
Dimension JavaDoc d = new Dimension JavaDoc();
201         for( int i=0; i<getCellCount(); i++ ) {
202             MultiSplitCell cell = cellAt( i );
203             int size = cell.getMinimumSize();
204             Dimension JavaDoc minDim = cell.getComponent().getMinimumSize();
205             if( isHorizontalSplit() ) {
206                 d.width += size;
207                 if( minDim.height > d.height )
208                     d.height = minDim.height;
209             } else {
210                 d.height += size;
211                 if( minDim.width > d.width )
212                     d.width = minDim.width;
213             }
214         }
215         //the minimum size must hold at least the size of all split bars
216
if( isHorizontalSplit() ) {
217             d.width += (getCellCount()-1) * getDividerSize();
218         } else {
219             d.height += (getCellCount()-1) * getDividerSize();
220         }
221         return d;
222     }
223
224     public void mouseMoved( MouseEvent JavaDoc e ) {
225         switchCursor( e );
226         e.consume();
227     }
228
229     public void mouseDragged( MouseEvent JavaDoc e ) {
230         if( null == draggingDivider )
231             return;
232         
233         draggingDivider.dragTo( e.getPoint() );
234         e.consume();
235     }
236
237     public void mouseReleased(MouseEvent JavaDoc e) {
238         if( null == draggingDivider )
239             return;
240         
241         final Point JavaDoc p = new Point JavaDoc( e.getPoint() );
242         draggingDivider.finishDraggingTo( p );
243         draggingDivider = null;
244         setCursor( Cursor.getDefaultCursor() );
245         e.consume();
246     }
247
248     public void mousePressed(MouseEvent JavaDoc e) {
249         MultiSplitDivider divider = dividerAtPoint( e.getPoint() );
250         if( null == divider )
251             return;
252         
253         draggingDivider = divider;
254         divider.startDragging( e.getPoint() );
255         e.consume();
256     }
257
258     public void mouseExited(MouseEvent JavaDoc e) {
259         if( null == draggingDivider ) {
260             setCursor( Cursor.getDefaultCursor() );
261         }
262         e.consume();
263     }
264
265     public void mouseEntered(MouseEvent JavaDoc e) {
266     }
267
268     public void mouseClicked(MouseEvent JavaDoc e) {
269     }
270     
271
272     private void switchCursor( MouseEvent JavaDoc e ) {
273         MultiSplitDivider divider = dividerAtPoint( e.getPoint() );
274         if( null == divider ) {
275             setCursor( Cursor.getDefaultCursor() );
276         } else {
277             if( divider.isHorizontal() ) {
278                 setCursor( Cursor.getPredefinedCursor( Cursor.E_RESIZE_CURSOR ) );
279             } else {
280                 setCursor( Cursor.getPredefinedCursor( Cursor.N_RESIZE_CURSOR ) );
281             }
282         }
283     }
284
285     private MultiSplitDivider dividerAtPoint( Point JavaDoc p ) {
286         for(MultiSplitDivider d: dividers) {
287             if( d.containsPoint( p ) )
288                 return d;
289         }
290         return null;
291     }
292
293     public void paint( Graphics JavaDoc g ) {
294         super.paint(g);
295         //paint split bars
296
for(MultiSplitDivider divider: dividers) {
297             divider.paint( g );
298         }
299     }
300     
301     /**
302      * Shrink/grow children components.
303      *
304      * @param newSize Split pane's new widht/height depending on split orientation.
305      */

306     private void resize( int newSize ) {
307         //find out what the delta is
308
int currentSize = 0;
309         for( int i=0; i<getCellCount(); i++ ) {
310             currentSize += cellAt( i ).getRequiredSize();
311         }
312         int totalDividerSize = getDividerSize() * (getCellCount()-1);
313         int newNetSize = newSize - totalDividerSize;
314         int delta = newNetSize - currentSize;
315
316         if( delta > 0 ) {
317             //the child cells will grow
318

319             grow( delta );
320
321         } else if( delta < 0 ) {
322
323             delta = shrink( delta );
324
325             if( delta > 0 ) {
326                 //the complete delta couldn't be distributed because of minimum sizes constraints
327
newNetSize -= delta;
328             }
329         }
330             
331         //check for rounding errors and add 'missing' pixel(s) to the last cell
332
int totalSize = 0;
333         for( int i=0; i<getCellCount(); i++ ) {
334             MultiSplitCell cell = cellAt( i );
335             totalSize += cell.getRequiredSize();
336         }
337         if( totalSize < newNetSize ) {
338             MultiSplitCell lastCell = cellAt( getCellCount()-1 );
339             lastCell.setRequiredSize( lastCell.getRequiredSize() + (newNetSize-totalSize) );
340         }
341     }
342     
343     /**
344      * Grow children cell dimensions.
345      */

346     private void grow( int delta ) {
347         //children with resize weight > 0 that are not collapsed
348
List JavaDoc<MultiSplitCell> hungryCells = getResizeHungryCells();
349
350         //grow some/all child windows
351
if( !hungryCells.isEmpty() ) {
352             //we have children with non-zero resize weight so let them consume the whole delta
353
normalizeResizeWeights( hungryCells );
354             distributeDelta( delta, hungryCells );
355         } else {
356             //resize all children proportionally
357
ArrayList JavaDoc<MultiSplitCell> resizeableCells = new ArrayList JavaDoc<MultiSplitCell>( cells );
358             normalizeResizeWeights( resizeableCells );
359             distributeDelta( delta, resizeableCells );
360         }
361     }
362     
363     /**
364      * Shrink children cell dimensions.
365      * The children cells will not shrink below their minimum sizes.
366      *
367      * @return The remaining resize delta that has not been distributed among children cells.
368      */

369     private int shrink( int negativeDelta ) {
370         int delta = -negativeDelta;
371
372         //children with resize weight > 0 that are not collapsed
373
List JavaDoc<MultiSplitCell> hungryCells = getResizeHungryCells();
374
375         //first find out how much cells with non-zero resize weight can shrink
376
int resizeArea = calculateShrinkableArea( hungryCells );
377         if( resizeArea >= delta ) {
378             resizeArea = delta;
379             delta = 0;
380         } else {
381             delta -= resizeArea;
382         }
383         if( resizeArea > 0 ) {
384             //shrink cells with non-zero resize weight
385
distributeDelta( -resizeArea, hungryCells );
386         }
387
388         if( delta > 0 ) {
389             //hungry cells did not consume the complete delta,
390
//distribute the remaining delta among other resizeable cells
391
ArrayList JavaDoc<MultiSplitCell> resizeableCells = new ArrayList JavaDoc<MultiSplitCell>( cells );
392
393             resizeArea = calculateShrinkableArea( resizeableCells );
394             if( resizeArea >= delta ) {
395                 resizeArea = delta;
396                 delta = 0;
397             } else {
398                 delta -= resizeArea;
399             }
400             if( resizeArea > 0 ) {
401                 distributeDelta( -resizeArea, resizeableCells );
402             }
403         }
404         return delta;
405     }
406     
407     /**
408      * Sum up the available resize space of given cells. The resize space is the difference
409      * between child cell's current size and child cell's minimum size.
410      * Children cells that cannot be resized are removed from the given list and
411      * resize weights of remaining cells are normalized.
412      */

413     private int calculateShrinkableArea( List JavaDoc<MultiSplitCell> cells ) {
414         int res = 0;
415         ArrayList JavaDoc<MultiSplitCell> nonShrinkable = new ArrayList JavaDoc<MultiSplitCell>( cells.size() );
416         for( int i=0; i<cells.size(); i++ ) {
417             MultiSplitCell c = (MultiSplitCell)cells.get( i );
418             int currentSize = c.getRequiredSize();
419             int minSize = c.getMinimumSize();
420             if( currentSize - minSize > 0 ) {
421                 res += currentSize - minSize;
422             } else {
423                 nonShrinkable.add( c );
424             }
425         }
426         
427         cells.removeAll( nonShrinkable );
428         for(MultiSplitCell c: cells) {
429             int currentSize = c.getRequiredSize();
430             int minSize = c.getMinimumSize();
431             c.setNormalizedResizeWeight( 1.0*(currentSize-minSize)/res );
432         }
433         
434         return res;
435     }
436     
437     /**
438      * Distribute the given delta among given cell dimensions using their normalized weights.
439      */

440     private void distributeDelta( int delta, List JavaDoc<MultiSplitCell> cells ) {
441         int totalDistributed = 0;
442         for( int i=0; i<cells.size(); i++ ) {
443             MultiSplitCell cell = cells.get( i );
444             int cellDelta = (int)(cell.getNormalizedResizeWeight()*delta);
445             totalDistributed += cellDelta;
446             if( i == cells.size()-1 ) //fix rounding errors
447
cellDelta += delta - totalDistributed;
448             cell.setRequiredSize( cell.getRequiredSize() + cellDelta );
449         }
450     }
451     
452     /**
453      * Normalize resize weights so that their sum equals to 1.
454      */

455     private void normalizeResizeWeights( List JavaDoc cells ) {
456         if( cells.isEmpty() )
457             return;
458         
459         double totalWeight = 0.0;
460         for( Iterator JavaDoc i=cells.iterator(); i.hasNext(); ) {
461             MultiSplitCell c = (MultiSplitCell)i.next();
462             totalWeight += c.getResizeWeight();
463         }
464         
465         double deltaWeight = (1.0 - totalWeight) / cells.size();
466
467         for( Iterator JavaDoc i=cells.iterator(); i.hasNext(); ) {
468             MultiSplitCell c = (MultiSplitCell)i.next();
469             c.setNormalizedResizeWeight( c.getResizeWeight() + deltaWeight );
470         }
471     }
472     
473     /**
474      * @return List of children cells with non-zero resize weight.
475      */

476     List JavaDoc<MultiSplitCell> getResizeHungryCells() {
477         List JavaDoc<MultiSplitCell> res = new ArrayList JavaDoc<MultiSplitCell>( cells.size() );
478         for( int i=0; i<getCellCount(); i++ ) {
479             MultiSplitCell cell = cellAt( i );
480             if( cell.getResizeWeight() <= 0.0 )
481                 continue;
482             res.add( cell );
483         }
484         return res;
485     }
486
487     /**
488      * (Re)create wrapper classes for split divider rectangles.
489      */

490     void createDividers() {
491         dividers.clear();
492         for( int i=0; i<getCellCount()-1; i++ ) {
493             MultiSplitCell first = cellAt( i );
494             MultiSplitCell second = cellAt( i+1 );
495
496             MultiSplitDivider divider = new MultiSplitDivider( MultiSplitPane.this, first, second );
497             dividers.add( divider );
498         }
499     }
500     
501     void splitterMoved() {
502         userMovedSplit = true;
503         validate();
504     }
505     
506     // *************************************************************************
507
// Accessibility
508

509     public AccessibleContext JavaDoc getAccessibleContext() {
510         if( accessibleContext == null ) {
511             accessibleContext = new AccessibleMultiSplitPane();
512         }
513         return accessibleContext;
514     }
515     
516     int getDividerAccessibleIndex( MultiSplitDivider divider ) {
517         int res = dividers.indexOf( divider );
518         res += getAccessibleContext().getAccessibleChildrenCount() - dividers.size();
519         return res;
520     }
521
522     protected class AccessibleMultiSplitPane extends AccessibleJComponent {
523         public AccessibleStateSet JavaDoc getAccessibleStateSet() {
524             AccessibleStateSet JavaDoc states = super.getAccessibleStateSet();
525             if( isHorizontalSplit() ) {
526                 states.add( AccessibleState.HORIZONTAL );
527             } else {
528                 states.add( AccessibleState.VERTICAL );
529             }
530             return states;
531         }
532
533         public AccessibleRole JavaDoc getAccessibleRole() {
534             return AccessibleRole.SPLIT_PANE;
535         }
536
537         public Accessible JavaDoc getAccessibleAt( Point JavaDoc p ) {
538             MultiSplitDivider divider = dividerAtPoint( p );
539             if( null != divider ) {
540                 return divider;
541             }
542             return super.getAccessibleAt( p );
543         }
544
545         public Accessible JavaDoc getAccessibleChild(int i) {
546
547             int childrenCount = super.getAccessibleChildrenCount();
548             if( i < childrenCount ) {
549                 return super.getAccessibleChild( i );
550             }
551             if( i-childrenCount >= dividers.size() ) {
552                 return null;
553             }
554             
555             MultiSplitDivider divider = dividers.get( i-childrenCount );
556             return divider;
557         }
558
559         public int getAccessibleChildrenCount() {
560             return super.getAccessibleChildrenCount() + dividers.size();
561         }
562         
563     } // inner class AccessibleMultiSplitPane
564

565     // *************************************************************************
566

567     protected class MultiSplitLayout implements LayoutManager JavaDoc {
568         
569         public void layoutContainer( Container JavaDoc c ) {
570             if( c != MultiSplitPane.this )
571                 return;
572             
573             int newSize = isHorizontalSplit() ? getSize().width : getSize().height;
574             //if the list of children has been modified then let the cells calculate
575
//their initial sizes
576
for( int i=0; i<getCellCount(); i++ ) {
577                 MultiSplitCell cell = cellAt( i );
578                 cell.maybeResetToInitialSize( newSize );
579             }
580             
581             //calculate new sizes for children cells
582
resize( newSize );
583             
584             //set children bounds
585
layoutCells();
586
587             if( userMovedSplit ) {
588                 //user dragged splitbar to a new location -> fire a property change
589
userMovedSplit = false;
590                 firePropertyChange( "splitPositions", null, this );
591             }
592             
593             //update the rectangles of split divider bars
594
createDividers();
595         }
596         
597         private void layoutCells() {
598             int x = 0;
599             int y = 0;
600             int width = getWidth();
601             int height = getHeight();
602             for( int i=0; i<getCellCount(); i++ ) {
603                 MultiSplitCell cell = cellAt( i );
604                 
605                 //the child component may have been removed from this container
606
//(e.g. the view has been maximalized)
607
if( cell.getComponent().getParent() != MultiSplitPane.this ) {
608                     add( cell.getComponent() );
609                 }
610                 
611                 if( isHorizontalSplit() ) {
612                     width = cell.getRequiredSize();
613                 } else {
614                     height = cell.getRequiredSize();
615                 }
616                 cell.layout( x, y, width, height );
617                 
618                 if( isHorizontalSplit() ) {
619                     x += width;
620                     if( i < getCellCount() ) {
621                         x += getDividerSize();
622                     }
623                 } else {
624                     y += height;
625                     if( i < getCellCount()-1 ) {
626                         y += getDividerSize();
627                     }
628                 }
629             }
630         }
631
632         public Dimension JavaDoc minimumLayoutSize(Container JavaDoc container) {
633             return container.getSize();
634         }
635
636         public Dimension JavaDoc preferredLayoutSize(Container JavaDoc container) {
637             return container.getSize();
638         }
639
640         public void removeLayoutComponent(Component JavaDoc c) {}
641
642         public void addLayoutComponent(String JavaDoc string, Component JavaDoc c) {}
643     }
644 }
645
Popular Tags