    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}