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 730 .getTextUIDrawing(frame).getStringWidth( 731 frame, fm, frameTitle) : 0; 732 int title_length = frameTitle != null ? frameTitle.length() : 0; 733 734 // Leave room for three characters in the title. 735 if (title_length > 3) { 736 int subtitle_w = SwingUtilities2 737 .getTextUIDrawing(frame).getStringWidth( 738 frame, fm, frameTitle.substring(0, 3) + "..."); 739 width += (title_w < subtitle_w) ? title_w : subtitle_w; 740 } else { 741 width += title_w; 742 } 743 744 // Calculate height. 745 int titleHeight = fm.getHeight() + getInt("title_vertical_pad"); 746 if (title_border != null) { 747 titleHeight += title_border.top + title_border.bottom; 748 } 749 int buttonHeight = buttonDim.height; 750 if (button_border != null) { 751 buttonHeight += button_border.top + button_border.bottom; 752 } 753 int height = Math.max(buttonHeight, titleHeight); 754 755 return new Dimension(width, height); 756 } 757 758 public void layoutContainer(Container c) { 759 JComponent titlePane = (JComponent)c; 760 Container titlePaneParent = titlePane.getParent(); 761 JInternalFrame frame; 762 if (titlePaneParent instanceof JInternalFrame) { 763 frame = (JInternalFrame)titlePaneParent; 764 } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) { 765 frame = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame(); 766 } else { 767 return; 768 } 769 Map<String, Object> gm = getFrameGeometry(); 770 771 int w = titlePane.getWidth(); 772 int h = titlePane.getHeight(); 773 774 JComponent menuButton = findChild(titlePane, "InternalFrameTitlePane.menuButton"); 775 JComponent minimizeButton = findChild(titlePane, "InternalFrameTitlePane.iconifyButton"); 776 JComponent maximizeButton = findChild(titlePane, "InternalFrameTitlePane.maximizeButton"); 777 JComponent closeButton = findChild(titlePane, "InternalFrameTitlePane.closeButton"); 778 779 Insets button_border = (Insets)gm.get("button_border"); 780 Dimension buttonDim = calculateButtonSize(titlePane); 781 782 int y = (button_border != null) ? button_border.top : 0; 783 if (titlePaneParent.getComponentOrientation().isLeftToRight()) { 784 int x = getInt("left_titlebar_edge"); 785 786 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height); 787 788 x = w - buttonDim.width - getInt("right_titlebar_edge"); 789 if (button_border != null) { 790 x -= button_border.right; 791 } 792 793 if (frame.isClosable()) { 794 closeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 795 x -= buttonDim.width; 796 } 797 798 if (frame.isMaximizable()) { 799 maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 800 x -= buttonDim.width; 801 } 802 803 if (frame.isIconifiable()) { 804 minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 805 } 806 } else { 807 int x = w - buttonDim.width - getInt("right_titlebar_edge"); 808 809 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height); 810 811 x = getInt("left_titlebar_edge"); 812 if (button_border != null) { 813 x += button_border.left; 814 } 815 816 if (frame.isClosable()) { 817 closeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 818 x += buttonDim.width; 819 } 820 821 if (frame.isMaximizable()) { 822 maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 823 x += buttonDim.width; 824 } 825 826 if (frame.isIconifiable()) { 827 minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height); 828 } 829 } 830 } 831 } // end TitlePaneLayout 832 833 protected Map<String, Object> getFrameGeometry() { 834 return frameGeometry; 835 } 836 837 protected void setFrameGeometry(JComponent titlePane, Map<String, Object> gm) { 838 this.frameGeometry = gm; 839 if (getInt("top_height") == 0 && titlePane != null) { 840 gm.put("top_height", Integer.valueOf(titlePane.getHeight())); 841 } 842 } 843 844 protected int getInt(String key) { 845 Integer i = (Integer)frameGeometry.get(key); 846 if (i == null) { 847 i = variables.get(key); 848 } 849 return (i != null) ? i.intValue() : 0; 850 } 851 852 protected boolean getBoolean(String key, boolean fallback) { 853 Boolean b = (Boolean)frameGeometry.get(key); 854 return (b != null) ? b.booleanValue() : fallback; 855 } 856 857 858 protected void drawArc(Node node, Graphics g) { 859 NamedNodeMap attrs = node.getAttributes(); 860 Color color = parseColor(getStringAttr(attrs, "color")); 861 int x = aee.evaluate(getStringAttr(attrs, "x")); 862 int y = aee.evaluate(getStringAttr(attrs, "y")); 863 int w = aee.evaluate(getStringAttr(attrs, "width")); 864 int h = aee.evaluate(getStringAttr(attrs, "height")); 865 int start_angle = aee.evaluate(getStringAttr(attrs, "start_angle")); 866 int extent_angle = aee.evaluate(getStringAttr(attrs, "extent_angle")); 867 boolean filled = getBooleanAttr(node, "filled", false); 868 if (getInt("width") == -1) { 869 x -= w; 870 } 871 if (getInt("height") == -1) { 872 y -= h; 873 } 874 g.setColor(color); 875 if (filled) { 876 g.fillArc(x, y, w, h, start_angle, extent_angle); 877 } else { 878 g.drawArc(x, y, w, h, start_angle, extent_angle); 879 } 880 } 881 882 protected void drawLine(Node node, Graphics g) { 883 NamedNodeMap attrs = node.getAttributes(); 884 Color color = parseColor(getStringAttr(attrs, "color")); 885 int x1 = aee.evaluate(getStringAttr(attrs, "x1")); 886 int y1 = aee.evaluate(getStringAttr(attrs, "y1")); 887 int x2 = aee.evaluate(getStringAttr(attrs, "x2")); 888 int y2 = aee.evaluate(getStringAttr(attrs, "y2")); 889 int lineWidth = aee.evaluate(getStringAttr(attrs, "width"), 1); 890 g.setColor(color); 891 if (lineWidth != 1) { 892 Graphics2D g2d = (Graphics2D)g; 893 Stroke stroke = g2d.getStroke(); 894 g2d.setStroke(new BasicStroke((float)lineWidth)); 895 g2d.drawLine(x1, y1, x2, y2); 896 g2d.setStroke(stroke); 897 } else { 898 g.drawLine(x1, y1, x2, y2); 899 } 900 } 901 902 protected void drawRectangle(Node node, Graphics g) { 903 NamedNodeMap attrs = node.getAttributes(); 904 Color color = parseColor(getStringAttr(attrs, "color")); 905 boolean filled = getBooleanAttr(node, "filled", false); 906 int x = aee.evaluate(getStringAttr(attrs, "x")); 907 int y = aee.evaluate(getStringAttr(attrs, "y")); 908 int w = aee.evaluate(getStringAttr(attrs, "width")); 909 int h = aee.evaluate(getStringAttr(attrs, "height")); 910 g.setColor(color); 911 if (getInt("width") == -1) { 912 x -= w; 913 } 914 if (getInt("height") == -1) { 915 y -= h; 916 } 917 if (filled) { 918 g.fillRect(x, y, w, h); 919 } else { 920 g.drawRect(x, y, w, h); 921 } 922 } 923 924 protected void drawTile(Node node, Graphics g, JInternalFrame jif) { 925 NamedNodeMap attrs = node.getAttributes(); 926 int x0 = aee.evaluate(getStringAttr(attrs, "x")); 927 int y0 = aee.evaluate(getStringAttr(attrs, "y")); 928 int w = aee.evaluate(getStringAttr(attrs, "width")); 929 int h = aee.evaluate(getStringAttr(attrs, "height")); 930 int tw = aee.evaluate(getStringAttr(attrs, "tile_width")); 931 int th = aee.evaluate(getStringAttr(attrs, "tile_height")); 932 int width = getInt("width"); 933 int height = getInt("height"); 934 if (width == -1) { 935 x0 -= w; 936 } 937 if (height == -1) { 938 y0 -= h; 939 } 940 Shape oldClip = g.getClip(); 941 if (g instanceof Graphics2D) { 942 ((Graphics2D)g).clip(new Rectangle(x0, y0, w, h)); 943 } 944 variables.put("width", tw); 945 variables.put("height", th); 946 947 Node draw_ops = getNode("draw_ops", new String[] { "name", getStringAttr(node, "name") }); 948 949 int y = y0; 950 while (y < y0 + h) { 951 int x = x0; 952 while (x < x0 + w) { 953 g.translate(x, y); 954 draw(draw_ops, g, jif); 955 g.translate(-x, -y); 956 x += tw; 957 } 958 y += th; 959 } 960 961 variables.put("width", width); 962 variables.put("height", height); 963 g.setClip(oldClip); 964 } 965 966 protected void drawTint(Node node, Graphics g) { 967 NamedNodeMap attrs = node.getAttributes(); 968 Color color = parseColor(getStringAttr(attrs, "color")); 969 float alpha = Float.parseFloat(getStringAttr(attrs, "alpha")); 970 int x = aee.evaluate(getStringAttr(attrs, "x")); 971 int y = aee.evaluate(getStringAttr(attrs, "y")); 972 int w = aee.evaluate(getStringAttr(attrs, "width")); 973 int h = aee.evaluate(getStringAttr(attrs, "height")); 974 if (getInt("width") == -1) { 975 x -= w; 976 } 977 if (getInt("height") == -1) { 978 y -= h; 979 } 980 if (g instanceof Graphics2D) { 981 Graphics2D g2 = (Graphics2D)g; 982 Composite oldComp = g2.getComposite(); 983 AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); 984 g2.setComposite(ac); 985 g2.setColor(color); 986 g2.fillRect(x, y, w, h); 987 g2.setComposite(oldComp); 988 } 989 } 990 991 protected void drawTitle(Node node, Graphics g, JInternalFrame jif) { 992 NamedNodeMap attrs = node.getAttributes(); 993 String colorStr = getStringAttr(attrs, "color"); 994 int i = colorStr.indexOf("gtk:fg["); 995 if (i > 0) { 996 colorStr = colorStr.substring(0, i) + "gtk:text[" + colorStr.substring(i+7); 997 } 998 Color color = parseColor(colorStr); 999 int x = aee.evaluate(getStringAttr(attrs, "x")); 1000 int y = aee.evaluate(getStringAttr(attrs, "y")); 1001 1002 String title = jif.getTitle(); 1003 if (title != null) { 1004 FontMetrics fm = SwingUtilities2.getFontMetrics(jif, g); 1005 title = SwingUtilities2.getTextUIDrawing(jif) 1006 .getClippedString(jif, fm, title, 1007 calculateTitleArea(jif).width); 1008 g.setColor(color); 1009 SwingUtilities2.getTextUIDrawing(jif) 1010 .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.getTextUIDrawing(jif).getStringWidth(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 = null; 1475 if (comp instanceof JInternalFrame) { 1476 jif = (JInternalFrame)comp; 1477 } else if (comp instanceof JInternalFrame.JDesktopIcon) { 1478 jif = ((JInternalFrame.JDesktopIcon)comp).getInternalFrame(); 1479 } else { 1480 assert false : "component is not JInternalFrame or JInternalFrame.JDesktopIcon"; 1481 return; 1482 } 1483 1484 if (frame_style_set == null) { 1485 Node window = getNode("window", new String[]{"type", "normal"}); 1486 1487 if (window != null) { 1488 frame_style_set = getNode("frame_style_set", 1489 new String[] {"name", getStringAttr(window, "style_set")}); 1490 } 1491 1492 if (frame_style_set == null) { 1493 frame_style_set = getNode("frame_style_set", new String[] {"name", "normal"}); 1494 } 1495 } 1496 1497 if (frame_style_set != null) { 1498 Node frame = getNode(frame_style_set, "frame", new String[] { 1499 "focus", (jif.isSelected() ? "yes" : "no"), 1500 "state", (jif.isMaximum() ? "maximized" : "normal") 1501 }); 1502 1503 if (frame != null) { 1504 Node frame_style = getNode("frame_style", new String[] { 1505 "name", getStringAttr(frame, "style") 1506 }); 1507 if (frame_style != null) { 1508 Map<String, Object> gm = frameGeometries.get(getStringAttr(frame_style, "geometry")); 1509 1510 setFrameGeometry(titlePane, gm); 1511 } 1512 } 1513 } 1514 } 1515 1516 1517 protected static void logError(String themeName, Exception ex) { 1518 logError(themeName, ex.toString()); 1519 } 1520 1521 protected static void logError(String themeName, String msg) { 1522 if (!errorLogged) { 1523 System.err.println("Exception in Metacity for theme \""+themeName+"\": "+msg); 1524 errorLogged = true; 1525 } 1526 } 1527 1528 1529 // XML Parsing 1530 1531 1532 protected static Document getXMLDoc(final URL xmlFile) 1533 throws IOException, 1534 ParserConfigurationException, 1535 SAXException { 1536 if (documentBuilder == null) { 1537 documentBuilder = 1538 DocumentBuilderFactory.newInstance().newDocumentBuilder(); 1539 } 1540 InputStream inputStream = 1541 AccessController.doPrivileged(new PrivilegedAction<InputStream>() { 1542 public InputStream run() { 1543 try { 1544 return new BufferedInputStream(xmlFile.openStream()); 1545 } catch (IOException ex) { 1546 return null; 1547 } 1548 } 1549 }); 1550 1551 Document doc = null; 1552 if (inputStream != null) { 1553 doc = documentBuilder.parse(inputStream); 1554 } 1555 return doc; 1556 } 1557 1558 1559 protected Node[] getNodesByName(Node parent, String name) { 1560 NodeList nodes = parent.getChildNodes(); // ElementNode 1561 int n = nodes.getLength(); 1562 ArrayList<Node> list = new ArrayList<Node>(); 1563 for (int i=0; i < n; i++) { 1564 Node node = nodes.item(i); 1565 if (name.equals(node.getNodeName())) { 1566 list.add(node); 1567 } 1568 } 1569 return list.toArray(new Node[list.size()]); 1570 } 1571 1572 1573 1574 protected Node getNode(String tagName, String[] attrs) { 1575 NodeList nodes = xmlDoc.getElementsByTagName(tagName); 1576 return (nodes != null) ? getNode(nodes, tagName, attrs) : null; 1577 } 1578 1579 protected Node getNode(Node parent, String name, String[] attrs) { 1580 Node node = null; 1581 NodeList nodes = parent.getChildNodes(); 1582 if (nodes != null) { 1583 node = getNode(nodes, name, attrs); 1584 } 1585 if (node == null) { 1586 String inheritFrom = getStringAttr(parent, "parent"); 1587 if (inheritFrom != null) { 1588 Node inheritFromNode = getNode(parent.getParentNode(), 1589 parent.getNodeName(), 1590 new String[] { "name", inheritFrom }); 1591 if (inheritFromNode != null) { 1592 node = getNode(inheritFromNode, name, attrs); 1593 } 1594 } 1595 } 1596 return node; 1597 } 1598 1599 protected Node getNode(NodeList nodes, String name, String[] attrs) { 1600 int n = nodes.getLength(); 1601 for (int i=0; i < n; i++) { 1602 Node node = nodes.item(i); 1603 if (name.equals(node.getNodeName())) { 1604 if (attrs != null) { 1605 NamedNodeMap nodeAttrs = node.getAttributes(); 1606 if (nodeAttrs != null) { 1607 boolean matches = true; 1608 int nAttrs = attrs.length / 2; 1609 for (int a = 0; a < nAttrs; a++) { 1610 String aName = attrs[a * 2]; 1611 String aValue = attrs[a * 2 + 1]; 1612 Node attr = nodeAttrs.getNamedItem(aName); 1613 if (attr == null || 1614 aValue != null && !aValue.equals(attr.getNodeValue())) { 1615 matches = false; 1616 break; 1617 } 1618 } 1619 if (matches) { 1620 return node; 1621 } 1622 } 1623 } else { 1624 return node; 1625 } 1626 } 1627 } 1628 return null; 1629 } 1630 1631 protected String getStringAttr(Node node, String name) { 1632 String value = null; 1633 NamedNodeMap attrs = node.getAttributes(); 1634 if (attrs != null) { 1635 value = getStringAttr(attrs, name); 1636 if (value == null) { 1637 String inheritFrom = getStringAttr(attrs, "parent"); 1638 if (inheritFrom != null) { 1639 Node inheritFromNode = getNode(node.getParentNode(), 1640 node.getNodeName(), 1641 new String[] { "name", inheritFrom }); 1642 if (inheritFromNode != null) { 1643 value = getStringAttr(inheritFromNode, name); 1644 } 1645 } 1646 } 1647 } 1648 return value; 1649 } 1650 1651 protected String getStringAttr(NamedNodeMap attrs, String name) { 1652 Node item = attrs.getNamedItem(name); 1653 return (item != null) ? item.getNodeValue() : null; 1654 } 1655 1656 protected boolean getBooleanAttr(Node node, String name, boolean fallback) { 1657 String str = getStringAttr(node, name); 1658 if (str != null) { 1659 return Boolean.valueOf(str).booleanValue(); 1660 } 1661 return fallback; 1662 } 1663 1664 protected int getIntAttr(Node node, String name, int fallback) { 1665 String str = getStringAttr(node, name); 1666 int value = fallback; 1667 if (str != null) { 1668 try { 1669 value = Integer.parseInt(str); 1670 } catch (NumberFormatException ex) { 1671 logError(themeName, ex); 1672 } 1673 } 1674 return value; 1675 } 1676 1677 protected float getFloatAttr(Node node, String name, float fallback) { 1678 String str = getStringAttr(node, name); 1679 float value = fallback; 1680 if (str != null) { 1681 try { 1682 value = Float.parseFloat(str); 1683 } catch (NumberFormatException ex) { 1684 logError(themeName, ex); 1685 } 1686 } 1687 return value; 1688 } 1689 1690 1691 1692 protected Color parseColor(String str) { 1693 StringTokenizer tokenizer = new StringTokenizer(str, "/"); 1694 int n = tokenizer.countTokens(); 1695 if (n > 1) { 1696 String function = tokenizer.nextToken(); 1697 if ("shade".equals(function)) { 1698 assert (n == 3); 1699 Color c = parseColor2(tokenizer.nextToken()); 1700 float alpha = Float.parseFloat(tokenizer.nextToken()); 1701 return GTKColorType.adjustColor(c, 1.0F, alpha, alpha); 1702 } else if ("blend".equals(function)) { 1703 assert (n == 4); 1704 Color bg = parseColor2(tokenizer.nextToken()); 1705 Color fg = parseColor2(tokenizer.nextToken()); 1706 float alpha = Float.parseFloat(tokenizer.nextToken()); 1707 if (alpha > 1.0f) { 1708 alpha = 1.0f / alpha; 1709 } 1710 1711 return new Color((int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)), 1712 (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)), 1713 (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha))); 1714 } else { 1715 System.err.println("Unknown Metacity color function="+str); 1716 return null; 1717 } 1718 } else { 1719 return parseColor2(str); 1720 } 1721 } 1722 1723 protected Color parseColor2(String str) { 1724 Color c = null; 1725 if (str.startsWith("gtk:")) { 1726 int i1 = str.indexOf('['); 1727 if (i1 > 3) { 1728 String typeStr = str.substring(4, i1).toLowerCase(); 1729 int i2 = str.indexOf(']'); 1730 if (i2 > i1+1) { 1731 String stateStr = str.substring(i1+1, i2).toUpperCase(); 1732 int state = -1; 1733 if ("ACTIVE".equals(stateStr)) { 1734 state = PRESSED; 1735 } else if ("INSENSITIVE".equals(stateStr)) { 1736 state = DISABLED; 1737 } else if ("NORMAL".equals(stateStr)) { 1738 state = ENABLED; 1739 } else if ("PRELIGHT".equals(stateStr)) { 1740 state = MOUSE_OVER; 1741 } else if ("SELECTED".equals(stateStr)) { 1742 state = SELECTED; 1743 } 1744 ColorType type = null; 1745 if ("fg".equals(typeStr)) { 1746 type = GTKColorType.FOREGROUND; 1747 } else if ("bg".equals(typeStr)) { 1748 type = GTKColorType.BACKGROUND; 1749 } else if ("base".equals(typeStr)) { 1750 type = GTKColorType.TEXT_BACKGROUND; 1751 } else if ("text".equals(typeStr)) { 1752 type = GTKColorType.TEXT_FOREGROUND; 1753 } else if ("dark".equals(typeStr)) { 1754 type = GTKColorType.DARK; 1755 } else if ("light".equals(typeStr)) { 1756 type = GTKColorType.LIGHT; 1757 } 1758 if (state >= 0 && type != null) { 1759 c = ((GTKStyle)context.getStyle()).getGTKColor(context, state, type); 1760 } 1761 } 1762 } 1763 } 1764 if (c == null) { 1765 c = parseColorString(str); 1766 } 1767 return c; 1768 } 1769 1770 private static Color parseColorString(String str) { 1771 if (str.charAt(0) == '#') { 1772 str = str.substring(1); 1773 1774 int i = str.length(); 1775 1776 if (i < 3 || i > 12 || (i % 3) != 0) { 1777 return null; 1778 } 1779 1780 i /= 3; 1781 1782 int r; 1783 int g; 1784 int b; 1785 1786 try { 1787 r = Integer.parseInt(str.substring(0, i), 16); 1788 g = Integer.parseInt(str.substring(i, i * 2), 16); 1789 b = Integer.parseInt(str.substring(i * 2, i * 3), 16); 1790 } catch (NumberFormatException nfe) { 1791 return null; 1792 } 1793 1794 if (i == 4) { 1795 return new ColorUIResource(r / 65535.0f, g / 65535.0f, b / 65535.0f); 1796 } else if (i == 1) { 1797 return new ColorUIResource(r / 15.0f, g / 15.0f, b / 15.0f); 1798 } else if (i == 2) { 1799 return new ColorUIResource(r, g, b); 1800 } else { 1801 return new ColorUIResource(r / 4095.0f, g / 4095.0f, b / 4095.0f); 1802 } 1803 } else { 1804 return XColors.lookupColor(str); 1805 } 1806 } 1807 1808 class ArithmeticExpressionEvaluator { 1809 private PeekableStringTokenizer tokenizer; 1810 1811 int evaluate(String expr) { 1812 tokenizer = new PeekableStringTokenizer(expr, " \t+-*/%()", true); 1813 return Math.round(expression()); 1814 } 1815 1816 int evaluate(String expr, int fallback) { 1817 return (expr != null) ? evaluate(expr) : fallback; 1818 } 1819 1820 public float expression() { 1821 float value = getTermValue(); 1822 boolean done = false; 1823 while (!done && tokenizer.hasMoreTokens()) { 1824 String next = tokenizer.peek(); 1825 if ("+".equals(next) || 1826 "-".equals(next) || 1827 "`max`".equals(next) || 1828 "`min`".equals(next)) { 1829 tokenizer.nextToken(); 1830 float value2 = getTermValue(); 1831 if ("+".equals(next)) { 1832 value += value2; 1833 } else if ("-".equals(next)) { 1834 value -= value2; 1835 } else if ("`max`".equals(next)) { 1836 value = Math.max(value, value2); 1837 } else if ("`min`".equals(next)) { 1838 value = Math.min(value, value2); 1839 } 1840 } else { 1841 done = true; 1842 } 1843 } 1844 return value; 1845 } 1846 1847 public float getTermValue() { 1848 float value = getFactorValue(); 1849 boolean done = false; 1850 while (!done && tokenizer.hasMoreTokens()) { 1851 String next = tokenizer.peek(); 1852 if ("*".equals(next) || "/".equals(next) || "%".equals(next)) { 1853 tokenizer.nextToken(); 1854 float value2 = getFactorValue(); 1855 if ("*".equals(next)) { 1856 value *= value2; 1857 } else if ("/".equals(next)) { 1858 value /= value2; 1859 } else { 1860 value %= value2; 1861 } 1862 } else { 1863 done = true; 1864 } 1865 } 1866 return value; 1867 } 1868 1869 public float getFactorValue() { 1870 float value; 1871 if ("(".equals(tokenizer.peek())) { 1872 tokenizer.nextToken(); 1873 value = expression(); 1874 tokenizer.nextToken(); // skip right paren 1875 } else { 1876 String token = tokenizer.nextToken(); 1877 if (Character.isDigit(token.charAt(0))) { 1878 value = Float.parseFloat(token); 1879 } else { 1880 Integer i = variables.get(token); 1881 if (i == null) { 1882 i = (Integer)getFrameGeometry().get(token); 1883 } 1884 if (i == null) { 1885 logError(themeName, "Variable \"" + token + "\" not defined"); 1886 return 0; 1887 } 1888 value = (i != null) ? i.intValue() : 0F; 1889 } 1890 } 1891 return value; 1892 } 1893 1894 1895 } 1896 1897 static class PeekableStringTokenizer extends StringTokenizer { 1898 String token = null; 1899 1900 public PeekableStringTokenizer(String str, String delim, 1901 boolean returnDelims) { 1902 super(str, delim, returnDelims); 1903 peek(); 1904 } 1905 1906 public String peek() { 1907 if (token == null) { 1908 token = nextToken(); 1909 } 1910 return token; 1911 } 1912 1913 public boolean hasMoreTokens() { 1914 return (token != null || super.hasMoreTokens()); 1915 } 1916 1917 public String nextToken() { 1918 if (token != null) { 1919 String t = token; 1920 token = null; 1921 if (hasMoreTokens()) { 1922 peek(); 1923 } 1924 return t; 1925 } else { 1926 String token = super.nextToken(); 1927 while ((token.equals(" ") || token.equals("\t")) 1928 && hasMoreTokens()) { 1929 token = super.nextToken(); 1930 } 1931 return token; 1932 } 1933 } 1934 } 1935 1936 1937 static class RoundRectClipShape extends RectangularShape { 1938 static final int TOP_LEFT = 1; 1939 static final int TOP_RIGHT = 2; 1940 static final int BOTTOM_LEFT = 4; 1941 static final int BOTTOM_RIGHT = 8; 1942 1943 int x; 1944 int y; 1945 int width; 1946 int height; 1947 int arcwidth; 1948 int archeight; 1949 int corners; 1950 1951 public RoundRectClipShape() { 1952 } 1953 1954 public RoundRectClipShape(int x, int y, int w, int h, 1955 int arcw, int arch, int corners) { 1956 setRoundedRect(x, y, w, h, arcw, arch, corners); 1957 } 1958 1959 public void setRoundedRect(int x, int y, int w, int h, 1960 int arcw, int arch, int corners) { 1961 this.corners = corners; 1962 this.x = x; 1963 this.y = y; 1964 this.width = w; 1965 this.height = h; 1966 this.arcwidth = arcw; 1967 this.archeight = arch; 1968 } 1969 1970 public double getX() { 1971 return (double)x; 1972 } 1973 1974 public double getY() { 1975 return (double)y; 1976 } 1977 1978 public double getWidth() { 1979 return (double)width; 1980 } 1981 1982 public double getHeight() { 1983 return (double)height; 1984 } 1985 1986 public double getArcWidth() { 1987 return (double)arcwidth; 1988 } 1989 1990 public double getArcHeight() { 1991 return (double)archeight; 1992 } 1993 1994 public boolean isEmpty() { 1995 return false; // Not called 1996 } 1997 1998 public Rectangle2D getBounds2D() { 1999 return null; // Not called 2000 } 2001 2002 public int getCornerFlags() { 2003 return corners; 2004 } 2005 2006 public void setFrame(double x, double y, double w, double h) { 2007 // Not called 2008 } 2009 2010 public boolean contains(double x, double y) { 2011 return false; // Not called 2012 } 2013 2014 private int classify(double coord, double left, double right, double arcsize) { 2015 return 0; // Not called 2016 } 2017 2018 public boolean intersects(double x, double y, double w, double h) { 2019 return false; // Not called 2020 } 2021 2022 public boolean contains(double x, double y, double w, double h) { 2023 return false; // Not called 2024 } 2025 2026 public PathIterator getPathIterator(AffineTransform at) { 2027 return new RoundishRectIterator(this, at); 2028 } 2029 2030 2031 static class RoundishRectIterator implements PathIterator { 2032 double x, y, w, h, aw, ah; 2033 AffineTransform affine; 2034 int index; 2035 2036 double ctrlpts[][]; 2037 int types[]; 2038 2039 private static final double angle = Math.PI / 4.0; 2040 private static final double a = 1.0 - Math.cos(angle); 2041 private static final double b = Math.tan(angle); 2042 private static final double c = Math.sqrt(1.0 + b * b) - 1 + a; 2043 private static final double cv = 4.0 / 3.0 * a * b / c; 2044 private static final double acv = (1.0 - cv) / 2.0; 2045 2046 // For each array: 2047 // 4 values for each point {v0, v1, v2, v3}: 2048 // point = (x + v0 * w + v1 * arcWidth, 2049 // y + v2 * h + v3 * arcHeight); 2050 private static final double CtrlPtTemplate[][] = { 2051 { 0.0, 0.0, 1.0, 0.0 }, /* BOTTOM LEFT corner */ 2052 { 0.0, 0.0, 1.0, -0.5 }, /* BOTTOM LEFT arc start */ 2053 { 0.0, 0.0, 1.0, -acv, /* BOTTOM LEFT arc curve */ 2054 0.0, acv, 1.0, 0.0, 2055 0.0, 0.5, 1.0, 0.0 }, 2056 { 1.0, 0.0, 1.0, 0.0 }, /* BOTTOM RIGHT corner */ 2057 { 1.0, -0.5, 1.0, 0.0 }, /* BOTTOM RIGHT arc start */ 2058 { 1.0, -acv, 1.0, 0.0, /* BOTTOM RIGHT arc curve */ 2059 1.0, 0.0, 1.0, -acv, 2060 1.0, 0.0, 1.0, -0.5 }, 2061 { 1.0, 0.0, 0.0, 0.0 }, /* TOP RIGHT corner */ 2062 { 1.0, 0.0, 0.0, 0.5 }, /* TOP RIGHT arc start */ 2063 { 1.0, 0.0, 0.0, acv, /* TOP RIGHT arc curve */ 2064 1.0, -acv, 0.0, 0.0, 2065 1.0, -0.5, 0.0, 0.0 }, 2066 { 0.0, 0.0, 0.0, 0.0 }, /* TOP LEFT corner */ 2067 { 0.0, 0.5, 0.0, 0.0 }, /* TOP LEFT arc start */ 2068 { 0.0, acv, 0.0, 0.0, /* TOP LEFT arc curve */ 2069 0.0, 0.0, 0.0, acv, 2070 0.0, 0.0, 0.0, 0.5 }, 2071 {}, /* Closing path element */ 2072 }; 2073 private static final int CornerFlags[] = { 2074 RoundRectClipShape.BOTTOM_LEFT, 2075 RoundRectClipShape.BOTTOM_RIGHT, 2076 RoundRectClipShape.TOP_RIGHT, 2077 RoundRectClipShape.TOP_LEFT, 2078 }; 2079 2080 RoundishRectIterator(RoundRectClipShape rr, AffineTransform at) { 2081 this.x = rr.getX(); 2082 this.y = rr.getY(); 2083 this.w = rr.getWidth(); 2084 this.h = rr.getHeight(); 2085 this.aw = Math.min(w, Math.abs(rr.getArcWidth())); 2086 this.ah = Math.min(h, Math.abs(rr.getArcHeight())); 2087 this.affine = at; 2088 if (w < 0 || h < 0) { 2089 // Don't draw anything... 2090 ctrlpts = new double[0][]; 2091 types = new int[0]; 2092 } else { 2093 int corners = rr.getCornerFlags(); 2094 int numedges = 5; // 4xCORNER_POINT, CLOSE 2095 for (int i = 1; i < 0x10; i <<= 1) { 2096 // Add one for each corner that has a curve 2097 if ((corners & i) != 0) numedges++; 2098 } 2099 ctrlpts = new double[numedges][]; 2100 types = new int[numedges]; 2101 int j = 0; 2102 for (int i = 0; i < 4; i++) { 2103 types[j] = SEG_LINETO; 2104 if ((corners & CornerFlags[i]) == 0) { 2105 ctrlpts[j++] = CtrlPtTemplate[i*3+0]; 2106 } else { 2107 ctrlpts[j++] = CtrlPtTemplate[i*3+1]; 2108 types[j] = SEG_CUBICTO; 2109 ctrlpts[j++] = CtrlPtTemplate[i*3+2]; 2110 } 2111 } 2112 types[j] = SEG_CLOSE; 2113 ctrlpts[j++] = CtrlPtTemplate[12]; 2114 types[0] = SEG_MOVETO; 2115 } 2116 } 2117 2118 public int getWindingRule() { 2119 return WIND_NON_ZERO; 2120 } 2121 2122 public boolean isDone() { 2123 return index >= ctrlpts.length; 2124 } 2125 2126 public void next() { 2127 index++; 2128 } 2129 2130 public int currentSegment(float[] coords) { 2131 if (isDone()) { 2132 throw new NoSuchElementException("roundrect iterator out of bounds"); 2133 } 2134 double ctrls[] = ctrlpts[index]; 2135 int nc = 0; 2136 for (int i = 0; i < ctrls.length; i += 4) { 2137 coords[nc++] = (float) (x + ctrls[i + 0] * w + ctrls[i + 1] * aw); 2138 coords[nc++] = (float) (y + ctrls[i + 2] * h + ctrls[i + 3] * ah); 2139 } 2140 if (affine != null) { 2141 affine.transform(coords, 0, coords, 0, nc / 2); 2142 } 2143 return types[index]; 2144 } 2145 2146 public int currentSegment(double[] coords) { 2147 if (isDone()) { 2148 throw new NoSuchElementException("roundrect iterator out of bounds"); 2149 } 2150 double ctrls[] = ctrlpts[index]; 2151 int nc = 0; 2152 for (int i = 0; i < ctrls.length; i += 4) { 2153 coords[nc++] = x + ctrls[i + 0] * w + ctrls[i + 1] * aw; 2154 coords[nc++] = y + ctrls[i + 2] * h + ctrls[i + 3] * ah; 2155 } 2156 if (affine != null) { 2157 affine.transform(coords, 0, coords, 0, nc / 2); 2158 } 2159 return types[index]; 2160 } 2161 } 2162 } 2163 }