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