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-2005, 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, 2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: FlowArrangement.java,v 1.13.2.2 2006/08/04 11:48:41 mungady Exp $
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 22-Oct-2004 : Version 1 (DG);
  40:  * 04-Feb-2005 : Implemented equals() and made serializable (DG);
  41:  * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
  42:  * 
  43:  */
  44: 
  45: package org.jfree.chart.block;
  46: 
  47: import java.awt.Graphics2D;
  48: import java.awt.geom.Rectangle2D;
  49: import java.io.Serializable;
  50: import java.util.ArrayList;
  51: import java.util.List;
  52: 
  53: import org.jfree.ui.HorizontalAlignment;
  54: import org.jfree.ui.Size2D;
  55: import org.jfree.ui.VerticalAlignment;
  56: 
  57: /**
  58:  * Arranges blocks in a flow layout.  This class is immutable.
  59:  */
  60: public class FlowArrangement implements Arrangement, Serializable {
  61: 
  62:     /** For serialization. */
  63:     private static final long serialVersionUID = 4543632485478613800L;
  64:     
  65:     /** The horizontal alignment of blocks. */
  66:     private HorizontalAlignment horizontalAlignment;
  67:     
  68:     /** The vertical alignment of blocks within each row. */
  69:     private VerticalAlignment verticalAlignment;
  70:     
  71:     /** The horizontal gap between items within rows. */
  72:     private double horizontalGap;
  73:     
  74:     /** The vertical gap between rows. */
  75:     private double verticalGap;
  76:     
  77:     /**
  78:      * Creates a new instance.
  79:      */
  80:     public FlowArrangement() {   
  81:         this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0);
  82:     }
  83:      
  84:     /**
  85:      * Creates a new instance.
  86:      * 
  87:      * @param hAlign  the horizontal alignment (currently ignored).
  88:      * @param vAlign  the vertical alignment (currently ignored).
  89:      * @param hGap  the horizontal gap.
  90:      * @param vGap  the vertical gap.
  91:      */
  92:     public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign,
  93:                            double hGap, double vGap) {   
  94:         this.horizontalAlignment = hAlign;
  95:         this.verticalAlignment = vAlign;
  96:         this.horizontalGap = hGap;
  97:         this.verticalGap = vGap;
  98:     }
  99:     
 100:     /**
 101:      * Adds a block to be managed by this instance.  This method is usually 
 102:      * called by the {@link BlockContainer}, you shouldn't need to call it 
 103:      * directly.
 104:      * 
 105:      * @param block  the block.
 106:      * @param key  a key that controls the position of the block.
 107:      */
 108:     public void add(Block block, Object key) {
 109:         // since the flow layout is relatively straightforward, 
 110:         // no information needs to be recorded here
 111:     }
 112:     
 113:     /**
 114:      * Calculates and sets the bounds of all the items in the specified 
 115:      * container, subject to the given constraint.  The <code>Graphics2D</code>
 116:      * can be used by some items (particularly items containing text) to 
 117:      * calculate sizing parameters.
 118:      * 
 119:      * @param container  the container whose items are being arranged.
 120:      * @param constraint  the size constraint.
 121:      * @param g2  the graphics device.
 122:      * 
 123:      * @return The size of the container after arrangement of the contents.
 124:      */
 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:     public void clear() {
 416:         // no action required.
 417:     }
 418:     
 419:     /**
 420:      * Tests this instance for equality with an arbitrary object.
 421:      * 
 422:      * @param obj  the object (<code>null</code> permitted).
 423:      * 
 424:      * @return A boolean.
 425:      */
 426:     public boolean equals(Object obj) {
 427:         if (obj == this) {
 428:             return true;   
 429:         }
 430:         if (!(obj instanceof FlowArrangement)) {
 431:             return false;   
 432:         }
 433:         FlowArrangement that = (FlowArrangement) obj;
 434:         if (this.horizontalAlignment != that.horizontalAlignment) {
 435:             return false;
 436:         }
 437:         if (this.verticalAlignment != that.verticalAlignment) {
 438:             return false;
 439:         }
 440:         if (this.horizontalGap != that.horizontalGap) {
 441:             return false;   
 442:         }
 443:         if (this.verticalGap != that.verticalGap) {
 444:             return false;   
 445:         }
 446:         return true;
 447:     }
 448:     
 449: }