jdk/src/share/classes/com/sun/java/swing/plaf/gtk/GTKPainter.java
changeset 2 90ce3da70b43
child 1837 efd004b550b7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/java/swing/plaf/gtk/GTKPainter.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1502 @@
+/*
+ * Copyright 2002-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package com.sun.java.swing.plaf.gtk;
+
+import sun.swing.plaf.synth.SynthUI;
+import sun.awt.UNIXToolkit;
+
+import javax.swing.plaf.synth.*;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+import com.sun.java.swing.plaf.gtk.GTKConstants.ArrowType;
+import com.sun.java.swing.plaf.gtk.GTKConstants.ExpanderStyle;
+import com.sun.java.swing.plaf.gtk.GTKConstants.Orientation;
+import com.sun.java.swing.plaf.gtk.GTKConstants.PositionType;
+import com.sun.java.swing.plaf.gtk.GTKConstants.ShadowType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @author Joshua Outwater
+ * @author Scott Violet
+ */
+// Need to support:
+// default_outside_border: Insets when default.
+// interior_focus: Indicates if focus should appear inside border, or
+//                       outside border.
+// focus-line-width: Integer giving size of focus border
+// focus-padding: Integer giving padding between border and focus
+//        indicator.
+// focus-line-pattern:
+//
+class GTKPainter extends SynthPainter {
+    private static final PositionType[] POSITIONS = {
+        PositionType.BOTTOM, PositionType.RIGHT,
+        PositionType.TOP, PositionType.LEFT
+    };
+
+    private static final ShadowType SHADOWS[] = {
+        ShadowType.NONE, ShadowType.IN, ShadowType.OUT,
+        ShadowType.ETCHED_IN, ShadowType.OUT
+    };
+
+    private final static GTKEngine ENGINE = GTKEngine.INSTANCE;
+    final static GTKPainter INSTANCE = new GTKPainter();
+
+    private GTKPainter() {
+    }
+
+    private String getName(SynthContext context) {
+        return (context.getRegion().isSubregion()) ? null :
+               context.getComponent().getName();
+    }
+
+    public void paintCheckBoxBackground(SynthContext context,
+            Graphics g, int x, int y, int w, int h) {
+        paintRadioButtonBackground(context, g, x, y, w, h);
+    }
+
+    public void paintCheckBoxMenuItemBackground(SynthContext context,
+            Graphics g, int x, int y, int w, int h) {
+        paintRadioButtonMenuItemBackground(context, g, x, y, w, h);
+    }
+
+    // FORMATTED_TEXT_FIELD
+    public void paintFormattedTextFieldBackground(SynthContext context,
+                                          Graphics g, int x, int y,
+                                          int w, int h) {
+        paintTextBackground(context, g, x, y, w, h);
+    }
+
+    //
+    // TOOL_BAR_DRAG_WINDOW
+    //
+    public void paintToolBarDragWindowBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        paintToolBarBackground(context, g, x, y, w, h);
+    }
+
+
+    //
+    // TOOL_BAR
+    //
+    public void paintToolBarBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+        int orientation = ((JToolBar)context.getComponent()).getOrientation();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id,
+                                          state, orientation))
+            {
+                ENGINE.startPainting(g, x, y, w, h, id, state, orientation);
+                ENGINE.paintBox(g, context, id, gtkState, ShadowType.OUT,
+                                "handlebox_bin", x, y, w, h);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintToolBarContentBackground(SynthContext context,
+                                              Graphics g,
+                                              int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        int orientation = ((JToolBar)context.getComponent()).getOrientation();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, orientation)) {
+                ENGINE.startPainting(g, x, y, w, h, id, orientation);
+                ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
+                                ShadowType.OUT, "toolbar", x, y, w, h);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    //
+    // PASSWORD_FIELD
+    //
+    public void paintPasswordFieldBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        paintTextBackground(context, g, x, y, w, h);
+    }
+
+    //
+    // TEXT_FIELD
+    //
+    public void paintTextFieldBackground(SynthContext context, Graphics g,
+                                         int x, int y, int w, int h) {
+        if (getName(context) == "Tree.cellEditor") {
+            paintTreeCellEditorBackground(context, g, x, y, w, h);
+        } else {
+            paintTextBackground(context, g, x, y, w, h);
+        }
+    }
+
+    //
+    // RADIO_BUTTON
+    //
+    // NOTE: this is called for JCheckBox too
+    public void paintRadioButtonBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        if (gtkState == SynthConstants.MOUSE_OVER) {
+            synchronized (UNIXToolkit.GTK_LOCK) {
+                if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                    ENGINE.startPainting(g, x, y, w, h, id);
+                    ENGINE.paintFlatBox(g, context, id,
+                            SynthConstants.MOUSE_OVER, ShadowType.ETCHED_OUT,
+                            "checkbutton", x, y, w, h, ColorType.BACKGROUND);
+                    ENGINE.finishPainting();
+                }
+            }
+        }
+    }
+
+    //
+    // RADIO_BUTTON_MENU_ITEM
+    //
+    // NOTE: this is called for JCheckBoxMenuItem too
+    public void paintRadioButtonMenuItemBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        if (gtkState == SynthConstants.MOUSE_OVER) {
+            synchronized (UNIXToolkit.GTK_LOCK) {
+                if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                    ShadowType shadow = (GTKLookAndFeel.is2_2() ?
+                        ShadowType.NONE : ShadowType.OUT);
+                    ENGINE.startPainting(g, x, y, w, h, id);
+                    ENGINE.paintBox(g, context, id, gtkState,
+                            shadow, "menuitem", x, y, w, h);
+                    ENGINE.finishPainting();
+                }
+            }
+        }
+    }
+
+    //
+    // LABEL
+    //
+    public void paintLabelBackground(SynthContext context,
+                                     Graphics g, int x, int y,
+                                     int w, int h) {
+        String name = getName(context);
+        JComponent c = context.getComponent();
+        Container  container = c.getParent();
+
+        if (name == "TableHeader.renderer" ||
+            name == "GTKFileChooser.directoryListLabel" ||
+            name == "GTKFileChooser.fileListLabel") {
+
+            paintButtonBackgroundImpl(context, g, Region.BUTTON, "button",
+                    x, y, w, h, true, false, false, false);
+        }
+        /*
+         * If the label is a ListCellRenderer and it's in a container
+         * (CellRendererPane) which is in a JComboBox then we paint the label
+         * as a TextField like a gtk_entry for a combobox.
+         */
+        else if (c instanceof ListCellRenderer &&
+                 container != null &&
+                 container.getParent() instanceof JComboBox ) {
+            paintTextBackground(context, g, x, y, w, h);
+        }
+    }
+
+    //
+    // INTERNAL_FRAME
+    //
+    public void paintInternalFrameBorder(SynthContext context,
+                                      Graphics g, int x, int y,
+                                      int w, int h) {
+        Metacity.INSTANCE.paintFrameBorder(context, g, x, y, w, h);
+    }
+
+    //
+    // DESKTOP_PANE
+    //
+    public void paintDesktopPaneBackground(SynthContext context,
+                                           Graphics g, int x, int y,
+                                           int w, int h) {
+        // Does not call into ENGINE for better performance
+        fillArea(context, g, x, y, w, h, ColorType.BACKGROUND);
+    }
+
+    //
+    // DESKTOP_ICON
+    //
+    public void paintDesktopIconBorder(SynthContext context,
+                                           Graphics g, int x, int y,
+                                           int w, int h) {
+        Metacity.INSTANCE.paintFrameBorder(context, g, x, y, w, h);
+    }
+
+    public void paintButtonBackground(SynthContext context, Graphics g,
+                                      int x, int y, int w, int h) {
+        String name = getName(context);
+        if (name != null && name.startsWith("InternalFrameTitlePane.")) {
+            Metacity.INSTANCE.paintButtonBackground(context, g, x, y, w, h);
+
+        } else {
+            AbstractButton button = (AbstractButton)context.getComponent();
+            boolean paintBG = button.isContentAreaFilled() &&
+                              button.isBorderPainted();
+            boolean paintFocus = button.isFocusPainted();
+            boolean defaultCapable = (button instanceof JButton) &&
+                    ((JButton)button).isDefaultCapable();
+            boolean toolButton = (button.getParent() instanceof JToolBar);
+            paintButtonBackgroundImpl(context, g, Region.BUTTON, "button",
+                    x, y, w, h, paintBG, paintFocus, defaultCapable, toolButton);
+        }
+    }
+
+    private void paintButtonBackgroundImpl(SynthContext context, Graphics g,
+            Region id, String detail, int x, int y, int w, int h,
+            boolean paintBackground, boolean paintFocus,
+            boolean defaultCapable, boolean toolButton) {
+        int state = context.getComponentState();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id, state, detail,
+                    paintBackground, paintFocus, defaultCapable, toolButton)) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id, state, detail,
+                paintBackground, paintFocus, defaultCapable, toolButton);
+
+            // Paint the default indicator
+            GTKStyle style = (GTKStyle)context.getStyle();
+            if (defaultCapable && !toolButton) {
+                Insets defaultInsets = (Insets)style.getClassSpecificInsetsValue(
+                        context, "default-border",
+                        GTKStyle.BUTTON_DEFAULT_BORDER_INSETS);
+
+                if (paintBackground && (state & SynthConstants.DEFAULT) != 0) {
+                    ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
+                            ShadowType.IN, "buttondefault", x, y, w, h);
+                }
+                x += defaultInsets.left;
+                y += defaultInsets.top;
+                w -= (defaultInsets.left + defaultInsets.right);
+                h -= (defaultInsets.top + defaultInsets.bottom);
+            }
+
+            boolean interiorFocus = style.getClassSpecificBoolValue(
+                    context, "interior-focus", true);
+            int focusSize = style.getClassSpecificIntValue(
+                    context, "focus-line-width",1);
+            int focusPad = style.getClassSpecificIntValue(
+                    context, "focus-padding", 1);
+
+            int totalFocusSize = focusSize + focusPad;
+            int xThickness = style.getXThickness();
+            int yThickness = style.getYThickness();
+
+            // Render the box.
+            if (!interiorFocus &&
+                    (state & SynthConstants.FOCUSED) == SynthConstants.FOCUSED) {
+                x += totalFocusSize;
+                y += totalFocusSize;
+                w -= 2 * totalFocusSize;
+                h -= 2 * totalFocusSize;
+            }
+
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+            boolean paintBg;
+            if (toolButton) {
+                // Toolbar buttons should only have their background painted
+                // in the PRESSED, SELECTED, or MOUSE_OVER states.
+                paintBg =
+                    (gtkState != SynthConstants.ENABLED) &&
+                    (gtkState != SynthConstants.DISABLED);
+            } else {
+                // Otherwise, always paint the button's background, unless
+                // the user has overridden it and we're in the ENABLED state.
+                paintBg =
+                    paintBackground ||
+                    (gtkState != SynthConstants.ENABLED);
+            }
+            if (paintBg) {
+                ShadowType shadowType = ShadowType.OUT;
+                if ((state & (SynthConstants.PRESSED |
+                              SynthConstants.SELECTED)) != 0) {
+                    shadowType = ShadowType.IN;
+                }
+                ENGINE.paintBox(g, context, id, gtkState,
+                        shadowType, detail, x, y, w, h);
+            }
+
+            // focus
+            if (paintFocus && (state & SynthConstants.FOCUSED) != 0) {
+                if (interiorFocus) {
+                    x += xThickness + focusPad;
+                    y += yThickness + focusPad;
+                    w -= 2 * (xThickness + focusPad);
+                    h -= 2 * (yThickness + focusPad);
+                } else {
+                    x -= totalFocusSize;
+                    y -= totalFocusSize;
+                    w += 2 * totalFocusSize;
+                    h += 2 * totalFocusSize;
+                }
+                ENGINE.paintFocus(g, context, id, gtkState, detail, x, y, w, h);
+            }
+            ENGINE.finishPainting();
+        }
+    }
+
+    //
+    // ARROW_BUTTON
+    //
+    public void paintArrowButtonForeground(SynthContext context, Graphics g,
+                                           int x, int y, int w, int h,
+                                           int direction) {
+        Region id = context.getRegion();
+        Component c = context.getComponent();
+        String name = c.getName();
+
+        ArrowType arrowType = null;
+        switch (direction) {
+            case SwingConstants.NORTH:
+                arrowType = ArrowType.UP; break;
+            case SwingConstants.SOUTH:
+                arrowType = ArrowType.DOWN; break;
+            case SwingConstants.EAST:
+                arrowType = ArrowType.RIGHT; break;
+            case SwingConstants.WEST:
+                arrowType = ArrowType.LEFT; break;
+        }
+
+        String detail = "arrow";
+        if (name == "ScrollBar.button") {
+            if (arrowType == ArrowType.UP || arrowType == ArrowType.DOWN) {
+                detail = "vscrollbar";
+            } else {
+                detail = "hscrollbar";
+            }
+        } else if (name == "Spinner.nextButton" ||
+                   name == "Spinner.previousButton") {
+            detail = "spinbutton";
+        } else if (name != "ComboBox.arrowButton") {
+            assert false;
+        }
+
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        ShadowType shadowType = (gtkState == SynthConstants.PRESSED ?
+            ShadowType.IN : ShadowType.OUT);
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h,
+                    gtkState, name, direction)) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, gtkState, name, direction);
+            ENGINE.paintArrow(g, context, id, gtkState,
+                    shadowType, arrowType, detail, x, y, w, h);
+            ENGINE.finishPainting();
+        }
+    }
+
+    public void paintArrowButtonBackground(SynthContext context,
+            Graphics g, int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        AbstractButton button = (AbstractButton)context.getComponent();
+
+        String name = button.getName();
+        String detail = "button";
+        int direction = SwingConstants.CENTER;
+        if (name == "ScrollBar.button") {
+            Integer prop = (Integer)
+                button.getClientProperty("__arrow_direction__");
+            direction = (prop != null) ?
+                prop.intValue() : SwingConstants.WEST;
+            switch (direction) {
+            default:
+            case SwingConstants.EAST:
+            case SwingConstants.WEST:
+                detail = "hscrollbar";
+                break;
+            case SwingConstants.NORTH:
+            case SwingConstants.SOUTH:
+                detail = "vscrollbar";
+                break;
+            }
+        } else if (name == "Spinner.previousButton") {
+            detail = "spinbutton_down";
+        } else if (name == "Spinner.nextButton") {
+            detail = "spinbutton_up";
+        } else if (name != "ComboBox.arrowButton") {
+            assert false;
+        }
+
+        int state = context.getComponentState();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id,
+                                        state, detail, direction))
+            {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id,
+                                 state, detail, direction);
+
+            if (detail.startsWith("spin")) {
+                /*
+                 * The ubuntulooks engine (and presumably others) expect us to
+                 * first draw the full "spinbutton" background, and then draw
+                 * the individual "spinbutton_up/down" buttons on top of that.
+                 * Note that it is the state of the JSpinner (not its arrow
+                 * button) that determines how we draw this background.
+                 */
+                int spinState = button.getParent().isEnabled() ?
+                    SynthConstants.ENABLED : SynthConstants.DISABLED;
+                int mody = (detail == "spinbutton_up") ? y : y-h;
+                int modh = h*2;
+                ENGINE.paintBox(g, context, id, spinState,
+                                ShadowType.IN, "spinbutton",
+                                x, mody, w, modh);
+            }
+
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+            ShadowType shadowType = ShadowType.OUT;
+            if ((gtkState & (SynthConstants.PRESSED |
+                             SynthConstants.SELECTED)) != 0)
+            {
+                shadowType = ShadowType.IN;
+            }
+            ENGINE.paintBox(g, context, id, gtkState,
+                            shadowType, detail,
+                            x, y, w, h);
+
+            ENGINE.finishPainting();
+        }
+    }
+
+
+    //
+    // LIST
+    //
+    public void paintListBackground(SynthContext context, Graphics g,
+                                    int x, int y, int w, int h) {
+        // Does not call into ENGINE for better performance
+        fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
+    }
+
+    public void paintMenuBarBackground(SynthContext context, Graphics g,
+                                       int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                return;
+            }
+            GTKStyle style = (GTKStyle)context.getStyle();
+            int shadow = style.getClassSpecificIntValue(
+                    context, "shadow-type", 2);
+            ShadowType shadowType = SHADOWS[shadow];
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                    id, context.getComponentState());
+            ENGINE.startPainting(g, x, y, w, h, id);
+            ENGINE.paintBox(g, context, id, gtkState,
+                shadowType, "menubar", x, y, w, h);
+            ENGINE.finishPainting();
+        }
+    }
+
+    //
+    // MENU
+    //
+    public void paintMenuBackground(SynthContext context,
+                                     Graphics g,
+                                     int x, int y, int w, int h) {
+        paintMenuItemBackground(context, g, x, y, w, h);
+    }
+
+    // This is called for both MENU and MENU_ITEM
+    public void paintMenuItemBackground(SynthContext context,
+                                     Graphics g,
+                                     int x, int y, int w, int h) {
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                context.getRegion(), context.getComponentState());
+        if (gtkState == SynthConstants.MOUSE_OVER) {
+            Region id = Region.MENU_ITEM;
+            synchronized (UNIXToolkit.GTK_LOCK) {
+                if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                    ShadowType shadow = (GTKLookAndFeel.is2_2() ?
+                        ShadowType.NONE : ShadowType.OUT);
+                    ENGINE.startPainting(g, x, y, w, h, id);
+                    ENGINE.paintBox(g, context, id, gtkState, shadow,
+                            "menuitem", x, y, w, h);
+                    ENGINE.finishPainting();
+                }
+            }
+        }
+    }
+
+    public void paintPopupMenuBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState)) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id, gtkState);
+            ENGINE.paintBox(g, context, id, gtkState,
+                    ShadowType.OUT, "menu", x, y, w, h);
+
+            GTKStyle style = (GTKStyle)context.getStyle();
+            int xThickness = style.getXThickness();
+            int yThickness = style.getYThickness();
+            ENGINE.paintBackground(g, context, id, gtkState,
+                    style.getGTKColor(context, gtkState, GTKColorType.BACKGROUND),
+                    x + xThickness, y + yThickness,
+                    w - xThickness - xThickness, h - yThickness - yThickness);
+            ENGINE.finishPainting();
+        }
+    }
+
+    public void paintProgressBarBackground(SynthContext context,
+                                            Graphics g,
+                                            int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                ENGINE.startPainting(g, x, y, w, h, id);
+                ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
+                        ShadowType.IN, "trough", x, y, w, h);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintProgressBarForeground(SynthContext context, Graphics g,
+                                            int x, int y, int w, int h,
+                                            int orientation) {
+        Region id = context.getRegion();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            // Note that we don't call paintCachedImage() here.  Since the
+            // progress bar foreground is painted differently for each value
+            // it would be wasteful to try to cache an image for each state,
+            // so instead we simply avoid caching in this case.
+            if (w <= 0 || h <= 0) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id, "fg");
+            ENGINE.paintBox(g, context, id, SynthConstants.MOUSE_OVER,
+                            ShadowType.OUT, "bar", x, y, w, h);
+            ENGINE.finishPainting(false); // don't bother caching the image
+        }
+    }
+
+    public void paintViewportBorder(SynthContext context, Graphics g,
+                                           int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                ENGINE.startPainting(g, x, y, w, h, id);
+                ENGINE.paintShadow(g, context, id, SynthConstants.ENABLED,
+                        ShadowType.IN, "scrolled_window", x, y, w, h);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintSeparatorBackground(SynthContext context,
+                                          Graphics g,
+                                          int x, int y, int w, int h,
+                                         int orientation) {
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+        JComponent c = context.getComponent();
+
+        /*
+         * Note: In theory, the style's x/y thickness values would determine
+         * the width of the separator content.  In practice, however, some
+         * engines will render a line that is wider than the corresponding
+         * thickness value.  For example, ubuntulooks reports x/y thickness
+         * values of 1 for separators, but always renders a 2-pixel wide line.
+         * As a result of all this, we need to be careful not to restrict
+         * the w/h values below too much, so that the full thickness of the
+         * rendered line will be captured by our image caching code.
+         */
+        String detail;
+        if (c instanceof JToolBar.Separator) {
+            /*
+             * GTK renders toolbar separators differently in that an
+             * artificial padding is added to each end of the separator.
+             * The value of 0.2f below is derived from the source code of
+             * gtktoolbar.c in the current version of GTK+ (2.8.20 at the
+             * time of this writing).  Specifically, the relevant values are:
+             *     SPACE_LINE_DIVISION 10.0
+             *     SPACE_LINE_START     2.0
+             *     SPACE_LINE_END       8.0
+             * These are used to determine the distance from the top (or left)
+             * edge of the toolbar to the other edge.  So for example, the
+             * starting/top point of a vertical separator is 2/10 of the
+             * height of a horizontal toolbar away from the top edge, which
+             * is how we arrive at 0.2f below.  Likewise, the ending/bottom
+             * point is 8/10 of the height away from the top edge, or in other
+             * words, it is 2/10 away from the bottom edge, which is again
+             * how we arrive at the 0.2f value below.
+             *
+             * The separator is also centered horizontally or vertically,
+             * depending on its orientation.  This was determined empirically
+             * and by examining the code referenced above.
+             */
+            detail = "toolbar";
+            float pct = 0.2f;
+            JToolBar.Separator sep = (JToolBar.Separator)c;
+            Dimension size = sep.getSeparatorSize();
+            GTKStyle style = (GTKStyle)context.getStyle();
+            if (orientation == JSeparator.HORIZONTAL) {
+                x += (int)(w * pct);
+                w -= (int)(w * pct * 2);
+                y += (size.height - style.getYThickness()) / 2;
+            } else {
+                y += (int)(h * pct);
+                h -= (int)(h * pct * 2);
+                x += (size.width - style.getXThickness()) / 2;
+            }
+        } else {
+            // For regular/menu separators, we simply subtract out the insets.
+            detail = "separator";
+            Insets insets = c.getInsets();
+            x += insets.left;
+            y += insets.top;
+            if (orientation == JSeparator.HORIZONTAL) {
+                w -= (insets.left + insets.right);
+            } else {
+                h -= (insets.top + insets.bottom);
+            }
+        }
+
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id,
+                                          state, detail, orientation)) {
+                ENGINE.startPainting(g, x, y, w, h, id,
+                                     state, detail, orientation);
+                if (orientation == JSeparator.HORIZONTAL) {
+                    ENGINE.paintHline(g, context, id, state,
+                                      detail, x, y, w, h);
+                } else {
+                    ENGINE.paintVline(g, context, id, state,
+                                      detail, x, y, w, h);
+                }
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintSliderTrackBackground(SynthContext context,
+                                       Graphics g,
+                                       int x, int y, int w,int h) {
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+
+        // For focused sliders, we paint focus rect outside the bounds passed.
+        // Need to adjust for that.
+        boolean focused = ((state & SynthConstants.FOCUSED) != 0);
+        int focusSize = 0;
+        if (focused) {
+            GTKStyle style = (GTKStyle)context.getStyle();
+            focusSize = style.getClassSpecificIntValue(
+                                context, "focus-line-width", 1) +
+                        style.getClassSpecificIntValue(
+                                context, "focus-padding", 1);
+            x -= focusSize;
+            y -= focusSize;
+            w += focusSize * 2;
+            h += focusSize * 2;
+        }
+
+        // The ubuntulooks engine paints slider troughs differently depending
+        // on the current slider value and its component orientation.
+        JSlider slider = (JSlider)context.getComponent();
+        double value = slider.getValue();
+        double min = slider.getMinimum();
+        double max = slider.getMaximum();
+        double visible = 20; // not used for sliders; any value will work
+
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            // Note that we don't call paintCachedImage() here.  Since some
+            // engines (e.g. ubuntulooks) paint the slider background
+            // differently for any given slider value, it would be wasteful
+            // to try to cache an image for each state, so instead we simply
+            // avoid caching in this case.
+            if (w <= 0 || h <= 0) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id, state, value);
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+            ENGINE.setRangeValue(context, id, value, min, max, visible);
+            ENGINE.paintBox(g, context, id, gtkState, ShadowType.IN,
+                            "trough", x + focusSize, y + focusSize,
+                            w - 2 * focusSize, h - 2 * focusSize);
+            if (focused) {
+                ENGINE.paintFocus(g, context, id, SynthConstants.ENABLED,
+                                  "trough", x, y, w, h);
+            }
+            ENGINE.finishPainting(false); // don't bother caching the image
+        }
+    }
+
+    public void paintSliderThumbBackground(SynthContext context,
+            Graphics g, int x, int y, int w, int h, int dir) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, dir)) {
+                Orientation orientation = (dir == JSlider.HORIZONTAL ?
+                    Orientation.HORIZONTAL : Orientation.VERTICAL);
+                String detail = (dir == JSlider.HORIZONTAL ?
+                    "hscale" : "vscale");
+                ENGINE.startPainting(g, x, y, w, h, id, gtkState, dir);
+                ENGINE.paintSlider(g, context, id, gtkState,
+                        ShadowType.OUT, detail, x, y, w, h, orientation);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    //
+    // SPINNER
+    //
+    public void paintSpinnerBackground(SynthContext context,
+                                        Graphics g,
+                                        int x, int y, int w, int h) {
+        // This is handled in paintTextFieldBackground
+    }
+
+    //
+    // SPLIT_PANE_DIVIDER
+    //
+    public void paintSplitPaneDividerBackground(SynthContext context,
+                                       Graphics g,
+                                       int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        JSplitPane splitPane = (JSplitPane)context.getComponent();
+        Orientation orientation =
+                (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ?
+                    Orientation.VERTICAL : Orientation.HORIZONTAL);
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h,
+                    id, gtkState, orientation)) {
+                ENGINE.startPainting(g, x, y, w, h, id, gtkState, orientation);
+                ENGINE.paintHandle(g, context, id, gtkState,
+                        ShadowType.OUT, "paned", x, y, w, h, orientation);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintSplitPaneDragDivider(SynthContext context,
+                                       Graphics g,int x, int y, int w, int h,
+                                       int orientation) {
+        paintSplitPaneDividerForeground(context, g, x, y, w, h, orientation);
+    }
+
+    public void paintTabbedPaneContentBackground(SynthContext context,
+                                      Graphics g, int x, int y, int w, int h) {
+        JTabbedPane pane = (JTabbedPane)context.getComponent();
+        int selectedIndex = pane.getSelectedIndex();
+        PositionType placement = GTKLookAndFeel.SwingOrientationConstantToGTK(
+                                                        pane.getTabPlacement());
+
+        int gapStart = 0;
+        int gapSize = 0;
+        if (selectedIndex != -1) {
+            Rectangle tabBounds = pane.getBoundsAt(selectedIndex);
+
+            if (placement == PositionType.TOP ||
+                placement == PositionType.BOTTOM) {
+
+                gapStart = tabBounds.x - x;
+                gapSize = tabBounds.width;
+            }
+            else {
+                gapStart = tabBounds.y - y;
+                gapSize = tabBounds.height;
+            }
+        }
+
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h,
+                    id, gtkState, placement, gapStart, gapSize)) {
+                ENGINE.startPainting(g, x, y, w, h,
+                        id, gtkState, placement, gapStart, gapSize);
+                ENGINE.paintBoxGap(g, context, id, gtkState, ShadowType.OUT,
+                        "notebook", x, y, w, h, placement, gapStart, gapSize);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintTabbedPaneTabBackground(SynthContext context,
+                                           Graphics g,
+                                           int x, int y, int w, int h,
+                                           int tabIndex) {
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+        int gtkState = ((state & SynthConstants.SELECTED) != 0 ?
+            SynthConstants.ENABLED : SynthConstants.PRESSED);
+        JTabbedPane pane = (JTabbedPane)context.getComponent();
+        int placement = pane.getTabPlacement();
+
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h,
+                    id, gtkState, placement, tabIndex)) {
+                PositionType side = POSITIONS[placement - 1];
+                ENGINE.startPainting(g, x, y, w, h,
+                        id, gtkState, placement, tabIndex);
+                ENGINE.paintExtension(g, context, id, gtkState,
+                        ShadowType.OUT, "tab", x, y, w, h, side, tabIndex);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    //
+    // TEXT_PANE
+    //
+    public void paintTextPaneBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        paintTextAreaBackground(context, g, x, y, w, h);
+    }
+
+    //
+    // EDITOR_PANE
+    //
+    public void paintEditorPaneBackground(SynthContext context, Graphics g,
+                                          int x, int y, int w, int h) {
+        paintTextAreaBackground(context, g, x, y, w, h);
+    }
+
+    //
+    // TEXT_AREA
+    //
+    public void paintTextAreaBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        // Does not call into ENGINE for better performance
+        fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
+    }
+
+    //
+    // TEXT_FIELD
+    //
+    // NOTE: Combobox and Label, Password and FormattedTextField calls this
+    // too.
+    private void paintTextBackground(SynthContext context, Graphics g,
+                                     int x, int y, int w, int h) {
+        // Text is odd in that it uses the TEXT_BACKGROUND vs BACKGROUND.
+        JComponent c = context.getComponent();
+        GTKStyle style = (GTKStyle)context.getStyle();
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
+                return;
+            }
+
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+            int focusSize = 0;
+            boolean interiorFocus = style.getClassSpecificBoolValue(
+                    context, "interior-focus", true);
+            if (!interiorFocus && (state & SynthConstants.FOCUSED) != 0) {
+                focusSize = style.getClassSpecificIntValue(context,
+                        "focus-line-width",1);
+                x += focusSize;
+                y += focusSize;
+                w -= 2 * focusSize;
+                h -= 2 * focusSize;
+            }
+
+            int xThickness = style.getXThickness();
+            int yThickness = style.getYThickness();
+
+            ENGINE.startPainting(g, x, y, w, h, id, state);
+            ENGINE.paintShadow(g, context, id, gtkState,
+                               ShadowType.IN, "entry", x, y, w, h);
+            ENGINE.paintFlatBox(g, context, id,
+                                gtkState, ShadowType.NONE, "entry_bg",
+                                x + xThickness,
+                                y + yThickness,
+                                w - (2 * xThickness),
+                                h - (2 * yThickness),
+                                ColorType.TEXT_BACKGROUND);
+
+            if (focusSize > 0) {
+                x -= focusSize;
+                y -= focusSize;
+                w += 2 * focusSize;
+                h += 2 * focusSize;
+                ENGINE.paintFocus(g, context, id, gtkState,
+                        "entry", x, y, w, h);
+            }
+            ENGINE.finishPainting();
+        }
+    }
+
+    private void paintTreeCellEditorBackground(SynthContext context, Graphics g,
+                                               int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState)) {
+                ENGINE.startPainting(g, x, y, w, h, id, gtkState);
+                ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
+                        "entry_bg", x, y, w, h, ColorType.TEXT_BACKGROUND);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+
+    //
+    // ROOT_PANE
+    //
+    public void paintRootPaneBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        // Does not call into ENGINE for better performance
+        fillArea(context, g, x, y, w, h, GTKColorType.BACKGROUND);
+    }
+
+    //
+    // TOGGLE_BUTTON
+    //
+    public void paintToggleButtonBackground(SynthContext context,
+                                            Graphics g,
+                                            int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        JToggleButton toggleButton = (JToggleButton)context.getComponent();
+        boolean paintBG = toggleButton.isContentAreaFilled() &&
+                          toggleButton.isBorderPainted();
+        boolean paintFocus = toggleButton.isFocusPainted();
+        boolean toolButton = (toggleButton.getParent() instanceof JToolBar);
+        paintButtonBackgroundImpl(context, g, id, "button",
+                                  x, y, w, h,
+                                  paintBG, paintFocus, false, toolButton);
+    }
+
+
+    //
+    // SCROLL_BAR
+    //
+    public void paintScrollBarBackground(SynthContext context,
+                                          Graphics g,
+                                          int x, int y, int w,int h) {
+        Region id = context.getRegion();
+        boolean focused =
+                (context.getComponentState() & SynthConstants.FOCUSED) != 0;
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (ENGINE.paintCachedImage(g, x, y, w, h, id, focused)) {
+                return;
+            }
+            ENGINE.startPainting(g, x, y, w, h, id, focused);
+
+            // Note: the scrollbar insets already include the "trough-border",
+            // which is needed to position the scrollbar buttons properly.
+            // But when we render, we need to take the trough border out
+            // of the equation so that we paint the entire area covered by
+            // the trough border and the scrollbar content itself.
+            Insets insets = context.getComponent().getInsets();
+            GTKStyle style = (GTKStyle)context.getStyle();
+            int troughBorder =
+                style.getClassSpecificIntValue(context, "trough-border", 1);
+            insets.left   -= troughBorder;
+            insets.right  -= troughBorder;
+            insets.top    -= troughBorder;
+            insets.bottom -= troughBorder;
+
+            ENGINE.paintBox(g, context, id, SynthConstants.PRESSED,
+                            ShadowType.IN, "trough",
+                            x + insets.left,
+                            y + insets.top,
+                            w - insets.left - insets.right,
+                            h - insets.top - insets.bottom);
+
+            if (focused) {
+                ENGINE.paintFocus(g, context, id,
+                        SynthConstants.ENABLED, "trough", x, y, w, h);
+            }
+            ENGINE.finishPainting();
+        }
+    }
+
+
+    //
+    // SCROLL_BAR_THUMB
+    //
+    public void paintScrollBarThumbBackground(SynthContext context,
+            Graphics g, int x, int y, int w, int h, int dir) {
+        Region id = context.getRegion();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                id, context.getComponentState());
+
+        // The clearlooks engine paints scrollbar thumbs differently
+        // depending on the current scroll value (specifically, it will avoid
+        // rendering a certain line when the thumb is at the starting or
+        // ending position).  Therefore, we normalize the current value to
+        // the range [0,100] here and then pass it down to setRangeValue()
+        // so that the native widget is configured appropriately.  Note that
+        // there are really only four values that matter (min, middle, max,
+        // or fill) so we restrict to one of those four values to avoid
+        // blowing out the image cache.
+        JScrollBar sb = (JScrollBar)context.getComponent();
+        boolean rtl =
+            sb.getOrientation() == JScrollBar.HORIZONTAL &&
+            !sb.getComponentOrientation().isLeftToRight();
+        double min = 0;
+        double max = 100;
+        double visible = 20;
+        double value;
+        if (sb.getMaximum() - sb.getMinimum() == sb.getVisibleAmount()) {
+            // In this case, the thumb fills the entire track, so it is
+            // touching both ends at the same time
+            value = 0;
+            visible = 100;
+        } else if (sb.getValue() == sb.getMinimum()) {
+            // At minimum
+            value = rtl ? 100 : 0;
+        } else if (sb.getValue() >= sb.getMaximum() - sb.getVisibleAmount()) {
+            // At maximum
+            value = rtl ? 0 : 100;
+        } else {
+            // Somewhere in between
+            value = 50;
+        }
+
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState,
+                                          dir, value, visible, rtl))
+            {
+                ENGINE.startPainting(g, x, y, w, h, id, gtkState,
+                                     dir, value, visible, rtl);
+                Orientation orientation = (dir == JScrollBar.HORIZONTAL ?
+                    Orientation.HORIZONTAL : Orientation.VERTICAL);
+                ENGINE.setRangeValue(context, id, value, min, max, visible);
+                ENGINE.paintSlider(g, context, id, gtkState,
+                        ShadowType.OUT, "slider", x, y, w, h, orientation);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    //
+    // TOOL_TIP
+    //
+    public void paintToolTipBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w,int h) {
+        Region id = context.getRegion();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                ENGINE.startPainting(g, x, y, w, h, id);
+                ENGINE.paintFlatBox(g, context, id, SynthConstants.ENABLED,
+                        ShadowType.OUT, "tooltip", x, y, w, h,
+                        ColorType.BACKGROUND);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+
+    //
+    // TREE_CELL
+    //
+    public void paintTreeCellBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        Region id = context.getRegion();
+        int state = context.getComponentState();
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
+                ENGINE.startPainting(g, x, y, w, h, id, state);
+                // the string arg should alternate based on row being painted,
+                // but we currently don't pass that in.
+                ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
+                        "cell_odd", x, y, w, h, ColorType.TEXT_BACKGROUND);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    public void paintTreeCellFocus(SynthContext context, Graphics g,
+                                    int x, int y, int w, int h) {
+        Region id = Region.TREE_CELL;
+        int state = context.getComponentState();
+        paintFocus(context, g, id, state, "treeview", x, y, w, h);
+    }
+
+
+    //
+    // TREE
+    //
+    public void paintTreeBackground(SynthContext context, Graphics g,
+                                    int x, int y, int w, int h) {
+        // As far as I can tell, these don't call into the ENGINE.
+        fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
+    }
+
+
+    //
+    // VIEWPORT
+    //
+    public void paintViewportBackground(SynthContext context, Graphics g,
+                                        int x, int y, int w, int h) {
+        // As far as I can tell, these don't call into the ENGINE.
+        // Also note that you don't want this to call into the ENGINE
+        // as if it where to paint a background JViewport wouldn't scroll
+        // correctly.
+        fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
+    }
+
+    void paintFocus(SynthContext context, Graphics g, Region id,
+            int state, String detail, int x, int y, int w, int h) {
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, "focus")) {
+                ENGINE.startPainting(g, x, y, w, h, id, gtkState, "focus");
+                ENGINE.paintFocus(g, context, id, gtkState, detail, x, y, w, h);
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    void paintMetacityElement(SynthContext context, Graphics g,
+            int gtkState, String detail, int x, int y, int w, int h,
+            ShadowType shadow, ArrowType direction) {
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(
+                    g, x, y, w, h, gtkState, detail, shadow, direction)) {
+                ENGINE.startPainting(
+                        g, x, y, w, h, gtkState, detail, shadow, direction);
+                if (detail == "metacity-arrow") {
+                    ENGINE.paintArrow(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
+                            gtkState, shadow, direction, "", x, y, w, h);
+
+                } else if (detail == "metacity-box") {
+                    ENGINE.paintBox(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
+                            gtkState, shadow, "", x, y, w, h);
+
+                } else if (detail == "metacity-vline") {
+                    ENGINE.paintVline(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
+                            gtkState, "", x, y, w, h);
+                }
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    void paintIcon(SynthContext context, Graphics g,
+            Method paintMethod, int x, int y, int w, int h) {
+        int state = context.getComponentState();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g, x, y, w, h, state, paintMethod)) {
+                ENGINE.startPainting(g, x, y, w, h, state, paintMethod);
+                try {
+                    paintMethod.invoke(this, context, g, state, x, y, w, h);
+                } catch (IllegalAccessException iae) {
+                    assert false;
+                } catch (InvocationTargetException ite) {
+                    assert false;
+                }
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    void paintIcon(SynthContext context, Graphics g,
+            Method paintMethod, int x, int y, int w, int h, Object direction) {
+        int state = context.getComponentState();
+        synchronized (UNIXToolkit.GTK_LOCK) {
+            if (! ENGINE.paintCachedImage(g,
+                    x, y, w, h, state, paintMethod, direction)) {
+                ENGINE.startPainting(g,
+                        x, y, w, h, state, paintMethod, direction);
+                try {
+                    paintMethod.invoke(this, context,
+                            g, state, x, y, w, h, direction);
+                } catch (IllegalAccessException iae) {
+                    assert false;
+                } catch (InvocationTargetException ite) {
+                    assert false;
+                }
+                ENGINE.finishPainting();
+            }
+        }
+    }
+
+    // All icon painting methods are called from under GTK_LOCK
+
+    public void paintTreeExpandedIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        ENGINE.paintExpander(g, context, Region.TREE,
+                GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
+                ExpanderStyle.EXPANDED, "treeview", x, y, w, h);
+    }
+
+    public void paintTreeCollapsedIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        ENGINE.paintExpander(g, context, Region.TREE,
+                GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
+                ExpanderStyle.COLLAPSED, "treeview", x, y, w, h);
+    }
+
+    public void paintCheckBoxIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        GTKStyle style = (GTKStyle)context.getStyle();
+        int size = style.getClassSpecificIntValue(context,
+                        "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
+        int offset = style.getClassSpecificIntValue(context,
+                        "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
+
+        ENGINE.paintCheck(g, context, Region.CHECK_BOX, "checkbutton",
+                x+offset, y+offset, size, size);
+    }
+
+    public void paintRadioButtonIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        GTKStyle style = (GTKStyle)context.getStyle();
+        int size = style.getClassSpecificIntValue(context,
+                        "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
+        int offset = style.getClassSpecificIntValue(context,
+                        "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
+
+        ENGINE.paintOption(g, context, Region.RADIO_BUTTON, "radiobutton",
+                x+offset, y+offset, size, size);
+    }
+
+    public void paintMenuArrowIcon(SynthContext context, Graphics g,
+            int state, int x, int y, int w, int h, ArrowType dir) {
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                context.getRegion(), state);
+        ShadowType shadow = ShadowType.OUT;
+        if (gtkState == SynthConstants.MOUSE_OVER) {
+            shadow = ShadowType.IN;
+        }
+        ENGINE.paintArrow(g, context, Region.MENU_ITEM, gtkState, shadow,
+                dir, "menuitem", x + 3, y + 3, 7, 7);
+    }
+
+    public void paintCheckBoxMenuItemCheckIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+
+        GTKStyle style = (GTKStyle)context.getStyle();
+        int size = style.getClassSpecificIntValue(context,"indicator-size",
+                GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
+
+        ENGINE.paintCheck(g, context, Region.CHECK_BOX_MENU_ITEM, "check",
+                x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
+                y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
+                size, size);
+    }
+
+    public void paintRadioButtonMenuItemCheckIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+
+        GTKStyle style = (GTKStyle)context.getStyle();
+        int size = style.getClassSpecificIntValue(context,"indicator-size",
+                GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
+
+        ENGINE.paintOption(g, context, Region.RADIO_BUTTON_MENU_ITEM, "option",
+                x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
+                y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
+                size, size);
+    }
+
+    public void paintToolBarHandleIcon(SynthContext context, Graphics g,
+            int state, int x, int y, int w, int h, Orientation orientation) {
+        int gtkState = GTKLookAndFeel.synthStateToGTKState(
+                context.getRegion(), state);
+
+        // The orientation parameter passed down by Synth refers to the
+        // orientation of the toolbar, but the one we pass to GTK refers
+        // to the orientation of the handle.  Therefore, we need to swap
+        // the value here: horizontal toolbars have vertical handles, and
+        // vice versa.
+        orientation = (orientation == Orientation.HORIZONTAL) ?
+            Orientation.VERTICAL : Orientation.HORIZONTAL;
+
+        ENGINE.paintHandle(g, context, Region.TOOL_BAR, gtkState,
+                ShadowType.OUT, "handlebox", x, y, w, h, orientation);
+    }
+
+    public void paintAscendingSortIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
+                ShadowType.IN, ArrowType.UP, "arrow", x, y, w, h);
+    }
+
+    public void paintDescendingSortIcon(SynthContext context,
+            Graphics g, int state, int x, int y, int w, int h) {
+        ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
+                ShadowType.IN, ArrowType.DOWN, "arrow", x, y, w, h);
+    }
+
+    /*
+     * Fill an area with color determined from this context's Style using the
+     * specified GTKColorType
+     */
+    private void fillArea(SynthContext context, Graphics g,
+                          int x, int y, int w, int h, ColorType colorType) {
+        if (context.getComponent().isOpaque()) {
+            Region id = context.getRegion();
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id,
+                    context.getComponentState());
+            GTKStyle style = (GTKStyle)context.getStyle();
+
+            g.setColor(style.getGTKColor(context, gtkState, colorType));
+            g.fillRect(x, y, w, h);
+        }
+    }
+
+    // Refer to GTKLookAndFeel for details on this.
+    static class ListTableFocusBorder extends AbstractBorder implements
+                          UIResource {
+
+        private boolean selectedCell;
+        private boolean focusedCell;
+
+        public static ListTableFocusBorder getSelectedCellBorder() {
+            return new ListTableFocusBorder(true, true);
+        }
+
+        public static ListTableFocusBorder getUnselectedCellBorder() {
+            return new ListTableFocusBorder(false, true);
+        }
+
+        public static ListTableFocusBorder getNoFocusCellBorder() {
+            return new ListTableFocusBorder(false, false);
+        }
+
+        public ListTableFocusBorder(boolean selectedCell, boolean focusedCell) {
+            this.selectedCell = selectedCell;
+            this.focusedCell = focusedCell;
+        }
+
+        private SynthContext getContext(Component c) {
+            SynthContext context = null;
+
+            ComponentUI ui = null;
+            if (c instanceof JLabel) {
+                ui = ((JLabel)c).getUI();
+            }
+
+            if (ui instanceof SynthUI) {
+                context = ((SynthUI)ui).getContext((JComponent)c);
+            }
+
+            return context;
+        }
+
+        public void paintBorder(Component c, Graphics g, int x, int y,
+                                int w, int h) {
+            if (focusedCell) {
+                SynthContext context = getContext(c);
+                int state = (selectedCell? SynthConstants.SELECTED:
+                             SynthConstants.FOCUSED | SynthConstants.ENABLED);
+
+                if (context != null) {
+                    GTKPainter.INSTANCE.paintFocus(context, g,
+                            Region.TABLE, state, "", x, y, w, h);
+                }
+            }
+        }
+
+        public Insets getBorderInsets(Component c) {
+            return getBorderInsets(c, null);
+        }
+
+        public Insets getBorderInsets(Component c, Insets i) {
+            SynthContext context = getContext(c);
+
+            if (context != null) {
+                i = context.getStyle().getInsets(context, i);
+            }
+
+            return i;
+        }
+
+        public boolean isBorderOpaque() {
+            return true;
+        }
+    }
+
+    // TitledBorder implementation for GTK L&F
+    static class TitledBorder extends AbstractBorder implements UIResource {
+
+        public void paintBorder(Component c, Graphics g, int x, int y,
+                                int w, int h) {
+            SynthContext context = getContext((JComponent)c);
+            Region id = context.getRegion();
+            int state = context.getComponentState();
+            int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
+
+            synchronized (UNIXToolkit.GTK_LOCK) {
+                if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
+                    ENGINE.startPainting(g, x, y, w, h, id);
+                    ENGINE.paintShadow(g, context, id, gtkState, ShadowType.ETCHED_IN,
+                                      "frame", x, y, w, h);
+                    ENGINE.finishPainting();
+                }
+            }
+        }
+
+        public Insets getBorderInsets(Component c, Insets i) {
+            SynthContext context = getContext((JComponent)c);
+            return context.getStyle().getInsets(context, i);
+        }
+
+        public boolean isBorderOpaque() {
+            return true;
+        }
+
+        private SynthStyle getStyle(JComponent c) {
+            return SynthLookAndFeel.getStyle(c, GTKEngine.CustomRegion.TITLED_BORDER);
+        }
+
+        private SynthContext getContext(JComponent c) {
+            int state = SynthConstants.DEFAULT;
+            return new SynthContext(c, GTKEngine.CustomRegion.TITLED_BORDER,
+                                    getStyle(c), state);
+        }
+    }
+}