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 }