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