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