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
049package org.jfree.chart.block;
050
051import java.awt.Graphics2D;
052import java.awt.geom.Rectangle2D;
053import java.io.IOException;
054import java.io.ObjectInputStream;
055import java.io.ObjectOutputStream;
056import java.io.Serializable;
057
058import org.jfree.data.Range;
059import org.jfree.io.SerialUtilities;
060import org.jfree.ui.RectangleInsets;
061import org.jfree.ui.Size2D;
062import org.jfree.util.ObjectUtilities;
063import org.jfree.util.PublicCloneable;
064import org.jfree.util.ShapeUtilities;
065
066/**
067 * A convenience class for creating new classes that implement
068 * the {@link Block} interface.
069 */
070public 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}