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