001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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-2008, 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     * 08-Apr-2008 : Fixed bug in arrangeFF() method where width is too small for
043     *               left and right blocks (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(container, constraint.getWidthRange(),
166                            constraint.getHeightRange(), g2);
167                }
168            }
169            return new Size2D(container.calculateTotalWidth(contentSize.getWidth()),
170                    container.calculateTotalHeight(contentSize.getHeight()));
171        }
172    
173        /**
174         * Performs an arrangement without constraints.
175         *
176         * @param container  the container.
177         * @param g2  the graphics device.
178         *
179         * @return The container size after the arrangement.
180         */
181        protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
182            double[] w = new double[5];
183            double[] h = new double[5];
184            if (this.topBlock != null) {
185                Size2D size = this.topBlock.arrange(g2, RectangleConstraint.NONE);
186                w[0] = size.width;
187                h[0] = size.height;
188            }
189            if (this.bottomBlock != null) {
190                Size2D size = this.bottomBlock.arrange(g2,
191                        RectangleConstraint.NONE);
192                w[1] = size.width;
193                h[1] = size.height;
194            }
195            if (this.leftBlock != null) {
196                Size2D size = this.leftBlock.arrange(g2, RectangleConstraint.NONE);
197                w[2] = size.width;
198                h[2] = size.height;
199           }
200            if (this.rightBlock != null) {
201                Size2D size = this.rightBlock.arrange(g2, RectangleConstraint.NONE);
202                w[3] = size.width;
203                h[3] = size.height;
204            }
205    
206            h[2] = Math.max(h[2], h[3]);
207            h[3] = h[2];
208    
209            if (this.centerBlock != null) {
210                Size2D size = this.centerBlock.arrange(g2,
211                        RectangleConstraint.NONE);
212                w[4] = size.width;
213                h[4] = size.height;
214            }
215            double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
216            double centerHeight = Math.max(h[2], Math.max(h[3], h[4]));
217            double height = h[0] + h[1] + centerHeight;
218            if (this.topBlock != null) {
219                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
220                        h[0]));
221            }
222            if (this.bottomBlock != null) {
223                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
224                        height - h[1], width, h[1]));
225            }
226            if (this.leftBlock != null) {
227                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
228                        centerHeight));
229            }
230            if (this.rightBlock != null) {
231                this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
232                        h[0], w[3], centerHeight));
233            }
234    
235            if (this.centerBlock != null) {
236                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
237                        width - w[2] - w[3], centerHeight));
238            }
239            return new Size2D(width, height);
240        }
241    
242        /**
243         * Performs an arrangement with a fixed width and a range for the height.
244         *
245         * @param container  the container.
246         * @param g2  the graphics device.
247         * @param constraint  the constraint.
248         *
249         * @return The container size after the arrangement.
250         */
251        protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
252                                   RectangleConstraint constraint) {
253            Size2D size1 = arrangeFN(container, g2, constraint.getWidth());
254            if (constraint.getHeightRange().contains(size1.getHeight())) {
255                return size1;
256            }
257            else {
258                double h = constraint.getHeightRange().constrain(size1.getHeight());
259                RectangleConstraint c2 = constraint.toFixedHeight(h);
260                return arrange(container, g2, c2);
261            }
262        }
263    
264        /**
265         * Arranges the container width a fixed width and no constraint on the
266         * height.
267         *
268         * @param container  the container.
269         * @param g2  the graphics device.
270         * @param width  the fixed width.
271         *
272         * @return The container size after arranging the contents.
273         */
274        protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
275                                   double width) {
276            double[] w = new double[5];
277            double[] h = new double[5];
278            RectangleConstraint c1 = new RectangleConstraint(width, null,
279                    LengthConstraintType.FIXED, 0.0, null,
280                    LengthConstraintType.NONE);
281            if (this.topBlock != null) {
282                Size2D size = this.topBlock.arrange(g2, c1);
283                w[0] = size.width;
284                h[0] = size.height;
285            }
286            if (this.bottomBlock != null) {
287                Size2D size = this.bottomBlock.arrange(g2, c1);
288                w[1] = size.width;
289                h[1] = size.height;
290            }
291            RectangleConstraint c2 = new RectangleConstraint(0.0,
292                    new Range(0.0, width), LengthConstraintType.RANGE,
293                    0.0, null, LengthConstraintType.NONE);
294            if (this.leftBlock != null) {
295                Size2D size = this.leftBlock.arrange(g2, c2);
296                w[2] = size.width;
297                h[2] = size.height;
298            }
299            if (this.rightBlock != null) {
300                double maxW = Math.max(width - w[2], 0.0);
301                RectangleConstraint c3 = new RectangleConstraint(0.0,
302                        new Range(Math.min(w[2], maxW), maxW),
303                        LengthConstraintType.RANGE, 0.0, null,
304                        LengthConstraintType.NONE);
305                Size2D size = this.rightBlock.arrange(g2, c3);
306                w[3] = size.width;
307                h[3] = size.height;
308            }
309    
310            h[2] = Math.max(h[2], h[3]);
311            h[3] = h[2];
312    
313            if (this.centerBlock != null) {
314                RectangleConstraint c4 = new RectangleConstraint(width - w[2]
315                        - w[3], null, LengthConstraintType.FIXED, 0.0, null,
316                        LengthConstraintType.NONE);
317                Size2D size = this.centerBlock.arrange(g2, c4);
318                w[4] = size.width;
319                h[4] = size.height;
320            }
321            double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
322            return arrange(container, g2, new RectangleConstraint(width, height));
323        }
324    
325        /**
326         * Performs an arrangement with range constraints on both the vertical
327         * and horizontal sides.
328         *
329         * @param container  the container.
330         * @param widthRange  the allowable range for the container width.
331         * @param heightRange  the allowable range for the container height.
332         * @param g2  the graphics device.
333         *
334         * @return The container size.
335         */
336        protected Size2D arrangeRR(BlockContainer container,
337                                   Range widthRange, Range heightRange,
338                                   Graphics2D g2) {
339            double[] w = new double[5];
340            double[] h = new double[5];
341            if (this.topBlock != null) {
342                RectangleConstraint c1 = new RectangleConstraint(widthRange,
343                        heightRange);
344                Size2D size = this.topBlock.arrange(g2, c1);
345                w[0] = size.width;
346                h[0] = size.height;
347            }
348            if (this.bottomBlock != null) {
349                Range heightRange2 = Range.shift(heightRange, -h[0], false);
350                RectangleConstraint c2 = new RectangleConstraint(widthRange,
351                        heightRange2);
352                Size2D size = this.bottomBlock.arrange(g2, c2);
353                w[1] = size.width;
354                h[1] = size.height;
355            }
356            Range heightRange3 = Range.shift(heightRange, -(h[0] + h[1]));
357            if (this.leftBlock != null) {
358                RectangleConstraint c3 = new RectangleConstraint(widthRange,
359                        heightRange3);
360                Size2D size = this.leftBlock.arrange(g2, c3);
361                w[2] = size.width;
362                h[2] = size.height;
363            }
364            Range widthRange2 = Range.shift(widthRange, -w[2], false);
365            if (this.rightBlock != null) {
366                RectangleConstraint c4 = new RectangleConstraint(widthRange2,
367                        heightRange3);
368                Size2D size = this.rightBlock.arrange(g2, c4);
369                w[3] = size.width;
370                h[3] = size.height;
371            }
372    
373            h[2] = Math.max(h[2], h[3]);
374            h[3] = h[2];
375            Range widthRange3 = Range.shift(widthRange, -(w[2] + w[3]), false);
376            if (this.centerBlock != null) {
377                RectangleConstraint c5 = new RectangleConstraint(widthRange3,
378                        heightRange3);
379                Size2D size = this.centerBlock.arrange(g2, c5);
380                w[4] = size.width;
381                h[4] = size.height;
382            }
383            double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
384            double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
385            if (this.topBlock != null) {
386                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
387                        h[0]));
388            }
389            if (this.bottomBlock != null) {
390                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
391                        height - h[1], width, h[1]));
392            }
393            if (this.leftBlock != null) {
394                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
395                        h[2]));
396            }
397            if (this.rightBlock != null) {
398                this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
399                        h[0], w[3], h[3]));
400            }
401    
402            if (this.centerBlock != null) {
403                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
404                        width - w[2] - w[3], height - h[0] - h[1]));
405            }
406            return new Size2D(width, height);
407        }
408    
409        /**
410         * Arranges the items within a container.
411         *
412         * @param container  the container.
413         * @param constraint  the constraint.
414         * @param g2  the graphics device.
415         *
416         * @return The container size after the arrangement.
417         */
418        protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
419                                   RectangleConstraint constraint) {
420            double[] w = new double[5];
421            double[] h = new double[5];
422            w[0] = constraint.getWidth();
423            if (this.topBlock != null) {
424                RectangleConstraint c1 = new RectangleConstraint(w[0], null,
425                        LengthConstraintType.FIXED, 0.0,
426                        new Range(0.0, constraint.getHeight()),
427                        LengthConstraintType.RANGE);
428                Size2D size = this.topBlock.arrange(g2, c1);
429                h[0] = size.height;
430            }
431            w[1] = w[0];
432            if (this.bottomBlock != null) {
433                RectangleConstraint c2 = new RectangleConstraint(w[0], null,
434                        LengthConstraintType.FIXED, 0.0, new Range(0.0,
435                        constraint.getHeight() - h[0]), LengthConstraintType.RANGE);
436                Size2D size = this.bottomBlock.arrange(g2, c2);
437                h[1] = size.height;
438            }
439            h[2] = constraint.getHeight() - h[1] - h[0];
440            if (this.leftBlock != null) {
441                RectangleConstraint c3 = new RectangleConstraint(0.0,
442                        new Range(0.0, constraint.getWidth()),
443                        LengthConstraintType.RANGE, h[2], null,
444                        LengthConstraintType.FIXED);
445                Size2D size = this.leftBlock.arrange(g2, c3);
446                w[2] = size.width;
447            }
448            h[3] = h[2];
449            if (this.rightBlock != null) {
450                RectangleConstraint c4 = new RectangleConstraint(0.0,
451                        new Range(0.0, Math.max(constraint.getWidth() - w[2], 0.0)),
452                        LengthConstraintType.RANGE, h[2], null,
453                        LengthConstraintType.FIXED);
454                Size2D size = this.rightBlock.arrange(g2, c4);
455                w[3] = size.width;
456            }
457            h[4] = h[2];
458            w[4] = constraint.getWidth() - w[3] - w[2];
459            RectangleConstraint c5 = new RectangleConstraint(w[4], h[4]);
460            if (this.centerBlock != null) {
461                this.centerBlock.arrange(g2, c5);
462            }
463    
464            if (this.topBlock != null) {
465                this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, w[0],
466                        h[0]));
467            }
468            if (this.bottomBlock != null) {
469                this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, h[0] + h[2],
470                        w[1], h[1]));
471            }
472            if (this.leftBlock != null) {
473                this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
474                        h[2]));
475            }
476            if (this.rightBlock != null) {
477                this.rightBlock.setBounds(new Rectangle2D.Double(w[2] + w[4], h[0],
478                        w[3], h[3]));
479            }
480            if (this.centerBlock != null) {
481                this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], w[4],
482                        h[4]));
483            }
484            return new Size2D(constraint.getWidth(), constraint.getHeight());
485        }
486    
487        /**
488         * Clears the layout.
489         */
490        public void clear() {
491            this.centerBlock = null;
492            this.topBlock = null;
493            this.bottomBlock = null;
494            this.leftBlock = null;
495            this.rightBlock = null;
496        }
497    
498        /**
499         * Tests this arrangement for equality with an arbitrary object.
500         *
501         * @param obj  the object (<code>null</code> permitted).
502         *
503         * @return A boolean.
504         */
505        public boolean equals(Object obj) {
506            if (obj == this) {
507                return true;
508            }
509            if (!(obj instanceof BorderArrangement)) {
510                return false;
511            }
512            BorderArrangement that = (BorderArrangement) obj;
513            if (!ObjectUtilities.equal(this.topBlock, that.topBlock)) {
514                return false;
515            }
516            if (!ObjectUtilities.equal(this.bottomBlock, that.bottomBlock)) {
517                return false;
518            }
519            if (!ObjectUtilities.equal(this.leftBlock, that.leftBlock)) {
520                return false;
521            }
522            if (!ObjectUtilities.equal(this.rightBlock, that.rightBlock)) {
523                return false;
524            }
525            if (!ObjectUtilities.equal(this.centerBlock, that.centerBlock)) {
526                return false;
527            }
528            return true;
529        }
530    }