001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------------
028 * AbstractBlock.java
029 * ------------------
030 * (C) Copyright 2004-2013, 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 * 02-Jul-2013 : Use ParamChecks (DG);
047 *
048 */
049
050package org.jfree.chart.block;
051
052import java.awt.Graphics2D;
053import java.awt.geom.Rectangle2D;
054import java.io.IOException;
055import java.io.ObjectInputStream;
056import java.io.ObjectOutputStream;
057import java.io.Serializable;
058import org.jfree.chart.util.ParamChecks;
059
060import org.jfree.data.Range;
061import org.jfree.io.SerialUtilities;
062import org.jfree.ui.RectangleInsets;
063import org.jfree.ui.Size2D;
064import org.jfree.util.ObjectUtilities;
065import org.jfree.util.PublicCloneable;
066import org.jfree.util.ShapeUtilities;
067
068/**
069 * A convenience class for creating new classes that implement
070 * the {@link Block} interface.
071 */
072public 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        ParamChecks.nullNotPermitted(margin, "margin");
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        ParamChecks.nullNotPermitted(frame, "frame");
293        this.frame = frame;
294    }
295
296    /**
297     * Returns the padding.
298     *
299     * @return The padding (never <code>null</code>).
300     *
301     * @see #setPadding(RectangleInsets)
302     */
303    public RectangleInsets getPadding() {
304        return this.padding;
305    }
306
307    /**
308     * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no
309     * padding).
310     *
311     * @param padding  the padding (<code>null</code> not permitted).
312     *
313     * @see #getPadding()
314     */
315    public void setPadding(RectangleInsets padding) {
316        ParamChecks.nullNotPermitted(padding, "padding");
317        this.padding = padding;
318    }
319
320    /**
321     * Sets the padding.
322     *
323     * @param top  the top padding.
324     * @param left  the left padding.
325     * @param bottom  the bottom padding.
326     * @param right  the right padding.
327     */
328    public void setPadding(double top, double left, double bottom,
329                           double right) {
330        setPadding(new RectangleInsets(top, left, bottom, right));
331    }
332
333    /**
334     * Returns the x-offset for the content within the block.
335     *
336     * @return The x-offset.
337     *
338     * @see #getContentYOffset()
339     */
340    public double getContentXOffset() {
341        return this.margin.getLeft() + this.frame.getInsets().getLeft()
342            + this.padding.getLeft();
343    }
344
345    /**
346     * Returns the y-offset for the content within the block.
347     *
348     * @return The y-offset.
349     *
350     * @see #getContentXOffset()
351     */
352    public double getContentYOffset() {
353        return this.margin.getTop() + this.frame.getInsets().getTop()
354            + this.padding.getTop();
355    }
356
357    /**
358     * Arranges the contents of the block, with no constraints, and returns
359     * the block size.
360     *
361     * @param g2  the graphics device.
362     *
363     * @return The block size (in Java2D units, never <code>null</code>).
364     */
365    public Size2D arrange(Graphics2D g2) {
366        return arrange(g2, RectangleConstraint.NONE);
367    }
368
369    /**
370     * Arranges the contents of the block, within the given constraints, and
371     * returns the block size.
372     *
373     * @param g2  the graphics device.
374     * @param constraint  the constraint (<code>null</code> not permitted).
375     *
376     * @return The block size (in Java2D units, never <code>null</code>).
377     */
378    public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
379        Size2D base = new Size2D(getWidth(), getHeight());
380        return constraint.calculateConstrainedSize(base);
381    }
382
383    /**
384     * Returns the current bounds of the block.
385     *
386     * @return The bounds.
387     *
388     * @see #setBounds(Rectangle2D)
389     */
390    public Rectangle2D getBounds() {
391        return this.bounds;
392    }
393
394    /**
395     * Sets the bounds of the block.
396     *
397     * @param bounds  the bounds (<code>null</code> not permitted).
398     *
399     * @see #getBounds()
400     */
401    public void setBounds(Rectangle2D bounds) {
402        ParamChecks.nullNotPermitted(bounds, "bounds");
403        this.bounds = bounds;
404    }
405
406    /**
407     * Calculate the width available for content after subtracting
408     * the margin, border and padding space from the specified fixed
409     * width.
410     *
411     * @param fixedWidth  the fixed width.
412     *
413     * @return The available space.
414     *
415     * @see #trimToContentHeight(double)
416     */
417    protected double trimToContentWidth(double fixedWidth) {
418        double result = this.margin.trimWidth(fixedWidth);
419        result = this.frame.getInsets().trimWidth(result);
420        result = this.padding.trimWidth(result);
421        return Math.max(result, 0.0);
422    }
423
424    /**
425     * Calculate the height available for content after subtracting
426     * the margin, border and padding space from the specified fixed
427     * height.
428     *
429     * @param fixedHeight  the fixed height.
430     *
431     * @return The available space.
432     *
433     * @see #trimToContentWidth(double)
434     */
435    protected double trimToContentHeight(double fixedHeight) {
436        double result = this.margin.trimHeight(fixedHeight);
437        result = this.frame.getInsets().trimHeight(result);
438        result = this.padding.trimHeight(result);
439        return Math.max(result, 0.0);
440    }
441
442    /**
443     * Returns a constraint for the content of this block that will result in
444     * the bounds of the block matching the specified constraint.
445     *
446     * @param c  the outer constraint (<code>null</code> not permitted).
447     *
448     * @return The content constraint.
449     */
450    protected RectangleConstraint toContentConstraint(RectangleConstraint c) {
451        ParamChecks.nullNotPermitted(c, "c");
452        if (c.equals(RectangleConstraint.NONE)) {
453            return c;
454        }
455        double w = c.getWidth();
456        Range wr = c.getWidthRange();
457        double h = c.getHeight();
458        Range hr = c.getHeightRange();
459        double ww = trimToContentWidth(w);
460        double hh = trimToContentHeight(h);
461        Range wwr = trimToContentWidth(wr);
462        Range hhr = trimToContentHeight(hr);
463        return new RectangleConstraint(ww, wwr, c.getWidthConstraintType(),
464            hh, hhr, c.getHeightConstraintType());
465    }
466
467    private Range trimToContentWidth(Range r) {
468        if (r == null) {
469            return null;
470        }
471        double lowerBound = 0.0;
472        double upperBound = Double.POSITIVE_INFINITY;
473        if (r.getLowerBound() > 0.0) {
474            lowerBound = trimToContentWidth(r.getLowerBound());
475        }
476        if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
477            upperBound = trimToContentWidth(r.getUpperBound());
478        }
479        return new Range(lowerBound, upperBound);
480    }
481
482    private Range trimToContentHeight(Range r) {
483        if (r == null) {
484            return null;
485        }
486        double lowerBound = 0.0;
487        double upperBound = Double.POSITIVE_INFINITY;
488        if (r.getLowerBound() > 0.0) {
489            lowerBound = trimToContentHeight(r.getLowerBound());
490        }
491        if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
492            upperBound = trimToContentHeight(r.getUpperBound());
493        }
494        return new Range(lowerBound, upperBound);
495    }
496
497    /**
498     * Adds the margin, border and padding to the specified content width.
499     *
500     * @param contentWidth  the content width.
501     *
502     * @return The adjusted width.
503     */
504    protected double calculateTotalWidth(double contentWidth) {
505        double result = contentWidth;
506        result = this.padding.extendWidth(result);
507        result = this.frame.getInsets().extendWidth(result);
508        result = this.margin.extendWidth(result);
509        return result;
510    }
511
512    /**
513     * Adds the margin, border and padding to the specified content height.
514     *
515     * @param contentHeight  the content height.
516     *
517     * @return The adjusted height.
518     */
519    protected double calculateTotalHeight(double contentHeight) {
520        double result = contentHeight;
521        result = this.padding.extendHeight(result);
522        result = this.frame.getInsets().extendHeight(result);
523        result = this.margin.extendHeight(result);
524        return result;
525    }
526
527    /**
528     * Reduces the specified area by the amount of space consumed
529     * by the margin.
530     *
531     * @param area  the area (<code>null</code> not permitted).
532     *
533     * @return The trimmed area.
534     */
535    protected Rectangle2D trimMargin(Rectangle2D area) {
536        // defer argument checking...
537        this.margin.trim(area);
538        return area;
539    }
540
541    /**
542     * Reduces the specified area by the amount of space consumed
543     * by the border.
544     *
545     * @param area  the area (<code>null</code> not permitted).
546     *
547     * @return The trimmed area.
548     */
549    protected Rectangle2D trimBorder(Rectangle2D area) {
550        // defer argument checking...
551        this.frame.getInsets().trim(area);
552        return area;
553    }
554
555    /**
556     * Reduces the specified area by the amount of space consumed
557     * by the padding.
558     *
559     * @param area  the area (<code>null</code> not permitted).
560     *
561     * @return The trimmed area.
562     */
563    protected Rectangle2D trimPadding(Rectangle2D area) {
564        // defer argument checking...
565        this.padding.trim(area);
566        return area;
567    }
568
569    /**
570     * Draws the border around the perimeter of the specified area.
571     *
572     * @param g2  the graphics device.
573     * @param area  the area.
574     */
575    protected void drawBorder(Graphics2D g2, Rectangle2D area) {
576        this.frame.draw(g2, area);
577    }
578
579    /**
580     * Tests this block for equality with an arbitrary object.
581     *
582     * @param obj  the object (<code>null</code> permitted).
583     *
584     * @return A boolean.
585     */
586    @Override
587    public boolean equals(Object obj) {
588        if (obj == this) {
589            return true;
590        }
591        if (!(obj instanceof AbstractBlock)) {
592            return false;
593        }
594        AbstractBlock that = (AbstractBlock) obj;
595        if (!ObjectUtilities.equal(this.id, that.id)) {
596            return false;
597        }
598        if (!this.frame.equals(that.frame)) {
599            return false;
600        }
601        if (!this.bounds.equals(that.bounds)) {
602            return false;
603        }
604        if (!this.margin.equals(that.margin)) {
605            return false;
606        }
607        if (!this.padding.equals(that.padding)) {
608            return false;
609        }
610        if (this.height != that.height) {
611            return false;
612        }
613        if (this.width != that.width) {
614            return false;
615        }
616        return true;
617    }
618
619    /**
620     * Returns a clone of this block.
621     *
622     * @return A clone.
623     *
624     * @throws CloneNotSupportedException if there is a problem creating the
625     *         clone.
626     */
627    @Override
628    public Object clone() throws CloneNotSupportedException {
629        AbstractBlock clone = (AbstractBlock) super.clone();
630        clone.bounds = (Rectangle2D) ShapeUtilities.clone(this.bounds);
631        if (this.frame instanceof PublicCloneable) {
632            PublicCloneable pc = (PublicCloneable) this.frame;
633            clone.frame = (BlockFrame) pc.clone();
634        }
635        return clone;
636    }
637
638    /**
639     * Provides serialization support.
640     *
641     * @param stream  the output stream.
642     *
643     * @throws IOException if there is an I/O error.
644     */
645    private void writeObject(ObjectOutputStream stream) throws IOException {
646        stream.defaultWriteObject();
647        SerialUtilities.writeShape(this.bounds, stream);
648    }
649
650    /**
651     * Provides serialization support.
652     *
653     * @param stream  the input stream.
654     *
655     * @throws IOException  if there is an I/O error.
656     * @throws ClassNotFoundException  if there is a classpath problem.
657     */
658    private void readObject(ObjectInputStream stream)
659        throws IOException, ClassNotFoundException {
660        stream.defaultReadObject();
661        this.bounds = (Rectangle2D) SerialUtilities.readShape(stream);
662    }
663
664}