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