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    
049    package org.jfree.ui;
050    
051    import java.awt.geom.Rectangle2D;
052    import java.io.Serializable;
053    
054    import 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     */
062    public 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    }