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.Font;
037import java.awt.Graphics2D;
038import java.awt.Insets;
039import java.awt.geom.Dimension2D;
040import java.awt.geom.Rectangle2D;
041import java.io.Serializable;
042import java.util.ArrayList;
043import java.util.List;
044import java.util.Map;
045
046import org.jfree.chart3d.graphics2d.TextAnchor;
047import org.jfree.chart3d.internal.Args;
048import org.jfree.chart3d.internal.TextUtils;
049
050/**
051 * A table element consisting of some text that will be drawn on one line.
052 * <br><br>
053 * NOTE: This class is serializable, but the serialization format is subject 
054 * to change in future releases and should not be relied upon for persisting 
055 * instances of this class.
056 */
057@SuppressWarnings("serial")
058public class TextElement extends AbstractTableElement 
059        implements TableElement, Serializable {
060
061    /**
062     * The default font.
063     * 
064     * @since 1.1
065     */
066    public static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 
067            12);
068    
069    /** The text (never {@code null}). */
070    private String text;
071    
072    /** The font (never {@code null}). */
073    private Font font;
074    
075    /** The color for the text (never {@code null}). */
076    private Color color;
077    
078    /** The horizontal alignment (never {@code null}). */
079    private HAlign alignment;
080   
081    /**
082     * Creates a new element that will display the specified text using the
083     * default font ({@link #DEFAULT_FONT}).
084     * 
085     * @param text  the text ({@code null} not permitted).
086     */
087    public TextElement(String text) {
088        this(text, DEFAULT_FONT);
089    }
090    
091    /**
092     * Creates a new instance.
093     * 
094     * @param text  the text ({@code null} not permitted).
095     * @param font  the font ({@code null} not permitted).
096     */
097    public TextElement(String text, Font font) {
098        super();
099        Args.nullNotPermitted(text, "text");
100        Args.nullNotPermitted(font, "font");
101        this.text = text;
102        this.font = font;
103        this.color = Color.BLACK;
104        this.alignment = HAlign.LEFT;
105    }
106    
107    /**
108     * Returns the font.  The default value is {@link #DEFAULT_FONT}.
109     * 
110     * @return The font (never {@code null}). 
111     */
112    public Font getFont() {
113        return this.font;
114    }
115    
116    /**
117     * Sets the font.
118     * 
119     * @param font  the font ({@code null} not permitted). 
120     */
121    public void setFont(Font font) {
122        Args.nullNotPermitted(font, "font");
123        this.font = font;
124    }
125
126    /** 
127     * Returns the foreground color for the text element.  The default value
128     * is {@code Color#BLACK}.
129     * 
130     * @return The foreground color (never {@code null}). 
131     */
132    public Color getColor() {
133        return this.color;
134    }
135    
136    /**
137     * Sets the foreground color for the text element.
138     * 
139     * @param color  the new color ({@code null} not permitted).
140     */
141    public void setColor(Color color) {
142        Args.nullNotPermitted(color, "color");
143        this.color = color;
144    }
145    
146    /**
147     * Returns the horizontal alignment that will be used when rendering the
148     * text.  The default value is {@code LEFT}.
149     * 
150     * @return The horizontal alignment (never {@code null}). 
151     */
152    public HAlign getHorizontalAlignment() {
153        return this.alignment;
154    }
155    
156    /**
157     * Sets the horizontal alignment.
158     * 
159     * @param align  the alignment ({@code null} not permitted). 
160     */
161    public void setHorizontalAligment(HAlign align) {
162        Args.nullNotPermitted(align, "align");
163        this.alignment = align;
164    }
165
166    /**
167     * Returns the preferred size of the element (including insets).
168     * 
169     * @param g2  the graphics target.
170     * @param bounds  the bounds.
171     * @param constraints  the constraints (ignored for now).
172     * 
173     * @return The preferred size. 
174     */
175    @Override
176    public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds, 
177            Map<String, Object> constraints) {
178        g2.setFont(this.font);
179        Rectangle2D textBounds = TextUtils.getTextBounds(this.text, 
180                g2.getFontMetrics(this.font));
181        Insets insets = getInsets();
182        double w = Math.min(textBounds.getWidth() + insets.left + insets.right, 
183                bounds.getWidth());
184        double h = Math.min(textBounds.getHeight() + insets.top + insets.bottom,
185                bounds.getHeight());
186        return new ElementDimension(w, h);
187    }
188
189    /**
190     * Performs a layout of this table element, returning a list of bounding
191     * rectangles for the element and its subelements.
192     * 
193     * @param g2  the graphics target.
194     * @param bounds  the bounds.
195     * @param constraints  the constraints (if any).
196     * 
197     * @return A list containing the bounding rectangle for the text (as the
198     *     only item in the list). 
199     */
200    @Override
201    public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds, 
202            Map<String, Object> constraints) {
203        g2.setFont(this.font);
204        Rectangle2D textBounds = TextUtils.getTextBounds(this.text, 
205                g2.getFontMetrics(this.font));
206        Insets insets = getInsets();
207        double width = textBounds.getWidth() + insets.left + insets.right;
208        double x = bounds.getX();
209        switch (this.alignment) {
210            case LEFT: 
211                x = bounds.getX();
212                break;
213            case CENTER:
214                x = bounds.getCenterX() - width / 2.0 - insets.left;
215                break;
216            case RIGHT:
217                x = bounds.getMaxX() - width - insets.right;
218                break;
219            default: 
220                throw new IllegalStateException("HAlign: " + this.alignment);
221        }
222        double y = bounds.getY();
223        double w = Math.min(width, bounds.getWidth());
224        double h = Math.min(textBounds.getHeight() + insets.top + insets.bottom,
225                bounds.getHeight());
226        List<Rectangle2D> result = new ArrayList<>(1);        
227        result.add(new Rectangle2D.Double(x, y, w, h));
228        return result;
229    }
230    
231    /**
232     * Receives a visitor.
233     * 
234     * @param visitor  the visitor ({@code null} not permitted).
235     * 
236     * @since 1.2
237     */
238    @Override
239    public void receive(TableElementVisitor visitor) {
240        visitor.visit(this);
241    }
242
243    /**
244     * Draws the element within the specified bounds.
245     * 
246     * @param g2  the graphics target.
247     * @param bounds  the bounds.
248     */
249    @Override
250    public void draw(Graphics2D g2, Rectangle2D bounds) {
251        draw(g2, bounds, null);
252    }
253    
254    /**
255     * Draws the element within the specified bounds.  If the 
256     * {@code recordBounds} flag is set, this element and each of its
257     * children will have their {@code BOUNDS_2D} property updated with 
258     * the current bounds.
259     * 
260     * @param g2  the graphics target ({@code null} not permitted).
261     * @param bounds  the bounds ({@code null} not permitted).
262     * @param onDrawHandler  an object that will receive notification before 
263     *     and after the element is drawn ({@code null} permitted).
264     */
265    @Override
266    public void draw(Graphics2D g2, Rectangle2D bounds, 
267            TableElementOnDraw onDrawHandler) {
268        if (onDrawHandler != null) {
269            onDrawHandler.beforeDraw(this, g2, bounds);
270        }
271        List<Rectangle2D> layout = layoutElements(g2, bounds, null);
272        Rectangle2D textBounds = layout.get(0);
273        if (getBackground() != null) {
274            getBackground().fill(g2, textBounds);
275        }
276        g2.setPaint(this.color);
277        g2.setFont(this.font);
278        Insets insets = getInsets();
279        TextUtils.drawAlignedString(this.text, g2, 
280                (float) (textBounds.getX() + insets.left), 
281                (float) (textBounds.getY() + insets.top), TextAnchor.TOP_LEFT);
282        if (onDrawHandler != null) {
283            onDrawHandler.afterDraw(this, g2, bounds);
284        }
285    }
286    
287    /**
288     * Tests this element for equality with an arbitrary object.
289     * 
290     * @param obj  the object ({@code null} permitted).
291     * 
292     * @return A boolean. 
293     */
294    @Override
295    public boolean equals(Object obj) {
296        if (obj == this) {
297            return true;
298        }
299        if (!(obj instanceof TextElement)) {
300            return false;
301        }
302        TextElement that = (TextElement) obj;
303        if (!this.text.equals(that.text)) {
304            return false;
305        }
306        if (!this.font.equals(that.font)) {
307            return false;
308        }
309        if (!this.color.equals(that.color)) {
310            return false;
311        }
312        if (this.alignment != that.alignment) {
313            return false;
314        }
315        return super.equals(obj);
316    }
317    
318    @Override
319    public String toString() {
320        return "TextElement[text=" + this.text + "]";
321    }
322}