1 /*
   2  * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.java.swing.plaf.gtk;
  26 
  27 import sun.awt.UNIXToolkit;
  28 
  29 import sun.awt.ModalExclude;
  30 import sun.awt.SunToolkit;
  31 import javax.swing.plaf.synth.*;
  32 import java.awt.*;
  33 import javax.swing.*;
  34 import javax.swing.border.*;
  35 import javax.swing.plaf.*;
  36 import com.sun.java.swing.plaf.gtk.GTKConstants.ArrowType;
  37 import com.sun.java.swing.plaf.gtk.GTKConstants.ExpanderStyle;
  38 import com.sun.java.swing.plaf.gtk.GTKConstants.Orientation;
  39 import com.sun.java.swing.plaf.gtk.GTKConstants.PositionType;
  40 import com.sun.java.swing.plaf.gtk.GTKConstants.ShadowType;
  41 import java.awt.image.BufferedImage;
  42 import java.lang.reflect.InvocationTargetException;
  43 import java.lang.reflect.Method;
  44 
  45 /**
  46  * @author Joshua Outwater
  47  * @author Scott Violet
  48  */
  49 // Need to support:
  50 // default_outside_border: Insets when default.
  51 // interior_focus: Indicates if focus should appear inside border, or
  52 //                       outside border.
  53 // focus-line-width: Integer giving size of focus border
  54 // focus-padding: Integer giving padding between border and focus
  55 //        indicator.
  56 // focus-line-pattern:
  57 //
  58 class GTKPainter extends SynthPainter {
  59     private static final PositionType[] POSITIONS = {
  60         PositionType.BOTTOM, PositionType.RIGHT,
  61         PositionType.TOP, PositionType.LEFT
  62     };
  63 
  64     private static final ShadowType SHADOWS[] = {
  65         ShadowType.NONE, ShadowType.IN, ShadowType.OUT,
  66         ShadowType.ETCHED_IN, ShadowType.OUT
  67     };
  68 
  69     private final static GTKEngine ENGINE = GTKEngine.INSTANCE;
  70     final static GTKPainter INSTANCE = new GTKPainter();
  71 
  72     private GTKPainter() {
  73     }
  74 
  75     private String getName(SynthContext context) {
  76         return (context.getRegion().isSubregion()) ? null :
  77                context.getComponent().getName();
  78     }
  79 
  80     public void paintCheckBoxBackground(SynthContext context,
  81             Graphics g, int x, int y, int w, int h) {
  82         paintRadioButtonBackground(context, g, x, y, w, h);
  83     }
  84 
  85     public void paintCheckBoxMenuItemBackground(SynthContext context,
  86             Graphics g, int x, int y, int w, int h) {
  87         paintRadioButtonMenuItemBackground(context, g, x, y, w, h);
  88     }
  89 
  90     // FORMATTED_TEXT_FIELD
  91     public void paintFormattedTextFieldBackground(SynthContext context,
  92                                           Graphics g, int x, int y,
  93                                           int w, int h) {
  94         paintTextBackground(context, g, x, y, w, h);
  95     }
  96 
  97     //
  98     // TOOL_BAR_DRAG_WINDOW
  99     //
 100     public void paintToolBarDragWindowBackground(SynthContext context,
 101                                      Graphics g, int x, int y,
 102                                      int w, int h) {
 103         paintToolBarBackground(context, g, x, y, w, h);
 104     }
 105 
 106 
 107     //
 108     // TOOL_BAR
 109     //
 110     public void paintToolBarBackground(SynthContext context,
 111                                      Graphics g, int x, int y,
 112                                      int w, int h) {
 113         Region id = context.getRegion();
 114         int state = context.getComponentState();
 115         int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 116         int orientation = ((JToolBar)context.getComponent()).getOrientation();
 117         synchronized (UNIXToolkit.GTK_LOCK) {
 118             if (! ENGINE.paintCachedImage(g, x, y, w, h, id,
 119                                           state, orientation))
 120             {
 121                 ENGINE.startPainting(g, x, y, w, h, id, state, orientation);
 122                 ENGINE.paintBox(g, context, id, gtkState, ShadowType.OUT,
 123                                 "handlebox_bin", x, y, w, h);
 124                 ENGINE.finishPainting();
 125             }
 126         }
 127     }
 128 
 129     public void paintToolBarContentBackground(SynthContext context,
 130                                               Graphics g,
 131                                               int x, int y, int w, int h) {
 132         Region id = context.getRegion();
 133         int orientation = ((JToolBar)context.getComponent()).getOrientation();
 134         synchronized (UNIXToolkit.GTK_LOCK) {
 135             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, orientation)) {
 136                 ENGINE.startPainting(g, x, y, w, h, id, orientation);
 137                 ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
 138                                 ShadowType.OUT, "toolbar", x, y, w, h);
 139                 ENGINE.finishPainting();
 140             }
 141         }
 142     }
 143 
 144     //
 145     // PASSWORD_FIELD
 146     //
 147     public void paintPasswordFieldBackground(SynthContext context,
 148                                      Graphics g, int x, int y,
 149                                      int w, int h) {
 150         paintTextBackground(context, g, x, y, w, h);
 151     }
 152 
 153     //
 154     // TEXT_FIELD
 155     //
 156     public void paintTextFieldBackground(SynthContext context, Graphics g,
 157                                          int x, int y, int w, int h) {
 158         if (getName(context) == "Tree.cellEditor") {
 159             paintTreeCellEditorBackground(context, g, x, y, w, h);
 160         } else {
 161             paintTextBackground(context, g, x, y, w, h);
 162         }
 163     }
 164 
 165     //
 166     // RADIO_BUTTON
 167     //
 168     // NOTE: this is called for JCheckBox too
 169     public void paintRadioButtonBackground(SynthContext context,
 170                                      Graphics g, int x, int y,
 171                                      int w, int h) {
 172         Region id = context.getRegion();
 173         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 174                 id, context.getComponentState());
 175         if (gtkState == SynthConstants.MOUSE_OVER) {
 176             synchronized (UNIXToolkit.GTK_LOCK) {
 177                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 178                     ENGINE.startPainting(g, x, y, w, h, id);
 179                     ENGINE.paintFlatBox(g, context, id,
 180                             SynthConstants.MOUSE_OVER, ShadowType.ETCHED_OUT,
 181                             "checkbutton", x, y, w, h, ColorType.BACKGROUND);
 182                     ENGINE.finishPainting();
 183                 }
 184             }
 185         }
 186     }
 187 
 188     //
 189     // RADIO_BUTTON_MENU_ITEM
 190     //
 191     // NOTE: this is called for JCheckBoxMenuItem too
 192     public void paintRadioButtonMenuItemBackground(SynthContext context,
 193                                      Graphics g, int x, int y,
 194                                      int w, int h) {
 195         Region id = context.getRegion();
 196         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 197                 id, context.getComponentState());
 198         if (gtkState == SynthConstants.MOUSE_OVER) {
 199             synchronized (UNIXToolkit.GTK_LOCK) {
 200                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 201                     ShadowType shadow = (GTKLookAndFeel.is2_2() ?
 202                         ShadowType.NONE : ShadowType.OUT);
 203                     ENGINE.startPainting(g, x, y, w, h, id);
 204                     ENGINE.paintBox(g, context, id, gtkState,
 205                             shadow, "menuitem", x, y, w, h);
 206                     ENGINE.finishPainting();
 207                 }
 208             }
 209         }
 210     }
 211 
 212     //
 213     // LABEL
 214     //
 215     public void paintLabelBackground(SynthContext context,
 216                                      Graphics g, int x, int y,
 217                                      int w, int h) {
 218         String name = getName(context);
 219         JComponent c = context.getComponent();
 220         Container  container = c.getParent();
 221 
 222         if (name == "TableHeader.renderer" ||
 223             name == "GTKFileChooser.directoryListLabel" ||
 224             name == "GTKFileChooser.fileListLabel") {
 225 
 226             paintButtonBackgroundImpl(context, g, Region.BUTTON, "button",
 227                     x, y, w, h, true, false, false, false);
 228         }
 229         /*
 230          * If the label is a ListCellRenderer and it's in a container
 231          * (CellRendererPane) which is in a JComboBox then we paint the label
 232          * as a TextField like a gtk_entry for a combobox.
 233          */
 234         else if (c instanceof ListCellRenderer &&
 235                  container != null &&
 236                  container.getParent() instanceof JComboBox ) {
 237             paintTextBackground(context, g, x, y, w, h);
 238         }
 239     }
 240 
 241     //
 242     // INTERNAL_FRAME
 243     //
 244     public void paintInternalFrameBorder(SynthContext context,
 245                                       Graphics g, int x, int y,
 246                                       int w, int h) {
 247         Metacity.INSTANCE.paintFrameBorder(context, g, x, y, w, h);
 248     }
 249 
 250     //
 251     // DESKTOP_PANE
 252     //
 253     public void paintDesktopPaneBackground(SynthContext context,
 254                                            Graphics g, int x, int y,
 255                                            int w, int h) {
 256         // Does not call into ENGINE for better performance
 257         fillArea(context, g, x, y, w, h, ColorType.BACKGROUND);
 258     }
 259 
 260     //
 261     // DESKTOP_ICON
 262     //
 263     public void paintDesktopIconBorder(SynthContext context,
 264                                            Graphics g, int x, int y,
 265                                            int w, int h) {
 266         Metacity.INSTANCE.paintFrameBorder(context, g, x, y, w, h);
 267     }
 268 
 269     public void paintButtonBackground(SynthContext context, Graphics g,
 270                                       int x, int y, int w, int h) {
 271         String name = getName(context);
 272         if (name != null && name.startsWith("InternalFrameTitlePane.")) {
 273             Metacity.INSTANCE.paintButtonBackground(context, g, x, y, w, h);
 274 
 275         } else {
 276             AbstractButton button = (AbstractButton)context.getComponent();
 277             boolean paintBG = button.isContentAreaFilled() &&
 278                               button.isBorderPainted();
 279             boolean paintFocus = button.isFocusPainted();
 280             boolean defaultCapable = (button instanceof JButton) &&
 281                     ((JButton)button).isDefaultCapable();
 282             boolean toolButton = (button.getParent() instanceof JToolBar);
 283             paintButtonBackgroundImpl(context, g, Region.BUTTON, "button",
 284                     x, y, w, h, paintBG, paintFocus, defaultCapable, toolButton);
 285         }
 286     }
 287 
 288     private void paintButtonBackgroundImpl(SynthContext context, Graphics g,
 289             Region id, String detail, int x, int y, int w, int h,
 290             boolean paintBackground, boolean paintFocus,
 291             boolean defaultCapable, boolean toolButton) {
 292         int state = context.getComponentState();
 293         synchronized (UNIXToolkit.GTK_LOCK) {
 294             if (ENGINE.paintCachedImage(g, x, y, w, h, id, state, detail,
 295                     paintBackground, paintFocus, defaultCapable, toolButton)) {
 296                 return;
 297             }
 298             ENGINE.startPainting(g, x, y, w, h, id, state, detail,
 299                 paintBackground, paintFocus, defaultCapable, toolButton);
 300 
 301             // Paint the default indicator
 302             GTKStyle style = (GTKStyle)context.getStyle();
 303             if (defaultCapable && !toolButton) {
 304                 Insets defaultInsets = style.getClassSpecificInsetsValue(
 305                         context, "default-border",
 306                         GTKStyle.BUTTON_DEFAULT_BORDER_INSETS);
 307 
 308                 if (paintBackground && (state & SynthConstants.DEFAULT) != 0) {
 309                     ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
 310                             ShadowType.IN, "buttondefault", x, y, w, h);
 311                 }
 312                 x += defaultInsets.left;
 313                 y += defaultInsets.top;
 314                 w -= (defaultInsets.left + defaultInsets.right);
 315                 h -= (defaultInsets.top + defaultInsets.bottom);
 316             }
 317 
 318             boolean interiorFocus = style.getClassSpecificBoolValue(
 319                     context, "interior-focus", true);
 320             int focusSize = style.getClassSpecificIntValue(
 321                     context, "focus-line-width",1);
 322             int focusPad = style.getClassSpecificIntValue(
 323                     context, "focus-padding", 1);
 324 
 325             int totalFocusSize = focusSize + focusPad;
 326             int xThickness = style.getXThickness();
 327             int yThickness = style.getYThickness();
 328 
 329             // Render the box.
 330             if (!interiorFocus &&
 331                     (state & SynthConstants.FOCUSED) == SynthConstants.FOCUSED) {
 332                 x += totalFocusSize;
 333                 y += totalFocusSize;
 334                 w -= 2 * totalFocusSize;
 335                 h -= 2 * totalFocusSize;
 336             }
 337 
 338             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 339             boolean paintBg;
 340             if (toolButton) {
 341                 // Toolbar buttons should only have their background painted
 342                 // in the PRESSED, SELECTED, or MOUSE_OVER states.
 343                 paintBg =
 344                     (gtkState != SynthConstants.ENABLED) &&
 345                     (gtkState != SynthConstants.DISABLED);
 346             } else {
 347                 // Otherwise, always paint the button's background, unless
 348                 // the user has overridden it and we're in the ENABLED state.
 349                 paintBg =
 350                     paintBackground ||
 351                     (gtkState != SynthConstants.ENABLED);
 352             }
 353             if (paintBg) {
 354                 ShadowType shadowType = ShadowType.OUT;
 355                 if ((state & (SynthConstants.PRESSED |
 356                               SynthConstants.SELECTED)) != 0) {
 357                     shadowType = ShadowType.IN;
 358                 }
 359                 ENGINE.paintBox(g, context, id, gtkState,
 360                         shadowType, detail, x, y, w, h);
 361             }
 362 
 363             // focus
 364             if (paintFocus && (state & SynthConstants.FOCUSED) != 0) {
 365                 if (interiorFocus) {
 366                     x += xThickness + focusPad;
 367                     y += yThickness + focusPad;
 368                     w -= 2 * (xThickness + focusPad);
 369                     h -= 2 * (yThickness + focusPad);
 370                 } else {
 371                     x -= totalFocusSize;
 372                     y -= totalFocusSize;
 373                     w += 2 * totalFocusSize;
 374                     h += 2 * totalFocusSize;
 375                 }
 376                 ENGINE.paintFocus(g, context, id, gtkState, detail, x, y, w, h);
 377             }
 378             ENGINE.finishPainting();
 379         }
 380     }
 381 
 382     //
 383     // ARROW_BUTTON
 384     //
 385     public void paintArrowButtonForeground(SynthContext context, Graphics g,
 386                                            int x, int y, int w, int h,
 387                                            int direction) {
 388         Region id = context.getRegion();
 389         Component c = context.getComponent();
 390         String name = c.getName();
 391 
 392         ArrowType arrowType = null;
 393         switch (direction) {
 394             case SwingConstants.NORTH:
 395                 arrowType = ArrowType.UP; break;
 396             case SwingConstants.SOUTH:
 397                 arrowType = ArrowType.DOWN; break;
 398             case SwingConstants.EAST:
 399                 arrowType = ArrowType.RIGHT; break;
 400             case SwingConstants.WEST:
 401                 arrowType = ArrowType.LEFT; break;
 402         }
 403 
 404         String detail = "arrow";
 405         if ((name == "ScrollBar.button") || (name == "TabbedPane.button")) {
 406             if (arrowType == ArrowType.UP || arrowType == ArrowType.DOWN) {
 407                 detail = "vscrollbar";
 408             } else {
 409                 detail = "hscrollbar";
 410             }
 411         } else if (name == "Spinner.nextButton" ||
 412                    name == "Spinner.previousButton") {
 413             detail = "spinbutton";
 414         } else if (name != "ComboBox.arrowButton") {
 415             assert false : "unexpected name: " + name;
 416         }
 417 
 418         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 419                 id, context.getComponentState());
 420         ShadowType shadowType = (gtkState == SynthConstants.PRESSED ?
 421             ShadowType.IN : ShadowType.OUT);
 422         synchronized (UNIXToolkit.GTK_LOCK) {
 423             if (ENGINE.paintCachedImage(g, x, y, w, h,
 424                     gtkState, name, direction)) {
 425                 return;
 426             }
 427             ENGINE.startPainting(g, x, y, w, h, gtkState, name, direction);
 428             ENGINE.paintArrow(g, context, id, gtkState,
 429                     shadowType, arrowType, detail, x, y, w, h);
 430             ENGINE.finishPainting();
 431         }
 432     }
 433 
 434     public void paintArrowButtonBackground(SynthContext context,
 435             Graphics g, int x, int y, int w, int h) {
 436         Region id = context.getRegion();
 437         AbstractButton button = (AbstractButton)context.getComponent();
 438 
 439         String name = button.getName();
 440         String detail = "button";
 441         int direction = SwingConstants.CENTER;
 442         if ((name == "ScrollBar.button") || (name == "TabbedPane.button")) {
 443             Integer prop = (Integer)
 444                 button.getClientProperty("__arrow_direction__");
 445             direction = (prop != null) ?
 446                 prop.intValue() : SwingConstants.WEST;
 447             switch (direction) {
 448             default:
 449             case SwingConstants.EAST:
 450             case SwingConstants.WEST:
 451                 detail = "hscrollbar";
 452                 break;
 453             case SwingConstants.NORTH:
 454             case SwingConstants.SOUTH:
 455                 detail = "vscrollbar";
 456                 break;
 457             }
 458         } else if (name == "Spinner.previousButton") {
 459             detail = "spinbutton_down";
 460         } else if (name == "Spinner.nextButton") {
 461             detail = "spinbutton_up";
 462         } else if (name != "ComboBox.arrowButton") {
 463             assert false : "unexpected name: " + name;
 464         }
 465 
 466         int state = context.getComponentState();
 467         synchronized (UNIXToolkit.GTK_LOCK) {
 468             if (ENGINE.paintCachedImage(g, x, y, w, h, id,
 469                                         state, detail, direction))
 470             {
 471                 return;
 472             }
 473             ENGINE.startPainting(g, x, y, w, h, id,
 474                                  state, detail, direction);
 475 
 476             if (detail.startsWith("spin")) {
 477                 /*
 478                  * The ubuntulooks engine (and presumably others) expect us to
 479                  * first draw the full "spinbutton" background, and then draw
 480                  * the individual "spinbutton_up/down" buttons on top of that.
 481                  * Note that it is the state of the JSpinner (not its arrow
 482                  * button) that determines how we draw this background.
 483                  */
 484                 int spinState = button.getParent().isEnabled() ?
 485                     SynthConstants.ENABLED : SynthConstants.DISABLED;
 486                 int mody = (detail == "spinbutton_up") ? y : y-h;
 487                 int modh = h*2;
 488                 ENGINE.paintBox(g, context, id, spinState,
 489                                 ShadowType.IN, "spinbutton",
 490                                 x, mody, w, modh);
 491             }
 492 
 493             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 494             ShadowType shadowType = ShadowType.OUT;
 495             if ((gtkState & (SynthConstants.PRESSED |
 496                              SynthConstants.SELECTED)) != 0)
 497             {
 498                 shadowType = ShadowType.IN;
 499             }
 500             ENGINE.paintBox(g, context, id, gtkState,
 501                             shadowType, detail,
 502                             x, y, w, h);
 503 
 504             ENGINE.finishPainting();
 505         }
 506     }
 507 
 508 
 509     //
 510     // LIST
 511     //
 512     public void paintListBackground(SynthContext context, Graphics g,
 513                                     int x, int y, int w, int h) {
 514         // Does not call into ENGINE for better performance
 515         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
 516     }
 517 
 518     public void paintMenuBarBackground(SynthContext context, Graphics g,
 519                                        int x, int y, int w, int h) {
 520         Region id = context.getRegion();
 521         synchronized (UNIXToolkit.GTK_LOCK) {
 522             if (ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 523                 return;
 524             }
 525             GTKStyle style = (GTKStyle)context.getStyle();
 526             int shadow = style.getClassSpecificIntValue(
 527                     context, "shadow-type", 2);
 528             ShadowType shadowType = SHADOWS[shadow];
 529             int gtkState = GTKLookAndFeel.synthStateToGTKState(
 530                     id, context.getComponentState());
 531             ENGINE.startPainting(g, x, y, w, h, id);
 532             ENGINE.paintBox(g, context, id, gtkState,
 533                 shadowType, "menubar", x, y, w, h);
 534             ENGINE.finishPainting();
 535         }
 536     }
 537 
 538     //
 539     // MENU
 540     //
 541     public void paintMenuBackground(SynthContext context,
 542                                      Graphics g,
 543                                      int x, int y, int w, int h) {
 544         paintMenuItemBackground(context, g, x, y, w, h);
 545     }
 546 
 547     // This is called for both MENU and MENU_ITEM
 548     public void paintMenuItemBackground(SynthContext context,
 549                                      Graphics g,
 550                                      int x, int y, int w, int h) {
 551         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 552                 context.getRegion(), context.getComponentState());
 553         if (gtkState == SynthConstants.MOUSE_OVER) {
 554             Region id = Region.MENU_ITEM;
 555             synchronized (UNIXToolkit.GTK_LOCK) {
 556                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 557                     ShadowType shadow = (GTKLookAndFeel.is2_2() ?
 558                         ShadowType.NONE : ShadowType.OUT);
 559                     ENGINE.startPainting(g, x, y, w, h, id);
 560                     ENGINE.paintBox(g, context, id, gtkState, shadow,
 561                             "menuitem", x, y, w, h);
 562                     ENGINE.finishPainting();
 563                 }
 564             }
 565         }
 566     }
 567 
 568     public void paintPopupMenuBackground(SynthContext context, Graphics g,
 569                                         int x, int y, int w, int h) {
 570         Region id = context.getRegion();
 571         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 572                 id, context.getComponentState());
 573         boolean isHW = SunToolkit.getHeavyweightComponent(
 574                 context.getComponent()) instanceof ModalExclude;
 575         synchronized (UNIXToolkit.GTK_LOCK) {
 576             if (ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, isHW)) {
 577                 return;
 578             }
 579             ENGINE.startPainting(g, x, y, w, h, id, gtkState);
 580             ENGINE.paintBox(g, context, id, gtkState,
 581                     ShadowType.OUT, "menu", x, y, w, h);
 582 
 583             GTKStyle style = (GTKStyle)context.getStyle();
 584             int xThickness = style.getXThickness();
 585             int yThickness = style.getYThickness();
 586             ENGINE.paintBackground(g, context, id, gtkState,
 587                     style.getGTKColor(context, gtkState, GTKColorType.BACKGROUND),
 588                     x + xThickness, y + yThickness,
 589                     w - xThickness - xThickness, h - yThickness - yThickness);
 590             BufferedImage img = ENGINE.finishPainting();
 591             if(!isHW) {
 592                 int border = img.getRGB(0, h / 2);
 593                 if (img != null && border == img.getRGB(w / 2, h / 2)) {
 594                     // fix no menu borders in Adwaita theme
 595                     Graphics g2 = img.getGraphics();
 596                     Color c = new Color(border);
 597                     g2.setColor(new Color(Math.max((int) (c.getRed() * 0.8), 0),
 598                             Math.max((int) (c.getGreen() * 0.8), 0),
 599                             Math.max((int) (c.getBlue() * 0.8), 0)));
 600                     g2.drawLine(0, 0, w - 1, 0);
 601                     g2.drawLine(w - 1, 0, w - 1, h - 1);
 602                     g2.drawLine(0, h - 1, 0, 1);
 603                     g2.setColor(c.darker());
 604                     g2.drawLine(w - 1, h - 1, 0, h - 1);
 605                     g2.dispose();
 606                     g.drawImage(img, x, y, null);
 607                 }
 608             }
 609         }
 610     }
 611 
 612     public void paintProgressBarBackground(SynthContext context,
 613                                             Graphics g,
 614                                             int x, int y, int w, int h) {
 615         Region id = context.getRegion();
 616         synchronized (UNIXToolkit.GTK_LOCK) {
 617             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 618                 ENGINE.startPainting(g, x, y, w, h, id);
 619                 ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
 620                         ShadowType.IN, "trough", x, y, w, h);
 621                 ENGINE.finishPainting();
 622             }
 623         }
 624     }
 625 
 626     public void paintProgressBarForeground(SynthContext context, Graphics g,
 627                                             int x, int y, int w, int h,
 628                                             int orientation) {
 629         Region id = context.getRegion();
 630         synchronized (UNIXToolkit.GTK_LOCK) {
 631             // Note that we don't call paintCachedImage() here.  Since the
 632             // progress bar foreground is painted differently for each value
 633             // it would be wasteful to try to cache an image for each state,
 634             // so instead we simply avoid caching in this case.
 635             if (w <= 0 || h <= 0) {
 636                 return;
 637             }
 638             ENGINE.startPainting(g, x, y, w, h, id, "fg");
 639             ENGINE.paintBox(g, context, id, SynthConstants.MOUSE_OVER,
 640                             ShadowType.OUT, "bar", x, y, w, h);
 641             ENGINE.finishPainting(false); // don't bother caching the image
 642         }
 643     }
 644 
 645     public void paintViewportBorder(SynthContext context, Graphics g,
 646                                            int x, int y, int w, int h) {
 647         Region id = context.getRegion();
 648         synchronized (UNIXToolkit.GTK_LOCK) {
 649             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 650                 ENGINE.startPainting(g, x, y, w, h, id);
 651                 ENGINE.paintShadow(g, context, id, SynthConstants.ENABLED,
 652                         ShadowType.IN, "scrolled_window", x, y, w, h);
 653                 ENGINE.finishPainting();
 654             }
 655         }
 656     }
 657 
 658     public void paintSeparatorBackground(SynthContext context,
 659                                           Graphics g,
 660                                           int x, int y, int w, int h,
 661                                          int orientation) {
 662         Region id = context.getRegion();
 663         int state = context.getComponentState();
 664         JComponent c = context.getComponent();
 665 
 666         /*
 667          * Note: In theory, the style's x/y thickness values would determine
 668          * the width of the separator content.  In practice, however, some
 669          * engines will render a line that is wider than the corresponding
 670          * thickness value.  For example, ubuntulooks reports x/y thickness
 671          * values of 1 for separators, but always renders a 2-pixel wide line.
 672          * As a result of all this, we need to be careful not to restrict
 673          * the w/h values below too much, so that the full thickness of the
 674          * rendered line will be captured by our image caching code.
 675          */
 676         String detail;
 677         if (c instanceof JToolBar.Separator) {
 678             /*
 679              * GTK renders toolbar separators differently in that an
 680              * artificial padding is added to each end of the separator.
 681              * The value of 0.2f below is derived from the source code of
 682              * gtktoolbar.c in the current version of GTK+ (2.8.20 at the
 683              * time of this writing).  Specifically, the relevant values are:
 684              *     SPACE_LINE_DIVISION 10.0
 685              *     SPACE_LINE_START     2.0
 686              *     SPACE_LINE_END       8.0
 687              * These are used to determine the distance from the top (or left)
 688              * edge of the toolbar to the other edge.  So for example, the
 689              * starting/top point of a vertical separator is 2/10 of the
 690              * height of a horizontal toolbar away from the top edge, which
 691              * is how we arrive at 0.2f below.  Likewise, the ending/bottom
 692              * point is 8/10 of the height away from the top edge, or in other
 693              * words, it is 2/10 away from the bottom edge, which is again
 694              * how we arrive at the 0.2f value below.
 695              *
 696              * The separator is also centered horizontally or vertically,
 697              * depending on its orientation.  This was determined empirically
 698              * and by examining the code referenced above.
 699              */
 700             detail = "toolbar";
 701             float pct = 0.2f;
 702             JToolBar.Separator sep = (JToolBar.Separator)c;
 703             Dimension size = sep.getSeparatorSize();
 704             GTKStyle style = (GTKStyle)context.getStyle();
 705             if (orientation == JSeparator.HORIZONTAL) {
 706                 x += (int)(w * pct);
 707                 w -= (int)(w * pct * 2);
 708                 y += (size.height - style.getYThickness()) / 2;
 709             } else {
 710                 y += (int)(h * pct);
 711                 h -= (int)(h * pct * 2);
 712                 x += (size.width - style.getXThickness()) / 2;
 713             }
 714         } else {
 715             // For regular/menu separators, we simply subtract out the insets.
 716             detail = "separator";
 717             Insets insets = c.getInsets();
 718             x += insets.left;
 719             y += insets.top;
 720             if (orientation == JSeparator.HORIZONTAL) {
 721                 w -= (insets.left + insets.right);
 722             } else {
 723                 h -= (insets.top + insets.bottom);
 724             }
 725             if (GTKLookAndFeel.is3()) {
 726                 if (id == Region.POPUP_MENU_SEPARATOR) {
 727                     detail = "menuitem";
 728                     h -= (insets.top + insets.bottom);
 729                 } else {
 730                     detail = "separator";
 731                 }
 732             } else {
 733                 detail = orientation == JSeparator.HORIZONTAL ?
 734                                                     "hseparator" : "vseparator";
 735             }
 736         }
 737 
 738         synchronized (UNIXToolkit.GTK_LOCK) {
 739             if (! ENGINE.paintCachedImage(g, x, y, w, h, id,
 740                                           state, detail, orientation)) {
 741                 ENGINE.startPainting(g, x, y, w, h, id,
 742                                      state, detail, orientation);
 743                 if (orientation == JSeparator.HORIZONTAL) {
 744                     ENGINE.paintHline(g, context, id, state,
 745                                       detail, x, y, w, h);
 746                 } else {
 747                     ENGINE.paintVline(g, context, id, state,
 748                                       detail, x, y, w, h);
 749                 }
 750                 ENGINE.finishPainting();
 751             }
 752         }
 753     }
 754 
 755     public void paintSliderTrackBackground(SynthContext context,
 756                                        Graphics g,
 757                                        int x, int y, int w,int h) {
 758         Region id = context.getRegion();
 759         int state = context.getComponentState();
 760 
 761         // For focused sliders, we paint focus rect outside the bounds passed.
 762         // Need to adjust for that.
 763         boolean focused = ((state & SynthConstants.FOCUSED) != 0);
 764         int focusSize = 0;
 765         if (focused) {
 766             GTKStyle style = (GTKStyle)context.getStyle();
 767             focusSize = style.getClassSpecificIntValue(
 768                                 context, "focus-line-width", 1) +
 769                         style.getClassSpecificIntValue(
 770                                 context, "focus-padding", 1);
 771             x -= focusSize;
 772             y -= focusSize;
 773             w += focusSize * 2;
 774             h += focusSize * 2;
 775         }
 776 
 777         // The ubuntulooks engine paints slider troughs differently depending
 778         // on the current slider value and its component orientation.
 779         JSlider slider = (JSlider)context.getComponent();
 780         if (GTKLookAndFeel.is3()) {
 781             if (slider.getOrientation() == JSlider.VERTICAL) {
 782                 y += 1;
 783                 h -= 2;
 784             } else {
 785                 x += 1;
 786                 w -= 2;
 787             }
 788         }
 789         double value = slider.getValue();
 790         double min = slider.getMinimum();
 791         double max = slider.getMaximum();
 792         double visible = 20; // not used for sliders; any value will work
 793 
 794         synchronized (UNIXToolkit.GTK_LOCK) {
 795             // Note that we don't call paintCachedImage() here.  Since some
 796             // engines (e.g. ubuntulooks) paint the slider background
 797             // differently for any given slider value, it would be wasteful
 798             // to try to cache an image for each state, so instead we simply
 799             // avoid caching in this case.
 800             if (w <= 0 || h <= 0) {
 801                 return;
 802             }
 803             ENGINE.startPainting(g, x, y, w, h, id, state, value);
 804             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 805             ENGINE.setRangeValue(context, id, value, min, max, visible);
 806             ENGINE.paintBox(g, context, id, gtkState, ShadowType.IN,
 807                             "trough", x + focusSize, y + focusSize,
 808                             w - 2 * focusSize, h - 2 * focusSize);
 809             if (focused) {
 810                 ENGINE.paintFocus(g, context, id, SynthConstants.ENABLED,
 811                                   "trough", x, y, w, h);
 812             }
 813             ENGINE.finishPainting(false); // don't bother caching the image
 814         }
 815     }
 816 
 817     public void paintSliderThumbBackground(SynthContext context,
 818             Graphics g, int x, int y, int w, int h, int dir) {
 819         Region id = context.getRegion();
 820         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 821                 id, context.getComponentState());
 822         boolean hasFocus = GTKLookAndFeel.is3() &&
 823                 ((context.getComponentState() & SynthConstants.FOCUSED) != 0);
 824         synchronized (UNIXToolkit.GTK_LOCK) {
 825             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, dir, hasFocus)) {
 826                 Orientation orientation = (dir == JSlider.HORIZONTAL ?
 827                     Orientation.HORIZONTAL : Orientation.VERTICAL);
 828                 String detail = (dir == JSlider.HORIZONTAL ?
 829                     "hscale" : "vscale");
 830                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, dir);
 831                 ENGINE.paintSlider(g, context, id, gtkState,
 832                         ShadowType.OUT, detail, x, y, w, h, orientation, hasFocus);
 833                 ENGINE.finishPainting();
 834             }
 835         }
 836     }
 837 
 838     //
 839     // SPINNER
 840     //
 841     public void paintSpinnerBackground(SynthContext context,
 842                                         Graphics g,
 843                                         int x, int y, int w, int h) {
 844         // This is handled in paintTextFieldBackground
 845     }
 846 
 847     //
 848     // SPLIT_PANE_DIVIDER
 849     //
 850     public void paintSplitPaneDividerBackground(SynthContext context,
 851                                        Graphics g,
 852                                        int x, int y, int w, int h) {
 853         Region id = context.getRegion();
 854         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 855                 id, context.getComponentState());
 856         JSplitPane splitPane = (JSplitPane)context.getComponent();
 857         Orientation orientation =
 858                 (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ?
 859                     Orientation.VERTICAL : Orientation.HORIZONTAL);
 860         synchronized (UNIXToolkit.GTK_LOCK) {
 861             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 862                     id, gtkState, orientation)) {
 863                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, orientation);
 864                 ENGINE.paintHandle(g, context, id, gtkState,
 865                         ShadowType.OUT, "paned", x, y, w, h, orientation);
 866                 ENGINE.finishPainting();
 867             }
 868         }
 869     }
 870 
 871     public void paintSplitPaneDragDivider(SynthContext context,
 872                                        Graphics g,int x, int y, int w, int h,
 873                                        int orientation) {
 874         paintSplitPaneDividerForeground(context, g, x, y, w, h, orientation);
 875     }
 876 
 877     public void paintTabbedPaneContentBackground(SynthContext context,
 878                                       Graphics g, int x, int y, int w, int h) {
 879         JTabbedPane pane = (JTabbedPane)context.getComponent();
 880         int selectedIndex = pane.getSelectedIndex();
 881         PositionType placement = GTKLookAndFeel.SwingOrientationConstantToGTK(
 882                                                         pane.getTabPlacement());
 883 
 884         int gapStart = 0;
 885         int gapSize = 0;
 886         if (selectedIndex != -1) {
 887             Rectangle tabBounds = pane.getBoundsAt(selectedIndex);
 888 
 889             if (placement == PositionType.TOP ||
 890                 placement == PositionType.BOTTOM) {
 891 
 892                 gapStart = tabBounds.x - x;
 893                 gapSize = tabBounds.width;
 894             }
 895             else {
 896                 gapStart = tabBounds.y - y;
 897                 gapSize = tabBounds.height;
 898             }
 899         }
 900 
 901         Region id = context.getRegion();
 902         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 903                 id, context.getComponentState());
 904         synchronized (UNIXToolkit.GTK_LOCK) {
 905             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 906                     id, gtkState, placement, gapStart, gapSize)) {
 907                 ENGINE.startPainting(g, x, y, w, h,
 908                         id, gtkState, placement, gapStart, gapSize);
 909                 ENGINE.paintBoxGap(g, context, id, gtkState, ShadowType.OUT,
 910                         "notebook", x, y, w, h, placement, gapStart, gapSize);
 911                 ENGINE.finishPainting();
 912             }
 913         }
 914     }
 915 
 916     public void paintTabbedPaneTabBackground(SynthContext context,
 917                                            Graphics g,
 918                                            int x, int y, int w, int h,
 919                                            int tabIndex) {
 920         Region id = context.getRegion();
 921         int state = context.getComponentState();
 922         int gtkState = ((state & SynthConstants.SELECTED) != 0 ?
 923             SynthConstants.ENABLED : SynthConstants.PRESSED);
 924         JTabbedPane pane = (JTabbedPane)context.getComponent();
 925         int placement = pane.getTabPlacement();
 926 
 927         synchronized (UNIXToolkit.GTK_LOCK) {
 928             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 929                     id, gtkState, placement, tabIndex)) {
 930                 PositionType side = POSITIONS[placement - 1];
 931                 ENGINE.startPainting(g, x, y, w, h,
 932                         id, gtkState, placement, tabIndex);
 933                 ENGINE.paintExtension(g, context, id, gtkState,
 934                         ShadowType.OUT, "tab", x, y, w, h, side, tabIndex);
 935                 ENGINE.finishPainting();
 936             }
 937         }
 938     }
 939 
 940     //
 941     // TEXT_PANE
 942     //
 943     public void paintTextPaneBackground(SynthContext context, Graphics g,
 944                                         int x, int y, int w, int h) {
 945         paintTextAreaBackground(context, g, x, y, w, h);
 946     }
 947 
 948     //
 949     // EDITOR_PANE
 950     //
 951     public void paintEditorPaneBackground(SynthContext context, Graphics g,
 952                                           int x, int y, int w, int h) {
 953         paintTextAreaBackground(context, g, x, y, w, h);
 954     }
 955 
 956     //
 957     // TEXT_AREA
 958     //
 959     public void paintTextAreaBackground(SynthContext context, Graphics g,
 960                                         int x, int y, int w, int h) {
 961         // Does not call into ENGINE for better performance
 962         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
 963     }
 964 
 965     //
 966     // TEXT_FIELD
 967     //
 968     // NOTE: Combobox and Label, Password and FormattedTextField calls this
 969     // too.
 970     private void paintTextBackground(SynthContext context, Graphics g,
 971                                      int x, int y, int w, int h) {
 972         // Text is odd in that it uses the TEXT_BACKGROUND vs BACKGROUND.
 973         JComponent c = context.getComponent();
 974         Container container = c.getParent();
 975         Container containerParent = null;
 976         GTKStyle style = (GTKStyle)context.getStyle();
 977         Region id = context.getRegion();
 978         int state = context.getComponentState();
 979 
 980         if (c instanceof ListCellRenderer && container != null) {
 981             containerParent = container.getParent();
 982             if (containerParent instanceof JComboBox
 983                     && containerParent.hasFocus()) {
 984                 state |= SynthConstants.FOCUSED;
 985             }
 986         }
 987 
 988         synchronized (UNIXToolkit.GTK_LOCK) {
 989             if (ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
 990                 return;
 991             }
 992 
 993             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 994             int focusSize = 0;
 995             boolean interiorFocus = style.getClassSpecificBoolValue(
 996                     context, "interior-focus", true);
 997 
 998             focusSize = style.getClassSpecificIntValue(context,
 999                     "focus-line-width",1);
1000             if (!interiorFocus && (state & SynthConstants.FOCUSED) != 0) {
1001                 x += focusSize;
1002                 y += focusSize;
1003                 w -= 2 * focusSize;
1004                 h -= 2 * focusSize;
1005             }
1006 
1007             int xThickness = style.getXThickness();
1008             int yThickness = style.getYThickness();
1009 
1010             ENGINE.startPainting(g, x, y, w, h, id, state);
1011             if (GTKLookAndFeel.is3()) {
1012                 ENGINE.paintBackground(g, context, id, gtkState, null,
1013                                                                     x, y, w, h);
1014             }
1015             ENGINE.paintShadow(g, context, id, gtkState,
1016                                ShadowType.IN, "entry", x, y, w, h);
1017             if (!GTKLookAndFeel.is3()) {
1018                 ENGINE.paintFlatBox(g, context, id,
1019                         gtkState, ShadowType.NONE, "entry_bg",
1020                         x + xThickness,
1021                         y + yThickness,
1022                         w - (2 * xThickness),
1023                         h - (2 * yThickness),
1024                         ColorType.TEXT_BACKGROUND);
1025             }
1026 
1027             if (focusSize > 0 && (state & SynthConstants.FOCUSED) != 0) {
1028                 if (!interiorFocus) {
1029                     x -=  focusSize;
1030                     y -=  focusSize;
1031                     w +=  2 * focusSize;
1032                     h +=  2 * focusSize;
1033                 } else {
1034                     if (containerParent instanceof JComboBox) {
1035                         x += (focusSize + 2);
1036                         y += focusSize + (GTKLookAndFeel.is3() ? 3 : 1);
1037                         w -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 1);
1038                         h -= 2 * focusSize + (GTKLookAndFeel.is3() ? 6 : 2);
1039                     } else {
1040                         x += focusSize + (GTKLookAndFeel.is3() ? 2 : 0);
1041                         y += focusSize + (GTKLookAndFeel.is3() ? 2 :0 );
1042                         w -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 0);
1043                         h -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 0);
1044                     }
1045                 }
1046                 ENGINE.paintFocus(g, context, id, gtkState,
1047                         "entry", x, y, w, h);
1048             }
1049             ENGINE.finishPainting();
1050         }
1051     }
1052 
1053     private void paintTreeCellEditorBackground(SynthContext context, Graphics g,
1054                                                int x, int y, int w, int h) {
1055         Region id = context.getRegion();
1056         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1057                 id, context.getComponentState());
1058         synchronized (UNIXToolkit.GTK_LOCK) {
1059             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState)) {
1060                 ENGINE.startPainting(g, x, y, w, h, id, gtkState);
1061                 ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
1062                         "entry_bg", x, y, w, h, ColorType.TEXT_BACKGROUND);
1063                 ENGINE.finishPainting();
1064             }
1065         }
1066     }
1067 
1068 
1069     //
1070     // ROOT_PANE
1071     //
1072     public void paintRootPaneBackground(SynthContext context, Graphics g,
1073                                         int x, int y, int w, int h) {
1074         // Does not call into ENGINE for better performance
1075         fillArea(context, g, x, y, w, h, GTKColorType.BACKGROUND);
1076     }
1077 
1078     //
1079     // TOGGLE_BUTTON
1080     //
1081     public void paintToggleButtonBackground(SynthContext context,
1082                                             Graphics g,
1083                                             int x, int y, int w, int h) {
1084         Region id = context.getRegion();
1085         JToggleButton toggleButton = (JToggleButton)context.getComponent();
1086         boolean paintBG = toggleButton.isContentAreaFilled() &&
1087                           toggleButton.isBorderPainted();
1088         boolean paintFocus = toggleButton.isFocusPainted();
1089         boolean toolButton = (toggleButton.getParent() instanceof JToolBar);
1090         paintButtonBackgroundImpl(context, g, id, "button",
1091                                   x, y, w, h,
1092                                   paintBG, paintFocus, false, toolButton);
1093     }
1094 
1095 
1096     //
1097     // SCROLL_BAR
1098     //
1099     public void paintScrollBarBackground(SynthContext context,
1100                                           Graphics g,
1101                                           int x, int y, int w,int h) {
1102         Region id = context.getRegion();
1103         boolean focused =
1104                 (context.getComponentState() & SynthConstants.FOCUSED) != 0;
1105         synchronized (UNIXToolkit.GTK_LOCK) {
1106             if (ENGINE.paintCachedImage(g, x, y, w, h, id, focused)) {
1107                 return;
1108             }
1109             ENGINE.startPainting(g, x, y, w, h, id, focused);
1110 
1111             // Note: the scrollbar insets already include the "trough-border",
1112             // which is needed to position the scrollbar buttons properly.
1113             // But when we render, we need to take the trough border out
1114             // of the equation so that we paint the entire area covered by
1115             // the trough border and the scrollbar content itself.
1116             Insets insets = context.getComponent().getInsets();
1117             GTKStyle style = (GTKStyle)context.getStyle();
1118             int troughBorder =
1119                 style.getClassSpecificIntValue(context, "trough-border", 1);
1120             insets.left   -= troughBorder;
1121             insets.right  -= troughBorder;
1122             insets.top    -= troughBorder;
1123             insets.bottom -= troughBorder;
1124 
1125             ENGINE.paintBox(g, context, id, SynthConstants.PRESSED,
1126                             ShadowType.IN, "trough",
1127                             x + insets.left,
1128                             y + insets.top,
1129                             w - insets.left - insets.right,
1130                             h - insets.top - insets.bottom);
1131 
1132             if (focused) {
1133                 ENGINE.paintFocus(g, context, id,
1134                         SynthConstants.ENABLED, "trough", x, y, w, h);
1135             }
1136             ENGINE.finishPainting();
1137         }
1138     }
1139 
1140 
1141     //
1142     // SCROLL_BAR_THUMB
1143     //
1144     public void paintScrollBarThumbBackground(SynthContext context,
1145             Graphics g, int x, int y, int w, int h, int dir) {
1146         Region id = context.getRegion();
1147         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1148                 id, context.getComponentState());
1149 
1150         // The clearlooks engine paints scrollbar thumbs differently
1151         // depending on the current scroll value (specifically, it will avoid
1152         // rendering a certain line when the thumb is at the starting or
1153         // ending position).  Therefore, we normalize the current value to
1154         // the range [0,100] here and then pass it down to setRangeValue()
1155         // so that the native widget is configured appropriately.  Note that
1156         // there are really only four values that matter (min, middle, max,
1157         // or fill) so we restrict to one of those four values to avoid
1158         // blowing out the image cache.
1159         JScrollBar sb = (JScrollBar)context.getComponent();
1160         boolean rtl =
1161             sb.getOrientation() == JScrollBar.HORIZONTAL &&
1162             !sb.getComponentOrientation().isLeftToRight();
1163         double min = 0;
1164         double max = 100;
1165         double visible = 20;
1166         double value;
1167         if (sb.getMaximum() - sb.getMinimum() == sb.getVisibleAmount()) {
1168             // In this case, the thumb fills the entire track, so it is
1169             // touching both ends at the same time
1170             value = 0;
1171             visible = 100;
1172         } else if (sb.getValue() == sb.getMinimum()) {
1173             // At minimum
1174             value = rtl ? 100 : 0;
1175         } else if (sb.getValue() >= sb.getMaximum() - sb.getVisibleAmount()) {
1176             // At maximum
1177             value = rtl ? 0 : 100;
1178         } else {
1179             // Somewhere in between
1180             value = 50;
1181         }
1182 
1183         synchronized (UNIXToolkit.GTK_LOCK) {
1184             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState,
1185                                           dir, value, visible, rtl))
1186             {
1187                 ENGINE.startPainting(g, x, y, w, h, id, gtkState,
1188                                      dir, value, visible, rtl);
1189                 Orientation orientation = (dir == JScrollBar.HORIZONTAL ?
1190                     Orientation.HORIZONTAL : Orientation.VERTICAL);
1191                 ENGINE.setRangeValue(context, id, value, min, max, visible);
1192                 ENGINE.paintSlider(g, context, id, gtkState,
1193                         ShadowType.OUT, "slider", x, y, w, h, orientation, false);
1194                 ENGINE.finishPainting();
1195             }
1196         }
1197     }
1198 
1199     //
1200     // TOOL_TIP
1201     //
1202     public void paintToolTipBackground(SynthContext context, Graphics g,
1203                                         int x, int y, int w,int h) {
1204         Region id = context.getRegion();
1205         synchronized (UNIXToolkit.GTK_LOCK) {
1206             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
1207                 ENGINE.startPainting(g, x, y, w, h, id);
1208                 ENGINE.paintFlatBox(g, context, id, SynthConstants.ENABLED,
1209                         ShadowType.OUT, "tooltip", x, y, w, h,
1210                         ColorType.BACKGROUND);
1211                 ENGINE.finishPainting();
1212             }
1213         }
1214     }
1215 
1216 
1217     //
1218     // TREE_CELL
1219     //
1220     public void paintTreeCellBackground(SynthContext context, Graphics g,
1221                                         int x, int y, int w, int h) {
1222         Region id = context.getRegion();
1223         int state = context.getComponentState();
1224         int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1225         synchronized (UNIXToolkit.GTK_LOCK) {
1226             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
1227                 ENGINE.startPainting(g, x, y, w, h, id, state);
1228                 // the string arg should alternate based on row being painted,
1229                 // but we currently don't pass that in.
1230                 ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
1231                         "cell_odd", x, y, w, h, ColorType.TEXT_BACKGROUND);
1232                 ENGINE.finishPainting();
1233             }
1234         }
1235     }
1236 
1237     public void paintTreeCellFocus(SynthContext context, Graphics g,
1238                                     int x, int y, int w, int h) {
1239         Region id = Region.TREE_CELL;
1240         int state = context.getComponentState();
1241         paintFocus(context, g, id, state, "treeview", x, y, w, h);
1242     }
1243 
1244 
1245     //
1246     // TREE
1247     //
1248     public void paintTreeBackground(SynthContext context, Graphics g,
1249                                     int x, int y, int w, int h) {
1250         // As far as I can tell, these don't call into the ENGINE.
1251         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
1252     }
1253 
1254 
1255     //
1256     // VIEWPORT
1257     //
1258     public void paintViewportBackground(SynthContext context, Graphics g,
1259                                         int x, int y, int w, int h) {
1260         // As far as I can tell, these don't call into the ENGINE.
1261         // Also note that you don't want this to call into the ENGINE
1262         // as if it where to paint a background JViewport wouldn't scroll
1263         // correctly.
1264         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
1265     }
1266 
1267     void paintFocus(SynthContext context, Graphics g, Region id,
1268             int state, String detail, int x, int y, int w, int h) {
1269         int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1270         synchronized (UNIXToolkit.GTK_LOCK) {
1271             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, "focus")) {
1272                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, "focus");
1273                 ENGINE.paintFocus(g, context, id, gtkState, detail, x, y, w, h);
1274                 ENGINE.finishPainting();
1275             }
1276         }
1277     }
1278 
1279     void paintMetacityElement(SynthContext context, Graphics g,
1280             int gtkState, String detail, int x, int y, int w, int h,
1281             ShadowType shadow, ArrowType direction) {
1282         synchronized (UNIXToolkit.GTK_LOCK) {
1283             if (! ENGINE.paintCachedImage(
1284                     g, x, y, w, h, gtkState, detail, shadow, direction)) {
1285                 ENGINE.startPainting(
1286                         g, x, y, w, h, gtkState, detail, shadow, direction);
1287                 if (detail == "metacity-arrow") {
1288                     ENGINE.paintArrow(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1289                             gtkState, shadow, direction, "", x, y, w, h);
1290 
1291                 } else if (detail == "metacity-box") {
1292                     ENGINE.paintBox(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1293                             gtkState, shadow, "", x, y, w, h);
1294 
1295                 } else if (detail == "metacity-vline") {
1296                     ENGINE.paintVline(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1297                             gtkState, "", x, y, w, h);
1298                 }
1299                 ENGINE.finishPainting();
1300             }
1301         }
1302     }
1303 
1304     void paintIcon(SynthContext context, Graphics g,
1305             Method paintMethod, int x, int y, int w, int h) {
1306         int state = context.getComponentState();
1307         synchronized (UNIXToolkit.GTK_LOCK) {
1308             if (! ENGINE.paintCachedImage(g, x, y, w, h, state, paintMethod)) {
1309                 ENGINE.startPainting(g, x, y, w, h, state, paintMethod);
1310                 try {
1311                     paintMethod.invoke(this, context, g, state, x, y, w, h);
1312                 } catch (IllegalAccessException iae) {
1313                     assert false;
1314                 } catch (InvocationTargetException ite) {
1315                     assert false;
1316                 }
1317                 ENGINE.finishPainting();
1318             }
1319         }
1320     }
1321 
1322     void paintIcon(SynthContext context, Graphics g,
1323             Method paintMethod, int x, int y, int w, int h, Object direction) {
1324         int state = context.getComponentState();
1325         synchronized (UNIXToolkit.GTK_LOCK) {
1326             if (! ENGINE.paintCachedImage(g,
1327                     x, y, w, h, state, paintMethod, direction)) {
1328                 ENGINE.startPainting(g,
1329                         x, y, w, h, state, paintMethod, direction);
1330                 try {
1331                     paintMethod.invoke(this, context,
1332                             g, state, x, y, w, h, direction);
1333                 } catch (IllegalAccessException iae) {
1334                     assert false;
1335                 } catch (InvocationTargetException ite) {
1336                     assert false;
1337                 }
1338                 ENGINE.finishPainting();
1339             }
1340         }
1341     }
1342 
1343     // All icon painting methods are called from under GTK_LOCK
1344 
1345     public void paintTreeExpandedIcon(SynthContext context,
1346             Graphics g, int state, int x, int y, int w, int h) {
1347         ENGINE.paintExpander(g, context, Region.TREE,
1348                 GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
1349                 ExpanderStyle.EXPANDED, "expander", x, y, w, h);
1350     }
1351 
1352     public void paintTreeCollapsedIcon(SynthContext context,
1353             Graphics g, int state, int x, int y, int w, int h) {
1354         ENGINE.paintExpander(g, context, Region.TREE,
1355                 GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
1356                 ExpanderStyle.COLLAPSED, "expander", x, y, w, h);
1357     }
1358 
1359     public void paintCheckBoxIcon(SynthContext context,
1360             Graphics g, int state, int x, int y, int w, int h) {
1361         GTKStyle style = (GTKStyle)context.getStyle();
1362         int size = style.getClassSpecificIntValue(context,
1363                         "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
1364         int offset = style.getClassSpecificIntValue(context,
1365                         "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
1366 
1367         ENGINE.paintCheck(g, context, Region.CHECK_BOX, "checkbutton",
1368                 x+offset, y+offset, size, size);
1369     }
1370 
1371     public void paintRadioButtonIcon(SynthContext context,
1372             Graphics g, int state, int x, int y, int w, int h) {
1373         GTKStyle style = (GTKStyle)context.getStyle();
1374         int size = style.getClassSpecificIntValue(context,
1375                         "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
1376         int offset = style.getClassSpecificIntValue(context,
1377                         "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
1378 
1379         ENGINE.paintOption(g, context, Region.RADIO_BUTTON, "radiobutton",
1380                 x+offset, y+offset, size, size);
1381     }
1382 
1383     public void paintMenuArrowIcon(SynthContext context, Graphics g,
1384             int state, int x, int y, int w, int h, ArrowType dir) {
1385         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1386                 context.getRegion(), state);
1387         ShadowType shadow = ShadowType.OUT;
1388         if (gtkState == SynthConstants.MOUSE_OVER) {
1389             shadow = ShadowType.IN;
1390         }
1391         if (!GTKLookAndFeel.is3()) {
1392             x += 3;
1393             y += 3;
1394             w = h = 7;
1395         }
1396         ENGINE.paintArrow(g, context, Region.MENU_ITEM, gtkState, shadow,
1397                 dir, "menuitem", x, y, w, h);
1398     }
1399 
1400     public void paintCheckBoxMenuItemCheckIcon(SynthContext context,
1401             Graphics g, int state, int x, int y, int w, int h) {
1402 
1403         GTKStyle style = (GTKStyle)context.getStyle();
1404         int size = style.getClassSpecificIntValue(context,"indicator-size",
1405                 GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
1406 
1407         ENGINE.paintCheck(g, context, Region.CHECK_BOX_MENU_ITEM, "check",
1408                 x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1409                 y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1410                 size, size);
1411     }
1412 
1413     public void paintRadioButtonMenuItemCheckIcon(SynthContext context,
1414             Graphics g, int state, int x, int y, int w, int h) {
1415 
1416         GTKStyle style = (GTKStyle)context.getStyle();
1417         int size = style.getClassSpecificIntValue(context,"indicator-size",
1418                 GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
1419 
1420         ENGINE.paintOption(g, context, Region.RADIO_BUTTON_MENU_ITEM, "option",
1421                 x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1422                 y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1423                 size, size);
1424     }
1425 
1426     public void paintToolBarHandleIcon(SynthContext context, Graphics g,
1427             int state, int x, int y, int w, int h, Orientation orientation) {
1428         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1429                 context.getRegion(), state);
1430 
1431         // The orientation parameter passed down by Synth refers to the
1432         // orientation of the toolbar, but the one we pass to GTK refers
1433         // to the orientation of the handle.  Therefore, we need to swap
1434         // the value here: horizontal toolbars have vertical handles, and
1435         // vice versa.
1436         orientation = (orientation == Orientation.HORIZONTAL) ?
1437             Orientation.VERTICAL : Orientation.HORIZONTAL;
1438 
1439         ENGINE.paintHandle(g, context, Region.TOOL_BAR, gtkState,
1440                 ShadowType.OUT, "handlebox", x, y, w, h, orientation);
1441     }
1442 
1443     public void paintAscendingSortIcon(SynthContext context,
1444             Graphics g, int state, int x, int y, int w, int h) {
1445         ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
1446                 ShadowType.IN, ArrowType.UP, "arrow", x, y, w, h);
1447     }
1448 
1449     public void paintDescendingSortIcon(SynthContext context,
1450             Graphics g, int state, int x, int y, int w, int h) {
1451         ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
1452                 ShadowType.IN, ArrowType.DOWN, "arrow", x, y, w, h);
1453     }
1454 
1455     /*
1456      * Fill an area with color determined from this context's Style using the
1457      * specified GTKColorType
1458      */
1459     private void fillArea(SynthContext context, Graphics g,
1460                           int x, int y, int w, int h, ColorType colorType) {
1461         if (context.getComponent().isOpaque()) {
1462             Region id = context.getRegion();
1463             int gtkState = GTKLookAndFeel.synthStateToGTKState(id,
1464                     context.getComponentState());
1465             GTKStyle style = (GTKStyle)context.getStyle();
1466 
1467             g.setColor(style.getGTKColor(context, gtkState, colorType));
1468             g.fillRect(x, y, w, h);
1469         }
1470     }
1471 
1472     // Refer to GTKLookAndFeel for details on this.
1473     static class ListTableFocusBorder extends AbstractBorder implements
1474                           UIResource {
1475 
1476         private boolean selectedCell;
1477         private boolean focusedCell;
1478 
1479         public static ListTableFocusBorder getSelectedCellBorder() {
1480             return new ListTableFocusBorder(true, true);
1481         }
1482 
1483         public static ListTableFocusBorder getUnselectedCellBorder() {
1484             return new ListTableFocusBorder(false, true);
1485         }
1486 
1487         public static ListTableFocusBorder getNoFocusCellBorder() {
1488             return new ListTableFocusBorder(false, false);
1489         }
1490 
1491         public ListTableFocusBorder(boolean selectedCell, boolean focusedCell) {
1492             this.selectedCell = selectedCell;
1493             this.focusedCell = focusedCell;
1494         }
1495 
1496         private SynthContext getContext(Component c) {
1497             SynthContext context = null;
1498 
1499             ComponentUI ui = null;
1500             if (c instanceof JLabel) {
1501                 ui = ((JLabel)c).getUI();
1502             }
1503 
1504             if (ui instanceof SynthUI) {
1505                 context = ((SynthUI)ui).getContext((JComponent)c);
1506             }
1507 
1508             return context;
1509         }
1510 
1511         public void paintBorder(Component c, Graphics g, int x, int y,
1512                                 int w, int h) {
1513             if (focusedCell) {
1514                 SynthContext context = getContext(c);
1515                 int state = (selectedCell? SynthConstants.SELECTED:
1516                              SynthConstants.FOCUSED | SynthConstants.ENABLED);
1517 
1518                 if (context != null) {
1519                     GTKPainter.INSTANCE.paintFocus(context, g,
1520                             Region.TABLE, state, "", x, y, w, h);
1521                 }
1522             }
1523         }
1524 
1525         public Insets getBorderInsets(Component c, Insets i) {
1526             SynthContext context = getContext(c);
1527 
1528             if (context != null) {
1529                 i = context.getStyle().getInsets(context, i);
1530             }
1531 
1532             return i;
1533         }
1534 
1535         public boolean isBorderOpaque() {
1536             return true;
1537         }
1538     }
1539 
1540     // TitledBorder implementation for GTK L&F
1541     static class TitledBorder extends AbstractBorder implements UIResource {
1542 
1543         public void paintBorder(Component c, Graphics g, int x, int y,
1544                                 int w, int h) {
1545             SynthContext context = getContext((JComponent)c);
1546             Region id = context.getRegion();
1547             int state = context.getComponentState();
1548             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1549 
1550             synchronized (UNIXToolkit.GTK_LOCK) {
1551                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
1552                     ENGINE.startPainting(g, x, y, w, h, id);
1553                     ENGINE.paintShadow(g, context, id, gtkState, ShadowType.ETCHED_IN,
1554                                       "frame", x, y, w, h);
1555                     ENGINE.finishPainting();
1556                 }
1557             }
1558         }
1559 
1560         public Insets getBorderInsets(Component c, Insets i) {
1561             SynthContext context = getContext((JComponent)c);
1562             return context.getStyle().getInsets(context, i);
1563         }
1564 
1565         public boolean isBorderOpaque() {
1566             return true;
1567         }
1568 
1569         private SynthStyle getStyle(JComponent c) {
1570             return SynthLookAndFeel.getStyle(c, GTKEngine.CustomRegion.TITLED_BORDER);
1571         }
1572 
1573         private SynthContext getContext(JComponent c) {
1574             int state = SynthConstants.DEFAULT;
1575             return new SynthContext(c, GTKEngine.CustomRegion.TITLED_BORDER,
1576                                     getStyle(c), state);
1577         }
1578     }
1579 }