001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * ----------------------
028     * BorderArrangement.java
029     * ----------------------
030     * (C) Copyright 2004-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes:
036     * --------
037     * 22-Oct-2004 : Version 1 (DG);
038     * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
039     * 24-Feb-2005 : Improved arrangeRR() method (DG);
040     * 03-May-2005 : Implemented Serializable and added equals() method (DG);
041     * 13-May-2005 : Fixed bugs in the arrange() method (DG);
042     * 
043     */
044    
045    package org.jfree.chart.block;
046    
047    import java.awt.Graphics2D;
048    import java.awt.geom.Rectangle2D;
049    import java.io.Serializable;
050    
051    import org.jfree.data.Range;
052    import org.jfree.ui.RectangleEdge;
053    import org.jfree.ui.Size2D;
054    import org.jfree.util.ObjectUtilities;
055    
056    /**
057     * An arrangement manager that lays out blocks in a similar way to
058     * Swing's BorderLayout class.
059     */
060    public class BorderArrangement implements Arrangement, Serializable {
061        
062        /** For serialization. */
063        private static final long serialVersionUID = 506071142274883745L;
064        
065        /** The block (if any) at the center of the layout. */
066        private Block centerBlock;
067    
068        /** The block (if any) at the top of the layout. */
069        private Block topBlock;
070        
071        /** The block (if any) at the bottom of the layout. */
072        private Block bottomBlock;
073        
074        /** The block (if any) at the left of the layout. */
075        private Block leftBlock;
076        
077        /** The block (if any) at the right of the layout. */
078        private Block rightBlock;
079        
080        /**
081         * Creates a new instance.
082         */
083        public BorderArrangement() {
084        }
085        
086        /**
087         * Adds a block to the arrangement manager at the specified edge.
088         * 
089         * @param block  the block (<code>null</code> permitted).
090         * @param key  the edge (an instance of {@link RectangleEdge}) or 
091         *             <code>null</code> for the center block.
092         */
093        public void add(Block block, Object key) {
094            
095            if (key == null) {
096                this.centerBlock = block;
097            }
098            else {
099                RectangleEdge edge = (RectangleEdge) key;
100                if (edge == RectangleEdge.TOP) {
101                    this.topBlock = block;
102                }
103                else if (edge == RectangleEdge.BOTTOM) {
104                    this.bottomBlock = block;
105                }
106                else if (edge == RectangleEdge.LEFT) {
107                    this.leftBlock = block;
108                }
109                else if (edge == RectangleEdge.RIGHT) {
110                    this.rightBlock = block;
111                }
112            }
113        }
114        
115        /**
116         * Arranges the items in the specified container, subject to the given 
117         * constraint.
118         * 
119         * @param container  the container.
120         * @param g2  the graphics device.
121         * @param constraint  the constraint.
122         * 
123         * @return The block size.
124         */
125        public Size2D arrange(BlockContainer container, 
126                              Graphics2D g2, 
127                              RectangleConstraint constraint) {
128            RectangleConstraint contentConstraint 
129                    = container.toContentConstraint(constraint);
130            Size2D contentSize = null;
131            LengthConstraintType w = contentConstraint.getWidthConstraintType();
132            LengthConstraintType h = contentConstraint.getHeightConstraintType();
133            if (w == LengthConstraintType.NONE) {
134                if (h == LengthConstraintType.NONE) {
135                    contentSize = arrangeNN(container, g2);  
136                }
137                else if (h == LengthConstraintType.FIXED) {
138                    throw new RuntimeException("Not implemented.");  
139                }
140                else if (h == LengthConstraintType.RANGE) {
141                    throw new RuntimeException("Not implemented.");  
142                }
143            }
144            else if (w == LengthConstraintType.FIXED) {
145                if (h == LengthConstraintType.NONE) {
146                    contentSize = arrangeFN(container, g2, constraint.getWidth());  
147                }
148                else if (h == LengthConstraintType.FIXED) {
149                    contentSize = arrangeFF(container, g2, constraint);  
150                }
151                else if (h == LengthConstraintType.RANGE) {
152                    contentSize = arrangeFR(container, g2, constraint);  
153                }
154            }
155            else if (w == LengthConstraintType.RANGE) {
156                if (h == LengthConstraintType.NONE) {
157                    throw new RuntimeException("Not implemented.");  
158                }
159                else if (h == LengthConstraintType.FIXED) {
160                    throw new RuntimeException("Not implemented.");  
161                }
162                else if (h == LengthConstraintType.RANGE) {
163                    contentSize = arrangeRR(container, constraint.getWidthRange(),
164                            constraint.getHeightRange(), g2);  
165                }
166            }
167            return new Size2D(container.calculateTotalWidth(contentSize.getWidth()),
168                    container.calculateTotalHeight(contentSize.getHeight()));
169        }
170        
171        /**
172         * Performs an arrangement without constraints.
173         * 
174         * @param container  the container.
175         * @param g2  the graphics device.
176         * 
177         * @return The container size after the arrangement.
178         */
179        protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
180            double[] w = new double[5];
181            double[] h = new double[5];
182            if (this.topBlock != null) {
183                Size2D size = this.topBlock.arrange(g2, RectangleConstraint.NONE);
184                w[0] = size.width;
185                h[0] = size.height;
186            }
187            if (this.bottomBlock != null) {
188                Size2D size = this.bottomBlock.arrange(g2, 
189                            RectangleConstraint.NONE);
190                w[1] = size.width;
191                h[1] = size.height;
192            }
193            if (this.leftBlock != null) {
194                Size2D size = this.leftBlock.arrange(g2, RectangleConstraint.NONE);
195                w[2] = size.width;
196                h[2] = size.height;
197           }
198            if (this.rightBlock != null) {
199                Size2D size = this.rightBlock.arrange(g2, RectangleConstraint.NONE);
200                w[3] = size.width;
201                h[3] = size.height;
202            }
203            
204            h[2] = Math.max(h[2], h[3]);
205            h[3] = h[2];
206            
207            if (this.centerBlock != null) {
208                Size2D size = this.centerBlock.arrange(g2, 
209                            RectangleConstraint.NONE);
210                w[4] = size.width;
211                h[4] = size.height;
212            }
213            double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
214            double centerHeight = Math.max(h[2], Math.max(h[3], h[4]));
215            double height = h[0] + h[1] + centerHeight;
216            if (this.topBlock != null) {
217                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width, 
218                            h[0]));
219            }
220            if (this.bottomBlock != null) {
221                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, 
222                            height - h[1], width, h[1]));
223            }
224            if (this.leftBlock != null) {
225                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2], 
226                            centerHeight));
227            }
228            if (this.rightBlock != null) {
229                this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3], 
230                            h[0], w[3], centerHeight));
231            }
232            
233            if (this.centerBlock != null) {
234                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], 
235                            width - w[2] - w[3], centerHeight));
236            }
237            return new Size2D(width, height);
238        }
239    
240        /**
241         * Performs an arrangement with a fixed width and a range for the height.
242         * 
243         * @param container  the container.
244         * @param g2  the graphics device.
245         * @param constraint  the constraint.
246         * 
247         * @return The container size after the arrangement.
248         */
249        protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
250                                   RectangleConstraint constraint) {
251            Size2D size1 = arrangeFN(container, g2, constraint.getWidth());
252            if (constraint.getHeightRange().contains(size1.getHeight())) {
253                return size1;   
254            }
255            else {
256                double h = constraint.getHeightRange().constrain(size1.getHeight());
257                RectangleConstraint c2 = constraint.toFixedHeight(h);
258                return arrange(container, g2, c2);   
259            }
260        }
261        
262        /** 
263         * Arranges the container width a fixed width and no constraint on the 
264         * height.
265         * 
266         * @param container  the container.
267         * @param g2  the graphics device.
268         * @param width  the fixed width.
269         * 
270         * @return The container size after arranging the contents.
271         */
272        protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
273                                   double width) {
274            double[] w = new double[5];
275            double[] h = new double[5];
276            RectangleConstraint c1 = new RectangleConstraint(width, null, 
277                            LengthConstraintType.FIXED, 0.0, null, 
278                            LengthConstraintType.NONE);
279            if (this.topBlock != null) {
280                Size2D size = this.topBlock.arrange(g2, c1);
281                w[0] = size.width;
282                h[0] = size.height;
283            }
284            if (this.bottomBlock != null) {
285                Size2D size = this.bottomBlock.arrange(g2, c1);
286                w[1] = size.width;
287                h[1] = size.height;
288            }
289            RectangleConstraint c2 = new RectangleConstraint(0.0, 
290                            new Range(0.0, width), LengthConstraintType.RANGE, 
291                            0.0, null, LengthConstraintType.NONE);
292            if (this.leftBlock != null) {
293                Size2D size = this.leftBlock.arrange(g2, c2);
294                w[2] = size.width;
295                h[2] = size.height;
296            }
297            if (this.rightBlock != null) {
298                double maxW = Math.max(width - w[2], 0.0);
299                RectangleConstraint c3 = new RectangleConstraint(0.0, 
300                            new Range(Math.min(w[2], maxW), maxW), 
301                            LengthConstraintType.RANGE, 0.0, null, 
302                            LengthConstraintType.NONE);    
303                Size2D size = this.rightBlock.arrange(g2, c3);
304                w[3] = size.width;
305                h[3] = size.height;
306            }
307            
308            h[2] = Math.max(h[2], h[3]);
309            h[3] = h[2];
310            
311            if (this.centerBlock != null) {
312                RectangleConstraint c4 = new RectangleConstraint(width - w[2] 
313                        - w[3], null, LengthConstraintType.FIXED, 0.0, null, 
314                        LengthConstraintType.NONE);    
315                Size2D size = this.centerBlock.arrange(g2, c4);
316                w[4] = size.width;
317                h[4] = size.height;
318            }
319            double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
320            return arrange(container, g2, new RectangleConstraint(width, height));
321        }
322    
323        /**
324         * Performs an arrangement with range constraints on both the vertical 
325         * and horizontal sides.
326         * 
327         * @param container  the container.
328         * @param widthRange  the allowable range for the container width.
329         * @param heightRange  the allowable range for the container height.
330         * @param g2  the graphics device.
331         * 
332         * @return The container size.
333         */
334        protected Size2D arrangeRR(BlockContainer container, 
335                                   Range widthRange, Range heightRange, 
336                                   Graphics2D g2) {
337            double[] w = new double[5];
338            double[] h = new double[5];
339            if (this.topBlock != null) {
340                RectangleConstraint c1 = new RectangleConstraint(widthRange, 
341                            heightRange);
342                Size2D size = this.topBlock.arrange(g2, c1);
343                w[0] = size.width;
344                h[0] = size.height;
345            }
346            if (this.bottomBlock != null) {
347                Range heightRange2 = Range.shift(heightRange, -h[0], false);
348                RectangleConstraint c2 = new RectangleConstraint(widthRange, 
349                            heightRange2);  
350                Size2D size = this.bottomBlock.arrange(g2, c2);
351                w[1] = size.width;
352                h[1] = size.height;
353            }
354            Range heightRange3 = Range.shift(heightRange, -(h[0] + h[1]));
355            if (this.leftBlock != null) {
356                RectangleConstraint c3 = new RectangleConstraint(widthRange, 
357                            heightRange3);
358                Size2D size = this.leftBlock.arrange(g2, c3);
359                w[2] = size.width;
360                h[2] = size.height;
361            }
362            Range widthRange2 = Range.shift(widthRange, -w[2], false);
363            if (this.rightBlock != null) {
364                RectangleConstraint c4 = new RectangleConstraint(widthRange2, 
365                            heightRange3);
366                Size2D size = this.rightBlock.arrange(g2, c4);
367                w[3] = size.width;
368                h[3] = size.height;
369            }
370            
371            h[2] = Math.max(h[2], h[3]);
372            h[3] = h[2];
373            Range widthRange3 = Range.shift(widthRange, -(w[2] + w[3]), false);
374            if (this.centerBlock != null) {
375                RectangleConstraint c5 = new RectangleConstraint(widthRange3, 
376                            heightRange3);
377                // TODO:  the width and height ranges should be reduced by the 
378                // height required for the top and bottom, and the width required
379                // by the left and right 
380                Size2D size = this.centerBlock.arrange(g2, c5);
381                w[4] = size.width;
382                h[4] = size.height;
383            }
384            double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
385            double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
386            if (this.topBlock != null) {
387                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width, 
388                            h[0]));
389            }
390            if (this.bottomBlock != null) {
391                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, 
392                            height - h[1], width, h[1]));
393            }
394            if (this.leftBlock != null) {
395                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2], 
396                            h[2]));
397            }
398            if (this.rightBlock != null) {
399                this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3], 
400                            h[0], w[3], h[3]));
401            }
402            
403            if (this.centerBlock != null) {
404                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], 
405                            width - w[2] - w[3], height - h[0] - h[1]));
406            }
407            return new Size2D(width, height);
408        }
409    
410        /**
411         * Arranges the items within a container.
412         * 
413         * @param container  the container.
414         * @param constraint  the constraint.
415         * @param g2  the graphics device.
416         * 
417         * @return The container size after the arrangement.
418         */
419        protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
420                                   RectangleConstraint constraint) {
421            double[] w = new double[5];
422            double[] h = new double[5];
423            w[0] = constraint.getWidth();
424            if (this.topBlock != null) {
425                RectangleConstraint c1 = new RectangleConstraint(w[0], null, 
426                            LengthConstraintType.FIXED, 0.0, 
427                            new Range(0.0, constraint.getHeight()), 
428                            LengthConstraintType.RANGE);
429                Size2D size = this.topBlock.arrange(g2, c1);
430                h[0] = size.height;
431            }
432            w[1] = w[0];
433            if (this.bottomBlock != null) {
434                RectangleConstraint c2 = new RectangleConstraint(w[0], null, 
435                            LengthConstraintType.FIXED, 0.0, new Range(0.0, 
436                            constraint.getHeight() - h[0]), LengthConstraintType.RANGE);
437                Size2D size = this.bottomBlock.arrange(g2, c2);
438                h[1] = size.height;
439            }
440            h[2] = constraint.getHeight() - h[1] - h[0];
441            if (this.leftBlock != null) {
442                RectangleConstraint c3 = new RectangleConstraint(0.0, 
443                            new Range(0.0, constraint.getWidth()), 
444                            LengthConstraintType.RANGE, h[2], null, 
445                            LengthConstraintType.FIXED);
446                Size2D size = this.leftBlock.arrange(g2, c3);
447                w[2] = size.width;            
448            }
449            h[3] = h[2];
450            if (this.rightBlock != null) {
451                RectangleConstraint c4 = new RectangleConstraint(0.0, 
452                            new Range(0.0, constraint.getWidth() - w[2]), 
453                            LengthConstraintType.RANGE, h[2], null, 
454                            LengthConstraintType.FIXED);
455                Size2D size = this.rightBlock.arrange(g2, c4);
456                w[3] = size.width;            
457            }
458            h[4] = h[2];
459            w[4] = constraint.getWidth() - w[3] - w[2];
460            RectangleConstraint c5 = new RectangleConstraint(w[4], h[4]);
461            if (this.centerBlock != null) {
462                this.centerBlock.arrange(g2, c5);   
463            }
464           
465            if (this.topBlock != null) {
466                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, w[0], 
467                            h[0]));
468            }
469            if (this.bottomBlock != null) {
470                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, h[0] + h[2],
471                            w[1], h[1]));
472            }
473            if (this.leftBlock != null) {
474                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2], 
475                            h[2]));
476            }
477            if (this.rightBlock != null) {
478                this.rightBlock.setBounds(new Rectangle2D.Double(w[2] + w[4], h[0],
479                            w[3], h[3]));
480            }
481            if (this.centerBlock != null) {
482                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], w[4],
483                            h[4]));
484            }
485            return new Size2D(constraint.getWidth(), constraint.getHeight());
486        }
487        
488        /**
489         * Clears the layout.
490         */
491        public void clear() {
492            this.centerBlock = null;
493            this.topBlock = null;
494            this.bottomBlock = null;
495            this.leftBlock = null;
496            this.rightBlock = null;
497        }
498        
499        /**
500         * Tests this arrangement for equality with an arbitrary object.
501         * 
502         * @param obj  the object (<code>null</code> permitted).
503         * 
504         * @return A boolean.
505         */
506        public boolean equals(Object obj) {
507            if (obj == this) {
508                return true;   
509            }
510            if (!(obj instanceof BorderArrangement)) {
511                return false;   
512            }
513            BorderArrangement that = (BorderArrangement) obj;
514            if (!ObjectUtilities.equal(this.topBlock, that.topBlock)) {
515                return false;   
516            }
517            if (!ObjectUtilities.equal(this.bottomBlock, that.bottomBlock)) {
518                return false;   
519            }
520            if (!ObjectUtilities.equal(this.leftBlock, that.leftBlock)) {
521                return false;   
522            }
523            if (!ObjectUtilities.equal(this.rightBlock, that.rightBlock)) {
524                return false;   
525            }
526            if (!ObjectUtilities.equal(this.centerBlock, that.centerBlock)) {
527                return false;   
528            }
529            return true;
530        }
531    }