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