001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2017, 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.ui;
030
031import java.awt.geom.Rectangle2D;
032import java.io.Serializable;
033import org.jfree.chart.util.UnitType;
034
035/**
036 * Represents the insets for a rectangle, specified in absolute or relative 
037 * terms. This class is immutable.
038 */
039public class RectangleInsets implements Serializable {
040
041    /** For serialization. */
042    private static final long serialVersionUID = 1902273207559319996L;
043    
044    /**
045     * A useful constant representing zero insets.
046     */
047    public static final RectangleInsets ZERO_INSETS = new RectangleInsets(
048        UnitType.ABSOLUTE, 0.0, 0.0, 0.0, 0.0);
049    
050    /** Absolute or relative units. */
051    private UnitType unitType;
052    
053    /** The top insets. */
054    private double top;
055    
056    /** The left insets. */
057    private double left;
058    
059    /** The bottom insets. */
060    private double bottom;
061    
062    /** The right insets. */
063    private double right;
064    
065    /**
066     * Creates a new instance with all insets initialised to {@code 1.0}.
067     * 
068     * @since 1.0.9
069     */
070    public RectangleInsets() {
071        this(1.0, 1.0, 1.0, 1.0);
072    }
073    
074    /**
075     * Creates a new instance with the specified insets (as 'absolute' units).
076     * 
077     * @param top  the top insets.
078     * @param left  the left insets.
079     * @param bottom  the bottom insets.
080     * @param right  the right insets.
081     */
082    public RectangleInsets(double top, double left, double bottom, 
083            double right) {
084        this(UnitType.ABSOLUTE, top, left, bottom, right);   
085    }
086    
087    /**
088     * Creates a new instance.
089     * 
090     * @param unitType  absolute or relative units ({@code null} not 
091     *                  permitted).
092     * @param top  the top insets.
093     * @param left  the left insets.
094     * @param bottom  the bottom insets.
095     * @param right  the right insets.
096     */
097    public RectangleInsets(UnitType unitType, double top, double left, 
098            double bottom, double right) {
099        if (unitType == null) {
100            throw new IllegalArgumentException("Null 'unitType' argument.");
101        }
102        this.unitType = unitType;
103        this.top = top;
104        this.bottom = bottom;
105        this.left = left;
106        this.right = right;
107    }
108    
109    /**
110     * Returns the unit type (absolute or relative).  This specifies whether 
111     * the insets are measured as Java2D units or percentages.
112     * 
113     * @return The unit type (never {@code null}).
114     */
115    public UnitType getUnitType() {
116        return this.unitType;
117    }
118  
119    /**
120     * Returns the top insets.
121     * 
122     * @return The top insets.
123     */
124    public double getTop() {
125        return this.top;
126    }
127    
128    /**
129     * Returns the bottom insets.
130     * 
131     * @return The bottom insets.
132     */
133    public double getBottom() {
134        return this.bottom;
135    }
136    
137    /**
138     * Returns the left insets.
139     * 
140     * @return The left insets.
141     */
142    public double getLeft() {
143        return this.left;
144    }
145    
146    /**
147     * Returns the right insets.
148     * 
149     * @return The right insets.
150     */
151    public double getRight() {
152        return this.right;
153    }
154    
155    /**
156     * Tests this instance for equality with an arbitrary object.
157     * 
158     * @param obj  the object ({@code null} permitted).
159     * 
160     * @return A boolean.
161     */
162    @Override
163    public boolean equals(Object obj) {
164        if (obj == this) {
165            return true;   
166        }
167        if (!(obj instanceof RectangleInsets)) {
168                return false;
169        }
170        final RectangleInsets that = (RectangleInsets) obj;
171        if (that.unitType != this.unitType) {
172            return false;   
173        }
174        if (this.left != that.left) {
175            return false;   
176        }
177        if (this.right != that.right) {
178            return false;   
179        }
180        if (this.top != that.top) {
181            return false;   
182        }
183        if (this.bottom != that.bottom) {
184            return false;   
185        }
186        return true;   
187    }
188
189    /**
190     * Returns a hash code for the object.
191     * 
192     * @return A hash code.
193     */
194    @Override
195    public int hashCode() {
196        int result;
197        long temp;
198        result = (this.unitType != null ? this.unitType.hashCode() : 0);
199        temp = this.top != +0.0d ? Double.doubleToLongBits(this.top) : 0L;
200        result = 29 * result + (int) (temp ^ (temp >>> 32));
201        temp = this.bottom != +0.0d ? Double.doubleToLongBits(this.bottom) : 0L;
202        result = 29 * result + (int) (temp ^ (temp >>> 32));
203        temp = this.left != +0.0d ? Double.doubleToLongBits(this.left) : 0L;
204        result = 29 * result + (int) (temp ^ (temp >>> 32));
205        temp = this.right != +0.0d ? Double.doubleToLongBits(this.right) : 0L;
206        result = 29 * result + (int) (temp ^ (temp >>> 32));
207        return result;
208    }
209
210    /**
211     * Returns a textual representation of this instance, useful for debugging
212     * purposes.
213     * 
214     * @return A string representing this instance.
215     */
216    @Override
217    public String toString() {
218        return "RectangleInsets[t=" + this.top + ",l=" + this.left
219                + ",b=" + this.bottom + ",r=" + this.right + "]";
220    }
221    
222    /**
223     * Creates an adjusted rectangle using the supplied rectangle, the insets
224     * specified by this instance, and the horizontal and vertical 
225     * adjustment types.
226     * 
227     * @param base  the base rectangle ({@code null} not permitted).
228     * @param horizontal  the horizontal adjustment type ({@code null} not
229     *                    permitted).
230     * @param vertical  the vertical adjustment type ({@code null} not 
231     *                  permitted).
232     * 
233     * @return The inset rectangle.
234     */
235    public Rectangle2D createAdjustedRectangle(Rectangle2D base,
236            LengthAdjustmentType horizontal, LengthAdjustmentType vertical) {
237        if (base == null) {
238            throw new IllegalArgumentException("Null 'base' argument.");
239        }
240        double x = base.getX();
241        double y = base.getY();
242        double w = base.getWidth();
243        double h = base.getHeight();
244        if (horizontal == LengthAdjustmentType.EXPAND) {
245            final double leftOutset = calculateLeftOutset(w);
246            x = x - leftOutset;
247            w = w + leftOutset + calculateRightOutset(w);
248        }
249        else if (horizontal == LengthAdjustmentType.CONTRACT) {
250            final double leftMargin = calculateLeftInset(w);
251            x = x + leftMargin;
252            w = w - leftMargin - calculateRightInset(w);
253        }
254        if (vertical == LengthAdjustmentType.EXPAND) {
255            final double topMargin = calculateTopOutset(h);
256            y = y - topMargin;
257            h = h + topMargin + calculateBottomOutset(h);
258        }
259        else if (vertical == LengthAdjustmentType.CONTRACT) {
260            final double topMargin = calculateTopInset(h);
261            y = y + topMargin;
262            h = h - topMargin - calculateBottomInset(h);
263        }
264        return new Rectangle2D.Double(x, y, w, h);
265    }
266    
267    /**
268     * Creates an 'inset' rectangle.
269     * 
270     * @param base  the base rectangle ({@code null} not permitted).
271     * 
272     * @return The inset rectangle.
273     */
274    public Rectangle2D createInsetRectangle(Rectangle2D base) {
275        return createInsetRectangle(base, true, true);
276    }
277    
278    /**
279     * Creates an 'inset' rectangle.
280     * 
281     * @param base  the base rectangle ({@code null} not permitted).
282     * @param horizontal  apply horizontal insets?
283     * @param vertical  apply vertical insets?
284     * 
285     * @return The inset rectangle.
286     */
287    public Rectangle2D createInsetRectangle(Rectangle2D base,
288            boolean horizontal, boolean vertical) {
289        if (base == null) {
290            throw new IllegalArgumentException("Null 'base' argument.");
291        }
292        double topMargin = 0.0;
293        double bottomMargin = 0.0;
294        if (vertical) {
295            topMargin = calculateTopInset(base.getHeight());
296            bottomMargin = calculateBottomInset(base.getHeight());
297        }
298        double leftMargin = 0.0;
299        double rightMargin = 0.0;
300        if (horizontal) {
301            leftMargin = calculateLeftInset(base.getWidth());
302            rightMargin = calculateRightInset(base.getWidth());
303        }
304        return new Rectangle2D.Double(base.getX() + leftMargin, 
305                base.getY() + topMargin,
306                base.getWidth() - leftMargin - rightMargin,
307                base.getHeight() - topMargin - bottomMargin);
308    }
309    
310    /**
311     * Creates an outset rectangle.
312     * 
313     * @param base  the base rectangle ({@code null} not permitted).
314     * 
315     * @return An outset rectangle.
316     */
317    public Rectangle2D createOutsetRectangle(Rectangle2D base) {
318        return createOutsetRectangle(base, true, true);
319    }
320    
321    /**
322     * Creates an outset rectangle.
323     * 
324     * @param base  the base rectangle ({@code null} not permitted).
325     * @param horizontal  apply horizontal insets?
326     * @param vertical  apply vertical insets? 
327     * 
328     * @return An outset rectangle.
329     */
330    public Rectangle2D createOutsetRectangle(Rectangle2D base,
331            boolean horizontal, boolean vertical) {
332        if (base == null) {
333            throw new IllegalArgumentException("Null 'base' argument.");
334        }
335        double topMargin = 0.0;
336        double bottomMargin = 0.0;
337        if (vertical) {
338            topMargin = calculateTopOutset(base.getHeight());
339            bottomMargin = calculateBottomOutset(base.getHeight());
340        }
341        double leftMargin = 0.0;
342        double rightMargin = 0.0;
343        if (horizontal) {
344            leftMargin = calculateLeftOutset(base.getWidth());
345            rightMargin = calculateRightOutset(base.getWidth());
346        }
347        return new Rectangle2D.Double(base.getX() - leftMargin, 
348                base.getY() - topMargin,
349                base.getWidth() + leftMargin + rightMargin,
350                base.getHeight() + topMargin + bottomMargin);
351    }
352    
353    /**
354     * Returns the top margin.
355     * 
356     * @param height  the height of the base rectangle.
357     * 
358     * @return The top margin (in Java2D units).
359     */
360    public double calculateTopInset(double height) {
361        double result = this.top;
362        if (this.unitType == UnitType.RELATIVE) {
363            result = (this.top * height);
364        }
365        return result;
366    }
367    
368    /**
369     * Returns the top margin.
370     * 
371     * @param height  the height of the base rectangle.
372     * 
373     * @return The top margin (in Java2D units).
374     */
375    public double calculateTopOutset(double height) {
376        double result = this.top;
377        if (this.unitType == UnitType.RELATIVE) {
378            result = (height / (1 - this.top - this.bottom)) * this.top;
379        }
380        return result;
381    }
382    
383    /**
384     * Returns the bottom margin.
385     * 
386     * @param height  the height of the base rectangle.
387     * 
388     * @return The bottom margin (in Java2D units).
389     */
390    public double calculateBottomInset(double height) {
391        double result = this.bottom;
392        if (this.unitType == UnitType.RELATIVE) {
393            result = (this.bottom * height);
394        }
395        return result;
396    }
397
398    /**
399     * Returns the bottom margin.
400     * 
401     * @param height  the height of the base rectangle.
402     * 
403     * @return The bottom margin (in Java2D units).
404     */
405    public double calculateBottomOutset(double height) {
406        double result = this.bottom;
407        if (this.unitType == UnitType.RELATIVE) {
408            result = (height / (1 - this.top - this.bottom)) * this.bottom;
409        }
410        return result;
411    }
412
413    /**
414     * Returns the left margin.
415     * 
416     * @param width  the width of the base rectangle.
417     * 
418     * @return The left margin (in Java2D units).
419     */
420    public double calculateLeftInset(double width) {
421        double result = this.left;
422        if (this.unitType == UnitType.RELATIVE) {
423            result = (this.left * width);
424        }
425        return result;
426    }
427    
428    /**
429     * Returns the left margin.
430     * 
431     * @param width  the width of the base rectangle.
432     * 
433     * @return The left margin (in Java2D units).
434     */
435    public double calculateLeftOutset(double width) {
436        double result = this.left;
437        if (this.unitType == UnitType.RELATIVE) {
438            result = (width / (1 - this.left - this.right)) * this.left;
439        }
440        return result;
441    }
442    
443    /**
444     * Returns the right margin.
445     * 
446     * @param width  the width of the base rectangle.
447     * 
448     * @return The right margin (in Java2D units).
449     */
450    public double calculateRightInset(double width) {
451        double result = this.right;
452        if (this.unitType == UnitType.RELATIVE) {
453            result = (this.right * width);
454        }
455        return result;
456    }
457    
458    /**
459     * Returns the right margin.
460     * 
461     * @param width  the width of the base rectangle.
462     * 
463     * @return The right margin (in Java2D units).
464     */
465    public double calculateRightOutset(double width) {
466        double result = this.right;
467        if (this.unitType == UnitType.RELATIVE) {
468            result = (width / (1 - this.left - this.right)) * this.right;
469        }
470        return result;
471    }
472    
473    /**
474     * Trims the given width to allow for the insets.
475     * 
476     * @param width  the width.
477     * 
478     * @return The trimmed width.
479     */
480    public double trimWidth(double width) {
481        return width - calculateLeftInset(width) - calculateRightInset(width);   
482    }
483    
484    /**
485     * Extends the given width to allow for the insets.
486     * 
487     * @param width  the width.
488     * 
489     * @return The extended width.
490     */
491    public double extendWidth(double width) {
492        return width + calculateLeftOutset(width) + calculateRightOutset(width);   
493    }
494
495    /**
496     * Trims the given height to allow for the insets.
497     * 
498     * @param height  the height.
499     * 
500     * @return The trimmed height.
501     */
502    public double trimHeight(double height) {
503        return height - calculateTopInset(height) 
504                - calculateBottomInset(height);   
505    }
506    
507    /**
508     * Extends the given height to allow for the insets.
509     * 
510     * @param height  the height.
511     * 
512     * @return The extended height.
513     */
514    public double extendHeight(double height) {
515        return height + calculateTopOutset(height) 
516                + calculateBottomOutset(height);   
517    }
518
519    /**
520     * Shrinks the given rectangle by the amount of these insets.
521     * 
522     * @param area  the area ({@code null} not permitted).
523     */
524    public void trim(Rectangle2D area) {
525        double w = area.getWidth();
526        double h = area.getHeight();
527        double l = calculateLeftInset(w);
528        double r = calculateRightInset(w);
529        double t = calculateTopInset(h);
530        double b = calculateBottomInset(h);
531        area.setRect(area.getX() + l, area.getY() + t, w - l - r, h - t - b);    
532    }
533    
534}
535