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.1 2005/10/25 20:39:38 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        public double getContentXOffset() {
264            return this.margin.getLeft() + this.border.getInsets().getLeft() + this.padding.getLeft();    
265        }
266        
267        public double getContentYOffset() {
268            return this.margin.getTop() + this.border.getInsets().getTop() + this.padding.getTop();   
269        }
270        
271        /**
272         * Sets the padding.
273         * 
274         * @param top  the top padding.
275         * @param left  the left padding.
276         * @param bottom  the bottom padding.
277         * @param right  the right padding.
278         */
279        public void setPadding(double top, double left, double bottom, 
280                               double right) {
281            setPadding(new RectangleInsets(top, left, bottom, right));
282        }
283        
284        /**
285         * Arranges the contents of the block, with no constraints, and returns 
286         * the block size.
287         * 
288         * @param g2  the graphics device.
289         * 
290         * @return The block size (in Java2D units, never <code>null</code>).
291         */
292        public Size2D arrange(Graphics2D g2) {  
293            return arrange(g2, RectangleConstraint.NONE);
294        }
295    
296        /**
297         * Arranges the contents of the block, within the given constraints, and 
298         * returns the block size.
299         * 
300         * @param g2  the graphics device.
301         * @param constraint  the constraint (<code>null</code> not permitted).
302         * 
303         * @return The block size (in Java2D units, never <code>null</code>).
304         */
305        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
306            Size2D base = new Size2D(getWidth(), getHeight());
307            return constraint.calculateConstrainedSize(base);
308        }
309    
310        /**
311         * Returns the current bounds of the block.
312         * 
313         * @return The bounds.
314         */
315        public Rectangle2D getBounds() {
316            return this.bounds;
317        }
318        
319        /**
320         * Sets the bounds of the block.
321         * 
322         * @param bounds  the bounds (<code>null</code> not permitted).
323         */
324        public void setBounds(Rectangle2D bounds) {
325            if (bounds == null) {
326                throw new IllegalArgumentException("Null 'bounds' argument.");
327            }
328            this.bounds = bounds;
329        }
330        
331        /**
332         * Calculate the width available for content after subtracting 
333         * the margin, border and padding space from the specified fixed 
334         * width.
335         * 
336         * @param fixedWidth  the fixed width.
337         * 
338         * @return The available space.
339         */
340        protected double trimToContentWidth(double fixedWidth) {
341            double result = this.margin.trimWidth(fixedWidth);
342            result = this.border.getInsets().trimWidth(result);
343            result = this.padding.trimWidth(result);
344            return Math.max(result, 0.0);
345        }
346    
347        /**
348         * Calculate the height available for content after subtracting 
349         * the margin, border and padding space from the specified fixed 
350         * height.
351         * 
352         * @param fixedHeight  the fixed height.
353         * 
354         * @return The available space.
355         */
356        protected double trimToContentHeight(double fixedHeight) {
357            double result = this.margin.trimHeight(fixedHeight);
358            result = this.border.getInsets().trimHeight(result);
359            result = this.padding.trimHeight(result);
360            return Math.max(result, 0.0);
361        }
362        
363        /**
364         * Returns a constraint for the content of this block that will result in
365         * the bounds of the block matching the specified constraint.
366         * 
367         * @param c  the outer constraint (<code>null</code> not permitted).
368         * 
369         * @return The content constraint.
370         */
371        protected RectangleConstraint toContentConstraint(RectangleConstraint c) {
372            if (c == null) {
373                throw new IllegalArgumentException("Null 'c' argument.");
374            }
375            if (c.equals(RectangleConstraint.NONE)) {
376                return c;
377            }
378            double w = c.getWidth();
379            Range wr = c.getWidthRange();
380            double h = c.getHeight();
381            Range hr = c.getHeightRange();
382            double ww = trimToContentWidth(w);
383            double hh = trimToContentHeight(h);
384            Range wwr = trimToContentWidth(wr);
385            Range hhr = trimToContentHeight(hr);
386            return new RectangleConstraint(
387                ww, wwr, c.getWidthConstraintType(), 
388                hh, hhr, c.getHeightConstraintType()
389            );
390        }
391    
392        private Range trimToContentWidth(Range r) {
393            if (r == null) {
394                return null;   
395            }
396            double lowerBound = 0.0;
397            double upperBound = Double.POSITIVE_INFINITY;
398            if (r.getLowerBound() > 0.0) {
399                lowerBound = trimToContentWidth(r.getLowerBound());   
400            }
401            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
402                upperBound = trimToContentWidth(r.getUpperBound());
403            }
404            return new Range(lowerBound, upperBound);
405        }
406        
407        private Range trimToContentHeight(Range r) {
408            if (r == null) {
409                return null;   
410            }
411            double lowerBound = 0.0;
412            double upperBound = Double.POSITIVE_INFINITY;
413            if (r.getLowerBound() > 0.0) {
414                lowerBound = trimToContentHeight(r.getLowerBound());   
415            }
416            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
417                upperBound = trimToContentHeight(r.getUpperBound());
418            }
419            return new Range(lowerBound, upperBound);
420        }
421        
422        /**
423         * Adds the margin, border and padding to the specified content width.
424         * 
425         * @param contentWidth  the content width.
426         * 
427         * @return The adjusted width.
428         */
429        protected double calculateTotalWidth(double contentWidth) {
430            double result = contentWidth;
431            result = this.padding.extendWidth(result);
432            result = this.border.getInsets().extendWidth(result);
433            result = this.margin.extendWidth(result);
434            return result;
435        }
436    
437        /**
438         * Adds the margin, border and padding to the specified content height.
439         * 
440         * @param contentHeight  the content height.
441         * 
442         * @return The adjusted height.
443         */
444        protected double calculateTotalHeight(double contentHeight) {
445            double result = contentHeight;
446            result = this.padding.extendHeight(result);
447            result = this.border.getInsets().extendHeight(result);
448            result = this.margin.extendHeight(result);
449            return result;
450        }
451    
452        /**
453         * Reduces the specified area by the amount of space consumed 
454         * by the margin.
455         * 
456         * @param area  the area (<code>null</code> not permitted).
457         * 
458         * @return The trimmed area.
459         */
460        protected Rectangle2D trimMargin(Rectangle2D area) {
461            // defer argument checking...
462            this.margin.trim(area);
463            return area;
464        }
465        
466        /**
467         * Reduces the specified area by the amount of space consumed 
468         * by the border.
469         * 
470         * @param area  the area (<code>null</code> not permitted).
471         * 
472         * @return The trimmed area.
473         */
474        protected Rectangle2D trimBorder(Rectangle2D area) {
475            // defer argument checking...
476            this.border.getInsets().trim(area);
477            return area;
478        }
479    
480        /**
481         * Reduces the specified area by the amount of space consumed 
482         * by the padding.
483         * 
484         * @param area  the area (<code>null</code> not permitted).
485         * 
486         * @return The trimmed area.
487         */
488        protected Rectangle2D trimPadding(Rectangle2D area) {
489            // defer argument checking...
490            this.padding.trim(area);
491            return area;
492        }
493    
494        /**
495         * Draws the border around the perimeter of the specified area.
496         * 
497         * @param g2  the graphics device.
498         * @param area  the area.
499         */
500        protected void drawBorder(Graphics2D g2, Rectangle2D area) {
501            this.border.draw(g2, area);
502        }
503        
504        /**
505         * Tests this block for equality with an arbitrary object.
506         * 
507         * @param obj  the object (<code>null</code> permitted).
508         * 
509         * @return A boolean.
510         */
511        public boolean equals(Object obj) {
512            if (obj == this) {
513                return true;   
514            }
515            if (!(obj instanceof AbstractBlock)) {
516                return false;   
517            }
518            AbstractBlock that = (AbstractBlock) obj;
519            if (!this.border.equals(that.border)) {
520                return false;   
521            }
522            if (!this.bounds.equals(that.bounds)) {
523                return false;   
524            }
525            if (!this.margin.equals(that.margin)) {
526                return false;   
527            }
528            if (!this.padding.equals(that.padding)) {
529                return false;   
530            }
531            if (this.height != that.height) {
532                return false;   
533            }
534            if (this.width != that.width) {
535                return false;   
536            }
537            return true;
538        }
539        
540        /**
541         * Provides serialization support.
542         *
543         * @param stream  the output stream.
544         *
545         * @throws IOException if there is an I/O error.
546         */
547        private void writeObject(ObjectOutputStream stream) throws IOException {
548            stream.defaultWriteObject();
549            SerialUtilities.writeShape(this.bounds, stream);
550        }
551    
552        /**
553         * Provides serialization support.
554         *
555         * @param stream  the input stream.
556         *
557         * @throws IOException  if there is an I/O error.
558         * @throws ClassNotFoundException  if there is a classpath problem.
559         */
560        private void readObject(ObjectInputStream stream) 
561            throws IOException, ClassNotFoundException {
562            stream.defaultReadObject();
563            this.bounds = (Rectangle2D) SerialUtilities.readShape(stream);
564        }
565    
566    }