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