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