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    
042    package org.jfree.chart.block;
043    
044    import java.awt.Graphics2D;
045    import java.awt.geom.Rectangle2D;
046    import java.io.Serializable;
047    import java.util.ArrayList;
048    import java.util.List;
049    
050    import org.jfree.ui.HorizontalAlignment;
051    import org.jfree.ui.Size2D;
052    import org.jfree.ui.VerticalAlignment;
053    
054    /**
055     * Arranges blocks in a column layout.  This class is immutable.
056     */
057    public 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    }