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