001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
006 * 
007 * https://github.com/jfree/orson-charts
008 * 
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as published by
011 * the Free Software Foundation, either version 3 of the License, or
012 * (at your option) any later version.
013 *
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
021 * 
022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
023 * Other names may be trademarks of their respective owners.]
024 * 
025 * If you do not wish to be bound by the terms of the GPL, an alternative
026 * commercial license can be purchased.  For details, please see visit the
027 * Orson Charts home page:
028 * 
029 * http://www.object-refinery.com/orsoncharts/index.html
030 * 
031 */
032
033package org.jfree.chart3d.table;
034
035import java.awt.Color;
036import java.awt.Dimension;
037import java.awt.Graphics2D;
038import java.awt.geom.Dimension2D;
039import java.awt.geom.Rectangle2D;
040import java.io.Serializable;
041import java.util.Map;
042import java.util.ArrayList;
043import java.util.List;
044import java.awt.Insets;
045
046import org.jfree.chart3d.data.DefaultKeyedValues2D;
047
048/**
049 * A table element that contains a grid of elements.  
050 * <br><br>
051 * NOTE: This class is serializable, but the serialization format is subject 
052 * to change in future releases and should not be relied upon for persisting 
053 * instances of this class.
054 */
055@SuppressWarnings("serial")
056public class GridElement<R extends Comparable<R>, C extends Comparable<C>> 
057        extends AbstractTableElement 
058        implements TableElement, Serializable {
059
060    private static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);
061    
062    /** Storage for the cell elements. */
063    private DefaultKeyedValues2D<R, C, TableElement> elements;
064    
065    /**
066     * Creates a new empty grid.
067     */
068    public GridElement() {
069        this.elements = new DefaultKeyedValues2D<>();
070        setBackgroundColor(TRANSPARENT_COLOR);
071    }
072    
073    /**
074     * Adds (or updates) a cell in the grid.
075     * 
076     * @param element  the element ({@code null} permitted).
077     * @param rowKey  the row key ({@code null} not permitted).
078     * @param columnKey  the column key ({@code null} not permitted).
079     */
080    public void setElement(TableElement element, R rowKey, C columnKey) {
081        // defer argument checking
082        this.elements.setValue(element, rowKey, columnKey);
083    }
084    
085    /**
086     * Receives a visitor by calling the visitor's {@code visit()} method 
087     * for each of the children in the grid, and finally for the grid itself. 
088     * 
089     * @param visitor  the visitor ({@code null} not permitted).
090     * 
091     * @since 1.2
092     */
093    @Override
094    public void receive(TableElementVisitor visitor) {
095        for (int r = 0; r < this.elements.getRowCount(); r++) {
096            for (int c = 0; c < this.elements.getColumnCount(); c++) {
097                TableElement element = this.elements.getValue(r, c);
098                if (element != null) {
099                    element.receive(visitor);
100                }
101            }
102        }
103        visitor.visit(this);
104    }
105    
106    /**
107     * Finds the cell dimensions.
108     * 
109     * @param g2  the graphics target (required to calculate font sizes).
110     * @param bounds  the bounds.
111     * 
112     * @return The cell dimensions (result[0] is the widths, result[1] is the 
113     *     heights). 
114     */
115    private double[][] findCellDimensions(Graphics2D g2, Rectangle2D bounds) {
116        int rowCount = this.elements.getRowCount();
117        int columnCount = this.elements.getColumnCount();
118        double[] widths = new double[columnCount];
119        double[] heights = new double[rowCount];
120        // calculate the maximum width for each column
121        for (int r = 0; r < elements.getRowCount(); r++) {
122            for (int c = 0; c < this.elements.getColumnCount(); c++) {
123                TableElement element = this.elements.getValue(r, c);
124                if (element == null) {
125                    continue;
126                }
127                Dimension2D dim = element.preferredSize(g2, bounds);
128                widths[c] = Math.max(widths[c], dim.getWidth());
129                heights[r] = Math.max(heights[r], dim.getHeight());
130            }
131        }
132        return new double[][] { widths, heights };
133    }
134    
135
136    /**
137     * Returns the preferred size of the element (including insets).
138     * 
139     * @param g2  the graphics target.
140     * @param bounds  the bounds.
141     * @param constraints  the constraints (ignored for now).
142     * 
143     * @return The preferred size. 
144     */
145    @Override
146    public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds, 
147            Map<String, Object> constraints) {
148        Insets insets = getInsets();
149        double[][] cellDimensions = findCellDimensions(g2, bounds);
150        double[] widths = cellDimensions[0];
151        double[] heights = cellDimensions[1];
152        double w = insets.left + insets.right;
153        for (int i = 0; i < widths.length; i++) {
154            w = w + widths[i];
155        }
156        double h = insets.top + insets.bottom;
157        for (int i = 0; i < heights.length; i++) {
158            h = h + heights[i];
159        }
160        return new Dimension((int) w, (int) h);
161    }
162
163    /**
164     * Performs a layout of this table element, returning a list of bounding
165     * rectangles for the element and its subelements.
166     * 
167     * @param g2  the graphics target.
168     * @param bounds  the bounds.
169     * @param constraints  the constraints (if any).
170     * 
171     * @return A list of bounding rectangles. 
172     */
173    @Override
174    public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds, 
175            Map<String, Object> constraints) {
176        double[][] cellDimensions = findCellDimensions(g2, bounds);
177        double[] widths = cellDimensions[0];
178        double[] heights = cellDimensions[1];
179        List<Rectangle2D> result = new ArrayList<>(
180                this.elements.getRowCount() * this.elements.getColumnCount());
181        double y = bounds.getY() + getInsets().top;
182        for (int r = 0; r < elements.getRowCount(); r++) {
183            double x = bounds.getX() + getInsets().left;
184            for (int c = 0; c < this.elements.getColumnCount(); c++) {
185                Rectangle2D cellBounds = new Rectangle2D.Double(x, y, widths[c], heights[r]);
186                TableElement element = this.elements.getValue(r, c);
187                element.layoutElements(g2, cellBounds, null);
188                result.add(cellBounds);
189                x += widths[c];
190            }
191            y = y + heights[r];
192        }
193        return result;
194    }
195
196    /**
197     * Draws the element within the specified bounds.
198     * 
199     * @param g2  the graphics target.
200     * @param bounds  the bounds.
201     */
202    @Override
203    public void draw(Graphics2D g2, Rectangle2D bounds) {
204        draw(g2, bounds, null);
205    }
206    
207    /**
208     * Draws the element within the specified bounds.  If the 
209     * {@code recordBounds} flag is set, this element and each of its
210     * children will have their {@code BOUNDS_2D} property updated with 
211     * the current bounds.
212     * 
213     * @param g2  the graphics target ({@code null} not permitted).
214     * @param bounds  the bounds ({@code null} not permitted).
215     * @param onDrawHandler  an object that will receive notification before 
216     *     and after the element is drawn ({@code null} permitted).
217     * 
218     * @since 1.3
219     */
220    @Override
221    public void draw(Graphics2D g2, Rectangle2D bounds, 
222            TableElementOnDraw onDrawHandler) {
223        if (onDrawHandler != null) {
224            onDrawHandler.beforeDraw(this, g2, bounds);
225        }
226        if (getBackground() != null) {
227            getBackground().fill(g2, bounds);
228        }
229        List<Rectangle2D> positions = layoutElements(g2, bounds, null);
230        for (int r = 0; r < this.elements.getRowCount(); r++) {
231            for (int c = 0; c < this.elements.getColumnCount(); c++) {
232                TableElement element = this.elements.getValue(r, c);
233                if (element == null) {
234                    continue;
235                }
236                Rectangle2D pos = positions.get(r * elements.getColumnCount() 
237                        + c);
238                element.draw(g2, pos, onDrawHandler);
239            }
240        }
241        if (onDrawHandler != null) {
242            onDrawHandler.afterDraw(this, g2, bounds);
243        }
244    }
245    
246    /**
247     * Tests this element for equality with an arbitrary object.
248     * 
249     * @param obj  the object ({@code null} permitted).
250     * 
251     * @return A boolean. 
252     */
253    @Override
254    public boolean equals(Object obj) {
255        if (obj == this) {
256            return true;
257        }
258        if (!(obj instanceof GridElement)) {
259            return false;
260        }
261        GridElement that = (GridElement) obj;
262        if (!this.elements.equals(that.elements)) {
263            return false;
264        }
265        return true;
266    }
267 
268    /**
269     * Returns a string representation of this element, primarily for
270     * debugging purposes.
271     * 
272     * @return A string representation of this element. 
273     */
274    @Override
275    public String toString() {
276        return "GridElement[rowCount=" + this.elements.getRowCount()
277                + ", columnCount=" + this.elements.getColumnCount() + "]";
278    }
279    
280}