PainterGenerator.java revision 8845:4be14673b9bf
1/*
2 * Copyright (c) 2002, 2007, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package build.tools.generatenimbus;
26
27import java.awt.geom.Point2D;
28import java.util.ArrayList;
29import java.util.HashMap;
30import java.util.LinkedHashMap;
31import java.util.List;
32import java.util.Map;
33
34
35/**
36 * PainterGenerator - Class for generating Painter class java source from a Canvas
37 *
38 * Following in the general theory that is used to generate a Painter file.
39 *
40 * Each Painter file represents a Region. So there is one painter file per region. In
41 * skin.laf we support Icon subregions, which are really just hacked versions of the
42 * parent region.
43 *
44 * In order to generate the most compact and efficient bytecode possible for the
45 * Painters, we actually perform the generation sequence in two steps. The first
46 * step is the analysis phase, where we walk through the SynthModel for the region
47 * and discover commonality among the different states in the region. For example,
48 * do they have common paths? Do they have common colors? Gradients? Is the painting
49 * code for the different states identical other than for colors?
50 *
51 * We gather this information up. On the second pass, we use this data to determine the
52 * methods that need to be generated, and the class variables that need to be generated.
53 * We try to keep the actual bytecode count as small as possible so that we may reduce
54 * the overall size of the look and feel significantly.
55 *
56 * @author  Richard Bair
57 * @author  Jasper Potts
58 */
59public class PainterGenerator {
60    //a handful of counters, incremented whenever the associated object type is encounted.
61    //These counters form the basis of the field and method suffixes.
62    //These are all 1 based, because I felt like it :-)
63    private int colorCounter = 1;
64    private int gradientCounter = 1;
65    private int radialCounter = 1;
66    private int pathCounter = 1;
67    private int rectCounter = 1;
68    private int roundRectCounter = 1;
69    private int ellipseCounter = 1;
70
71    private int stateTypeCounter = 1;
72
73    //during the first pass, we will construct these maps
74    private Map<String, String> colors = new HashMap<String, String>();
75    /**
76     * Code=>method name.
77     */
78    private Map<String, String> methods = new HashMap<String, String>();
79
80    //these variables hold the generated code
81    /**
82     * The source code in this variable will be used to define the various state types
83     */
84    private StringBuilder stateTypeCode = new StringBuilder();
85    /**
86     * The source code in this variable will be used to define the switch statement for painting
87     */
88    private StringBuilder switchCode = new StringBuilder();
89    /**
90     * The source code in this variable will be used to define the methods for painting each state
91     */
92    private StringBuilder paintingCode = new StringBuilder();
93    /**
94     * The source code in this variable will be used to add getExtendedCacheKeys
95     * implementation if needed.
96     */
97    private StringBuilder getExtendedCacheKeysCode = new StringBuilder();
98    /**
99     * The source code in this variable will be used to define the methods for decoding gradients
100     * and shapes.
101     */
102    private StringBuilder gradientsCode = new StringBuilder();
103    private StringBuilder colorCode = new StringBuilder();
104    private StringBuilder shapesCode = new StringBuilder();
105    /**
106     * Map of component colors keyed by state constant name
107     */
108    private Map<String, List<ComponentColor>> componentColorsMap =
109            new LinkedHashMap<String, List<ComponentColor>>();
110    /**
111     * For the current state the list of all component colors used by this
112     * painter, the index in this list is also the index in the runtime array
113     * of defaults and keys.
114     */
115    private List<ComponentColor> componentColors = null;
116
117    PainterGenerator(UIRegion r) {
118        generate(r);
119    }
120
121    private void generate(UIRegion r) {
122        for (UIState state : r.getBackgroundStates()) {
123            Canvas canvas = state.getCanvas();
124            String type = (r instanceof UIIconRegion ? r.getKey() : "Background");
125            generate(state, canvas, type);
126        }
127        for (UIState state : r.getForegroundStates()) {
128            Canvas canvas = state.getCanvas();
129            generate(state, canvas, "Foreground");
130        }
131        for (UIState state : r.getBorderStates()) {
132            Canvas canvas = state.getCanvas();
133            generate(state, canvas, "Border");
134        }
135        //now check for any uiIconRegions, since these are collapsed together.
136        for (UIRegion sub : r.getSubRegions()) {
137            if (sub instanceof UIIconRegion) {
138                generate(sub);
139            }
140        }
141        //generate all the code for component colors
142        if (!componentColorsMap.isEmpty()) {
143            getExtendedCacheKeysCode
144                    .append("    protected Object[] getExtendedCacheKeys(JComponent c) {\n")
145                    .append("        Object[] extendedCacheKeys = null;\n")
146                    .append("        switch(state) {\n");
147            for (Map.Entry<String, List<ComponentColor>> entry : componentColorsMap.entrySet()) {
148                getExtendedCacheKeysCode
149                    .append("            case ")
150                    .append(entry.getKey()).append(":\n")
151                    .append("                extendedCacheKeys = new Object[] {\n");
152                for (int i=0; i<entry.getValue().size(); i++) {
153                    ComponentColor cc = entry.getValue().get(i);
154                    cc.write(getExtendedCacheKeysCode);
155                    if (i + 1 < entry.getValue().size()) {
156                        getExtendedCacheKeysCode.append("),\n");
157                    } else {
158                        getExtendedCacheKeysCode.append(")");
159                    }
160                }
161                getExtendedCacheKeysCode.append("};\n")
162                    .append("                break;\n");
163            }
164            getExtendedCacheKeysCode
165                    .append("        }\n")
166                    .append("        return extendedCacheKeys;\n")
167                    .append("    }");
168        }
169    }
170
171    //type is background, foreground, border, upArrowIcon, etc.
172    private void generate(UIState state, Canvas canvas, String type) {
173        String states = state.getStateKeys();
174        String stateType = Utils.statesToConstantName(type + "_" + states);
175        String paintMethodName = "paint" + type + Utils.statesToClassName(states);
176        //create new array for component colors for this state
177        componentColors = new ArrayList<ComponentColor>();
178
179        stateTypeCode.append("    static final int ").append(stateType).append(" = ").append(stateTypeCounter++).append(";\n");
180
181        if (canvas.isBlank()) {
182            return;
183        }
184
185        switchCode.append("            case ").append(stateType).append(": ").append(paintMethodName).append("(g); break;\n");
186        paintingCode.append("    private void ").append(paintMethodName).append("(Graphics2D g) {\n");
187
188        //start by setting up common info needed to encode the control points
189        Insets in = canvas.getStretchingInsets();
190        float a = in.left;
191        float b = canvas.getSize().width - in.right;
192        float c = in.top;
193        float d = canvas.getSize().height - in.bottom;
194        float width = canvas.getSize().width;
195        float height = canvas.getSize().height;
196        float cw = b - a;
197        float ch = d - c;
198
199        Layer[] layers = canvas.getLayers().toArray(new Layer[0]);
200        for (int index=layers.length-1; index >= 0; index--) {
201            Layer layer = layers[index];
202
203            //shapes must be painted in reverse order
204            List<Shape> shapes = layer.getShapes();
205            for (int i=shapes.size()-1; i>=0; i--) {
206                Shape shape = shapes.get(i);
207                Paint paint = shape.getPaint();
208
209                /*
210                    We attempt to write the minimal number of bytecodes as possible when
211                    generating code. Due to the inherit complexities in determining what
212                    is extraneous, we use the following system:
213
214                    We first generate the code for the shape. Then, we check to see if
215                    this shape has already been generated. If so, then we defer to an
216                    existing method. If not, then we will create a new methods, stick
217                    the code in it, and refer to that method.
218                */
219
220                String shapeMethodName = null; // will contain the name of the method which creates the shape
221                String shapeVariable = null; // will be one of rect, roundRect, ellipse, or path.
222                String shapeMethodBody = null;
223
224                if (shape instanceof Rectangle) {
225                    Rectangle rshape = (Rectangle) shape;
226                    float x1 = encode((float)rshape.getX1(), a, b, width);
227                    float y1 = encode((float)rshape.getY1(), c, d, height);
228                    float x2 = encode((float)rshape.getX2(), a, b, width);
229                    float y2 = encode((float)rshape.getY2(), c, d, height);
230                    if (rshape.isRounded()) {
231                        //it is a rounded rectangle
232                        float rounding = (float)rshape.getRounding();
233
234                        shapeMethodBody =
235                                "        roundRect.setRoundRect(" +
236                                writeDecodeX(x1) + ", //x\n" +
237                                "                               " + writeDecodeY(y1) + ", //y\n" +
238                                "                               " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
239                                "                               " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + ", //height\n" +
240                                "                               " + rounding + "f, " + rounding + "f); //rounding";
241                        shapeVariable = "roundRect";
242                    } else {
243                        shapeMethodBody =
244                                "            rect.setRect(" +
245                                writeDecodeX(x1) + ", //x\n" +
246                                "                         " + writeDecodeY(y1) + ", //y\n" +
247                                "                         " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
248                                "                         " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";
249                        shapeVariable = "rect";
250                    }
251                } else if (shape instanceof Ellipse) {
252                    Ellipse eshape = (Ellipse) shape;
253                    float x1 = encode((float)eshape.getX1(), a, b, width);
254                    float y1 = encode((float)eshape.getY1(), c, d, height);
255                    float x2 = encode((float)eshape.getX2(), a, b, width);
256                    float y2 = encode((float)eshape.getY2(), c, d, height);
257                    shapeMethodBody =
258                            "        ellipse.setFrame(" +
259                            writeDecodeX(x1) + ", //x\n" +
260                            "                         " + writeDecodeY(y1) + ", //y\n" +
261                            "                         " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
262                            "                         " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";
263                    shapeVariable = "ellipse";
264                } else if (shape instanceof Path) {
265                    Path pshape = (Path) shape;
266                    List<Point> controlPoints = pshape.getControlPoints();
267                    Point first, last;
268                    first = last = controlPoints.get(0);
269                    StringBuilder buffer = new StringBuilder();
270                    buffer.append("        path.reset();\n");
271                    buffer.append("        path.moveTo(" + writeDecodeX(encode((float)first.getX(), a, b, width)) + ", " + writeDecodeY(encode((float)first.getY(), c, d, height)) + ");\n");
272                    for (int j=1; j<controlPoints.size(); j++) {
273                        Point cp = controlPoints.get(j);
274                        if (last.isP2Sharp() && cp.isP1Sharp()) {
275                            float x = encode((float)cp.getX(), a, b, width);
276                            float y = encode((float)cp.getY(), c, d, height);
277                            buffer.append("        path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");
278                        } else {
279                            float x1 = encode((float)last.getX(), a, b, width);
280                            float y1 = encode((float)last.getY(), c, d, height);
281                            float x2 = encode((float)cp.getX(), a, b, width);
282                            float y2 = encode((float)cp.getY(), c, d, height);
283                            buffer.append(
284                                    "        path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "
285                                                            + writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "
286                                                            + writeDecodeBezierX(x2, cp.getX(), cp.getCp1X()) + ", "
287                                                            + writeDecodeBezierY(y2, cp.getY(), cp.getCp1Y()) + ", "
288                                                            + writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");
289                        }
290                        last = cp;
291                    }
292                    if (last.isP2Sharp() && first.isP1Sharp()) {
293                        float x = encode((float)first.getX(), a, b, width);
294                        float y = encode((float)first.getY(), c, d, height);
295                        buffer.append("        path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");
296                    } else {
297                        float x1 = encode((float)last.getX(), a, b, width);
298                        float y1 = encode((float)last.getY(), c, d, height);
299                        float x2 = encode((float)first.getX(), a, b, width);
300                        float y2 = encode((float)first.getY(), c, d, height);
301                        buffer.append(
302                                "        path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "
303                                                        + writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "
304                                                        + writeDecodeBezierX(x2, first.getX(), first.getCp1X()) + ", "
305                                                        + writeDecodeBezierY(y2, first.getY(), first.getCp1Y()) + ", "
306                                                        + writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");
307                    }
308                    buffer.append("        path.closePath();");
309                    shapeMethodBody = buffer.toString();
310                    shapeVariable = "path";
311                } else {
312                    throw new RuntimeException("Cannot happen unless a new Shape has been defined");
313                }
314
315                //now that we have the shape defined in shapeMethodBody, and a shapeVariable name,
316                //look to see if such a body has been previously defined.
317                shapeMethodName = methods.get(shapeMethodBody);
318                String returnType = null;
319                if (shapeMethodName == null) {
320                    if ("rect".equals(shapeVariable)) {
321                        shapeMethodName = "decodeRect" + rectCounter++;
322                        returnType = "Rectangle2D";
323                    } else if ("roundRect".equals(shapeVariable)) {
324                        shapeMethodName = "decodeRoundRect" + roundRectCounter++;
325                        returnType = "RoundRectangle2D";
326                    } else if ("ellipse".equals(shapeVariable)) {
327                        shapeMethodName = "decodeEllipse" + ellipseCounter++;
328                        returnType = "Ellipse2D";
329                    } else {
330                        shapeMethodName = "decodePath" + pathCounter++;
331                        returnType = "Path2D";
332                    }
333                    methods.put(shapeMethodBody, shapeMethodName);
334
335                    //since the method wasn't previously defined, time to define it
336                    shapesCode.append("    private ").append(returnType).append(" ").append(shapeMethodName).append("() {\n");
337                    shapesCode.append(shapeMethodBody);
338                    shapesCode.append("\n");
339                    shapesCode.append("        return " + shapeVariable + ";\n");
340                    shapesCode.append("    }\n\n");
341                }
342
343                //now that the method has been defined, I can go on and decode the
344                //paint. After the paint is decoded, I can write the g.fill() method call,
345                //using the result of the shapeMethodName. Yay!
346
347//            if (shapeVariable != null) {
348            //first, calculate the bounds of the shape being painted and store in variables
349                paintingCode.append("        ").append(shapeVariable).append(" = ").append(shapeMethodName).append("();\n");
350
351                if (paint instanceof Matte) {
352                    String colorVariable = encodeMatte((Matte)paint);
353                    paintingCode.append("        g.setPaint(").append(colorVariable).append(");\n");
354                } else if (paint instanceof Gradient) {
355                    String gradientMethodName = encodeGradient(shape, (Gradient)paint);
356                    paintingCode.append("        g.setPaint(").append(gradientMethodName).append("(").append(shapeVariable).append("));\n");
357                } else if (paint instanceof RadialGradient) {
358                    String radialMethodName = encodeRadial(shape, (RadialGradient)paint);
359                    paintingCode.append("        g.setPaint(").append(radialMethodName).append("(").append(shapeVariable).append("));\n");
360                }
361                paintingCode.append("        g.fill(").append(shapeVariable).append(");\n");
362            }
363        }
364
365        paintingCode.append("\n    }\n\n");
366
367        //collect component colors
368        if (!componentColors.isEmpty()) {
369            componentColorsMap.put(stateType, componentColors);
370            componentColors = null;
371        }
372    }
373
374    private float encode(float x, float a, float b, float w) {
375        float r = 0;
376        if (x < a) {
377            r = (x / a);
378        } else if (x > b) {
379            r = 2 + ((x - b) / (w - b));
380        } else if (x == a && x == b) {
381            return 1.5f;
382        } else {
383            r = 1 + ((x - a) / (b - a));
384        }
385
386        if (Float.isNaN(r)) {
387            System.err.println("[Error] Encountered NaN: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
388            return 0;
389        } else if (Float.isInfinite(r)) {
390            System.err.println("[Error] Encountered Infinity: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
391            return 0;
392        } else if (r < 0) {
393            System.err.println("[Error] encoded value was less than 0: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
394            return 0;
395        } else if (r > 3) {
396            System.err.println("[Error] encoded value was greater than 3: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
397            return 3;
398        } else {
399            return r;
400        }
401    }
402
403    private String writeDecodeX(float encodedX) {
404        return "decodeX(" + encodedX + "f)";
405    }
406
407    private String writeDecodeY(float encodedY) {
408        return "decodeY(" + encodedY + "f)";
409    }
410
411    /**
412     *
413     * @param ex encoded x value
414     * @param x unencoded x value
415     * @param cpx unencoded cpx value
416     * @return
417     */
418    private static String writeDecodeBezierX(double ex, double x, double cpx) {
419        return "decodeAnchorX(" + ex + "f, " + (cpx - x) + "f)";
420    }
421
422    /**
423     *
424     * @param ey encoded y value
425     * @param y unencoded y value
426     * @param cpy unencoded cpy value
427     * @return
428     */
429    private static String writeDecodeBezierY(double ey, double y, double cpy) {
430        return "decodeAnchorY(" + ey + "f, " + (cpy - y) + "f)";
431    }
432
433    private String encodeMatte(Matte m) {
434        String declaration = m.getDeclaration();
435        String variableName = colors.get(declaration);
436        if (variableName == null) {
437            variableName = "color" + colorCounter++;
438            colors.put(declaration, variableName);
439            colorCode.append(String.format("    private Color %s = %s;\n",
440                                           variableName, declaration));
441        }
442        // handle component colors
443        if (m.getComponentPropertyName() != null) {
444            ComponentColor cc = m.createComponentColor(variableName);
445            int index = componentColors.indexOf(cc);
446            if (index == -1) {
447                index = componentColors.size();
448                componentColors.add(cc);
449            }
450            return "(Color)componentColors[" + index + "]";
451        } else {
452            return variableName;
453        }
454    }
455
456    private String encodeGradient(Shape ps, Gradient g) {
457        StringBuilder b = new StringBuilder();
458        float x1 = (float)ps.getPaintX1();
459        float y1 = (float)ps.getPaintY1();
460        float x2 = (float)ps.getPaintX2();
461        float y2 = (float)ps.getPaintY2();
462        b.append("        return decodeGradient((");
463        b.append(x1);
464        b.append("f * w) + x, (");
465        b.append(y1);
466        b.append("f * h) + y, (");
467        b.append(x2);
468        b.append("f * w) + x, (");
469        b.append(y2);
470        b.append("f * h) + y,\n");
471        encodeGradientColorsAndFractions(g,b);
472        b.append(");");
473
474        String methodBody = b.toString();
475        String methodName = methods.get(methodBody);
476        if (methodName == null) {
477            methodName = "decodeGradient" + gradientCounter++;
478            gradientsCode.append("    private Paint ").append(methodName).append("(Shape s) {\n");
479            gradientsCode.append("        Rectangle2D bounds = s.getBounds2D();\n");
480            gradientsCode.append("        float x = (float)bounds.getX();\n");
481            gradientsCode.append("        float y = (float)bounds.getY();\n");
482            gradientsCode.append("        float w = (float)bounds.getWidth();\n");
483            gradientsCode.append("        float h = (float)bounds.getHeight();\n");
484            gradientsCode.append(methodBody);
485            gradientsCode.append("\n    }\n\n");
486            methods.put(methodBody, methodName);
487        }
488        return methodName;
489    }
490
491    /**
492     * Takes a abstract gradient and creates the code for the fractions float
493     * array and the colors array that can be used in the constructors of linear
494     * and radial gradients.
495     *
496     * @param g The abstract gradient to get stops from
497     * @param b Append code string of the form "new float[]{...},
498     *          new Color[]{...}" to this StringBuilder
499     */
500    private void encodeGradientColorsAndFractions(AbstractGradient g,
501                                                    StringBuilder b) {
502        List<GradientStop> stops = g.getStops();
503        // there are stops.size() number of main stops. Between each is a
504        // fractional stop. Thus, there are: stops.size() + stops.size() - 1
505        // number of fractions and colors.
506        float[] fractions = new float[stops.size() + stops.size() - 1];
507        String[] colors = new String[fractions.length];
508        //for each stop, create the stop and it's associated fraction
509        int index = 0; // the index into fractions and colors
510        for (int i = 0; i < stops.size(); i++) {
511            GradientStop s = stops.get(i);
512            //copy over the stop's data
513            colors[index] = encodeMatte(s.getColor());
514            fractions[index] = s.getPosition();
515
516            //If this isn't the last stop, then add in the fraction
517            if (index < fractions.length - 1) {
518                float f1 = s.getPosition();
519                float f2 = stops.get(i + 1).getPosition();
520                index++;
521                fractions[index] = f1 + (f2 - f1) * s.getMidpoint();
522                colors[index] = "decodeColor("+
523                        colors[index - 1]+","+
524                        encodeMatte(stops.get(i + 1).getColor())+",0.5f)";
525            }
526            index++;
527        }
528        // Check boundry conditions
529        for (int i = 1; i < fractions.length; i++) {
530            //to avoid an error with LinearGradientPaint where two fractions
531            //are identical, bump up the fraction value by a miniscule amount
532            //if it is identical to the previous one
533            //NOTE: The <= is critical because the previous value may already
534            //have been bumped up
535            if (fractions[i] <= fractions[i - 1]) {
536                fractions[i] = fractions[i - 1] + .000001f;
537            }
538        }
539        //another boundary condition where multiple stops are all at the end. The
540        //previous loop bumped all but one of these past 1.0, which is bad.
541        //so remove any fractions (and their colors!) that are beyond 1.0
542        int outOfBoundsIndex = -1;
543        for (int i = 0; i < fractions.length; i++) {
544            if (fractions[i] > 1) {
545                outOfBoundsIndex = i;
546                break;
547            }
548        }
549        if (outOfBoundsIndex >= 0) {
550            float[] f = fractions;
551            String[] c = colors;
552            fractions = new float[outOfBoundsIndex];
553            colors = new String[outOfBoundsIndex];
554            System.arraycopy(f, 0, fractions, 0, outOfBoundsIndex);
555            System.arraycopy(c, 0, colors, 0, outOfBoundsIndex);
556        }
557        // build string
558        b.append("                new float[] { ");
559        for (int i = 0; i < fractions.length; i++) {
560            if (i>0)b.append(',');
561            b.append(fractions[i]);
562            b.append('f');
563        }
564        b.append(" },\n                new Color[] { ");
565        for (int i = 0; i < colors.length; i++) {
566            if (i>0) b.append(",\n                            ");
567            b.append(colors[i]);
568        }
569        b.append("}");
570    }
571
572    private String encodeRadial(Shape ps, RadialGradient g) {
573        float centerX1 = (float)ps.getPaintX1();
574        float centerY1 = (float)ps.getPaintY1();
575        float x2 = (float)ps.getPaintX2();
576        float y2 = (float)ps.getPaintY2();
577        float radius = (float)Point2D.distance(centerX1, centerY1, x2, y2);
578        StringBuilder b = new StringBuilder();
579
580        b.append("        return decodeRadialGradient((");
581        b.append(centerX1);
582        b.append("f * w) + x, (");
583        b.append(centerY1);
584        b.append("f * h) + y, ");
585        b.append(radius);
586        b.append("f,\n");
587        encodeGradientColorsAndFractions(g,b);
588        b.append(");");
589
590        String methodBody = b.toString();
591        String methodName = methods.get(methodBody);
592        if (methodName == null) {
593            methodName = "decodeRadial" + radialCounter++;
594            gradientsCode.append("    private Paint ").append(methodName).append("(Shape s) {\n");
595            gradientsCode.append("        Rectangle2D bounds = s.getBounds2D();\n");
596            gradientsCode.append("        float x = (float)bounds.getX();\n");
597            gradientsCode.append("        float y = (float)bounds.getY();\n");
598            gradientsCode.append("        float w = (float)bounds.getWidth();\n");
599            gradientsCode.append("        float h = (float)bounds.getHeight();\n");
600            gradientsCode.append(methodBody);
601            gradientsCode.append("\n    }\n\n");
602            methods.put(methodBody, methodName);
603        }
604        return methodName;
605    }
606
607    //note that this method is not thread-safe. In fact, none of this class is.
608    public static void writePainter(UIRegion r, String painterName) {
609        //Need only write out the stuff for this region, don't need to worry about subregions
610        //since this method will be called for each of those (and they go in their own file, anyway).
611        //The only subregion that we compound into this is the one for icons.
612        PainterGenerator gen = new PainterGenerator(r);
613        System.out.println("Generating source file: " + painterName + ".java");
614
615        Map<String, String> variables = Generator.getVariables();
616        variables.put("PAINTER_NAME", painterName);
617        variables.put("STATIC_DECL", gen.stateTypeCode.toString());
618        variables.put("COLORS_DECL", gen.colorCode.toString());
619        variables.put("DO_PAINT_SWITCH_BODY", gen.switchCode.toString());
620        variables.put("PAINTING_DECL", gen.paintingCode.toString());
621        variables.put("GET_EXTENDED_CACHE_KEYS", gen.getExtendedCacheKeysCode.toString());
622        variables.put("SHAPES_DECL", gen.shapesCode.toString());
623        variables.put("GRADIENTS_DECL", gen.gradientsCode.toString());
624
625        Generator.writeSrcFile("PainterImpl", variables, painterName);
626    }
627}
628