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