1 /* 2 * Copyright (c) 2002, 2014, 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 com.sun.java.swing.plaf.gtk; 26 27 import sun.swing.SwingUtilities2; 28 import com.sun.java.swing.plaf.gtk.GTKConstants.ArrowType; 29 import com.sun.java.swing.plaf.gtk.GTKConstants.ShadowType; 30 31 import javax.swing.plaf.ColorUIResource; 32 import javax.swing.plaf.synth.*; 33 34 import java.awt.*; 35 import java.awt.geom.*; 36 import java.awt.image.*; 37 import java.io.*; 38 import java.net.*; 39 import java.security.*; 40 import java.util.*; 41 42 import javax.swing.*; 43 import javax.swing.border.*; 44 45 import javax.xml.parsers.*; 46 import org.xml.sax.SAXException; 47 import org.w3c.dom.*; 48 49 /** 50 */ 51 class Metacity implements SynthConstants { 52 // Tutorial: 53 // http://developer.gnome.org/doc/tutorials/metacity/metacity-themes.html 54 55 // Themes: 56 // http://art.gnome.org/theme_list.php?category=metacity 57 58 static Metacity INSTANCE; 59 60 private static final String[] themeNames = { 61 getUserTheme(), 62 "blueprint", 63 "Bluecurve", 64 "Crux", 65 "SwingFallbackTheme" 66 }; 67 68 static { 69 for (String themeName : themeNames) { 70 if (themeName != null) { 71 try { 72 INSTANCE = new Metacity(themeName); 73 } catch (FileNotFoundException ex) { 74 } catch (IOException ex) { 75 logError(themeName, ex); 76 } catch (ParserConfigurationException ex) { 77 logError(themeName, ex); 78 } catch (SAXException ex) { 79 logError(themeName, ex); 80 } 81 } 82 if (INSTANCE != null) { 83 break; 84 } 85 } 86 if (INSTANCE == null) { 87 throw new Error("Could not find any installed metacity theme, and fallback failed"); 88 } 89 } 90 91 private static boolean errorLogged = false; 92 private static DocumentBuilder documentBuilder; 93 private static Document xmlDoc; 94 private static String userHome; 95 96 private Node frame_style_set; 97 private Map<String, Object> frameGeometry; 98 private Map<String, Map<String, Object>> frameGeometries; 99 100 private LayoutManager titlePaneLayout = new TitlePaneLayout(); 101 102 private ColorizeImageFilter imageFilter = new ColorizeImageFilter(); 103 private URL themeDir = null; 104 private SynthContext context; 105 private String themeName; 106 107 private ArithmeticExpressionEvaluator aee = new ArithmeticExpressionEvaluator(); 108 private Map<String, Integer> variables; 109 110 // Reusable clip shape object 111 private RoundRectClipShape roundedClipShape; 112 113 protected Metacity(String themeName) throws IOException, ParserConfigurationException, SAXException { 114 this.themeName = themeName; 115 themeDir = getThemeDir(themeName); 116 if (themeDir != null) { 117 URL themeURL = new URL(themeDir, "metacity-theme-1.xml"); 118 xmlDoc = getXMLDoc(themeURL); 119 if (xmlDoc == null) { 120 throw new IOException(themeURL.toString()); 121 } 122 } else { 123 throw new FileNotFoundException(themeName); 124 } 125 126 // Initialize constants 127 variables = new HashMap<String, Integer>(); 128 NodeList nodes = xmlDoc.getElementsByTagName("constant"); 129 int n = nodes.getLength(); 130 for (int i = 0; i < n; i++) { 131 Node node = nodes.item(i); 132 String name = getStringAttr(node, "name"); 133 if (name != null) { 134 String value = getStringAttr(node, "value"); 135 if (value != null) { 136 try { 137 variables.put(name, Integer.parseInt(value)); 138 } catch (NumberFormatException ex) { 139 logError(themeName, ex); 140 // Ignore bad value 141 } 142 } 143 } 144 } 145 146 // Cache frame geometries 147 frameGeometries = new HashMap<String, Map<String, Object>>(); 148 nodes = xmlDoc.getElementsByTagName("frame_geometry"); 149 n = nodes.getLength(); 150 for (int i = 0; i < n; i++) { 151 Node node = nodes.item(i); 152 String name = getStringAttr(node, "name"); 153 if (name != null) { 154 HashMap<String, Object> gm = new HashMap<String, Object>(); 155 frameGeometries.put(name, gm); 156 157 String parentGM = getStringAttr(node, "parent"); 158 if (parentGM != null) { 159 gm.putAll(frameGeometries.get(parentGM)); 160 } 161 162 gm.put("has_title", 163 Boolean.valueOf(getBooleanAttr(node, "has_title", true))); 164 gm.put("rounded_top_left", 165 Boolean.valueOf(getBooleanAttr(node, "rounded_top_left", false))); 166 gm.put("rounded_top_right", 167 Boolean.valueOf(getBooleanAttr(node, "rounded_top_right", false))); 168 gm.put("rounded_bottom_left", 169 Boolean.valueOf(getBooleanAttr(node, "rounded_bottom_left", false))); 170 gm.put("rounded_bottom_right", 171 Boolean.valueOf(getBooleanAttr(node, "rounded_bottom_right", false))); 172 173 NodeList childNodes = node.getChildNodes(); 174 int nc = childNodes.getLength(); 175 for (int j = 0; j < nc; j++) { 176 Node child = childNodes.item(j); 177 if (child.getNodeType() == Node.ELEMENT_NODE) { 178 name = child.getNodeName(); 179 Object value = null; 180 if ("distance".equals(name)) { 181 value = Integer.valueOf(getIntAttr(child, "value", 0)); 182 } else if ("border".equals(name)) { 183 value = new Insets(getIntAttr(child, "top", 0), 184 getIntAttr(child, "left", 0), 185 getIntAttr(child, "bottom", 0), 186 getIntAttr(child, "right", 0)); 187 } else if ("aspect_ratio".equals(name)) { 188 value = new Float(getFloatAttr(child, "value", 1.0F)); 189 } else { 190 logError(themeName, "Unknown Metacity frame geometry value type: "+name); 191 } 192 String childName = getStringAttr(child, "name"); 193 if (childName != null && value != null) { 194 gm.put(childName, value); 195 } 196 } 197 } 198 } 199 } 200 frameGeometry = frameGeometries.get("normal"); 201 } 202 203 204 public static LayoutManager getTitlePaneLayout() { 205 return INSTANCE.titlePaneLayout; 206 } 207 208 private Shape getRoundedClipShape(int x, int y, int w, int h, 209 int arcw, int arch, int corners) { 210 if (roundedClipShape == null) { 211 roundedClipShape = new RoundRectClipShape(); 212 } 213 roundedClipShape.setRoundedRect(x, y, w, h, arcw, arch, corners); 214 215 return roundedClipShape; 216 } 217 218 void paintButtonBackground(SynthContext context, Graphics g, int x, int y, int w, int h) { 219 updateFrameGeometry(context); 220 221 this.context = context; 222 JButton button = (JButton)context.getComponent(); 223 String buttonName = button.getName(); 224 int buttonState = context.getComponentState(); 225 226 JComponent titlePane = (JComponent)button.getParent(); 227 Container titlePaneParent = titlePane.getParent(); 228 229 JInternalFrame jif; 230 if (titlePaneParent instanceof JInternalFrame) { 231 jif = (JInternalFrame)titlePaneParent; 232 } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) { 233 jif = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame(); 234 } else { 235 return; 236 } 237 238 boolean active = jif.isSelected(); 239 button.setOpaque(false); 240 241 String state = "normal"; 242 if ((buttonState & PRESSED) != 0) { 243 state = "pressed"; 244 } else if ((buttonState & MOUSE_OVER) != 0) { 245 state = "prelight"; 246 } 247 248 String function = null; 249 String location = null; 250 boolean left_corner = false; 251 boolean right_corner = false; 252 253 254 if (buttonName == "InternalFrameTitlePane.menuButton") { 255 function = "menu"; 256 location = "left_left"; 257 left_corner = true; 258 } else if (buttonName == "InternalFrameTitlePane.iconifyButton") { 259 function = "minimize"; 260 int nButtons = ((jif.isIconifiable() ? 1 : 0) + 261 (jif.isMaximizable() ? 1 : 0) + 262 (jif.isClosable() ? 1 : 0)); 263 right_corner = (nButtons == 1); 264 switch (nButtons) { 265 case 1: location = "right_right"; break; 266 case 2: location = "right_middle"; break; 267 case 3: location = "right_left"; break; 268 } 269 } else if (buttonName == "InternalFrameTitlePane.maximizeButton") { 270 function = "maximize"; 271 right_corner = !jif.isClosable(); 272 location = jif.isClosable() ? "right_middle" : "right_right"; 273 } else if (buttonName == "InternalFrameTitlePane.closeButton") { 274 function = "close"; 275 right_corner = true; 276 location = "right_right"; 277 } 278 279 Node frame = getNode(frame_style_set, "frame", new String[] { 280 "focus", (active ? "yes" : "no"), 281 "state", (jif.isMaximum() ? "maximized" : "normal") 282 }); 283 284 if (function != null && frame != null) { 285 Node frame_style = getNode("frame_style", new String[] { 286 "name", getStringAttr(frame, "style") 287 }); 288 if (frame_style != null) { 289 Shape oldClip = g.getClip(); 290 if ((right_corner && getBoolean("rounded_top_right", false)) || 291 (left_corner && getBoolean("rounded_top_left", false))) { 292 293 Point buttonLoc = button.getLocation(); 294 if (right_corner) { 295 g.setClip(getRoundedClipShape(0, 0, w, h, 296 12, 12, RoundRectClipShape.TOP_RIGHT)); 297 } else { 298 g.setClip(getRoundedClipShape(0, 0, w, h, 299 11, 11, RoundRectClipShape.TOP_LEFT)); 300 } 301 302 Rectangle clipBounds = oldClip.getBounds(); 303 g.clipRect(clipBounds.x, clipBounds.y, 304 clipBounds.width, clipBounds.height); 305 } 306 drawButton(frame_style, location+"_background", state, g, w, h, jif); 307 drawButton(frame_style, function, state, g, w, h, jif); 308 g.setClip(oldClip); 309 } 310 } 311 } 312 313 protected void drawButton(Node frame_style, String function, String state, 314 Graphics g, int w, int h, JInternalFrame jif) { 315 Node buttonNode = getNode(frame_style, "button", 316 new String[] { "function", function, "state", state }); 317 if (buttonNode == null && !state.equals("normal")) { 318 buttonNode = getNode(frame_style, "button", 319 new String[] { "function", function, "state", "normal" }); 320 } 321 if (buttonNode != null) { 322 Node draw_ops; 323 String draw_ops_name = getStringAttr(buttonNode, "draw_ops"); 324 if (draw_ops_name != null) { 325 draw_ops = getNode("draw_ops", new String[] { "name", draw_ops_name }); 326 } else { 327 draw_ops = getNode(buttonNode, "draw_ops", null); 328 } 329 variables.put("width", w); 330 variables.put("height", h); 331 draw(draw_ops, g, jif); 332 } 333 } 334 335 void paintFrameBorder(SynthContext context, Graphics g, int x0, int y0, int width, int height) { 336 updateFrameGeometry(context); 337 338 this.context = context; 339 JComponent comp = context.getComponent(); 340 JComponent titlePane = findChild(comp, "InternalFrame.northPane"); 341 342 if (titlePane == null) { 343 return; 344 } 345 346 JInternalFrame jif = null; 347 if (comp instanceof JInternalFrame) { 348 jif = (JInternalFrame)comp; 349 } else if (comp instanceof JInternalFrame.JDesktopIcon) { 350 jif = ((JInternalFrame.JDesktopIcon)comp).getInternalFrame(); 351 } else { 352 assert false : "component is not JInternalFrame or JInternalFrame.JDesktopIcon"; 353 return; 354 } 355 356 boolean active = jif.isSelected(); 357 Font oldFont = g.getFont(); 358 g.setFont(titlePane.getFont()); 359 g.translate(x0, y0); 360 361 Rectangle titleRect = calculateTitleArea(jif); 362 JComponent menuButton = findChild(titlePane, "InternalFrameTitlePane.menuButton"); 363 364 Icon frameIcon = jif.getFrameIcon(); 365 variables.put("mini_icon_width", 366 (frameIcon != null) ? frameIcon.getIconWidth() : 0); 367 variables.put("mini_icon_height", 368 (frameIcon != null) ? frameIcon.getIconHeight() : 0); 369 variables.put("title_width", calculateTitleTextWidth(g, jif)); 370 FontMetrics fm = SwingUtilities2.getFontMetrics(jif, g); 371 variables.put("title_height", fm.getAscent() + fm.getDescent()); 372 373 // These don't seem to apply here, but the Galaxy theme uses them. Not sure why. 374 variables.put("icon_width", 32); 375 variables.put("icon_height", 32); 376 377 if (frame_style_set != null) { 378 Node frame = getNode(frame_style_set, "frame", new String[] { 379 "focus", (active ? "yes" : "no"), 380 "state", (jif.isMaximum() ? "maximized" : "normal") 381 }); 382 383 if (frame != null) { 384 Node frame_style = getNode("frame_style", new String[] { 385 "name", getStringAttr(frame, "style") 386 }); 387 if (frame_style != null) { 388 Shape oldClip = g.getClip(); 389 boolean roundTopLeft = getBoolean("rounded_top_left", false); 390 boolean roundTopRight = getBoolean("rounded_top_right", false); 391 boolean roundBottomLeft = getBoolean("rounded_bottom_left", false); 392 boolean roundBottomRight = getBoolean("rounded_bottom_right", false); 393 394 if (roundTopLeft || roundTopRight || roundBottomLeft || roundBottomRight) { 395 jif.setOpaque(false); 396 397 g.setClip(getRoundedClipShape(0, 0, width, height, 12, 12, 398 (roundTopLeft ? RoundRectClipShape.TOP_LEFT : 0) | 399 (roundTopRight ? RoundRectClipShape.TOP_RIGHT : 0) | 400 (roundBottomLeft ? RoundRectClipShape.BOTTOM_LEFT : 0) | 401 (roundBottomRight ? RoundRectClipShape.BOTTOM_RIGHT : 0))); 402 } 403 404 Rectangle clipBounds = oldClip.getBounds(); 405 g.clipRect(clipBounds.x, clipBounds.y, 406 clipBounds.width, clipBounds.height); 407 408 int titleHeight = titlePane.getHeight(); 409 410 boolean minimized = jif.isIcon(); 411 Insets insets = getBorderInsets(context, null); 412 413 int leftTitlebarEdge = getInt("left_titlebar_edge"); 414 int rightTitlebarEdge = getInt("right_titlebar_edge"); 415 int topTitlebarEdge = getInt("top_titlebar_edge"); 416 int bottomTitlebarEdge = getInt("bottom_titlebar_edge"); 417 418 if (!minimized) { 419 drawPiece(frame_style, g, "entire_background", 420 0, 0, width, height, jif); 421 } 422 drawPiece(frame_style, g, "titlebar", 423 0, 0, width, titleHeight, jif); 424 drawPiece(frame_style, g, "titlebar_middle", 425 leftTitlebarEdge, topTitlebarEdge, 426 width - leftTitlebarEdge - rightTitlebarEdge, 427 titleHeight - topTitlebarEdge - bottomTitlebarEdge, 428 jif); 429 drawPiece(frame_style, g, "left_titlebar_edge", 430 0, 0, leftTitlebarEdge, titleHeight, jif); 431 drawPiece(frame_style, g, "right_titlebar_edge", 432 width - rightTitlebarEdge, 0, 433 rightTitlebarEdge, titleHeight, jif); 434 drawPiece(frame_style, g, "top_titlebar_edge", 435 0, 0, width, topTitlebarEdge, jif); 436 drawPiece(frame_style, g, "bottom_titlebar_edge", 437 0, titleHeight - bottomTitlebarEdge, 438 width, bottomTitlebarEdge, jif); 439 drawPiece(frame_style, g, "title", 440 titleRect.x, titleRect.y, titleRect.width, titleRect.height, jif); 441 if (!minimized) { 442 drawPiece(frame_style, g, "left_edge", 443 0, titleHeight, insets.left, height-titleHeight, jif); 444 drawPiece(frame_style, g, "right_edge", 445 width-insets.right, titleHeight, insets.right, height-titleHeight, jif); 446 drawPiece(frame_style, g, "bottom_edge", 447 0, height - insets.bottom, width, insets.bottom, jif); 448 drawPiece(frame_style, g, "overlay", 449 0, 0, width, height, jif); 450 } 451 g.setClip(oldClip); 452 } 453 } 454 } 455 g.translate(-x0, -y0); 456 g.setFont(oldFont); 457 } 458 459 460 461 private static class Privileged implements PrivilegedAction<Object> { 462 private static int GET_THEME_DIR = 0; 463 private static int GET_USER_THEME = 1; 464 private static int GET_IMAGE = 2; 465 private int type; 466 private Object arg; 467 468 public Object doPrivileged(int type, Object arg) { 469 this.type = type; 470 this.arg = arg; 471 return AccessController.doPrivileged(this); 472 } 473 474 public Object run() { 475 if (type == GET_THEME_DIR) { 476 String sep = File.separator; 477 String[] dirs = new String[] { 478 userHome + sep + ".themes", 479 System.getProperty("swing.metacitythemedir"), 480 "/usr/X11R6/share/themes", 481 "/usr/X11R6/share/gnome/themes", 482 "/usr/local/share/themes", 483 "/usr/local/share/gnome/themes", 484 "/usr/share/themes", 485 "/usr/gnome/share/themes", // Debian/Redhat/Solaris 486 "/opt/gnome2/share/themes" // SuSE 487 }; 488 489 URL themeDir = null; 490 for (int i = 0; i < dirs.length; i++) { 491 // System property may not be set so skip null directories. 492 if (dirs[i] == null) { 493 continue; 494 } 495 File dir = 496 new File(dirs[i] + sep + arg + sep + "metacity-1"); 497 if (new File(dir, "metacity-theme-1.xml").canRead()) { 498 try { 499 themeDir = dir.toURI().toURL(); 500 } catch (MalformedURLException ex) { 501 themeDir = null; 502 } 503 break; 504 } 505 } 506 if (themeDir == null) { 507 String filename = "resources/metacity/" + arg + 508 "/metacity-1/metacity-theme-1.xml"; 509 URL url = getClass().getResource(filename); 510 if (url != null) { 511 String str = url.toString(); 512 try { 513 themeDir = new URL(str.substring(0, str.lastIndexOf('/'))+"/"); 514 } catch (MalformedURLException ex) { 515 themeDir = null; 516 } 517 } 518 } 519 return themeDir; 520 } else if (type == GET_USER_THEME) { 521 try { 522 // Set userHome here because we need the privilege 523 userHome = System.getProperty("user.home"); 524 525 String theme = System.getProperty("swing.metacitythemename"); 526 if (theme != null) { 527 return theme; 528 } 529 // Note: this is a small file (< 1024 bytes) so it's not worth 530 // starting an XML parser or even to use a buffered reader. 531 URL url = new URL(new File(userHome).toURI().toURL(), 532 ".gconf/apps/metacity/general/%25gconf.xml"); 533 // Pending: verify character encoding spec for gconf 534 Reader reader = new InputStreamReader(url.openStream(), "ISO-8859-1"); 535 char[] buf = new char[1024]; 536 StringBuilder sb = new StringBuilder(); 537 int n; 538 while ((n = reader.read(buf)) >= 0) { 539 sb.append(buf, 0, n); 540 } 541 reader.close(); 542 String str = sb.toString(); 543 if (str != null) { 544 String strLowerCase = str.toLowerCase(); 545 int i = strLowerCase.indexOf("<entry name=\"theme\""); 546 if (i >= 0) { 547 i = strLowerCase.indexOf("<stringvalue>", i); 548 if (i > 0) { 549 i += "<stringvalue>".length(); 550 int i2 = str.indexOf('<', i); 551 return str.substring(i, i2); 552 } 553 } 554 } 555 } catch (MalformedURLException ex) { 556 // OK to just ignore. We'll use a fallback theme. 557 } catch (IOException ex) { 558 // OK to just ignore. We'll use a fallback theme. 559 } 560 return null; 561 } else if (type == GET_IMAGE) { 562 return new ImageIcon((URL)arg).getImage(); 563 } else { 564 return null; 565 } 566 } 567 } 568 569 private static URL getThemeDir(String themeName) { 570 return (URL)new Privileged().doPrivileged(Privileged.GET_THEME_DIR, themeName); 571 } 572 573 private static String getUserTheme() { 574 return (String)new Privileged().doPrivileged(Privileged.GET_USER_THEME, null); 575 } 576 577 protected void tileImage(Graphics g, Image image, int x0, int y0, int w, int h, float[] alphas) { 578 Graphics2D g2 = (Graphics2D)g; 579 Composite oldComp = g2.getComposite(); 580 581 int sw = image.getWidth(null); 582 int sh = image.getHeight(null); 583 int y = y0; 584 while (y < y0 + h) { 585 sh = Math.min(sh, y0 + h - y); 586 int x = x0; 587 while (x < x0 + w) { 588 float f = (alphas.length - 1.0F) * x / (x0 + w); 589 int i = (int)f; 590 f -= (int)f; 591 float alpha = (1-f) * alphas[i]; 592 if (i+1 < alphas.length) { 593 alpha += f * alphas[i+1]; 594 } 595 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 596 int swm = Math.min(sw, x0 + w - x); 597 g.drawImage(image, x, y, x+swm, y+sh, 0, 0, swm, sh, null); 598 x += swm; 599 } 600 y += sh; 601 } 602 g2.setComposite(oldComp); 603 } 604 605 private HashMap<String, Image> images = new HashMap<String, Image>(); 606 607 protected Image getImage(String key, Color c) { 608 Image image = images.get(key+"-"+c.getRGB()); 609 if (image == null) { 610 image = imageFilter.colorize(getImage(key), c); 611 if (image != null) { 612 images.put(key+"-"+c.getRGB(), image); 613 } 614 } 615 return image; 616 } 617 618 protected Image getImage(String key) { 619 Image image = images.get(key); 620 if (image == null) { 621 if (themeDir != null) { 622 try { 623 URL url = new URL(themeDir, key); 624 image = (Image)new Privileged().doPrivileged(Privileged.GET_IMAGE, url); 625 } catch (MalformedURLException ex) { 626 //log("Bad image url: "+ themeDir + "/" + key); 627 } 628 } 629 if (image != null) { 630 images.put(key, image); 631 } 632 } 633 return image; 634 } 635 636 private class ColorizeImageFilter extends RGBImageFilter { 637 double cr, cg, cb; 638 639 public ColorizeImageFilter() { 640 canFilterIndexColorModel = true; 641 } 642 643 public void setColor(Color color) { 644 cr = color.getRed() / 255.0; 645 cg = color.getGreen() / 255.0; 646 cb = color.getBlue() / 255.0; 647 } 648 649 public Image colorize(Image fromImage, Color c) { 650 setColor(c); 651 ImageProducer producer = new FilteredImageSource(fromImage.getSource(), this); 652 return new ImageIcon(context.getComponent().createImage(producer)).getImage(); 653 } 654 655 public int filterRGB(int x, int y, int rgb) { 656 // Assume all rgb values are shades of gray 657 double grayLevel = 2 * (rgb & 0xff) / 255.0; 658 double r, g, b; 659 660 if (grayLevel <= 1.0) { 661 r = cr * grayLevel; 662 g = cg * grayLevel; 663 b = cb * grayLevel; 664 } else { 665 grayLevel -= 1.0; 666 r = cr + (1.0 - cr) * grayLevel; 667 g = cg + (1.0 - cg) * grayLevel; 668 b = cb + (1.0 - cb) * grayLevel; 669 } 670 671 return ((rgb & 0xff000000) + 672 (((int)(r * 255)) << 16) + 673 (((int)(g * 255)) << 8) + 674 (int)(b * 255)); 675 } 676 } 677 678 protected static JComponent findChild(JComponent parent, String name) { 679 int n = parent.getComponentCount(); 680 for (int i = 0; i < n; i++) { 681 JComponent c = (JComponent)parent.getComponent(i); 682 if (name.equals(c.getName())) { 683 return c; 684 } 685 } 686 return null; 687 } 688 689 690 protected class TitlePaneLayout implements LayoutManager { 691 public void addLayoutComponent(String name, Component c) {} 692 public void removeLayoutComponent(Component c) {} 693 public Dimension preferredLayoutSize(Container c) { 694 return minimumLayoutSize(c); 695 } 696 697 public Dimension minimumLayoutSize(Container c) { 698 JComponent titlePane = (JComponent)c; 699 Container titlePaneParent = titlePane.getParent(); 700 JInternalFrame frame; 701 if (titlePaneParent instanceof JInternalFrame) { 702 frame = (JInternalFrame)titlePaneParent; 703 } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) { 704 frame = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame(); 705 } else { 706 return null; 707 } 708 709 Dimension buttonDim = calculateButtonSize(titlePane); 710 Insets title_border = (Insets)getFrameGeometry().get("title_border"); 711 Insets button_border = (Insets)getFrameGeometry().get("button_border"); 712 713 // Calculate width. 714 int width = getInt("left_titlebar_edge") + buttonDim.width + getInt("right_titlebar_edge"); 715 if (title_border != null) { 716 width += title_border.left + title_border.right; 717 } 718 if (frame.isClosable()) { 719 width += buttonDim.width; 720 } 721 if (frame.isMaximizable()) { 722 width += buttonDim.width; 723 } 724 if (frame.isIconifiable()) { 725 width += buttonDim.width; 726 } 727 FontMetrics fm = frame.getFontMetrics(titlePane.getFont()); 728 String frameTitle = frame.getTitle(); 729 int title_w = frameTitle != null ? SwingUtilities2.stringWidth( 730 frame, fm, frameTitle) : 0; 731 int title_length = frameTitle != null ? frameTitle.length() : 0; 732 733 // Leave room for three characters in the title. 734 if (title_length > 3) { 735 int subtitle_w = SwingUtilities2.stringWidth( 736 frame, fm, frameTitle.substring(0, 3) + "..."); 737 width += (title_w < subtitle_w) ? title_w : subtitle_w; 738 } else { 739 width += title_w; 740 } 741 742 // Calculate height. 743 int titleHeight = fm.getHeight() + getInt("title_vertical_pad"); 744 if (title_border != null) { 745 titleHeight += title_border.top + title_border.bottom; 746 } 747 int buttonHeight = buttonDim.height; 748 if (button_border != null) { 749 buttonHeight += button_border.top + button_border.bottom; 750 } 751 int height = Math.max(buttonHeight, titleHeight); 752 753 return new Dimension(width, height); 754 } 755 756 public void layoutContainer(Container c) { 757 JComponent titlePane = (JComponent)c; 758 Container titlePaneParent = titlePane.getParent(); 759 JInternalFrame frame; 760 if (titlePaneParent instanceof JInternalFrame) { 761 frame = (JInternalFrame)titlePaneParent; 762 } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) { 763 frame = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame(); 764 } else { 765 return; 766 } 767 Map<String, Object> gm = getFrameGeometry(); 768 769 int w = titlePane.getWidth(); 770 int h = titlePane.getHeight(); 771 772 JComponent menuButton = findChild(titlePane, "InternalFrameTitlePane.menuButton"); 773 JComponent minimizeButton = findChild(titlePane, "InternalFrameTitlePane.iconifyButton"); 774 JComponent maximizeButton = findChild(titlePane, "InternalFrameTitlePane.maximizeButton"); 775 JComponent closeButton = findChild(titlePane, "InternalFrameTitlePane.closeButton"); 776 777 Insets button_border = (Insets)gm.get("button_border"); 778 Dimension buttonDim = calculateButtonSize(titlePane); 779 780 int y = (button_border != null) ? button_border.top : 0; 781 if (titlePaneParent.getComponentOrientation().isLeftToRight()) { 782 int x = getInt("left_titlebar_edge"); 783 784 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height); 785 786 x = w - buttonDim.width - getInt("right_titlebar_edge"); 787 if (button_border != null) { 788 x -= button_border.right; 789 } 790 791 if (frame.isClosable()) { 792 closeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 793 x -= buttonDim.width; 794 } 795 796 if (frame.isMaximizable()) { 797 maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 798 x -= buttonDim.width; 799 } 800 801 if (frame.isIconifiable()) { 802 minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 803 } 804 } else { 805 int x = w - buttonDim.width - getInt("right_titlebar_edge"); 806 807 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height); 808 809 x = getInt("left_titlebar_edge"); 810 if (button_border != null) { 811 x += button_border.left; 812 } 813 814 if (frame.isClosable()) { 815 closeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 816 x += buttonDim.width; 817 } 818 819 if (frame.isMaximizable()) { 820 maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 821 x += buttonDim.width; 822 } 823 824 if (frame.isIconifiable()) { 825 minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 826 } 827 } 828 } 829 } // end TitlePaneLayout 830 831 protected Map<String, Object> getFrameGeometry() { 832 return frameGeometry; 833 } 834 835 protected void setFrameGeometry(JComponent titlePane, Map<String, Object> gm) { 836 this.frameGeometry = gm; 837 if (getInt("top_height") == 0 && titlePane != null) { 838 gm.put("top_height", Integer.valueOf(titlePane.getHeight())); 839 } 840 } 841 842 protected int getInt(String key) { 843 Integer i = (Integer)frameGeometry.get(key); 844 if (i == null) { 845 i = variables.get(key); 846 } 847 return (i != null) ? i.intValue() : 0; 848 } 849 850 protected boolean getBoolean(String key, boolean fallback) { 851 Boolean b = (Boolean)frameGeometry.get(key); 852 return (b != null) ? b.booleanValue() : fallback; 853 } 854 855 856 protected void drawArc(Node node, Graphics g) { 857 NamedNodeMap attrs = node.getAttributes(); 858 Color color = parseColor(getStringAttr(attrs, "color")); 859 int x = aee.evaluate(getStringAttr(attrs, "x")); 860 int y = aee.evaluate(getStringAttr(attrs, "y")); 861 int w = aee.evaluate(getStringAttr(attrs, "width")); 862 int h = aee.evaluate(getStringAttr(attrs, "height")); 863 int start_angle = aee.evaluate(getStringAttr(attrs, "start_angle")); 864 int extent_angle = aee.evaluate(getStringAttr(attrs, "extent_angle")); 865 boolean filled = getBooleanAttr(node, "filled", false); 866 if (getInt("width") == -1) { 867 x -= w; 868 } 869 if (getInt("height") == -1) { 870 y -= h; 871 } 872 g.setColor(color); 873 if (filled) { 874 g.fillArc(x, y, w, h, start_angle, extent_angle); 875 } else { 876 g.drawArc(x, y, w, h, start_angle, extent_angle); 877 } 878 } 879 880 protected void drawLine(Node node, Graphics g) { 881 NamedNodeMap attrs = node.getAttributes(); 882 Color color = parseColor(getStringAttr(attrs, "color")); 883 int x1 = aee.evaluate(getStringAttr(attrs, "x1")); 884 int y1 = aee.evaluate(getStringAttr(attrs, "y1")); 885 int x2 = aee.evaluate(getStringAttr(attrs, "x2")); 886 int y2 = aee.evaluate(getStringAttr(attrs, "y2")); 887 int lineWidth = aee.evaluate(getStringAttr(attrs, "width"), 1); 888 g.setColor(color); 889 if (lineWidth != 1) { 890 Graphics2D g2d = (Graphics2D)g; 891 Stroke stroke = g2d.getStroke(); 892 g2d.setStroke(new BasicStroke((float)lineWidth)); 893 g2d.drawLine(x1, y1, x2, y2); 894 g2d.setStroke(stroke); 895 } else { 896 g.drawLine(x1, y1, x2, y2); 897 } 898 } 899 900 protected void drawRectangle(Node node, Graphics g) { 901 NamedNodeMap attrs = node.getAttributes(); 902 Color color = parseColor(getStringAttr(attrs, "color")); 903 boolean filled = getBooleanAttr(node, "filled", false); 904 int x = aee.evaluate(getStringAttr(attrs, "x")); 905 int y = aee.evaluate(getStringAttr(attrs, "y")); 906 int w = aee.evaluate(getStringAttr(attrs, "width")); 907 int h = aee.evaluate(getStringAttr(attrs, "height")); 908 g.setColor(color); 909 if (getInt("width") == -1) { 910 x -= w; 911 } 912 if (getInt("height") == -1) { 913 y -= h; 914 } 915 if (filled) { 916 g.fillRect(x, y, w, h); 917 } else { 918 g.drawRect(x, y, w, h); 919 } 920 } 921 922 protected void drawTile(Node node, Graphics g, JInternalFrame jif) { 923 NamedNodeMap attrs = node.getAttributes(); 924 int x0 = aee.evaluate(getStringAttr(attrs, "x")); 925 int y0 = aee.evaluate(getStringAttr(attrs, "y")); 926 int w = aee.evaluate(getStringAttr(attrs, "width")); 927 int h = aee.evaluate(getStringAttr(attrs, "height")); 928 int tw = aee.evaluate(getStringAttr(attrs, "tile_width")); 929 int th = aee.evaluate(getStringAttr(attrs, "tile_height")); 930 int width = getInt("width"); 931 int height = getInt("height"); 932 if (width == -1) { 933 x0 -= w; 934 } 935 if (height == -1) { 936 y0 -= h; 937 } 938 Shape oldClip = g.getClip(); 939 if (g instanceof Graphics2D) { 940 ((Graphics2D)g).clip(new Rectangle(x0, y0, w, h)); 941 } 942 variables.put("width", tw); 943 variables.put("height", th); 944 945 Node draw_ops = getNode("draw_ops", new String[] { "name", getStringAttr(node, "name") }); 946 947 int y = y0; 948 while (y < y0 + h) { 949 int x = x0; 950 while (x < x0 + w) { 951 g.translate(x, y); 952 draw(draw_ops, g, jif); 953 g.translate(-x, -y); 954 x += tw; 955 } 956 y += th; 957 } 958 959 variables.put("width", width); 960 variables.put("height", height); 961 g.setClip(oldClip); 962 } 963 964 protected void drawTint(Node node, Graphics g) { 965 NamedNodeMap attrs = node.getAttributes(); 966 Color color = parseColor(getStringAttr(attrs, "color")); 967 float alpha = Float.parseFloat(getStringAttr(attrs, "alpha")); 968 int x = aee.evaluate(getStringAttr(attrs, "x")); 969 int y = aee.evaluate(getStringAttr(attrs, "y")); 970 int w = aee.evaluate(getStringAttr(attrs, "width")); 971 int h = aee.evaluate(getStringAttr(attrs, "height")); 972 if (getInt("width") == -1) { 973 x -= w; 974 } 975 if (getInt("height") == -1) { 976 y -= h; 977 } 978 if (g instanceof Graphics2D) { 979 Graphics2D g2 = (Graphics2D)g; 980 Composite oldComp = g2.getComposite(); 981 AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); 982 g2.setComposite(ac); 983 g2.setColor(color); 984 g2.fillRect(x, y, w, h); 985 g2.setComposite(oldComp); 986 } 987 } 988 989 protected void drawTitle(Node node, Graphics g, JInternalFrame jif) { 990 NamedNodeMap attrs = node.getAttributes(); 991 String colorStr = getStringAttr(attrs, "color"); 992 int i = colorStr.indexOf("gtk:fg["); 993 if (i > 0) { 994 colorStr = colorStr.substring(0, i) + "gtk:text[" + colorStr.substring(i+7); 995 } 996 Color color = parseColor(colorStr); 997 int x = aee.evaluate(getStringAttr(attrs, "x")); 998 int y = aee.evaluate(getStringAttr(attrs, "y")); 999 1000 String title = jif.getTitle(); 1001 if (title != null) { 1002 FontMetrics fm = SwingUtilities2.getFontMetrics(jif, g); 1003 title = SwingUtilities2.clipStringIfNecessary(jif, fm, title, 1004 calculateTitleArea(jif).width); 1005 g.setColor(color); 1006 SwingUtilities2.drawString(jif, g, title, x, y + fm.getAscent()); 1007 } 1008 } 1009 1010 protected Dimension calculateButtonSize(JComponent titlePane) { 1011 int buttonHeight = getInt("button_height"); 1012 if (buttonHeight == 0) { 1013 buttonHeight = titlePane.getHeight(); 1014 if (buttonHeight == 0) { 1015 buttonHeight = 13; 1016 } else { 1017 Insets button_border = (Insets)frameGeometry.get("button_border"); 1018 if (button_border != null) { 1019 buttonHeight -= (button_border.top + button_border.bottom); 1020 } 1021 } 1022 } 1023 int buttonWidth = getInt("button_width"); 1024 if (buttonWidth == 0) { 1025 buttonWidth = buttonHeight; 1026 Float aspect_ratio = (Float)frameGeometry.get("aspect_ratio"); 1027 if (aspect_ratio != null) { 1028 buttonWidth = (int)(buttonHeight / aspect_ratio.floatValue()); 1029 } 1030 } 1031 return new Dimension(buttonWidth, buttonHeight); 1032 } 1033 1034 protected Rectangle calculateTitleArea(JInternalFrame jif) { 1035 JComponent titlePane = findChild(jif, "InternalFrame.northPane"); 1036 Dimension buttonDim = calculateButtonSize(titlePane); 1037 Insets title_border = (Insets)frameGeometry.get("title_border"); 1038 Insets button_border = (Insets)getFrameGeometry().get("button_border"); 1039 1040 Rectangle r = new Rectangle(); 1041 r.x = getInt("left_titlebar_edge"); 1042 r.y = 0; 1043 r.height = titlePane.getHeight(); 1044 if (title_border != null) { 1045 r.x += title_border.left; 1046 r.y += title_border.top; 1047 r.height -= (title_border.top + title_border.bottom); 1048 } 1049 1050 if (titlePane.getParent().getComponentOrientation().isLeftToRight()) { 1051 r.x += buttonDim.width; 1052 if (button_border != null) { 1053 r.x += button_border.left; 1054 } 1055 r.width = titlePane.getWidth() - r.x - getInt("right_titlebar_edge"); 1056 if (jif.isClosable()) { 1057 r.width -= buttonDim.width; 1058 } 1059 if (jif.isMaximizable()) { 1060 r.width -= buttonDim.width; 1061 } 1062 if (jif.isIconifiable()) { 1063 r.width -= buttonDim.width; 1064 } 1065 } else { 1066 if (jif.isClosable()) { 1067 r.x += buttonDim.width; 1068 } 1069 if (jif.isMaximizable()) { 1070 r.x += buttonDim.width; 1071 } 1072 if (jif.isIconifiable()) { 1073 r.x += buttonDim.width; 1074 } 1075 r.width = titlePane.getWidth() - r.x - getInt("right_titlebar_edge") 1076 - buttonDim.width; 1077 if (button_border != null) { 1078 r.x -= button_border.right; 1079 } 1080 } 1081 if (title_border != null) { 1082 r.width -= title_border.right; 1083 } 1084 return r; 1085 } 1086 1087 1088 protected int calculateTitleTextWidth(Graphics g, JInternalFrame jif) { 1089 String title = jif.getTitle(); 1090 if (title != null) { 1091 Rectangle r = calculateTitleArea(jif); 1092 return Math.min(SwingUtilities2.stringWidth(jif, 1093 SwingUtilities2.getFontMetrics(jif, g), title), r.width); 1094 } 1095 return 0; 1096 } 1097 1098 protected void setClip(Node node, Graphics g) { 1099 NamedNodeMap attrs = node.getAttributes(); 1100 int x = aee.evaluate(getStringAttr(attrs, "x")); 1101 int y = aee.evaluate(getStringAttr(attrs, "y")); 1102 int w = aee.evaluate(getStringAttr(attrs, "width")); 1103 int h = aee.evaluate(getStringAttr(attrs, "height")); 1104 if (getInt("width") == -1) { 1105 x -= w; 1106 } 1107 if (getInt("height") == -1) { 1108 y -= h; 1109 } 1110 if (g instanceof Graphics2D) { 1111 ((Graphics2D)g).clip(new Rectangle(x, y, w, h)); 1112 } 1113 } 1114 1115 protected void drawGTKArrow(Node node, Graphics g) { 1116 NamedNodeMap attrs = node.getAttributes(); 1117 String arrow = getStringAttr(attrs, "arrow"); 1118 String shadow = getStringAttr(attrs, "shadow"); 1119 String stateStr = getStringAttr(attrs, "state").toUpperCase(); 1120 int x = aee.evaluate(getStringAttr(attrs, "x")); 1121 int y = aee.evaluate(getStringAttr(attrs, "y")); 1122 int w = aee.evaluate(getStringAttr(attrs, "width")); 1123 int h = aee.evaluate(getStringAttr(attrs, "height")); 1124 1125 int state = -1; 1126 if ("NORMAL".equals(stateStr)) { 1127 state = ENABLED; 1128 } else if ("SELECTED".equals(stateStr)) { 1129 state = SELECTED; 1130 } else if ("INSENSITIVE".equals(stateStr)) { 1131 state = DISABLED; 1132 } else if ("PRELIGHT".equals(stateStr)) { 1133 state = MOUSE_OVER; 1134 } 1135 1136 ShadowType shadowType = null; 1137 if ("in".equals(shadow)) { 1138 shadowType = ShadowType.IN; 1139 } else if ("out".equals(shadow)) { 1140 shadowType = ShadowType.OUT; 1141 } else if ("etched_in".equals(shadow)) { 1142 shadowType = ShadowType.ETCHED_IN; 1143 } else if ("etched_out".equals(shadow)) { 1144 shadowType = ShadowType.ETCHED_OUT; 1145 } else if ("none".equals(shadow)) { 1146 shadowType = ShadowType.NONE; 1147 } 1148 1149 ArrowType direction = null; 1150 if ("up".equals(arrow)) { 1151 direction = ArrowType.UP; 1152 } else if ("down".equals(arrow)) { 1153 direction = ArrowType.DOWN; 1154 } else if ("left".equals(arrow)) { 1155 direction = ArrowType.LEFT; 1156 } else if ("right".equals(arrow)) { 1157 direction = ArrowType.RIGHT; 1158 } 1159 1160 GTKPainter.INSTANCE.paintMetacityElement(context, g, state, 1161 "metacity-arrow", x, y, w, h, shadowType, direction); 1162 } 1163 1164 protected void drawGTKBox(Node node, Graphics g) { 1165 NamedNodeMap attrs = node.getAttributes(); 1166 String shadow = getStringAttr(attrs, "shadow"); 1167 String stateStr = getStringAttr(attrs, "state").toUpperCase(); 1168 int x = aee.evaluate(getStringAttr(attrs, "x")); 1169 int y = aee.evaluate(getStringAttr(attrs, "y")); 1170 int w = aee.evaluate(getStringAttr(attrs, "width")); 1171 int h = aee.evaluate(getStringAttr(attrs, "height")); 1172 1173 int state = -1; 1174 if ("NORMAL".equals(stateStr)) { 1175 state = ENABLED; 1176 } else if ("SELECTED".equals(stateStr)) { 1177 state = SELECTED; 1178 } else if ("INSENSITIVE".equals(stateStr)) { 1179 state = DISABLED; 1180 } else if ("PRELIGHT".equals(stateStr)) { 1181 state = MOUSE_OVER; 1182 } 1183 1184 ShadowType shadowType = null; 1185 if ("in".equals(shadow)) { 1186 shadowType = ShadowType.IN; 1187 } else if ("out".equals(shadow)) { 1188 shadowType = ShadowType.OUT; 1189 } else if ("etched_in".equals(shadow)) { 1190 shadowType = ShadowType.ETCHED_IN; 1191 } else if ("etched_out".equals(shadow)) { 1192 shadowType = ShadowType.ETCHED_OUT; 1193 } else if ("none".equals(shadow)) { 1194 shadowType = ShadowType.NONE; 1195 } 1196 GTKPainter.INSTANCE.paintMetacityElement(context, g, state, 1197 "metacity-box", x, y, w, h, shadowType, null); 1198 } 1199 1200 protected void drawGTKVLine(Node node, Graphics g) { 1201 NamedNodeMap attrs = node.getAttributes(); 1202 String stateStr = getStringAttr(attrs, "state").toUpperCase(); 1203 1204 int x = aee.evaluate(getStringAttr(attrs, "x")); 1205 int y1 = aee.evaluate(getStringAttr(attrs, "y1")); 1206 int y2 = aee.evaluate(getStringAttr(attrs, "y2")); 1207 1208 int state = -1; 1209 if ("NORMAL".equals(stateStr)) { 1210 state = ENABLED; 1211 } else if ("SELECTED".equals(stateStr)) { 1212 state = SELECTED; 1213 } else if ("INSENSITIVE".equals(stateStr)) { 1214 state = DISABLED; 1215 } else if ("PRELIGHT".equals(stateStr)) { 1216 state = MOUSE_OVER; 1217 } 1218 1219 GTKPainter.INSTANCE.paintMetacityElement(context, g, state, 1220 "metacity-vline", x, y1, 1, y2 - y1, null, null); 1221 } 1222 1223 protected void drawGradient(Node node, Graphics g) { 1224 NamedNodeMap attrs = node.getAttributes(); 1225 String type = getStringAttr(attrs, "type"); 1226 float alpha = getFloatAttr(node, "alpha", -1F); 1227 int x = aee.evaluate(getStringAttr(attrs, "x")); 1228 int y = aee.evaluate(getStringAttr(attrs, "y")); 1229 int w = aee.evaluate(getStringAttr(attrs, "width")); 1230 int h = aee.evaluate(getStringAttr(attrs, "height")); 1231 if (getInt("width") == -1) { 1232 x -= w; 1233 } 1234 if (getInt("height") == -1) { 1235 y -= h; 1236 } 1237 1238 // Get colors from child nodes 1239 Node[] colorNodes = getNodesByName(node, "color"); 1240 Color[] colors = new Color[colorNodes.length]; 1241 for (int i = 0; i < colorNodes.length; i++) { 1242 colors[i] = parseColor(getStringAttr(colorNodes[i], "value")); 1243 } 1244 1245 boolean horizontal = ("diagonal".equals(type) || "horizontal".equals(type)); 1246 boolean vertical = ("diagonal".equals(type) || "vertical".equals(type)); 1247 1248 if (g instanceof Graphics2D) { 1249 Graphics2D g2 = (Graphics2D)g; 1250 Composite oldComp = g2.getComposite(); 1251 if (alpha >= 0F) { 1252 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 1253 } 1254 int n = colors.length - 1; 1255 for (int i = 0; i < n; i++) { 1256 g2.setPaint(new GradientPaint(x + (horizontal ? (i*w/n) : 0), 1257 y + (vertical ? (i*h/n) : 0), 1258 colors[i], 1259 x + (horizontal ? ((i+1)*w/n) : 0), 1260 y + (vertical ? ((i+1)*h/n) : 0), 1261 colors[i+1])); 1262 g2.fillRect(x + (horizontal ? (i*w/n) : 0), 1263 y + (vertical ? (i*h/n) : 0), 1264 (horizontal ? (w/n) : w), 1265 (vertical ? (h/n) : h)); 1266 } 1267 g2.setComposite(oldComp); 1268 } 1269 } 1270 1271 protected void drawImage(Node node, Graphics g) { 1272 NamedNodeMap attrs = node.getAttributes(); 1273 String filename = getStringAttr(attrs, "filename"); 1274 String colorizeStr = getStringAttr(attrs, "colorize"); 1275 Color colorize = (colorizeStr != null) ? parseColor(colorizeStr) : null; 1276 String alpha = getStringAttr(attrs, "alpha"); 1277 Image object = (colorize != null) ? getImage(filename, colorize) : getImage(filename); 1278 variables.put("object_width", object.getWidth(null)); 1279 variables.put("object_height", object.getHeight(null)); 1280 String fill_type = getStringAttr(attrs, "fill_type"); 1281 int x = aee.evaluate(getStringAttr(attrs, "x")); 1282 int y = aee.evaluate(getStringAttr(attrs, "y")); 1283 int w = aee.evaluate(getStringAttr(attrs, "width")); 1284 int h = aee.evaluate(getStringAttr(attrs, "height")); 1285 if (getInt("width") == -1) { 1286 x -= w; 1287 } 1288 if (getInt("height") == -1) { 1289 y -= h; 1290 } 1291 1292 if (alpha != null) { 1293 if ("tile".equals(fill_type)) { 1294 StringTokenizer tokenizer = new StringTokenizer(alpha, ":"); 1295 float[] alphas = new float[tokenizer.countTokens()]; 1296 for (int i = 0; i < alphas.length; i++) { 1297 alphas[i] = Float.parseFloat(tokenizer.nextToken()); 1298 } 1299 tileImage(g, object, x, y, w, h, alphas); 1300 } else { 1301 float a = Float.parseFloat(alpha); 1302 if (g instanceof Graphics2D) { 1303 Graphics2D g2 = (Graphics2D)g; 1304 Composite oldComp = g2.getComposite(); 1305 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a)); 1306 g2.drawImage(object, x, y, w, h, null); 1307 g2.setComposite(oldComp); 1308 } 1309 } 1310 } else { 1311 g.drawImage(object, x, y, w, h, null); 1312 } 1313 } 1314 1315 protected void drawIcon(Node node, Graphics g, JInternalFrame jif) { 1316 Icon icon = jif.getFrameIcon(); 1317 if (icon == null) { 1318 return; 1319 } 1320 1321 NamedNodeMap attrs = node.getAttributes(); 1322 String alpha = getStringAttr(attrs, "alpha"); 1323 int x = aee.evaluate(getStringAttr(attrs, "x")); 1324 int y = aee.evaluate(getStringAttr(attrs, "y")); 1325 int w = aee.evaluate(getStringAttr(attrs, "width")); 1326 int h = aee.evaluate(getStringAttr(attrs, "height")); 1327 if (getInt("width") == -1) { 1328 x -= w; 1329 } 1330 if (getInt("height") == -1) { 1331 y -= h; 1332 } 1333 1334 if (alpha != null) { 1335 float a = Float.parseFloat(alpha); 1336 if (g instanceof Graphics2D) { 1337 Graphics2D g2 = (Graphics2D)g; 1338 Composite oldComp = g2.getComposite(); 1339 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a)); 1340 icon.paintIcon(jif, g, x, y); 1341 g2.setComposite(oldComp); 1342 } 1343 } else { 1344 icon.paintIcon(jif, g, x, y); 1345 } 1346 } 1347 1348 protected void drawInclude(Node node, Graphics g, JInternalFrame jif) { 1349 int oldWidth = getInt("width"); 1350 int oldHeight = getInt("height"); 1351 1352 NamedNodeMap attrs = node.getAttributes(); 1353 int x = aee.evaluate(getStringAttr(attrs, "x"), 0); 1354 int y = aee.evaluate(getStringAttr(attrs, "y"), 0); 1355 int w = aee.evaluate(getStringAttr(attrs, "width"), -1); 1356 int h = aee.evaluate(getStringAttr(attrs, "height"), -1); 1357 1358 if (w != -1) { 1359 variables.put("width", w); 1360 } 1361 if (h != -1) { 1362 variables.put("height", h); 1363 } 1364 1365 Node draw_ops = getNode("draw_ops", new String[] { 1366 "name", getStringAttr(node, "name") 1367 }); 1368 g.translate(x, y); 1369 draw(draw_ops, g, jif); 1370 g.translate(-x, -y); 1371 1372 if (w != -1) { 1373 variables.put("width", oldWidth); 1374 } 1375 if (h != -1) { 1376 variables.put("height", oldHeight); 1377 } 1378 } 1379 1380 protected void draw(Node draw_ops, Graphics g, JInternalFrame jif) { 1381 if (draw_ops != null) { 1382 NodeList nodes = draw_ops.getChildNodes(); 1383 if (nodes != null) { 1384 Shape oldClip = g.getClip(); 1385 for (int i = 0; i < nodes.getLength(); i++) { 1386 Node child = nodes.item(i); 1387 if (child.getNodeType() == Node.ELEMENT_NODE) { 1388 try { 1389 String name = child.getNodeName(); 1390 if ("include".equals(name)) { 1391 drawInclude(child, g, jif); 1392 } else if ("arc".equals(name)) { 1393 drawArc(child, g); 1394 } else if ("clip".equals(name)) { 1395 setClip(child, g); 1396 } else if ("gradient".equals(name)) { 1397 drawGradient(child, g); 1398 } else if ("gtk_arrow".equals(name)) { 1399 drawGTKArrow(child, g); 1400 } else if ("gtk_box".equals(name)) { 1401 drawGTKBox(child, g); 1402 } else if ("gtk_vline".equals(name)) { 1403 drawGTKVLine(child, g); 1404 } else if ("image".equals(name)) { 1405 drawImage(child, g); 1406 } else if ("icon".equals(name)) { 1407 drawIcon(child, g, jif); 1408 } else if ("line".equals(name)) { 1409 drawLine(child, g); 1410 } else if ("rectangle".equals(name)) { 1411 drawRectangle(child, g); 1412 } else if ("tint".equals(name)) { 1413 drawTint(child, g); 1414 } else if ("tile".equals(name)) { 1415 drawTile(child, g, jif); 1416 } else if ("title".equals(name)) { 1417 drawTitle(child, g, jif); 1418 } else { 1419 System.err.println("Unknown Metacity drawing op: "+child); 1420 } 1421 } catch (NumberFormatException ex) { 1422 logError(themeName, ex); 1423 } 1424 } 1425 } 1426 g.setClip(oldClip); 1427 } 1428 } 1429 } 1430 1431 protected void drawPiece(Node frame_style, Graphics g, String position, int x, int y, 1432 int width, int height, JInternalFrame jif) { 1433 Node piece = getNode(frame_style, "piece", new String[] { "position", position }); 1434 if (piece != null) { 1435 Node draw_ops; 1436 String draw_ops_name = getStringAttr(piece, "draw_ops"); 1437 if (draw_ops_name != null) { 1438 draw_ops = getNode("draw_ops", new String[] { "name", draw_ops_name }); 1439 } else { 1440 draw_ops = getNode(piece, "draw_ops", null); 1441 } 1442 variables.put("width", width); 1443 variables.put("height", height); 1444 g.translate(x, y); 1445 draw(draw_ops, g, jif); 1446 g.translate(-x, -y); 1447 } 1448 } 1449 1450 1451 Insets getBorderInsets(SynthContext context, Insets insets) { 1452 updateFrameGeometry(context); 1453 1454 if (insets == null) { 1455 insets = new Insets(0, 0, 0, 0); 1456 } 1457 insets.top = ((Insets)frameGeometry.get("title_border")).top; 1458 insets.bottom = getInt("bottom_height"); 1459 insets.left = getInt("left_width"); 1460 insets.right = getInt("right_width"); 1461 return insets; 1462 } 1463 1464 1465 private void updateFrameGeometry(SynthContext context) { 1466 this.context = context; 1467 JComponent comp = context.getComponent(); 1468 JComponent titlePane = findChild(comp, "InternalFrame.northPane"); 1469 1470 JInternalFrame jif = null; 1471 if (comp instanceof JInternalFrame) { 1472 jif = (JInternalFrame)comp; 1473 } else if (comp instanceof JInternalFrame.JDesktopIcon) { 1474 jif = ((JInternalFrame.JDesktopIcon)comp).getInternalFrame(); 1475 } else { 1476 assert false : "component is not JInternalFrame or JInternalFrame.JDesktopIcon"; 1477 return; 1478 } 1479 1480 if (frame_style_set == null) { 1481 Node window = getNode("window", new String[]{"type", "normal"}); 1482 1483 if (window != null) { 1484 frame_style_set = getNode("frame_style_set", 1485 new String[] {"name", getStringAttr(window, "style_set")}); 1486 } 1487 1488 if (frame_style_set == null) { 1489 frame_style_set = getNode("frame_style_set", new String[] {"name", "normal"}); 1490 } 1491 } 1492 1493 if (frame_style_set != null) { 1494 Node frame = getNode(frame_style_set, "frame", new String[] { 1495 "focus", (jif.isSelected() ? "yes" : "no"), 1496 "state", (jif.isMaximum() ? "maximized" : "normal") 1497 }); 1498 1499 if (frame != null) { 1500 Node frame_style = getNode("frame_style", new String[] { 1501 "name", getStringAttr(frame, "style") 1502 }); 1503 if (frame_style != null) { 1504 Map<String, Object> gm = frameGeometries.get(getStringAttr(frame_style, "geometry")); 1505 1506 setFrameGeometry(titlePane, gm); 1507 } 1508 } 1509 } 1510 } 1511 1512 1513 protected static void logError(String themeName, Exception ex) { 1514 logError(themeName, ex.toString()); 1515 } 1516 1517 protected static void logError(String themeName, String msg) { 1518 if (!errorLogged) { 1519 System.err.println("Exception in Metacity for theme \""+themeName+"\": "+msg); 1520 errorLogged = true; 1521 } 1522 } 1523 1524 1525 // XML Parsing 1526 1527 1528 protected static Document getXMLDoc(final URL xmlFile) 1529 throws IOException, 1530 ParserConfigurationException, 1531 SAXException { 1532 if (documentBuilder == null) { 1533 documentBuilder = 1534 DocumentBuilderFactory.newInstance().newDocumentBuilder(); 1535 } 1536 InputStream inputStream = 1537 AccessController.doPrivileged(new PrivilegedAction<InputStream>() { 1538 public InputStream run() { 1539 try { 1540 return new BufferedInputStream(xmlFile.openStream()); 1541 } catch (IOException ex) { 1542 return null; 1543 } 1544 } 1545 }); 1546 1547 Document doc = null; 1548 if (inputStream != null) { 1549 doc = documentBuilder.parse(inputStream); 1550 } 1551 return doc; 1552 } 1553 1554 1555 protected Node[] getNodesByName(Node parent, String name) { 1556 NodeList nodes = parent.getChildNodes(); // ElementNode 1557 int n = nodes.getLength(); 1558 ArrayList<Node> list = new ArrayList<Node>(); 1559 for (int i=0; i < n; i++) { 1560 Node node = nodes.item(i); 1561 if (name.equals(node.getNodeName())) { 1562 list.add(node); 1563 } 1564 } 1565 return list.toArray(new Node[list.size()]); 1566 } 1567 1568 1569 1570 protected Node getNode(String tagName, String[] attrs) { 1571 NodeList nodes = xmlDoc.getElementsByTagName(tagName); 1572 return (nodes != null) ? getNode(nodes, tagName, attrs) : null; 1573 } 1574 1575 protected Node getNode(Node parent, String name, String[] attrs) { 1576 Node node = null; 1577 NodeList nodes = parent.getChildNodes(); 1578 if (nodes != null) { 1579 node = getNode(nodes, name, attrs); 1580 } 1581 if (node == null) { 1582 String inheritFrom = getStringAttr(parent, "parent"); 1583 if (inheritFrom != null) { 1584 Node inheritFromNode = getNode(parent.getParentNode(), 1585 parent.getNodeName(), 1586 new String[] { "name", inheritFrom }); 1587 if (inheritFromNode != null) { 1588 node = getNode(inheritFromNode, name, attrs); 1589 } 1590 } 1591 } 1592 return node; 1593 } 1594 1595 protected Node getNode(NodeList nodes, String name, String[] attrs) { 1596 int n = nodes.getLength(); 1597 for (int i=0; i < n; i++) { 1598 Node node = nodes.item(i); 1599 if (name.equals(node.getNodeName())) { 1600 if (attrs != null) { 1601 NamedNodeMap nodeAttrs = node.getAttributes(); 1602 if (nodeAttrs != null) { 1603 boolean matches = true; 1604 int nAttrs = attrs.length / 2; 1605 for (int a = 0; a < nAttrs; a++) { 1606 String aName = attrs[a * 2]; 1607 String aValue = attrs[a * 2 + 1]; 1608 Node attr = nodeAttrs.getNamedItem(aName); 1609 if (attr == null || 1610 aValue != null && !aValue.equals(attr.getNodeValue())) { 1611 matches = false; 1612 break; 1613 } 1614 } 1615 if (matches) { 1616 return node; 1617 } 1618 } 1619 } else { 1620 return node; 1621 } 1622 } 1623 } 1624 return null; 1625 } 1626 1627 protected String getStringAttr(Node node, String name) { 1628 String value = null; 1629 NamedNodeMap attrs = node.getAttributes(); 1630 if (attrs != null) { 1631 value = getStringAttr(attrs, name); 1632 if (value == null) { 1633 String inheritFrom = getStringAttr(attrs, "parent"); 1634 if (inheritFrom != null) { 1635 Node inheritFromNode = getNode(node.getParentNode(), 1636 node.getNodeName(), 1637 new String[] { "name", inheritFrom }); 1638 if (inheritFromNode != null) { 1639 value = getStringAttr(inheritFromNode, name); 1640 } 1641 } 1642 } 1643 } 1644 return value; 1645 } 1646 1647 protected String getStringAttr(NamedNodeMap attrs, String name) { 1648 Node item = attrs.getNamedItem(name); 1649 return (item != null) ? item.getNodeValue() : null; 1650 } 1651 1652 protected boolean getBooleanAttr(Node node, String name, boolean fallback) { 1653 String str = getStringAttr(node, name); 1654 if (str != null) { 1655 return Boolean.valueOf(str).booleanValue(); 1656 } 1657 return fallback; 1658 } 1659 1660 protected int getIntAttr(Node node, String name, int fallback) { 1661 String str = getStringAttr(node, name); 1662 int value = fallback; 1663 if (str != null) { 1664 try { 1665 value = Integer.parseInt(str); 1666 } catch (NumberFormatException ex) { 1667 logError(themeName, ex); 1668 } 1669 } 1670 return value; 1671 } 1672 1673 protected float getFloatAttr(Node node, String name, float fallback) { 1674 String str = getStringAttr(node, name); 1675 float value = fallback; 1676 if (str != null) { 1677 try { 1678 value = Float.parseFloat(str); 1679 } catch (NumberFormatException ex) { 1680 logError(themeName, ex); 1681 } 1682 } 1683 return value; 1684 } 1685 1686 1687 1688 protected Color parseColor(String str) { 1689 StringTokenizer tokenizer = new StringTokenizer(str, "/"); 1690 int n = tokenizer.countTokens(); 1691 if (n > 1) { 1692 String function = tokenizer.nextToken(); 1693 if ("shade".equals(function)) { 1694 assert (n == 3); 1695 Color c = parseColor2(tokenizer.nextToken()); 1696 float alpha = Float.parseFloat(tokenizer.nextToken()); 1697 return GTKColorType.adjustColor(c, 1.0F, alpha, alpha); 1698 } else if ("blend".equals(function)) { 1699 assert (n == 4); 1700 Color bg = parseColor2(tokenizer.nextToken()); 1701 Color fg = parseColor2(tokenizer.nextToken()); 1702 float alpha = Float.parseFloat(tokenizer.nextToken()); 1703 if (alpha > 1.0f) { 1704 alpha = 1.0f / alpha; 1705 } 1706 1707 return new Color((int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)), 1708 (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)), 1709 (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha))); 1710 } else { 1711 System.err.println("Unknown Metacity color function="+str); 1712 return null; 1713 } 1714 } else { 1715 return parseColor2(str); 1716 } 1717 } 1718 1719 protected Color parseColor2(String str) { 1720 Color c = null; 1721 if (str.startsWith("gtk:")) { 1722 int i1 = str.indexOf('['); 1723 if (i1 > 3) { 1724 String typeStr = str.substring(4, i1).toLowerCase(); 1725 int i2 = str.indexOf(']'); 1726 if (i2 > i1+1) { 1727 String stateStr = str.substring(i1+1, i2).toUpperCase(); 1728 int state = -1; 1729 if ("ACTIVE".equals(stateStr)) { 1730 state = PRESSED; 1731 } else if ("INSENSITIVE".equals(stateStr)) { 1732 state = DISABLED; 1733 } else if ("NORMAL".equals(stateStr)) { 1734 state = ENABLED; 1735 } else if ("PRELIGHT".equals(stateStr)) { 1736 state = MOUSE_OVER; 1737 } else if ("SELECTED".equals(stateStr)) { 1738 state = SELECTED; 1739 } 1740 ColorType type = null; 1741 if ("fg".equals(typeStr)) { 1742 type = GTKColorType.FOREGROUND; 1743 } else if ("bg".equals(typeStr)) { 1744 type = GTKColorType.BACKGROUND; 1745 } else if ("base".equals(typeStr)) { 1746 type = GTKColorType.TEXT_BACKGROUND; 1747 } else if ("text".equals(typeStr)) { 1748 type = GTKColorType.TEXT_FOREGROUND; 1749 } else if ("dark".equals(typeStr)) { 1750 type = GTKColorType.DARK; 1751 } else if ("light".equals(typeStr)) { 1752 type = GTKColorType.LIGHT; 1753 } 1754 if (state >= 0 && type != null) { 1755 c = ((GTKStyle)context.getStyle()).getGTKColor(context, state, type); 1756 } 1757 } 1758 } 1759 } 1760 if (c == null) { 1761 c = parseColorString(str); 1762 } 1763 return c; 1764 } 1765 1766 private static Color parseColorString(String str) { 1767 if (str.charAt(0) == '#') { 1768 str = str.substring(1); 1769 1770 int i = str.length(); 1771 1772 if (i < 3 || i > 12 || (i % 3) != 0) { 1773 return null; 1774 } 1775 1776 i /= 3; 1777 1778 int r; 1779 int g; 1780 int b; 1781 1782 try { 1783 r = Integer.parseInt(str.substring(0, i), 16); 1784 g = Integer.parseInt(str.substring(i, i * 2), 16); 1785 b = Integer.parseInt(str.substring(i * 2, i * 3), 16); 1786 } catch (NumberFormatException nfe) { 1787 return null; 1788 } 1789 1790 if (i == 4) { 1791 return new ColorUIResource(r / 65535.0f, g / 65535.0f, b / 65535.0f); 1792 } else if (i == 1) { 1793 return new ColorUIResource(r / 15.0f, g / 15.0f, b / 15.0f); 1794 } else if (i == 2) { 1795 return new ColorUIResource(r, g, b); 1796 } else { 1797 return new ColorUIResource(r / 4095.0f, g / 4095.0f, b / 4095.0f); 1798 } 1799 } else { 1800 return XColors.lookupColor(str); 1801 } 1802 } 1803 1804 class ArithmeticExpressionEvaluator { 1805 private PeekableStringTokenizer tokenizer; 1806 1807 int evaluate(String expr) { 1808 tokenizer = new PeekableStringTokenizer(expr, " \t+-*/%()", true); 1809 return Math.round(expression()); 1810 } 1811 1812 int evaluate(String expr, int fallback) { 1813 return (expr != null) ? evaluate(expr) : fallback; 1814 } 1815 1816 public float expression() { 1817 float value = getTermValue(); 1818 boolean done = false; 1819 while (!done && tokenizer.hasMoreTokens()) { 1820 String next = tokenizer.peek(); 1821 if ("+".equals(next) || 1822 "-".equals(next) || 1823 "`max`".equals(next) || 1824 "`min`".equals(next)) { 1825 tokenizer.nextToken(); 1826 float value2 = getTermValue(); 1827 if ("+".equals(next)) { 1828 value += value2; 1829 } else if ("-".equals(next)) { 1830 value -= value2; 1831 } else if ("`max`".equals(next)) { 1832 value = Math.max(value, value2); 1833 } else if ("`min`".equals(next)) { 1834 value = Math.min(value, value2); 1835 } 1836 } else { 1837 done = true; 1838 } 1839 } 1840 return value; 1841 } 1842 1843 public float getTermValue() { 1844 float value = getFactorValue(); 1845 boolean done = false; 1846 while (!done && tokenizer.hasMoreTokens()) { 1847 String next = tokenizer.peek(); 1848 if ("*".equals(next) || "/".equals(next) || "%".equals(next)) { 1849 tokenizer.nextToken(); 1850 float value2 = getFactorValue(); 1851 if ("*".equals(next)) { 1852 value *= value2; 1853 } else if ("/".equals(next)) { 1854 value /= value2; 1855 } else { 1856 value %= value2; 1857 } 1858 } else { 1859 done = true; 1860 } 1861 } 1862 return value; 1863 } 1864 1865 public float getFactorValue() { 1866 float value; 1867 if ("(".equals(tokenizer.peek())) { 1868 tokenizer.nextToken(); 1869 value = expression(); 1870 tokenizer.nextToken(); // skip right paren 1871 } else { 1872 String token = tokenizer.nextToken(); 1873 if (Character.isDigit(token.charAt(0))) { 1874 value = Float.parseFloat(token); 1875 } else { 1876 Integer i = variables.get(token); 1877 if (i == null) { 1878 i = (Integer)getFrameGeometry().get(token); 1879 } 1880 if (i == null) { 1881 logError(themeName, "Variable \"" + token + "\" not defined"); 1882 return 0; 1883 } 1884 value = (i != null) ? i.intValue() : 0F; 1885 } 1886 } 1887 return value; 1888 } 1889 1890 1891 } 1892 1893 static class PeekableStringTokenizer extends StringTokenizer { 1894 String token = null; 1895 1896 public PeekableStringTokenizer(String str, String delim, 1897 boolean returnDelims) { 1898 super(str, delim, returnDelims); 1899 peek(); 1900 } 1901 1902 public String peek() { 1903 if (token == null) { 1904 token = nextToken(); 1905 } 1906 return token; 1907 } 1908 1909 public boolean hasMoreTokens() { 1910 return (token != null || super.hasMoreTokens()); 1911 } 1912 1913 public String nextToken() { 1914 if (token != null) { 1915 String t = token; 1916 token = null; 1917 if (hasMoreTokens()) { 1918 peek(); 1919 } 1920 return t; 1921 } else { 1922 String token = super.nextToken(); 1923 while ((token.equals(" ") || token.equals("\t")) 1924 && hasMoreTokens()) { 1925 token = super.nextToken(); 1926 } 1927 return token; 1928 } 1929 } 1930 } 1931 1932 1933 static class RoundRectClipShape extends RectangularShape { 1934 static final int TOP_LEFT = 1; 1935 static final int TOP_RIGHT = 2; 1936 static final int BOTTOM_LEFT = 4; 1937 static final int BOTTOM_RIGHT = 8; 1938 1939 int x; 1940 int y; 1941 int width; 1942 int height; 1943 int arcwidth; 1944 int archeight; 1945 int corners; 1946 1947 public RoundRectClipShape() { 1948 } 1949 1950 public RoundRectClipShape(int x, int y, int w, int h, 1951 int arcw, int arch, int corners) { 1952 setRoundedRect(x, y, w, h, arcw, arch, corners); 1953 } 1954 1955 public void setRoundedRect(int x, int y, int w, int h, 1956 int arcw, int arch, int corners) { 1957 this.corners = corners; 1958 this.x = x; 1959 this.y = y; 1960 this.width = w; 1961 this.height = h; 1962 this.arcwidth = arcw; 1963 this.archeight = arch; 1964 } 1965 1966 public double getX() { 1967 return (double)x; 1968 } 1969 1970 public double getY() { 1971 return (double)y; 1972 } 1973 1974 public double getWidth() { 1975 return (double)width; 1976 } 1977 1978 public double getHeight() { 1979 return (double)height; 1980 } 1981 1982 public double getArcWidth() { 1983 return (double)arcwidth; 1984 } 1985 1986 public double getArcHeight() { 1987 return (double)archeight; 1988 } 1989 1990 public boolean isEmpty() { 1991 return false; // Not called 1992 } 1993 1994 public Rectangle2D getBounds2D() { 1995 return null; // Not called 1996 } 1997 1998 public int getCornerFlags() { 1999 return corners; 2000 } 2001 2002 public void setFrame(double x, double y, double w, double h) { 2003 // Not called 2004 } 2005 2006 public boolean contains(double x, double y) { 2007 return false; // Not called 2008 } 2009 2010 private int classify(double coord, double left, double right, double arcsize) { 2011 return 0; // Not called 2012 } 2013 2014 public boolean intersects(double x, double y, double w, double h) { 2015 return false; // Not called 2016 } 2017 2018 public boolean contains(double x, double y, double w, double h) { 2019 return false; // Not called 2020 } 2021 2022 public PathIterator getPathIterator(AffineTransform at) { 2023 return new RoundishRectIterator(this, at); 2024 } 2025 2026 2027 static class RoundishRectIterator implements PathIterator { 2028 double x, y, w, h, aw, ah; 2029 AffineTransform affine; 2030 int index; 2031 2032 double ctrlpts[][]; 2033 int types[]; 2034 2035 private static final double angle = Math.PI / 4.0; 2036 private static final double a = 1.0 - Math.cos(angle); 2037 private static final double b = Math.tan(angle); 2038 private static final double c = Math.sqrt(1.0 + b * b) - 1 + a; 2039 private static final double cv = 4.0 / 3.0 * a * b / c; 2040 private static final double acv = (1.0 - cv) / 2.0; 2041 2042 // For each array: 2043 // 4 values for each point {v0, v1, v2, v3}: 2044 // point = (x + v0 * w + v1 * arcWidth, 2045 // y + v2 * h + v3 * arcHeight); 2046 private static final double CtrlPtTemplate[][] = { 2047 { 0.0, 0.0, 1.0, 0.0 }, /* BOTTOM LEFT corner */ 2048 { 0.0, 0.0, 1.0, -0.5 }, /* BOTTOM LEFT arc start */ 2049 { 0.0, 0.0, 1.0, -acv, /* BOTTOM LEFT arc curve */ 2050 0.0, acv, 1.0, 0.0, 2051 0.0, 0.5, 1.0, 0.0 }, 2052 { 1.0, 0.0, 1.0, 0.0 }, /* BOTTOM RIGHT corner */ 2053 { 1.0, -0.5, 1.0, 0.0 }, /* BOTTOM RIGHT arc start */ 2054 { 1.0, -acv, 1.0, 0.0, /* BOTTOM RIGHT arc curve */ 2055 1.0, 0.0, 1.0, -acv, 2056 1.0, 0.0, 1.0, -0.5 }, 2057 { 1.0, 0.0, 0.0, 0.0 }, /* TOP RIGHT corner */ 2058 { 1.0, 0.0, 0.0, 0.5 }, /* TOP RIGHT arc start */ 2059 { 1.0, 0.0, 0.0, acv, /* TOP RIGHT arc curve */ 2060 1.0, -acv, 0.0, 0.0, 2061 1.0, -0.5, 0.0, 0.0 }, 2062 { 0.0, 0.0, 0.0, 0.0 }, /* TOP LEFT corner */ 2063 { 0.0, 0.5, 0.0, 0.0 }, /* TOP LEFT arc start */ 2064 { 0.0, acv, 0.0, 0.0, /* TOP LEFT arc curve */ 2065 0.0, 0.0, 0.0, acv, 2066 0.0, 0.0, 0.0, 0.5 }, 2067 {}, /* Closing path element */ 2068 }; 2069 private static final int CornerFlags[] = { 2070 RoundRectClipShape.BOTTOM_LEFT, 2071 RoundRectClipShape.BOTTOM_RIGHT, 2072 RoundRectClipShape.TOP_RIGHT, 2073 RoundRectClipShape.TOP_LEFT, 2074 }; 2075 2076 RoundishRectIterator(RoundRectClipShape rr, AffineTransform at) { 2077 this.x = rr.getX(); 2078 this.y = rr.getY(); 2079 this.w = rr.getWidth(); 2080 this.h = rr.getHeight(); 2081 this.aw = Math.min(w, Math.abs(rr.getArcWidth())); 2082 this.ah = Math.min(h, Math.abs(rr.getArcHeight())); 2083 this.affine = at; 2084 if (w < 0 || h < 0) { 2085 // Don't draw anything... 2086 ctrlpts = new double[0][]; 2087 types = new int[0]; 2088 } else { 2089 int corners = rr.getCornerFlags(); 2090 int numedges = 5; // 4xCORNER_POINT, CLOSE 2091 for (int i = 1; i < 0x10; i <<= 1) { 2092 // Add one for each corner that has a curve 2093 if ((corners & i) != 0) numedges++; 2094 } 2095 ctrlpts = new double[numedges][]; 2096 types = new int[numedges]; 2097 int j = 0; 2098 for (int i = 0; i < 4; i++) { 2099 types[j] = SEG_LINETO; 2100 if ((corners & CornerFlags[i]) == 0) { 2101 ctrlpts[j++] = CtrlPtTemplate[i*3+0]; 2102 } else { 2103 ctrlpts[j++] = CtrlPtTemplate[i*3+1]; 2104 types[j] = SEG_CUBICTO; 2105 ctrlpts[j++] = CtrlPtTemplate[i*3+2]; 2106 } 2107 } 2108 types[j] = SEG_CLOSE; 2109 ctrlpts[j++] = CtrlPtTemplate[12]; 2110 types[0] = SEG_MOVETO; 2111 } 2112 } 2113 2114 public int getWindingRule() { 2115 return WIND_NON_ZERO; 2116 } 2117 2118 public boolean isDone() { 2119 return index >= ctrlpts.length; 2120 } 2121 2122 public void next() { 2123 index++; 2124 } 2125 2126 public int currentSegment(float[] coords) { 2127 if (isDone()) { 2128 throw new NoSuchElementException("roundrect iterator out of bounds"); 2129 } 2130 double ctrls[] = ctrlpts[index]; 2131 int nc = 0; 2132 for (int i = 0; i < ctrls.length; i += 4) { 2133 coords[nc++] = (float) (x + ctrls[i + 0] * w + ctrls[i + 1] * aw); 2134 coords[nc++] = (float) (y + ctrls[i + 2] * h + ctrls[i + 3] * ah); 2135 } 2136 if (affine != null) { 2137 affine.transform(coords, 0, coords, 0, nc / 2); 2138 } 2139 return types[index]; 2140 } 2141 2142 public int currentSegment(double[] coords) { 2143 if (isDone()) { 2144 throw new NoSuchElementException("roundrect iterator out of bounds"); 2145 } 2146 double ctrls[] = ctrlpts[index]; 2147 int nc = 0; 2148 for (int i = 0; i < ctrls.length; i += 4) { 2149 coords[nc++] = x + ctrls[i + 0] * w + ctrls[i + 1] * aw; 2150 coords[nc++] = y + ctrls[i + 2] * h + ctrls[i + 3] * ah; 2151 } 2152 if (affine != null) { 2153 affine.transform(coords, 0, coords, 0, nc / 2); 2154 } 2155 return types[index]; 2156 } 2157 } 2158 } 2159 }