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