001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 */
028
029package org.jfree.chart.text;
030
031import java.awt.BasicStroke;
032import java.awt.Color;
033import java.awt.Font;
034import java.awt.Graphics2D;
035import java.awt.Paint;
036import java.awt.Stroke;
037import java.awt.geom.Rectangle2D;
038import java.io.IOException;
039import java.io.ObjectInputStream;
040import java.io.ObjectOutputStream;
041import java.io.Serializable;
042import java.util.Objects;
043
044import org.jfree.chart.api.RectangleAnchor;
045import org.jfree.chart.api.RectangleInsets;
046import org.jfree.chart.block.Size2D;
047import org.jfree.chart.internal.SerialUtils;
048
049/**
050 * A box containing a text block.
051 */
052public class TextBox implements Serializable {
053
054    /** For serialization. */
055    private static final long serialVersionUID = 3360220213180203706L;
056
057    /** The outline paint. */
058    private transient Paint outlinePaint;
059
060    /** The outline stroke. */
061    private transient Stroke outlineStroke;
062
063    /** The interior space. */
064    private RectangleInsets interiorGap;
065
066    /** The background paint. */
067    private transient Paint backgroundPaint;
068
069    /** The shadow paint. */
070    private transient Paint shadowPaint;
071
072    /** The shadow x-offset. */
073    private double shadowXOffset = 2.0;
074
075    /** The shadow y-offset. */
076    private double shadowYOffset = 2.0;
077
078    /** The text block. */
079    private TextBlock textBlock;
080
081    /**
082     * Creates an empty text box.
083     */
084    public TextBox() {
085        this((TextBlock) null);
086    }
087
088    /**
089     * Creates a text box.
090     *
091     * @param text  the text.
092     */
093    public TextBox(String text) {
094        this((TextBlock) null);
095        if (text != null) {
096            this.textBlock = new TextBlock();
097            this.textBlock.addLine(text, new Font("SansSerif", Font.PLAIN, 10),
098                    Color.BLACK);
099        }
100    }
101
102    /**
103     * Creates a new text box.
104     *
105     * @param block  the text block.
106     */
107    public TextBox(TextBlock block) {
108        this.outlinePaint = Color.BLACK;
109        this.outlineStroke = new BasicStroke(1.0f);
110        this.interiorGap = new RectangleInsets(1.0, 3.0, 1.0, 3.0);
111        this.backgroundPaint = new Color(255, 255, 192);
112        this.shadowPaint = Color.GRAY;
113        this.shadowXOffset = 2.0;
114        this.shadowYOffset = 2.0;
115        this.textBlock = block;
116    }
117
118    /**
119     * Returns the outline paint.
120     *
121     * @return The outline paint.
122     */
123    public Paint getOutlinePaint() {
124        return this.outlinePaint;
125    }
126
127    /**
128     * Sets the outline paint.
129     *
130     * @param paint  the paint.
131     */
132    public void setOutlinePaint(Paint paint) {
133        this.outlinePaint = paint;
134    }
135
136    /**
137     * Returns the outline stroke.
138     *
139     * @return The outline stroke.
140     */
141    public Stroke getOutlineStroke() {
142        return this.outlineStroke;
143    }
144
145    /**
146     * Sets the outline stroke.
147     *
148     * @param stroke  the stroke.
149     */
150    public void setOutlineStroke(Stroke stroke) {
151        this.outlineStroke = stroke;
152    }
153
154    /**
155     * Returns the interior gap.
156     *
157     * @return The interior gap.
158     */
159    public RectangleInsets getInteriorGap() {
160        return this.interiorGap;
161    }
162
163    /**
164     * Sets the interior gap.
165     *
166     * @param gap  the gap.
167     */
168    public void setInteriorGap(RectangleInsets gap) {
169        this.interiorGap = gap;
170    }
171
172    /**
173     * Returns the background paint.
174     *
175     * @return The background paint.
176     */
177    public Paint getBackgroundPaint() {
178        return this.backgroundPaint;
179    }
180
181    /**
182     * Sets the background paint.
183     *
184     * @param paint  the paint.
185     */
186    public void setBackgroundPaint(Paint paint) {
187        this.backgroundPaint = paint;
188    }
189
190    /**
191     * Returns the shadow paint.
192     *
193     * @return The shadow paint.
194     */
195    public Paint getShadowPaint() {
196        return this.shadowPaint;
197    }
198
199    /**
200     * Sets the shadow paint.
201     *
202     * @param paint  the paint.
203     */
204    public void setShadowPaint(Paint paint) {
205        this.shadowPaint = paint;
206    }
207
208    /**
209     * Returns the x-offset for the shadow effect.
210     *
211     * @return The offset.
212     */
213    public double getShadowXOffset() {
214        return this.shadowXOffset;
215    }
216
217    /**
218     * Sets the x-offset for the shadow effect.
219     *
220     * @param offset  the offset (in Java2D units).
221     */
222    public void setShadowXOffset(double offset) {
223        this.shadowXOffset = offset;
224    }
225
226    /**
227     * Returns the y-offset for the shadow effect.
228     *
229     * @return The offset.
230     */
231    public double getShadowYOffset() {
232        return this.shadowYOffset;
233    }
234
235    /**
236     * Sets the y-offset for the shadow effect.
237     *
238     * @param offset  the offset (in Java2D units).
239     */
240    public void setShadowYOffset(double offset) {
241        this.shadowYOffset = offset;
242    }
243
244    /**
245     * Returns the text block.
246     *
247     * @return The text block.
248     */
249    public TextBlock getTextBlock() {
250        return this.textBlock;
251    }
252
253    /**
254     * Sets the text block.
255     *
256     * @param block  the block.
257     */
258    public void setTextBlock(TextBlock block) {
259        this.textBlock = block;
260    }
261
262    /**
263     * Draws the text box.
264     *
265     * @param g2  the graphics device.
266     * @param x  the x-coordinate.
267     * @param y  the y-coordinate.
268     * @param anchor  the anchor point.
269     */
270    public void draw(Graphics2D g2, float x, float y, RectangleAnchor anchor) {
271        final Size2D d1 = this.textBlock.calculateDimensions(g2);
272        final double w = this.interiorGap.extendWidth(d1.getWidth());
273        final double h = this.interiorGap.extendHeight(d1.getHeight());
274        final Size2D d2 = new Size2D(w, h);
275        final Rectangle2D bounds
276                = RectangleAnchor.createRectangle(d2, x, y, anchor);
277        double xx = bounds.getX();
278        double yy = bounds.getY();
279
280        if (this.shadowPaint != null) {
281            final Rectangle2D shadow = new Rectangle2D.Double(
282                xx + this.shadowXOffset, yy + this.shadowYOffset,
283                bounds.getWidth(), bounds.getHeight());
284            g2.setPaint(this.shadowPaint);
285            g2.fill(shadow);
286        }
287        if (this.backgroundPaint != null) {
288            g2.setPaint(this.backgroundPaint);
289            g2.fill(bounds);
290        }
291
292        if (this.outlinePaint != null && this.outlineStroke != null) {
293            g2.setPaint(this.outlinePaint);
294            g2.setStroke(this.outlineStroke);
295            g2.draw(bounds);
296        }
297
298        this.textBlock.draw(g2,
299                (float) (xx + this.interiorGap.calculateLeftInset(w)),
300                (float) (yy + this.interiorGap.calculateTopInset(h)),
301                TextBlockAnchor.TOP_LEFT);
302
303    }
304
305    /**
306     * Returns the height of the text box.
307     *
308     * @param g2  the graphics device.
309     *
310     * @return The height (in Java2D units).
311     */
312    public double getHeight(Graphics2D g2) {
313        final Size2D d = this.textBlock.calculateDimensions(g2);
314        return this.interiorGap.extendHeight(d.getHeight());
315    }
316
317    /**
318     * Tests this object for equality with an arbitrary object.
319     *
320     * @param obj  the object to test against ({@code null} permitted).
321     *
322     * @return A boolean.
323     */
324    @Override
325    public boolean equals(Object obj) {
326        if (obj == this) {
327            return true;
328        }
329        if (!(obj instanceof TextBox)) {
330            return false;
331        }
332        final TextBox that = (TextBox) obj;
333        if (!Objects.equals(this.outlinePaint, that.outlinePaint)) {
334            return false;
335        }
336        if (!Objects.equals(this.outlineStroke, that.outlineStroke)) {
337            return false;
338        }
339        if (!Objects.equals(this.interiorGap, that.interiorGap)) {
340            return false;
341        }
342        if (!Objects.equals(this.backgroundPaint, that.backgroundPaint)) {
343            return false;
344        }
345        if (!Objects.equals(this.shadowPaint, that.shadowPaint)) {
346            return false;
347        }
348        if (this.shadowXOffset != that.shadowXOffset) {
349            return false;
350        }
351        if (this.shadowYOffset != that.shadowYOffset) {
352            return false;
353        }
354        if (!Objects.equals(this.textBlock, that.textBlock)) {
355            return false;
356        }
357
358        return true;
359    }
360
361    /**
362     * Returns a hash code for this object.
363     *
364     * @return A hash code.
365     */
366    @Override
367    public int hashCode() {
368        int result;
369        long temp;
370        result = (this.outlinePaint != null ? this.outlinePaint.hashCode() : 0);
371        result = 29 * result + (this.outlineStroke != null
372                ? this.outlineStroke.hashCode() : 0);
373        result = 29 * result + (this.interiorGap != null
374                ? this.interiorGap.hashCode() : 0);
375        result = 29 * result + (this.backgroundPaint != null
376                ? this.backgroundPaint.hashCode() : 0);
377        result = 29 * result + (this.shadowPaint != null
378                ? this.shadowPaint.hashCode() : 0);
379        temp = this.shadowXOffset != +0.0d
380                ? Double.doubleToLongBits(this.shadowXOffset) : 0L;
381        result = 29 * result + (int) (temp ^ (temp >>> 32));
382        temp = this.shadowYOffset != +0.0d
383                ? Double.doubleToLongBits(this.shadowYOffset) : 0L;
384        result = 29 * result + (int) (temp ^ (temp >>> 32));
385        result = 29 * result + (this.textBlock != null
386                ? this.textBlock.hashCode() : 0);
387        return result;
388    }
389
390    /**
391     * Provides serialization support.
392     *
393     * @param stream  the output stream.
394     *
395     * @throws IOException  if there is an I/O error.
396     */
397    private void writeObject(ObjectOutputStream stream) throws IOException {
398        stream.defaultWriteObject();
399        SerialUtils.writePaint(this.outlinePaint, stream);
400        SerialUtils.writeStroke(this.outlineStroke, stream);
401        SerialUtils.writePaint(this.backgroundPaint, stream);
402        SerialUtils.writePaint(this.shadowPaint, stream);
403    }
404
405    /**
406     * Provides serialization support.
407     *
408     * @param stream  the input stream.
409     *
410     * @throws IOException  if there is an I/O error.
411     * @throws ClassNotFoundException  if there is a classpath problem.
412     */
413    private void readObject(ObjectInputStream stream) throws IOException, 
414            ClassNotFoundException {
415        stream.defaultReadObject();
416        this.outlinePaint = SerialUtils.readPaint(stream);
417        this.outlineStroke = SerialUtils.readStroke(stream);
418        this.backgroundPaint = SerialUtils.readPaint(stream);
419        this.shadowPaint = SerialUtils.readPaint(stream);
420    }
421
422}
423