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