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