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