001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2005, 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: * FlowArrangement.java
029: * --------------------
030: * (C) Copyright 2004, 2005, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: FlowArrangement.java,v 1.13.2.2 2006/08/04 11:48:41 mungady Exp $
036: *
037: * Changes:
038: * --------
039: * 22-Oct-2004 : Version 1 (DG);
040: * 04-Feb-2005 : Implemented equals() and made serializable (DG);
041: * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
042: *
043: */
044:
045: package org.jfree.chart.block;
046:
047: import java.awt.Graphics2D;
048: import java.awt.geom.Rectangle2D;
049: import java.io.Serializable;
050: import java.util.ArrayList;
051: import java.util.List;
052:
053: import org.jfree.ui.HorizontalAlignment;
054: import org.jfree.ui.Size2D;
055: import org.jfree.ui.VerticalAlignment;
056:
057: /**
058: * Arranges blocks in a flow layout. This class is immutable.
059: */
060: public class FlowArrangement implements Arrangement, Serializable {
061:
062: /** For serialization. */
063: private static final long serialVersionUID = 4543632485478613800L;
064:
065: /** The horizontal alignment of blocks. */
066: private HorizontalAlignment horizontalAlignment;
067:
068: /** The vertical alignment of blocks within each row. */
069: private VerticalAlignment verticalAlignment;
070:
071: /** The horizontal gap between items within rows. */
072: private double horizontalGap;
073:
074: /** The vertical gap between rows. */
075: private double verticalGap;
076:
077: /**
078: * Creates a new instance.
079: */
080: public FlowArrangement() {
081: this (HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0,
082: 2.0);
083: }
084:
085: /**
086: * Creates a new instance.
087: *
088: * @param hAlign the horizontal alignment (currently ignored).
089: * @param vAlign the vertical alignment (currently ignored).
090: * @param hGap the horizontal gap.
091: * @param vGap the vertical gap.
092: */
093: public FlowArrangement(HorizontalAlignment hAlign,
094: VerticalAlignment vAlign, double hGap, double vGap) {
095: this .horizontalAlignment = hAlign;
096: this .verticalAlignment = vAlign;
097: this .horizontalGap = hGap;
098: this .verticalGap = vGap;
099: }
100:
101: /**
102: * Adds a block to be managed by this instance. This method is usually
103: * called by the {@link BlockContainer}, you shouldn't need to call it
104: * directly.
105: *
106: * @param block the block.
107: * @param key a key that controls the position of the block.
108: */
109: public void add(Block block, Object key) {
110: // since the flow layout is relatively straightforward,
111: // no information needs to be recorded here
112: }
113:
114: /**
115: * Calculates and sets the bounds of all the items in the specified
116: * container, subject to the given constraint. The <code>Graphics2D</code>
117: * can be used by some items (particularly items containing text) to
118: * calculate sizing parameters.
119: *
120: * @param container the container whose items are being arranged.
121: * @param constraint the size constraint.
122: * @param g2 the graphics device.
123: *
124: * @return The size of the container after arrangement of the contents.
125: */
126: public Size2D arrange(BlockContainer container, Graphics2D g2,
127: RectangleConstraint constraint) {
128:
129: LengthConstraintType w = constraint.getWidthConstraintType();
130: LengthConstraintType h = constraint.getHeightConstraintType();
131: if (w == LengthConstraintType.NONE) {
132: if (h == LengthConstraintType.NONE) {
133: return arrangeNN(container, g2);
134: } else if (h == LengthConstraintType.FIXED) {
135: return arrangeNF(container, g2, constraint);
136: } else if (h == LengthConstraintType.RANGE) {
137: throw new RuntimeException("Not implemented.");
138: }
139: } else if (w == LengthConstraintType.FIXED) {
140: if (h == LengthConstraintType.NONE) {
141: return arrangeFN(container, g2, constraint);
142: } else if (h == LengthConstraintType.FIXED) {
143: return arrangeFF(container, g2, constraint);
144: } else if (h == LengthConstraintType.RANGE) {
145: return arrangeFR(container, g2, constraint);
146: }
147: } else if (w == LengthConstraintType.RANGE) {
148: if (h == LengthConstraintType.NONE) {
149: return arrangeRN(container, g2, constraint);
150: } else if (h == LengthConstraintType.FIXED) {
151: return arrangeRF(container, g2, constraint);
152: } else if (h == LengthConstraintType.RANGE) {
153: return arrangeRR(container, g2, constraint);
154: }
155: }
156: throw new RuntimeException("Unrecognised constraint type.");
157:
158: }
159:
160: /**
161: * Arranges the blocks in the container with a fixed width and no height
162: * constraint.
163: *
164: * @param container the container.
165: * @param constraint the constraint.
166: * @param g2 the graphics device.
167: *
168: * @return The size.
169: */
170: protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
171: RectangleConstraint constraint) {
172:
173: List blocks = container.getBlocks();
174: double width = constraint.getWidth();
175:
176: double x = 0.0;
177: double y = 0.0;
178: double maxHeight = 0.0;
179: List itemsInRow = new ArrayList();
180: for (int i = 0; i < blocks.size(); i++) {
181: Block block = (Block) blocks.get(i);
182: Size2D size = block.arrange(g2, RectangleConstraint.NONE);
183: if (x + size.width <= width) {
184: itemsInRow.add(block);
185: block.setBounds(new Rectangle2D.Double(x, y,
186: size.width, size.height));
187: x = x + size.width + this .horizontalGap;
188: maxHeight = Math.max(maxHeight, size.height);
189: } else {
190: if (itemsInRow.isEmpty()) {
191: // place in this row (truncated) anyway
192: block.setBounds(new Rectangle2D.Double(x, y, Math
193: .min(size.width, width - x), size.height));
194: x = 0.0;
195: y = y + size.height + this .verticalGap;
196: } else {
197: // start new row
198: itemsInRow.clear();
199: x = 0.0;
200: y = y + maxHeight + this .verticalGap;
201: maxHeight = size.height;
202: block.setBounds(new Rectangle2D.Double(x, y, Math
203: .min(size.width, width), size.height));
204: x = size.width + this .horizontalGap;
205: itemsInRow.add(block);
206: }
207: }
208: }
209: return new Size2D(constraint.getWidth(), y + maxHeight);
210: }
211:
212: /**
213: * Arranges the blocks in the container with a fixed width and a range
214: * constraint on the height.
215: *
216: * @param container the container.
217: * @param constraint the constraint.
218: * @param g2 the graphics device.
219: *
220: * @return The size following the arrangement.
221: */
222: protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
223: RectangleConstraint constraint) {
224:
225: Size2D s = arrangeFN(container, g2, constraint);
226: if (constraint.getHeightRange().contains(s.height)) {
227: return s;
228: } else {
229: RectangleConstraint c = constraint.toFixedHeight(constraint
230: .getHeightRange().constrain(s.getHeight()));
231: return arrangeFF(container, g2, c);
232: }
233: }
234:
235: /**
236: * Arranges the blocks in the container with the overall height and width
237: * specified as fixed constraints.
238: *
239: * @param container the container.
240: * @param constraint the constraint.
241: * @param g2 the graphics device.
242: *
243: * @return The size following the arrangement.
244: */
245: protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
246: RectangleConstraint constraint) {
247:
248: // TODO: implement this properly
249: return arrangeFN(container, g2, constraint);
250: }
251:
252: /**
253: * Arranges the blocks with the overall width and height to fit within
254: * specified ranges.
255: *
256: * @param container the container.
257: * @param constraint the constraint.
258: * @param g2 the graphics device.
259: *
260: * @return The size after the arrangement.
261: */
262: protected Size2D arrangeRR(BlockContainer container, Graphics2D g2,
263: RectangleConstraint constraint) {
264:
265: // first arrange without constraints, and see if this fits within
266: // the required ranges...
267: Size2D s1 = arrangeNN(container, g2);
268: if (constraint.getWidthRange().contains(s1.width)) {
269: return s1; // TODO: we didn't check the height yet
270: } else {
271: RectangleConstraint c = constraint.toFixedWidth(constraint
272: .getWidthRange().getUpperBound());
273: return arrangeFR(container, g2, c);
274: }
275: }
276:
277: /**
278: * Arranges the blocks in the container with a range constraint on the
279: * width and a fixed height.
280: *
281: * @param container the container.
282: * @param constraint the constraint.
283: * @param g2 the graphics device.
284: *
285: * @return The size following the arrangement.
286: */
287: protected Size2D arrangeRF(BlockContainer container, Graphics2D g2,
288: RectangleConstraint constraint) {
289:
290: Size2D s = arrangeNF(container, g2, constraint);
291: if (constraint.getWidthRange().contains(s.width)) {
292: return s;
293: } else {
294: RectangleConstraint c = constraint.toFixedWidth(constraint
295: .getWidthRange().constrain(s.getWidth()));
296: return arrangeFF(container, g2, c);
297: }
298: }
299:
300: /**
301: * Arranges the block with a range constraint on the width, and no
302: * constraint on the height.
303: *
304: * @param container the container.
305: * @param constraint the constraint.
306: * @param g2 the graphics device.
307: *
308: * @return The size following the arrangement.
309: */
310: protected Size2D arrangeRN(BlockContainer container, Graphics2D g2,
311: RectangleConstraint constraint) {
312: // first arrange without constraints, then see if the width fits
313: // within the required range...if not, call arrangeFN() at max width
314: Size2D s1 = arrangeNN(container, g2);
315: if (constraint.getWidthRange().contains(s1.width)) {
316: return s1;
317: } else {
318: RectangleConstraint c = constraint.toFixedWidth(constraint
319: .getWidthRange().getUpperBound());
320: return arrangeFN(container, g2, c);
321: }
322: }
323:
324: /**
325: * Arranges the blocks without any constraints. This puts all blocks
326: * into a single row.
327: *
328: * @param container the container.
329: * @param g2 the graphics device.
330: *
331: * @return The size after the arrangement.
332: */
333: protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
334: double x = 0.0;
335: double width = 0.0;
336: double maxHeight = 0.0;
337: List blocks = container.getBlocks();
338: int blockCount = blocks.size();
339: if (blockCount > 0) {
340: Size2D[] sizes = new Size2D[blocks.size()];
341: for (int i = 0; i < blocks.size(); i++) {
342: Block block = (Block) blocks.get(i);
343: sizes[i] = block.arrange(g2, RectangleConstraint.NONE);
344: width = width + sizes[i].getWidth();
345: maxHeight = Math.max(sizes[i].height, maxHeight);
346: block.setBounds(new Rectangle2D.Double(x, 0.0,
347: sizes[i].width, sizes[i].height));
348: x = x + sizes[i].width + this .horizontalGap;
349: }
350: if (blockCount > 1) {
351: width = width + this .horizontalGap * (blockCount - 1);
352: }
353: if (this .verticalAlignment != VerticalAlignment.TOP) {
354: for (int i = 0; i < blocks.size(); i++) {
355: //Block b = (Block) blocks.get(i);
356: if (this .verticalAlignment == VerticalAlignment.CENTER) {
357: //TODO: shift block down by half
358: } else if (this .verticalAlignment == VerticalAlignment.BOTTOM) {
359: //TODO: shift block down to bottom
360: }
361: }
362: }
363: }
364: return new Size2D(width, maxHeight);
365: }
366:
367: /**
368: * Arranges the blocks with no width constraint and a fixed height
369: * constraint. This puts all blocks into a single row.
370: *
371: * @param container the container.
372: * @param constraint the constraint.
373: * @param g2 the graphics device.
374: *
375: * @return The size after the arrangement.
376: */
377: protected Size2D arrangeNF(BlockContainer container, Graphics2D g2,
378: RectangleConstraint constraint) {
379: // TODO: for now we are ignoring the height constraint
380: return arrangeNN(container, g2);
381: }
382:
383: /**
384: * Clears any cached information.
385: */
386: public void clear() {
387: // no action required.
388: }
389:
390: /**
391: * Tests this instance for equality with an arbitrary object.
392: *
393: * @param obj the object (<code>null</code> permitted).
394: *
395: * @return A boolean.
396: */
397: public boolean equals(Object obj) {
398: if (obj == this ) {
399: return true;
400: }
401: if (!(obj instanceof FlowArrangement)) {
402: return false;
403: }
404: FlowArrangement that = (FlowArrangement) obj;
405: if (this .horizontalAlignment != that.horizontalAlignment) {
406: return false;
407: }
408: if (this .verticalAlignment != that.verticalAlignment) {
409: return false;
410: }
411: if (this .horizontalGap != that.horizontalGap) {
412: return false;
413: }
414: if (this .verticalGap != that.verticalGap) {
415: return false;
416: }
417: return true;
418: }
419:
420: }
|