001    /*
002     * $Id: PaintUtils.java 3352 2009-05-25 16:37:52Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     *
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.util;
023    
024    import java.awt.Color;
025    import java.awt.FontMetrics;
026    import java.awt.GradientPaint;
027    import java.awt.Graphics;
028    import java.awt.Graphics2D;
029    import java.awt.Paint;
030    import java.awt.Rectangle;
031    import java.awt.geom.Point2D;
032    import java.awt.geom.Rectangle2D;
033    import java.lang.reflect.Constructor;
034    import java.lang.reflect.InvocationTargetException;
035    import java.lang.reflect.Method;
036    
037    import javax.swing.JComponent;
038    import javax.swing.JLabel;
039    import javax.swing.SwingConstants;
040    
041    /**
042     * A collection of utilties for painting visual effects.
043     *
044     * @author Mark Davidson
045     */
046    public class PaintUtils {
047        public static final GradientPaint BLUE_EXPERIENCE = new GradientPaint(
048                new Point2D.Double(0, 0),
049                new Color(168, 204, 241),
050                new Point2D.Double(0, 1),
051                new Color(44, 61, 146));
052        public static final GradientPaint MAC_OSX_SELECTED = new GradientPaint(
053                new Point2D.Double(0, 0),
054                new Color(81, 141, 236),
055                new Point2D.Double(0, 1),
056                new Color(36, 96, 192));
057        public static final GradientPaint MAC_OSX = new GradientPaint(
058                new Point2D.Double(0, 0),
059                new Color(167, 210, 250),
060                new Point2D.Double(0, 1),
061                new Color(99, 147, 206));
062        public static final GradientPaint AERITH = new GradientPaint(
063                new Point2D.Double(0, 0),
064                Color.WHITE,
065                new Point2D.Double(0, 1),
066                new Color(64, 110, 161));
067        public static final GradientPaint GRAY = new GradientPaint(
068                new Point2D.Double(0, 0),
069                new Color(226, 226, 226),
070                new Point2D.Double(0, 1),
071                new Color(250, 248, 248));
072        public static final GradientPaint RED_XP = new GradientPaint(
073                new Point2D.Double(0, 0),
074                new Color(236, 81, 81),
075                new Point2D.Double(0, 1),
076                new Color(192, 36, 36));
077        public static final GradientPaint NIGHT_GRAY = new GradientPaint(
078                new Point2D.Double(0, 0),
079                new Color(102, 111, 127),
080                new Point2D.Double(0, 1),
081                new Color(38, 45, 61));
082        public static final GradientPaint NIGHT_GRAY_LIGHT = new GradientPaint(
083                new Point2D.Double(0, 0),
084                new Color(129, 138, 155),
085                new Point2D.Double(0, 1),
086                new Color(58, 66, 82));
087        
088        
089        private PaintUtils() {
090        }
091        
092        /**
093         * Returns the bounds that the text of a label will be drawn into.
094         * Takes into account the current font metrics.
095         */
096        public static Rectangle getTextBounds(Graphics g, JLabel label) {
097            FontMetrics fm = g.getFontMetrics();
098            Rectangle2D r2d = fm.getStringBounds(label.getText(), g);
099            Rectangle rect = r2d.getBounds();
100            int xOffset = 0;
101            switch (label.getHorizontalAlignment()) {
102                case SwingConstants.RIGHT:
103                case SwingConstants.TRAILING:
104                    xOffset = label.getBounds().width - rect.width;
105                    break;
106                case SwingConstants.CENTER:
107                    xOffset = (label.getBounds().width - rect.width) / 2;
108                    break;
109                default:
110                case SwingConstants.LEFT:
111                case SwingConstants.LEADING:
112                    xOffset = 0;
113                    break;
114            }
115            int yOffset = 0;
116            switch (label.getVerticalAlignment()) {
117                case SwingConstants.TOP:
118                    yOffset = 0;
119                    break;
120                case SwingConstants.CENTER:
121                    yOffset = (label.getBounds().height - rect.height) / 2;
122                    break;
123                case SwingConstants.BOTTOM:
124                    yOffset = label.getBounds().height - rect.height;
125                    break;
126            }
127            return new Rectangle(xOffset, yOffset, rect.width, rect.height);
128        }
129        
130        /**
131         * Paints a top to bottom gradient fill over the component bounds
132         * from color1 to color2.
133         */
134        public static void paintGradient(Graphics g, JComponent comp,
135                Color color1, Color color2) {
136            GradientPaint paint = new GradientPaint(0, 0, color1,
137                    0, comp.getHeight(), color2,
138                    true);
139            Graphics2D g2 = (Graphics2D) g;
140            Paint oldPaint = g2.getPaint();
141            g2.setPaint(paint);
142            g2.fillRect(0, 0, comp.getWidth(), comp.getHeight());
143            g2.setPaint(oldPaint);
144        }
145        
146        /** Resizes a gradient to fill the width and height available. If the
147         * gradient is left to right it will be resized to fill the entire width.
148         * If the gradient is top to bottom it will be resized to fill the entire
149         * height. If the gradient is on an angle it will be resized to go from
150         * one corner to the other of the rectangle formed by (0,0 -> width,height).
151         *
152         * This method can resize java.awt.GradientPaint, java.awt.LinearGradientPaint,
153         * and the LinearGradientPaint implementation from Apache's Batik project. Note,
154         * this method does not require the MultipleGradientPaint.jar from Apache to
155         * compile or to run. MultipleGradientPaint.jar *is* required if you want
156         * to resize the LinearGradientPaint from that jar.
157         *
158         * Any paint passed into this method which is not a kind of gradient paint (like
159         * a Color or TexturePaint) will be returned unmodified. It will not throw
160         * an exception. If the gradient cannot be resized due to other errors the
161         * original paint will be returned unmodified. It will not throw an
162         * exception.
163         *
164         */
165        public static Paint resizeGradient(Paint p, int width, int height) {
166            if(p == null) return p;
167            
168            if(p instanceof GradientPaint) {
169                GradientPaint gp = (GradientPaint)p;
170                Point2D[] pts = new Point2D[2];
171                pts[0] = gp.getPoint1();
172                pts[1] = gp.getPoint2();
173                pts = adjustPoints(pts, width, height);
174                return new GradientPaint(pts[0], gp.getColor1(), pts[1], gp.getColor2(), gp.isCyclic());
175            }
176            
177            if("java.awt.LinearGradientPaint".equals(p.getClass().getName()) ||
178               "org.apache.batik.ext.awt.LinearGradientPaint".equals(p.getClass().getName())) {
179                return resizeLinearGradient(p,width,height);
180            }
181            return p;
182        }
183        
184        
185        private static Paint resizeLinearGradient(Paint p, int width, int height) {
186            try {
187                Point2D[] pts = new Point2D[2];
188                pts[0] = (Point2D) invokeMethod(p,"getStartPoint");
189                pts[1] = (Point2D) invokeMethod(p,"getEndPoint");
190                pts = adjustPoints(pts, width, height);
191                float[] fractions = (float[]) invokeMethod(p,"getFractions");
192                Color[] colors = (Color[]) invokeMethod(p,"getColors");
193                
194                Constructor<?> con = p.getClass().getDeclaredConstructor(
195                        Point2D.class, Point2D.class,
196                        new float[0].getClass(),
197                        new Color[0].getClass());
198                return (Paint) con.newInstance(pts[0],pts[1],fractions, colors);
199            } catch (Exception ex) {
200                ex.printStackTrace();
201            }
202            return p;
203        }
204        
205        private static Object invokeMethod(final Object p, final String methodName)
206                throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, SecurityException, IllegalAccessException {
207            Method meth = p.getClass().getMethod(methodName);
208            return meth.invoke(p);
209        }
210        
211        
212        private static Point2D[] adjustPoints(Point2D[] pts, int width, int height) {
213            Point2D start = pts[0];
214            Point2D end = pts[1];
215            
216            double angle = calcAngle(start,end);
217            double a2 = Math.toDegrees(angle);
218            double e = 1;
219            
220            // if it is near 0 degrees
221            if(Math.abs(angle) < Math.toRadians(e) ||
222                    Math.abs(angle) > Math.toRadians(360-e)) {
223                start = new Point2D.Float(0,0);
224                end = new Point2D.Float(width,0);
225            }
226            
227            // near 45
228            if(isNear(a2, 45, e)) {
229                start = new Point2D.Float(0,0);
230                end = new Point2D.Float(width,height);
231            }
232            
233            // near 90
234            if(isNear(a2,  90, e)) {
235                start = new Point2D.Float(0,0);
236                end = new Point2D.Float(0,height);
237            }
238            
239            // near 135
240            if(isNear(a2, 135, e)) {
241                start = new Point2D.Float(width,0);
242                end = new Point2D.Float(0,height);
243            }
244            
245            // near 180
246            if(isNear(a2, 180, e)) {
247                start = new Point2D.Float(width,0);
248                end = new Point2D.Float(0,0);
249            }
250            
251            // near 225
252            if(isNear(a2, 225, e)) {
253                start = new Point2D.Float(width,height);
254                end = new Point2D.Float(0,0);
255            }
256            
257            // near 270
258            if(isNear(a2, 270, e)) {
259                start = new Point2D.Float(0,height);
260                end = new Point2D.Float(0,0);
261            }
262            
263            // near 315
264            if(isNear(a2, 315, e)) {
265                start = new Point2D.Float(0,height);
266                end = new Point2D.Float(width,0);
267            }
268            
269            return new Point2D[] { start, end };
270        }
271        
272        private static boolean isNear(double angle, double target, double error) {
273            return Math.abs(target - Math.abs(angle)) < error;
274        }
275        
276        private static double calcAngle(Point2D p1, Point2D p2) {
277            double x_off = p2.getX() - p1.getX();
278            double y_off = p2.getY() - p1.getY();
279            double angle = Math.atan(y_off / x_off);
280            if (x_off < 0) {
281                angle = angle + Math.PI;
282            }
283            
284            if(angle < 0) { angle+= 2*Math.PI; }
285            if(angle > 2*Math.PI) { angle -= 2*Math.PI; }
286            return angle;
287        }
288    /*    
289        public static void main(String ... args) {
290            LinearGradientPaint in = new LinearGradientPaint(
291                    new Point(0,0), new Point(10,0),
292                    new float[] {0f, 0.5f, 1f},
293                    new Color[] {Color.RED, Color.GREEN, Color.BLUE});
294            log.fine("in  = " + toString(in));
295            Paint out = resizeGradient(in,100,100);
296            log.fine(("out = " + toString((MultipleGradientPaint) out));
297        }*/
298        /*
299        private static String toString(MultipleGradientPaint paint) {
300            StringBuffer buffer = new StringBuffer();
301            buffer.append(paint.getClass().getName());
302            Color[] colors = paint.getColors();
303            float[] values = paint.getFractions();
304            buffer.append("[");
305            for(int i=0; i<colors.length; i++) {
306                buffer.append("#").append(Integer.toHexString(colors[i].getRGB()));
307                buffer.append(":");
308                buffer.append(values[i]);
309                buffer.append(", ");
310            }
311            buffer.append("]");
312            if(paint instanceof LinearGradientPaint) {
313                LinearGradientPaint lgp = (LinearGradientPaint) paint;
314                buffer.append(", ");
315                buffer.append(""+lgp.getStartPoint().getX() + ", " + lgp.getStartPoint().getY());
316                buffer.append("->");
317                buffer.append(""+lgp.getEndPoint().getX() + ", " + lgp.getEndPoint().getY());
318            }
319            
320            return buffer.toString();
321        }*/
322        
323    //    private static void p(String string) {
324    //        log.fine((string);
325    //    }
326    
327    }