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     * AbstractBlock.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     * $Id: AbstractBlock.java,v 1.12.2.6 2007/04/04 09:14:20 mungady Exp $
036     *
037     * Changes:
038     * --------
039     * 22-Oct-2004 : Version 1 (DG);
040     * 02-Feb-2005 : Added accessor methods for margin (DG);
041     * 04-Feb-2005 : Added equals() method and implemented Serializable (DG);
042     * 03-May-2005 : Added null argument checks (DG);
043     * 06-May-2005 : Added convenience methods for setting margin, border and 
044     *               padding (DG);
045     * ------------- JFREECHART 1.0.x ---------------------------------------------
046     * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated 
047     *               equals(), and implemented Cloneable (DG);
048     * 
049     */
050    
051    package org.jfree.chart.block;
052    
053    import java.awt.Graphics2D;
054    import java.awt.geom.Rectangle2D;
055    import java.io.IOException;
056    import java.io.ObjectInputStream;
057    import java.io.ObjectOutputStream;
058    import java.io.Serializable;
059    
060    import org.jfree.data.Range;
061    import org.jfree.io.SerialUtilities;
062    import org.jfree.ui.RectangleInsets;
063    import org.jfree.ui.Size2D;
064    import org.jfree.util.ObjectUtilities;
065    import org.jfree.util.PublicCloneable;
066    import org.jfree.util.ShapeUtilities;
067    
068    /**
069     * A convenience class for creating new classes that implement 
070     * the {@link Block} interface.
071     */
072    public class AbstractBlock implements Cloneable, Serializable {
073    
074        /** For serialization. */
075        private static final long serialVersionUID = 7689852412141274563L;
076        
077        /** The id for the block. */
078        private String id;
079        
080        /** The margin around the outside of the block. */
081        private RectangleInsets margin;
082        
083        /** The frame (or border) for the block. */
084        private BlockFrame frame;
085    
086        /** The padding between the block content and the border. */
087        private RectangleInsets padding;
088        
089        /** 
090         * The natural width of the block (may be overridden if there are 
091         * constraints in sizing).
092         */
093        private double width;
094        
095        /** 
096         * The natural height of the block (may be overridden if there are 
097         * constraints in sizing).
098         */
099        private double height;
100        
101        /**
102         * The current bounds for the block (position of the block in Java2D space).
103         */
104        private transient Rectangle2D bounds;
105        
106        /**
107         * Creates a new block.
108         */
109        protected AbstractBlock() {
110            this.id = null;
111            this.width = 0.0;
112            this.height = 0.0;
113            this.bounds = new Rectangle2D.Float();
114            this.margin = RectangleInsets.ZERO_INSETS;
115            this.frame = BlockBorder.NONE; 
116            this.padding = RectangleInsets.ZERO_INSETS;
117        }
118        
119        /**
120         * Returns the id.
121         * 
122         * @return The id (possibly <code>null</code>).
123         * 
124         * @see #setID(String)
125         */
126        public String getID() {
127            return this.id;   
128        }
129        
130        /**
131         * Sets the id for the block.
132         * 
133         * @param id  the id (<code>null</code> permitted).
134         * 
135         * @see #getID()
136         */
137        public void setID(String id) {
138            this.id = id;   
139        }
140        
141        /**
142         * Returns the natural width of the block, if this is known in advance.
143         * The actual width of the block may be overridden if layout constraints
144         * make this necessary.  
145         * 
146         * @return The width.
147         * 
148         * @see #setWidth(double)
149         */
150        public double getWidth() {
151            return this.width;
152        }
153        
154        /**
155         * Sets the natural width of the block, if this is known in advance.
156         * 
157         * @param width  the width (in Java2D units)
158         * 
159         * @see #getWidth()
160         */
161        public void setWidth(double width) {
162            this.width = width;
163        }
164        
165        /**
166         * Returns the natural height of the block, if this is known in advance.
167         * The actual height of the block may be overridden if layout constraints
168         * make this necessary.  
169         * 
170         * @return The height.
171         * 
172         * @see #setHeight(double)
173         */
174        public double getHeight() {
175            return this.height;
176        }
177        
178        /**
179         * Sets the natural width of the block, if this is known in advance.
180         * 
181         * @param height  the width (in Java2D units)
182         * 
183         * @see #getHeight()
184         */
185        public void setHeight(double height) {
186            this.height = height;
187        }
188        
189        /**
190         * Returns the margin.
191         * 
192         * @return The margin (never <code>null</code>).
193         * 
194         * @see #getMargin()
195         */
196        public RectangleInsets getMargin() {
197            return this.margin;
198        }
199            
200        /**
201         * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no 
202         * padding).
203         * 
204         * @param margin  the margin (<code>null</code> not permitted).
205         * 
206         * @see #getMargin()
207         */
208        public void setMargin(RectangleInsets margin) {
209            if (margin == null) {
210                throw new IllegalArgumentException("Null 'margin' argument.");   
211            }
212            this.margin = margin;
213        }
214    
215        /**
216         * Sets the margin.
217         * 
218         * @param top  the top margin.
219         * @param left  the left margin.
220         * @param bottom  the bottom margin.
221         * @param right  the right margin.
222         * 
223         * @see #getMargin()
224         */
225        public void setMargin(double top, double left, double bottom, 
226                              double right) {
227            setMargin(new RectangleInsets(top, left, bottom, right));
228        }
229    
230        /**
231         * Returns the border.
232         * 
233         * @return The border (never <code>null</code>).
234         * 
235         * @deprecated Use {@link #getFrame()} instead.
236         */
237        public BlockBorder getBorder() {
238            if (this.frame instanceof BlockBorder) {
239                return (BlockBorder) this.frame;
240            }
241            else {
242                return null;
243            }
244        }
245        
246        /**
247         * Sets the border for the block (use {@link BlockBorder#NONE} for
248         * no border).
249         * 
250         * @param border  the border (<code>null</code> not permitted).
251         * 
252         * @see #getBorder()
253         * 
254         * @deprecated Use {@link #setFrame(BlockFrame)} instead.
255         */
256        public void setBorder(BlockBorder border) {
257            setFrame(border);
258        }
259        
260        /**
261         * Sets a black border with the specified line widths.
262         * 
263         * @param top  the top border line width.
264         * @param left  the left border line width.
265         * @param bottom  the bottom border line width.
266         * @param right  the right border line width.
267         */
268        public void setBorder(double top, double left, double bottom, 
269                              double right) {
270            setFrame(new BlockBorder(top, left, bottom, right));
271        }
272        
273        /**
274         * Returns the current frame (border).
275         * 
276         * @return The frame.
277         * 
278         * @since 1.0.5
279         * @see #setFrame(BlockFrame)
280         */
281        public BlockFrame getFrame() {
282            return this.frame;
283        }
284        
285        /**
286         * Sets the frame (or border).
287         * 
288         * @param frame  the frame (<code>null</code> not permitted).
289         * 
290         * @since 1.0.5
291         * @see #getFrame()
292         */
293        public void setFrame(BlockFrame frame) {
294            if (frame == null) {
295                throw new IllegalArgumentException("Null 'frame' argument.");   
296            }
297            this.frame = frame;
298        }
299        
300        /**
301         * Returns the padding.
302         * 
303         * @return The padding (never <code>null</code>).
304         * 
305         * @see #setPadding(RectangleInsets)
306         */
307        public RectangleInsets getPadding() {
308            return this.padding;
309        }
310        
311        /**
312         * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no 
313         * padding).
314         * 
315         * @param padding  the padding (<code>null</code> not permitted).
316         * 
317         * @see #getPadding()
318         */
319        public void setPadding(RectangleInsets padding) {
320            if (padding == null) {
321                throw new IllegalArgumentException("Null 'padding' argument.");   
322            }
323            this.padding = padding;
324        }
325    
326        /**
327         * Sets the padding.
328         * 
329         * @param top  the top padding.
330         * @param left  the left padding.
331         * @param bottom  the bottom padding.
332         * @param right  the right padding.
333         */
334        public void setPadding(double top, double left, double bottom, 
335                               double right) {
336            setPadding(new RectangleInsets(top, left, bottom, right));
337        }
338        
339        /**
340         * Returns the x-offset for the content within the block.
341         * 
342         * @return The x-offset.
343         * 
344         * @see #getContentYOffset()
345         */
346        public double getContentXOffset() {
347            return this.margin.getLeft() + this.frame.getInsets().getLeft() 
348                + this.padding.getLeft();    
349        }
350        
351        /**
352         * Returns the y-offset for the content within the block.
353         * 
354         * @return The y-offset.
355         * 
356         * @see #getContentXOffset()
357         */
358        public double getContentYOffset() {
359            return this.margin.getTop() + this.frame.getInsets().getTop() 
360                + this.padding.getTop();   
361        }
362        
363        /**
364         * Arranges the contents of the block, with no constraints, and returns 
365         * the block size.
366         * 
367         * @param g2  the graphics device.
368         * 
369         * @return The block size (in Java2D units, never <code>null</code>).
370         */
371        public Size2D arrange(Graphics2D g2) {  
372            return arrange(g2, RectangleConstraint.NONE);
373        }
374    
375        /**
376         * Arranges the contents of the block, within the given constraints, and 
377         * returns the block size.
378         * 
379         * @param g2  the graphics device.
380         * @param constraint  the constraint (<code>null</code> not permitted).
381         * 
382         * @return The block size (in Java2D units, never <code>null</code>).
383         */
384        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
385            Size2D base = new Size2D(getWidth(), getHeight());
386            return constraint.calculateConstrainedSize(base);
387        }
388    
389        /**
390         * Returns the current bounds of the block.
391         * 
392         * @return The bounds.
393         * 
394         * @see #setBounds(Rectangle2D)
395         */
396        public Rectangle2D getBounds() {
397            return this.bounds;
398        }
399        
400        /**
401         * Sets the bounds of the block.
402         * 
403         * @param bounds  the bounds (<code>null</code> not permitted).
404         * 
405         * @see #getBounds()
406         */
407        public void setBounds(Rectangle2D bounds) {
408            if (bounds == null) {
409                throw new IllegalArgumentException("Null 'bounds' argument.");
410            }
411            this.bounds = bounds;
412        }
413        
414        /**
415         * Calculate the width available for content after subtracting 
416         * the margin, border and padding space from the specified fixed 
417         * width.
418         * 
419         * @param fixedWidth  the fixed width.
420         * 
421         * @return The available space.
422         * 
423         * @see #trimToContentHeight(double)
424         */
425        protected double trimToContentWidth(double fixedWidth) {
426            double result = this.margin.trimWidth(fixedWidth);
427            result = this.frame.getInsets().trimWidth(result);
428            result = this.padding.trimWidth(result);
429            return Math.max(result, 0.0);
430        }
431    
432        /**
433         * Calculate the height available for content after subtracting 
434         * the margin, border and padding space from the specified fixed 
435         * height.
436         * 
437         * @param fixedHeight  the fixed height.
438         * 
439         * @return The available space.
440         * 
441         * @see #trimToContentWidth(double)
442         */
443        protected double trimToContentHeight(double fixedHeight) {
444            double result = this.margin.trimHeight(fixedHeight);
445            result = this.frame.getInsets().trimHeight(result);
446            result = this.padding.trimHeight(result);
447            return Math.max(result, 0.0);
448        }
449        
450        /**
451         * Returns a constraint for the content of this block that will result in
452         * the bounds of the block matching the specified constraint.
453         * 
454         * @param c  the outer constraint (<code>null</code> not permitted).
455         * 
456         * @return The content constraint.
457         */
458        protected RectangleConstraint toContentConstraint(RectangleConstraint c) {
459            if (c == null) {
460                throw new IllegalArgumentException("Null 'c' argument.");
461            }
462            if (c.equals(RectangleConstraint.NONE)) {
463                return c;
464            }
465            double w = c.getWidth();
466            Range wr = c.getWidthRange();
467            double h = c.getHeight();
468            Range hr = c.getHeightRange();
469            double ww = trimToContentWidth(w);
470            double hh = trimToContentHeight(h);
471            Range wwr = trimToContentWidth(wr);
472            Range hhr = trimToContentHeight(hr);
473            return new RectangleConstraint(
474                ww, wwr, c.getWidthConstraintType(), 
475                hh, hhr, c.getHeightConstraintType()
476            );
477        }
478    
479        private Range trimToContentWidth(Range r) {
480            if (r == null) {
481                return null;   
482            }
483            double lowerBound = 0.0;
484            double upperBound = Double.POSITIVE_INFINITY;
485            if (r.getLowerBound() > 0.0) {
486                lowerBound = trimToContentWidth(r.getLowerBound());   
487            }
488            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
489                upperBound = trimToContentWidth(r.getUpperBound());
490            }
491            return new Range(lowerBound, upperBound);
492        }
493        
494        private Range trimToContentHeight(Range r) {
495            if (r == null) {
496                return null;   
497            }
498            double lowerBound = 0.0;
499            double upperBound = Double.POSITIVE_INFINITY;
500            if (r.getLowerBound() > 0.0) {
501                lowerBound = trimToContentHeight(r.getLowerBound());   
502            }
503            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
504                upperBound = trimToContentHeight(r.getUpperBound());
505            }
506            return new Range(lowerBound, upperBound);
507        }
508        
509        /**
510         * Adds the margin, border and padding to the specified content width.
511         * 
512         * @param contentWidth  the content width.
513         * 
514         * @return The adjusted width.
515         */
516        protected double calculateTotalWidth(double contentWidth) {
517            double result = contentWidth;
518            result = this.padding.extendWidth(result);
519            result = this.frame.getInsets().extendWidth(result);
520            result = this.margin.extendWidth(result);
521            return result;
522        }
523    
524        /**
525         * Adds the margin, border and padding to the specified content height.
526         * 
527         * @param contentHeight  the content height.
528         * 
529         * @return The adjusted height.
530         */
531        protected double calculateTotalHeight(double contentHeight) {
532            double result = contentHeight;
533            result = this.padding.extendHeight(result);
534            result = this.frame.getInsets().extendHeight(result);
535            result = this.margin.extendHeight(result);
536            return result;
537        }
538    
539        /**
540         * Reduces the specified area by the amount of space consumed 
541         * by the margin.
542         * 
543         * @param area  the area (<code>null</code> not permitted).
544         * 
545         * @return The trimmed area.
546         */
547        protected Rectangle2D trimMargin(Rectangle2D area) {
548            // defer argument checking...
549            this.margin.trim(area);
550            return area;
551        }
552        
553        /**
554         * Reduces the specified area by the amount of space consumed 
555         * by the border.
556         * 
557         * @param area  the area (<code>null</code> not permitted).
558         * 
559         * @return The trimmed area.
560         */
561        protected Rectangle2D trimBorder(Rectangle2D area) {
562            // defer argument checking...
563            this.frame.getInsets().trim(area);
564            return area;
565        }
566    
567        /**
568         * Reduces the specified area by the amount of space consumed 
569         * by the padding.
570         * 
571         * @param area  the area (<code>null</code> not permitted).
572         * 
573         * @return The trimmed area.
574         */
575        protected Rectangle2D trimPadding(Rectangle2D area) {
576            // defer argument checking...
577            this.padding.trim(area);
578            return area;
579        }
580    
581        /**
582         * Draws the border around the perimeter of the specified area.
583         * 
584         * @param g2  the graphics device.
585         * @param area  the area.
586         */
587        protected void drawBorder(Graphics2D g2, Rectangle2D area) {
588            this.frame.draw(g2, area);
589        }
590        
591        /**
592         * Tests this block for equality with an arbitrary object.
593         * 
594         * @param obj  the object (<code>null</code> permitted).
595         * 
596         * @return A boolean.
597         */
598        public boolean equals(Object obj) {
599            if (obj == this) {
600                return true;   
601            }
602            if (!(obj instanceof AbstractBlock)) {
603                return false;   
604            }
605            AbstractBlock that = (AbstractBlock) obj;
606            if (!ObjectUtilities.equal(this.id, that.id)) {
607                return false;
608            }
609            if (!this.frame.equals(that.frame)) {
610                return false;   
611            }
612            if (!this.bounds.equals(that.bounds)) {
613                return false;   
614            }
615            if (!this.margin.equals(that.margin)) {
616                return false;   
617            }
618            if (!this.padding.equals(that.padding)) {
619                return false;   
620            }
621            if (this.height != that.height) {
622                return false;   
623            }
624            if (this.width != that.width) {
625                return false;   
626            }
627            return true;
628        }
629        
630        /**
631         * Returns a clone of this block.
632         * 
633         * @return A clone.
634         * 
635         * @throws CloneNotSupportedException if there is a problem creating the
636         *         clone.
637         */
638        public Object clone() throws CloneNotSupportedException {
639            AbstractBlock clone = (AbstractBlock) super.clone();
640            clone.bounds = (Rectangle2D) ShapeUtilities.clone(this.bounds);
641            if (this.frame instanceof PublicCloneable) {
642                PublicCloneable pc = (PublicCloneable) this.frame;
643                clone.frame = (BlockFrame) pc.clone();
644            }
645            return clone;
646        }
647        
648        /**
649         * Provides serialization support.
650         *
651         * @param stream  the output stream.
652         *
653         * @throws IOException if there is an I/O error.
654         */
655        private void writeObject(ObjectOutputStream stream) throws IOException {
656            stream.defaultWriteObject();
657            SerialUtilities.writeShape(this.bounds, stream);
658        }
659    
660        /**
661         * Provides serialization support.
662         *
663         * @param stream  the input stream.
664         *
665         * @throws IOException  if there is an I/O error.
666         * @throws ClassNotFoundException  if there is a classpath problem.
667         */
668        private void readObject(ObjectInputStream stream) 
669            throws IOException, ClassNotFoundException {
670            stream.defaultReadObject();
671            this.bounds = (Rectangle2D) SerialUtilities.readShape(stream);
672        }
673    
674    }