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