Source for org.jfree.chart.block.FlowArrangement

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------
  28:  * FlowArrangement.java
  29:  * --------------------
  30:  * (C) Copyright 2004-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes:
  36:  * --------
  37:  * 22-Oct-2004 : Version 1 (DG);
  38:  * 04-Feb-2005 : Implemented equals() and made serializable (DG);
  39:  * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
  40:  * 
  41:  */
  42: 
  43: package org.jfree.chart.block;
  44: 
  45: import java.awt.Graphics2D;
  46: import java.awt.geom.Rectangle2D;
  47: import java.io.Serializable;
  48: import java.util.ArrayList;
  49: import java.util.List;
  50: 
  51: import org.jfree.ui.HorizontalAlignment;
  52: import org.jfree.ui.Size2D;
  53: import org.jfree.ui.VerticalAlignment;
  54: 
  55: /**
  56:  * Arranges blocks in a flow layout.  This class is immutable.
  57:  */
  58: public class FlowArrangement implements Arrangement, Serializable {
  59: 
  60:     /** For serialization. */
  61:     private static final long serialVersionUID = 4543632485478613800L;
  62:     
  63:     /** The horizontal alignment of blocks. */
  64:     private HorizontalAlignment horizontalAlignment;
  65:     
  66:     /** The vertical alignment of blocks within each row. */
  67:     private VerticalAlignment verticalAlignment;
  68:     
  69:     /** The horizontal gap between items within rows. */
  70:     private double horizontalGap;
  71:     
  72:     /** The vertical gap between rows. */
  73:     private double verticalGap;
  74:     
  75:     /**
  76:      * Creates a new instance.
  77:      */
  78:     public FlowArrangement() {   
  79:         this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0);
  80:     }
  81:      
  82:     /**
  83:      * Creates a new instance.
  84:      * 
  85:      * @param hAlign  the horizontal alignment (currently ignored).
  86:      * @param vAlign  the vertical alignment (currently ignored).
  87:      * @param hGap  the horizontal gap.
  88:      * @param vGap  the vertical gap.
  89:      */
  90:     public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign,
  91:                            double hGap, double vGap) {   
  92:         this.horizontalAlignment = hAlign;
  93:         this.verticalAlignment = vAlign;
  94:         this.horizontalGap = hGap;
  95:         this.verticalGap = vGap;
  96:     }
  97:     
  98:     /**
  99:      * 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: }