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