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