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