001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
006 * 
007 * http://www.object-refinery.com/orsoncharts/index.html
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 com.orsoncharts.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 com.orsoncharts.util.ArgChecks;
047import com.orsoncharts.util.TextAnchor;
048import com.orsoncharts.util.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        ArgChecks.nullNotPermitted(text, "text");
100        ArgChecks.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        ArgChecks.nullNotPermitted(font, "font");
123        this.font = font;
124    }
125    
126    public Color getColor() {
127        return this.color;
128    }
129    
130    public void setColor(Color color) {
131        ArgChecks.nullNotPermitted(color, "color");
132        this.color = color;
133    }
134    
135    /**
136     * Returns the horizontal alignment that will be used when rendering the
137     * text.  The default value is {@code LEFT}.
138     * 
139     * @return The horizontal alignment (never {@code null}). 
140     */
141    public HAlign getHorizontalAlignment() {
142        return this.alignment;
143    }
144    
145    /**
146     * Sets the horizontal alignment.
147     * 
148     * @param align  the alignment ({@code null} not permitted). 
149     */
150    public void setHorizontalAligment(HAlign align) {
151        ArgChecks.nullNotPermitted(align, "align");
152        this.alignment = align;
153    }
154
155    /**
156     * Returns the preferred size of the element (including insets).
157     * 
158     * @param g2  the graphics target.
159     * @param bounds  the bounds.
160     * @param constraints  the constraints (ignored for now).
161     * 
162     * @return The preferred size. 
163     */
164    @Override
165    public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds, 
166            Map<String, Object> constraints) {
167        g2.setFont(this.font);
168        Rectangle2D textBounds = TextUtils.getTextBounds(this.text, 
169                g2.getFontMetrics(this.font));
170        Insets insets = getInsets();
171        double w = Math.min(textBounds.getWidth() + insets.left + insets.right, 
172                bounds.getWidth());
173        double h = Math.min(textBounds.getHeight() + insets.top + insets.bottom,
174                bounds.getHeight());
175        return new ElementDimension(w, h);
176    }
177
178    /**
179     * Performs a layout of this table element, returning a list of bounding
180     * rectangles for the element and its subelements.
181     * 
182     * @param g2  the graphics target.
183     * @param bounds  the bounds.
184     * @param constraints  the constraints (if any).
185     * 
186     * @return A list containing the bounding rectangle for the text (as the
187     *     only item in the list). 
188     */
189    @Override
190    public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds, 
191            Map<String, Object> constraints) {
192        g2.setFont(this.font);
193        Rectangle2D textBounds = TextUtils.getTextBounds(this.text, 
194                g2.getFontMetrics(this.font));
195        Insets insets = getInsets();
196        double width = textBounds.getWidth() + insets.left + insets.right;
197        double x = bounds.getX();
198        switch (this.alignment) {
199            case LEFT: 
200                x = bounds.getX();
201                break;
202            case CENTER:
203                x = bounds.getCenterX() - width / 2.0 - insets.left;
204                break;
205            case RIGHT:
206                x = bounds.getMaxX() - width - insets.right;
207                break;
208            default: 
209                throw new IllegalStateException("HAlign: " + this.alignment);
210        }
211        double y = bounds.getY();
212        double w = Math.min(width, bounds.getWidth());
213        double h = Math.min(textBounds.getHeight() + insets.top + insets.bottom,
214                bounds.getHeight());
215        List<Rectangle2D> result = new ArrayList<Rectangle2D>(1);        
216        result.add(new Rectangle2D.Double(x, y, w, h));
217        return result;
218    }
219    
220    /**
221     * Receives a visitor.
222     * 
223     * @param visitor  the visitor ({@code null} not permitted).
224     * 
225     * @since 1.2
226     */
227    @Override
228    public void receive(TableElementVisitor visitor) {
229        visitor.visit(this);
230    }
231
232    /**
233     * Draws the element within the specified bounds.
234     * 
235     * @param g2  the graphics target.
236     * @param bounds  the bounds.
237     */
238    @Override
239    public void draw(Graphics2D g2, Rectangle2D bounds) {
240        draw(g2, bounds, null);
241    }
242    
243    /**
244     * Draws the element within the specified bounds.  If the 
245     * {@code recordBounds} flag is set, this element and each of its
246     * children will have their {@code BOUNDS_2D} property updated with 
247     * the current bounds.
248     * 
249     * @param g2  the graphics target ({@code null} not permitted).
250     * @param bounds  the bounds ({@code null} not permitted).
251     * @param onDrawHandler  an object that will receive notification before 
252     *     and after the element is drawn ({@code null} permitted).
253     */
254    @Override
255    public void draw(Graphics2D g2, Rectangle2D bounds, 
256            TableElementOnDraw onDrawHandler) {
257        if (onDrawHandler != null) {
258            onDrawHandler.beforeDraw(this, g2, bounds);
259        }
260        List<Rectangle2D> layout = layoutElements(g2, bounds, null);
261        Rectangle2D textBounds = layout.get(0);
262        if (getBackground() != null) {
263            getBackground().fill(g2, textBounds);
264        }
265        g2.setPaint(this.color);
266        g2.setFont(this.font);
267        Insets insets = getInsets();
268        TextUtils.drawAlignedString(this.text, g2, 
269                (float) (textBounds.getX() + insets.left), 
270                (float) (textBounds.getY() + insets.top), TextAnchor.TOP_LEFT);
271        if (onDrawHandler != null) {
272            onDrawHandler.afterDraw(this, g2, bounds);
273        }
274    }
275    
276    /**
277     * Tests this element for equality with an arbitrary object.
278     * 
279     * @param obj  the object ({@code null} permitted).
280     * 
281     * @return A boolean. 
282     */
283    @Override
284    public boolean equals(Object obj) {
285        if (obj == this) {
286            return true;
287        }
288        if (!(obj instanceof TextElement)) {
289            return false;
290        }
291        TextElement that = (TextElement) obj;
292        if (!this.text.equals(that.text)) {
293            return false;
294        }
295        if (!this.font.equals(that.font)) {
296            return false;
297        }
298        if (!this.color.equals(that.color)) {
299            return false;
300        }
301        if (this.alignment != that.alignment) {
302            return false;
303        }
304        return super.equals(obj);
305    }
306    
307    @Override
308    public String toString() {
309        StringBuilder sb = new StringBuilder();
310        sb.append("TextElement[text=").append(this.text).append("]");
311        return sb.toString();
312    }
313}