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