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