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