1 /*
   2  * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.java.swing.plaf.gtk;
  26 
  27 import sun.awt.ModalExclude;
  28 import sun.awt.SunToolkit;
  29 import sun.awt.UNIXToolkit;
  30 
  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 static final GTKEngine ENGINE = GTKEngine.INSTANCE;
  70     static final 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             Insets insets = style.getInsets(context, null);
 585             ENGINE.paintBackground(g, context, id, gtkState,
 586                 style.getGTKColor(context, gtkState, GTKColorType.BACKGROUND),
 587                 x + insets.left, y + insets.top, w - insets.left - insets.right,
 588                 h - insets.top - insets.bottom);
 589             BufferedImage img = ENGINE.finishPainting();
 590             if(!isHW) {
 591                 int border = img.getRGB(0, h / 2);
 592                 if (img != null && border == img.getRGB(w / 2, h / 2)) {
 593                     // fix no menu borders in Adwaita theme
 594                     Graphics g2 = img.getGraphics();
 595                     Color c = new Color(border);
 596                     g2.setColor(new Color(Math.max((int) (c.getRed() * 0.8), 0),
 597                             Math.max((int) (c.getGreen() * 0.8), 0),
 598                             Math.max((int) (c.getBlue() * 0.8), 0)));
 599                     g2.drawLine(0, 0, w - 1, 0);
 600                     g2.drawLine(w - 1, 0, w - 1, h - 1);
 601                     g2.drawLine(0, h - 1, 0, 1);
 602                     g2.setColor(c.darker());
 603                     g2.drawLine(w - 1, h - 1, 0, h - 1);
 604                     g2.dispose();
 605                     g.drawImage(img, x, y, null);
 606                 }
 607             }
 608         }
 609     }
 610 
 611     public void paintProgressBarBackground(SynthContext context,
 612                                             Graphics g,
 613                                             int x, int y, int w, int h) {
 614         Region id = context.getRegion();
 615         synchronized (UNIXToolkit.GTK_LOCK) {
 616             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 617                 ENGINE.startPainting(g, x, y, w, h, id);
 618                 ENGINE.paintBox(g, context, id, SynthConstants.ENABLED,
 619                         ShadowType.IN, "trough", x, y, w, h);
 620                 ENGINE.finishPainting();
 621             }
 622         }
 623     }
 624 
 625     public void paintProgressBarForeground(SynthContext context, Graphics g,
 626                                             int x, int y, int w, int h,
 627                                             int orientation) {
 628         Region id = context.getRegion();
 629         synchronized (UNIXToolkit.GTK_LOCK) {
 630             // Note that we don't call paintCachedImage() here.  Since the
 631             // progress bar foreground is painted differently for each value
 632             // it would be wasteful to try to cache an image for each state,
 633             // so instead we simply avoid caching in this case.
 634             if (w <= 0 || h <= 0) {
 635                 return;
 636             }
 637             ENGINE.startPainting(g, x, y, w, h, id, "fg");
 638             ENGINE.paintBox(g, context, id, SynthConstants.MOUSE_OVER,
 639                             ShadowType.OUT, "bar", x, y, w, h);
 640             ENGINE.finishPainting(false); // don't bother caching the image
 641         }
 642     }
 643 
 644     public void paintViewportBorder(SynthContext context, Graphics g,
 645                                            int x, int y, int w, int h) {
 646         Region id = context.getRegion();
 647         synchronized (UNIXToolkit.GTK_LOCK) {
 648             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
 649                 ENGINE.startPainting(g, x, y, w, h, id);
 650                 ENGINE.paintShadow(g, context, id, SynthConstants.ENABLED,
 651                         ShadowType.IN, "scrolled_window", x, y, w, h);
 652                 ENGINE.finishPainting();
 653             }
 654         }
 655     }
 656 
 657     public void paintSeparatorBackground(SynthContext context,
 658                                           Graphics g,
 659                                           int x, int y, int w, int h,
 660                                          int orientation) {
 661         Region id = context.getRegion();
 662         int state = context.getComponentState();
 663         JComponent c = context.getComponent();
 664 
 665         GTKStyle style = (GTKStyle) context.getStyle();
 666         String detail;
 667         // wide-separators are painted using box not line
 668         if (style.getClassSpecificBoolValue(context,
 669                                           "wide-separators", false)) {
 670             Insets insets = c.getInsets();
 671             x += insets.left;
 672             y += insets.top;
 673             if (orientation == JSeparator.HORIZONTAL) {
 674                 w -= (insets.left + insets.right);
 675             } else {
 676                 h -= (insets.top + insets.bottom);
 677             }
 678             if (GTKLookAndFeel.is3()) {
 679                 if (id == Region.POPUP_MENU_SEPARATOR) {
 680                     detail = "menuitem";
 681                     h -= (insets.top + insets.bottom);
 682                 } else {
 683                     detail = "separator";
 684                 }
 685             } else {
 686                 detail = orientation == JSeparator.HORIZONTAL ?
 687                                                     "hseparator" : "vseparator";
 688             }
 689             synchronized (UNIXToolkit.GTK_LOCK) {
 690                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id, state,
 691                             detail, orientation)) {
 692                     ENGINE.startPainting(g, x, y, w, h, id, state,
 693                             detail, orientation);
 694                     ENGINE.paintBox(g, context, id, state,
 695                             ShadowType.ETCHED_OUT, detail, x, y, w, h);
 696                     ENGINE.finishPainting();
 697                 }
 698             }
 699             return;
 700         }
 701 
 702         /*
 703          * Note: In theory, the style's x/y thickness values would determine
 704          * the width of the separator content.  In practice, however, some
 705          * engines will render a line that is wider than the corresponding
 706          * thickness value.  For example, ubuntulooks reports x/y thickness
 707          * values of 1 for separators, but always renders a 2-pixel wide line.
 708          * As a result of all this, we need to be careful not to restrict
 709          * the w/h values below too much, so that the full thickness of the
 710          * rendered line will be captured by our image caching code.
 711          */
 712         if (c instanceof JToolBar.Separator) {
 713             /*
 714              * GTK renders toolbar separators differently in that an
 715              * artificial padding is added to each end of the separator.
 716              * The value of 0.2f below is derived from the source code of
 717              * gtktoolbar.c in the current version of GTK+ (2.8.20 at the
 718              * time of this writing).  Specifically, the relevant values are:
 719              *     SPACE_LINE_DIVISION 10.0
 720              *     SPACE_LINE_START     2.0
 721              *     SPACE_LINE_END       8.0
 722              * These are used to determine the distance from the top (or left)
 723              * edge of the toolbar to the other edge.  So for example, the
 724              * starting/top point of a vertical separator is 2/10 of the
 725              * height of a horizontal toolbar away from the top edge, which
 726              * is how we arrive at 0.2f below.  Likewise, the ending/bottom
 727              * point is 8/10 of the height away from the top edge, or in other
 728              * words, it is 2/10 away from the bottom edge, which is again
 729              * how we arrive at the 0.2f value below.
 730              *
 731              * The separator is also centered horizontally or vertically,
 732              * depending on its orientation.  This was determined empirically
 733              * and by examining the code referenced above.
 734              */
 735             detail = "toolbar";
 736             float pct = 0.2f;
 737             JToolBar.Separator sep = (JToolBar.Separator)c;
 738             Dimension size = sep.getSeparatorSize();
 739             if (orientation == JSeparator.HORIZONTAL) {
 740                 x += (int)(w * pct);
 741                 w -= (int)(w * pct * 2);
 742                 y += (size.height - style.getYThickness()) / 2;
 743             } else {
 744                 y += (int)(h * pct);
 745                 h -= (int)(h * pct * 2);
 746                 x += (size.width - style.getXThickness()) / 2;
 747             }
 748         } else {
 749             // For regular/menu separators, we simply subtract out the insets.
 750             detail = "separator";
 751             Insets insets = c.getInsets();
 752             x += insets.left;
 753             y += insets.top;
 754             if (orientation == JSeparator.HORIZONTAL) {
 755                 w -= (insets.left + insets.right);
 756             } else {
 757                 h -= (insets.top + insets.bottom);
 758             }
 759         }
 760 
 761         synchronized (UNIXToolkit.GTK_LOCK) {
 762             if (! ENGINE.paintCachedImage(g, x, y, w, h, id,
 763                                           state, detail, orientation)) {
 764                 ENGINE.startPainting(g, x, y, w, h, id,
 765                                      state, detail, orientation);
 766                 if (orientation == JSeparator.HORIZONTAL) {
 767                     ENGINE.paintHline(g, context, id, state,
 768                                       detail, x, y, w, h);
 769                 } else {
 770                     ENGINE.paintVline(g, context, id, state,
 771                                       detail, x, y, w, h);
 772                 }
 773                 ENGINE.finishPainting();
 774             }
 775         }
 776     }
 777 
 778     public void paintSliderTrackBackground(SynthContext context,
 779                                        Graphics g,
 780                                        int x, int y, int w,int h) {
 781         Region id = context.getRegion();
 782         int state = context.getComponentState();
 783 
 784         // For focused sliders, we paint focus rect outside the bounds passed.
 785         // Need to adjust for that.
 786         boolean focused = ((state & SynthConstants.FOCUSED) != 0);
 787         int focusSize = 0;
 788         if (focused) {
 789             GTKStyle style = (GTKStyle)context.getStyle();
 790             focusSize = style.getClassSpecificIntValue(
 791                                 context, "focus-line-width", 1) +
 792                         style.getClassSpecificIntValue(
 793                                 context, "focus-padding", 1);
 794             x -= focusSize;
 795             y -= focusSize;
 796             w += focusSize * 2;
 797             h += focusSize * 2;
 798         }
 799 
 800         // The ubuntulooks engine paints slider troughs differently depending
 801         // on the current slider value and its component orientation.
 802         JSlider slider = (JSlider)context.getComponent();
 803         if (GTKLookAndFeel.is3()) {
 804             if (slider.getOrientation() == JSlider.VERTICAL) {
 805                 y += 1;
 806                 h -= 2;
 807             } else {
 808                 x += 1;
 809                 w -= 2;
 810             }
 811         }
 812         double value = slider.getValue();
 813         double min = slider.getMinimum();
 814         double max = slider.getMaximum();
 815         double visible = 20; // not used for sliders; any value will work
 816 
 817         synchronized (UNIXToolkit.GTK_LOCK) {
 818             // Note that we don't call paintCachedImage() here.  Since some
 819             // engines (e.g. ubuntulooks) paint the slider background
 820             // differently for any given slider value, it would be wasteful
 821             // to try to cache an image for each state, so instead we simply
 822             // avoid caching in this case.
 823             if (w <= 0 || h <= 0) {
 824                 return;
 825             }
 826             ENGINE.startPainting(g, x, y, w, h, id, state, value);
 827             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
 828             ENGINE.setRangeValue(context, id, value, min, max, visible);
 829             ENGINE.paintBox(g, context, id, gtkState, ShadowType.IN,
 830                             "trough", x + focusSize, y + focusSize,
 831                             w - 2 * focusSize, h - 2 * focusSize);
 832             if (focused) {
 833                 ENGINE.paintFocus(g, context, id, SynthConstants.ENABLED,
 834                                   "trough", x, y, w, h);
 835             }
 836             ENGINE.finishPainting(false); // don't bother caching the image
 837         }
 838     }
 839 
 840     public void paintSliderThumbBackground(SynthContext context,
 841             Graphics g, int x, int y, int w, int h, int dir) {
 842         Region id = context.getRegion();
 843         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 844                 id, context.getComponentState());
 845         boolean hasFocus = GTKLookAndFeel.is3() &&
 846                 ((context.getComponentState() & SynthConstants.FOCUSED) != 0);
 847         synchronized (UNIXToolkit.GTK_LOCK) {
 848             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, dir,
 849                                                                     hasFocus)) {
 850                 Orientation orientation = (dir == JSlider.HORIZONTAL ?
 851                     Orientation.HORIZONTAL : Orientation.VERTICAL);
 852                 String detail = (dir == JSlider.HORIZONTAL ?
 853                     "hscale" : "vscale");
 854                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, dir);
 855                 ENGINE.paintSlider(g, context, id, gtkState,
 856                         ShadowType.OUT, detail, x, y, w, h, orientation,
 857                                                                      hasFocus);
 858                 ENGINE.finishPainting();
 859             }
 860         }
 861     }
 862 
 863     //
 864     // SPINNER
 865     //
 866     public void paintSpinnerBackground(SynthContext context,
 867                                         Graphics g,
 868                                         int x, int y, int w, int h) {
 869         // This is handled in paintTextFieldBackground
 870     }
 871 
 872     //
 873     // SPLIT_PANE_DIVIDER
 874     //
 875     public void paintSplitPaneDividerBackground(SynthContext context,
 876                                        Graphics g,
 877                                        int x, int y, int w, int h) {
 878         Region id = context.getRegion();
 879         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 880                 id, context.getComponentState());
 881         JSplitPane splitPane = (JSplitPane)context.getComponent();
 882         Orientation orientation =
 883                 (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ?
 884                     Orientation.VERTICAL : Orientation.HORIZONTAL);
 885         synchronized (UNIXToolkit.GTK_LOCK) {
 886             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 887                     id, gtkState, orientation)) {
 888                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, orientation);
 889                 ENGINE.paintHandle(g, context, id, gtkState,
 890                         ShadowType.OUT, "paned", x, y, w, h, orientation);
 891                 ENGINE.finishPainting();
 892             }
 893         }
 894     }
 895 
 896     public void paintSplitPaneDragDivider(SynthContext context,
 897                                        Graphics g,int x, int y, int w, int h,
 898                                        int orientation) {
 899         paintSplitPaneDividerForeground(context, g, x, y, w, h, orientation);
 900     }
 901 
 902     public void paintTabbedPaneContentBackground(SynthContext context,
 903                                       Graphics g, int x, int y, int w, int h) {
 904         JTabbedPane pane = (JTabbedPane)context.getComponent();
 905         int selectedIndex = pane.getSelectedIndex();
 906         PositionType placement = GTKLookAndFeel.SwingOrientationConstantToGTK(
 907                                                         pane.getTabPlacement());
 908 
 909         int gapStart = 0;
 910         int gapSize = 0;
 911         if (selectedIndex != -1) {
 912             Rectangle tabBounds = pane.getBoundsAt(selectedIndex);
 913 
 914             if (placement == PositionType.TOP ||
 915                 placement == PositionType.BOTTOM) {
 916 
 917                 gapStart = tabBounds.x - x;
 918                 gapSize = tabBounds.width;
 919             }
 920             else {
 921                 gapStart = tabBounds.y - y;
 922                 gapSize = tabBounds.height;
 923             }
 924         }
 925 
 926         Region id = context.getRegion();
 927         int gtkState = GTKLookAndFeel.synthStateToGTKState(
 928                 id, context.getComponentState());
 929         synchronized (UNIXToolkit.GTK_LOCK) {
 930             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 931                     id, gtkState, placement, gapStart, gapSize)) {
 932                 ENGINE.startPainting(g, x, y, w, h,
 933                         id, gtkState, placement, gapStart, gapSize);
 934                 ENGINE.paintBoxGap(g, context, id, gtkState, ShadowType.OUT,
 935                         "notebook", x, y, w, h, placement, gapStart, gapSize);
 936                 ENGINE.finishPainting();
 937             }
 938         }
 939     }
 940 
 941     public void paintTabbedPaneTabBackground(SynthContext context,
 942                                            Graphics g,
 943                                            int x, int y, int w, int h,
 944                                            int tabIndex) {
 945         Region id = context.getRegion();
 946         int state = context.getComponentState();
 947         int gtkState = ((state & SynthConstants.SELECTED) != 0 ?
 948             SynthConstants.ENABLED : SynthConstants.PRESSED);
 949         JTabbedPane pane = (JTabbedPane)context.getComponent();
 950         int placement = pane.getTabPlacement();
 951 
 952         synchronized (UNIXToolkit.GTK_LOCK) {
 953             if (! ENGINE.paintCachedImage(g, x, y, w, h,
 954                     id, gtkState, placement, tabIndex)) {
 955                 PositionType side = POSITIONS[placement - 1];
 956                 ENGINE.startPainting(g, x, y, w, h,
 957                         id, gtkState, placement, tabIndex);
 958                 ENGINE.paintExtension(g, context, id, gtkState,
 959                         ShadowType.OUT, "tab", x, y, w, h, side, tabIndex);
 960                 ENGINE.finishPainting();
 961             }
 962         }
 963     }
 964 
 965     //
 966     // TEXT_PANE
 967     //
 968     public void paintTextPaneBackground(SynthContext context, Graphics g,
 969                                         int x, int y, int w, int h) {
 970         paintTextAreaBackground(context, g, x, y, w, h);
 971     }
 972 
 973     //
 974     // EDITOR_PANE
 975     //
 976     public void paintEditorPaneBackground(SynthContext context, Graphics g,
 977                                           int x, int y, int w, int h) {
 978         paintTextAreaBackground(context, g, x, y, w, h);
 979     }
 980 
 981     //
 982     // TEXT_AREA
 983     //
 984     public void paintTextAreaBackground(SynthContext context, Graphics g,
 985                                         int x, int y, int w, int h) {
 986         // Does not call into ENGINE for better performance
 987         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
 988     }
 989 
 990     //
 991     // TEXT_FIELD
 992     //
 993     // NOTE: Combobox and Label, Password and FormattedTextField calls this
 994     // too.
 995     private void paintTextBackground(SynthContext context, Graphics g,
 996                                      int x, int y, int w, int h) {
 997         // Text is odd in that it uses the TEXT_BACKGROUND vs BACKGROUND.
 998         JComponent c = context.getComponent();
 999         Container container = c.getParent();
1000         Container containerParent = null;
1001         GTKStyle style = (GTKStyle)context.getStyle();
1002         Region id = context.getRegion();
1003         int state = context.getComponentState();
1004 
1005         if (c instanceof ListCellRenderer && container != null) {
1006             containerParent = container.getParent();
1007             if (containerParent instanceof JComboBox
1008                     && containerParent.hasFocus()) {
1009                 state |= SynthConstants.FOCUSED;
1010             }
1011         }
1012 
1013         synchronized (UNIXToolkit.GTK_LOCK) {
1014             if (ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
1015                 return;
1016             }
1017 
1018             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1019             int focusSize = 0;
1020             boolean interiorFocus = style.getClassSpecificBoolValue(
1021                     context, "interior-focus", true);
1022 
1023             focusSize = style.getClassSpecificIntValue(context,
1024                     "focus-line-width",1);
1025             if (!interiorFocus && (state & SynthConstants.FOCUSED) != 0) {
1026                 x += focusSize;
1027                 y += focusSize;
1028                 w -= 2 * focusSize;
1029                 h -= 2 * focusSize;
1030             }
1031 
1032             int xThickness = style.getXThickness();
1033             int yThickness = style.getYThickness();
1034 
1035             ENGINE.startPainting(g, x, y, w, h, id, state);
1036             if (GTKLookAndFeel.is3()) {
1037                 ENGINE.paintBackground(g, context, id, gtkState, null,
1038                                                                     x, y, w, h);
1039             }
1040             ENGINE.paintShadow(g, context, id, gtkState,
1041                                ShadowType.IN, "entry", x, y, w, h);
1042             if (!GTKLookAndFeel.is3()) {
1043                 ENGINE.paintFlatBox(g, context, id,
1044                         gtkState, ShadowType.NONE, "entry_bg",
1045                         x + xThickness,
1046                         y + yThickness,
1047                         w - (2 * xThickness),
1048                         h - (2 * yThickness),
1049                         ColorType.TEXT_BACKGROUND);
1050             }
1051 
1052             if (focusSize > 0 && (state & SynthConstants.FOCUSED) != 0) {
1053                 if (!interiorFocus) {
1054                     x -=  focusSize;
1055                     y -=  focusSize;
1056                     w +=  2 * focusSize;
1057                     h +=  2 * focusSize;
1058                 } else {
1059                     if (containerParent instanceof JComboBox) {
1060                         x += (focusSize + 2);
1061                         y += focusSize + (GTKLookAndFeel.is3() ? 3 : 1);
1062                         w -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 1);
1063                         h -= 2 * focusSize + (GTKLookAndFeel.is3() ? 6 : 2);
1064                     } else {
1065                         x += focusSize + (GTKLookAndFeel.is3() ? 2 : 0);
1066                         y += focusSize + (GTKLookAndFeel.is3() ? 2 :0 );
1067                         w -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 0);
1068                         h -= 2 * focusSize + (GTKLookAndFeel.is3() ? 4 : 0);
1069                     }
1070                 }
1071                 ENGINE.paintFocus(g, context, id, gtkState,
1072                         "entry", x, y, w, h);
1073             }
1074             ENGINE.finishPainting();
1075         }
1076     }
1077 
1078     private void paintTreeCellEditorBackground(SynthContext context, Graphics g,
1079                                                int x, int y, int w, int h) {
1080         Region id = context.getRegion();
1081         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1082                 id, context.getComponentState());
1083         synchronized (UNIXToolkit.GTK_LOCK) {
1084             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState)) {
1085                 ENGINE.startPainting(g, x, y, w, h, id, gtkState);
1086                 ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
1087                         "entry_bg", x, y, w, h, ColorType.TEXT_BACKGROUND);
1088                 ENGINE.finishPainting();
1089             }
1090         }
1091     }
1092 
1093 
1094     //
1095     // ROOT_PANE
1096     //
1097     public void paintRootPaneBackground(SynthContext context, Graphics g,
1098                                         int x, int y, int w, int h) {
1099         // Does not call into ENGINE for better performance
1100         fillArea(context, g, x, y, w, h, GTKColorType.BACKGROUND);
1101     }
1102 
1103     //
1104     // TOGGLE_BUTTON
1105     //
1106     public void paintToggleButtonBackground(SynthContext context,
1107                                             Graphics g,
1108                                             int x, int y, int w, int h) {
1109         Region id = context.getRegion();
1110         JToggleButton toggleButton = (JToggleButton)context.getComponent();
1111         boolean paintBG = toggleButton.isContentAreaFilled() &&
1112                           toggleButton.isBorderPainted();
1113         boolean paintFocus = toggleButton.isFocusPainted();
1114         boolean toolButton = (toggleButton.getParent() instanceof JToolBar);
1115         paintButtonBackgroundImpl(context, g, id, "button",
1116                                   x, y, w, h,
1117                                   paintBG, paintFocus, false, toolButton);
1118     }
1119 
1120 
1121     //
1122     // SCROLL_BAR
1123     //
1124     public void paintScrollBarBackground(SynthContext context,
1125                                           Graphics g,
1126                                           int x, int y, int w,int h) {
1127         Region id = context.getRegion();
1128         boolean focused =
1129                 (context.getComponentState() & SynthConstants.FOCUSED) != 0;
1130         synchronized (UNIXToolkit.GTK_LOCK) {
1131             if (ENGINE.paintCachedImage(g, x, y, w, h, id, focused)) {
1132                 return;
1133             }
1134             ENGINE.startPainting(g, x, y, w, h, id, focused);
1135 
1136             // Note: the scrollbar insets already include the "trough-border",
1137             // which is needed to position the scrollbar buttons properly.
1138             // But when we render, we need to take the trough border out
1139             // of the equation so that we paint the entire area covered by
1140             // the trough border and the scrollbar content itself.
1141             Insets insets = context.getComponent().getInsets();
1142             GTKStyle style = (GTKStyle)context.getStyle();
1143             int troughBorder =
1144                 style.getClassSpecificIntValue(context, "trough-border", 1);
1145             insets.left   -= troughBorder;
1146             insets.right  -= troughBorder;
1147             insets.top    -= troughBorder;
1148             insets.bottom -= troughBorder;
1149 
1150             ENGINE.paintBox(g, context, id, SynthConstants.PRESSED,
1151                             ShadowType.IN, "trough",
1152                             x + insets.left,
1153                             y + insets.top,
1154                             w - insets.left - insets.right,
1155                             h - insets.top - insets.bottom);
1156 
1157             if (focused) {
1158                 ENGINE.paintFocus(g, context, id,
1159                         SynthConstants.ENABLED, "trough", x, y, w, h);
1160             }
1161             ENGINE.finishPainting();
1162         }
1163     }
1164 
1165 
1166     //
1167     // SCROLL_BAR_THUMB
1168     //
1169     public void paintScrollBarThumbBackground(SynthContext context,
1170             Graphics g, int x, int y, int w, int h, int dir) {
1171         Region id = context.getRegion();
1172         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1173                 id, context.getComponentState());
1174 
1175         // The clearlooks engine paints scrollbar thumbs differently
1176         // depending on the current scroll value (specifically, it will avoid
1177         // rendering a certain line when the thumb is at the starting or
1178         // ending position).  Therefore, we normalize the current value to
1179         // the range [0,100] here and then pass it down to setRangeValue()
1180         // so that the native widget is configured appropriately.  Note that
1181         // there are really only four values that matter (min, middle, max,
1182         // or fill) so we restrict to one of those four values to avoid
1183         // blowing out the image cache.
1184         JScrollBar sb = (JScrollBar)context.getComponent();
1185         boolean rtl =
1186             sb.getOrientation() == JScrollBar.HORIZONTAL &&
1187             !sb.getComponentOrientation().isLeftToRight();
1188         double min = 0;
1189         double max = 100;
1190         double visible = 20;
1191         double value;
1192         if (sb.getMaximum() - sb.getMinimum() == sb.getVisibleAmount()) {
1193             // In this case, the thumb fills the entire track, so it is
1194             // touching both ends at the same time
1195             value = 0;
1196             visible = 100;
1197         } else if (sb.getValue() == sb.getMinimum()) {
1198             // At minimum
1199             value = rtl ? 100 : 0;
1200         } else if (sb.getValue() >= sb.getMaximum() - sb.getVisibleAmount()) {
1201             // At maximum
1202             value = rtl ? 0 : 100;
1203         } else {
1204             // Somewhere in between
1205             value = 50;
1206         }
1207 
1208         synchronized (UNIXToolkit.GTK_LOCK) {
1209             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState,
1210                                           dir, value, visible, rtl))
1211             {
1212                 ENGINE.startPainting(g, x, y, w, h, id, gtkState,
1213                                      dir, value, visible, rtl);
1214                 Orientation orientation = (dir == JScrollBar.HORIZONTAL ?
1215                     Orientation.HORIZONTAL : Orientation.VERTICAL);
1216                 ENGINE.setRangeValue(context, id, value, min, max, visible);
1217                 ENGINE.paintSlider(g, context, id, gtkState, ShadowType.OUT,
1218                                       "slider", x, y, w, h, orientation, false);
1219                 ENGINE.finishPainting();
1220             }
1221         }
1222     }
1223 
1224     //
1225     // TOOL_TIP
1226     //
1227     public void paintToolTipBackground(SynthContext context, Graphics g,
1228                                         int x, int y, int w,int h) {
1229         Region id = context.getRegion();
1230         synchronized (UNIXToolkit.GTK_LOCK) {
1231             if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
1232                 ENGINE.startPainting(g, x, y, w, h, id);
1233                 ENGINE.paintFlatBox(g, context, id, SynthConstants.ENABLED,
1234                         ShadowType.OUT, "tooltip", x, y, w, h,
1235                         ColorType.BACKGROUND);
1236                 ENGINE.finishPainting();
1237             }
1238         }
1239     }
1240 
1241 
1242     //
1243     // TREE_CELL
1244     //
1245     public void paintTreeCellBackground(SynthContext context, Graphics g,
1246                                         int x, int y, int w, int h) {
1247         Region id = context.getRegion();
1248         int state = context.getComponentState();
1249         int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1250         synchronized (UNIXToolkit.GTK_LOCK) {
1251             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, state)) {
1252                 ENGINE.startPainting(g, x, y, w, h, id, state);
1253                 // the string arg should alternate based on row being painted,
1254                 // but we currently don't pass that in.
1255                 ENGINE.paintFlatBox(g, context, id, gtkState, ShadowType.NONE,
1256                         "cell_odd", x, y, w, h, ColorType.TEXT_BACKGROUND);
1257                 ENGINE.finishPainting();
1258             }
1259         }
1260     }
1261 
1262     public void paintTreeCellFocus(SynthContext context, Graphics g,
1263                                     int x, int y, int w, int h) {
1264         Region id = Region.TREE_CELL;
1265         int state = context.getComponentState();
1266         paintFocus(context, g, id, state, "treeview", x, y, w, h);
1267     }
1268 
1269 
1270     //
1271     // TREE
1272     //
1273     public void paintTreeBackground(SynthContext context, Graphics g,
1274                                     int x, int y, int w, int h) {
1275         // As far as I can tell, these don't call into the ENGINE.
1276         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
1277     }
1278 
1279 
1280     //
1281     // VIEWPORT
1282     //
1283     public void paintViewportBackground(SynthContext context, Graphics g,
1284                                         int x, int y, int w, int h) {
1285         // As far as I can tell, these don't call into the ENGINE.
1286         // Also note that you don't want this to call into the ENGINE
1287         // as if it where to paint a background JViewport wouldn't scroll
1288         // correctly.
1289         fillArea(context, g, x, y, w, h, GTKColorType.TEXT_BACKGROUND);
1290     }
1291 
1292     void paintFocus(SynthContext context, Graphics g, Region id,
1293             int state, String detail, int x, int y, int w, int h) {
1294         int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1295         synchronized (UNIXToolkit.GTK_LOCK) {
1296             if (! ENGINE.paintCachedImage(g, x, y, w, h, id, gtkState, "focus")) {
1297                 ENGINE.startPainting(g, x, y, w, h, id, gtkState, "focus");
1298                 ENGINE.paintFocus(g, context, id, gtkState, detail, x, y, w, h);
1299                 ENGINE.finishPainting();
1300             }
1301         }
1302     }
1303 
1304     void paintMetacityElement(SynthContext context, Graphics g,
1305             int gtkState, String detail, int x, int y, int w, int h,
1306             ShadowType shadow, ArrowType direction) {
1307         synchronized (UNIXToolkit.GTK_LOCK) {
1308             if (! ENGINE.paintCachedImage(
1309                     g, x, y, w, h, gtkState, detail, shadow, direction)) {
1310                 ENGINE.startPainting(
1311                         g, x, y, w, h, gtkState, detail, shadow, direction);
1312                 if (detail == "metacity-arrow") {
1313                     ENGINE.paintArrow(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1314                             gtkState, shadow, direction, "", x, y, w, h);
1315 
1316                 } else if (detail == "metacity-box") {
1317                     ENGINE.paintBox(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1318                             gtkState, shadow, "", x, y, w, h);
1319 
1320                 } else if (detail == "metacity-vline") {
1321                     ENGINE.paintVline(g, context, Region.INTERNAL_FRAME_TITLE_PANE,
1322                             gtkState, "", x, y, w, h);
1323                 }
1324                 ENGINE.finishPainting();
1325             }
1326         }
1327     }
1328 
1329     void paintIcon(SynthContext context, Graphics g,
1330             Method paintMethod, int x, int y, int w, int h) {
1331         int state = context.getComponentState();
1332         synchronized (UNIXToolkit.GTK_LOCK) {
1333             if (! ENGINE.paintCachedImage(g, x, y, w, h, state, paintMethod)) {
1334                 ENGINE.startPainting(g, x, y, w, h, state, paintMethod);
1335                 try {
1336                     paintMethod.invoke(this, context, g, state, x, y, w, h);
1337                 } catch (IllegalAccessException iae) {
1338                     assert false;
1339                 } catch (InvocationTargetException ite) {
1340                     assert false;
1341                 }
1342                 ENGINE.finishPainting();
1343             }
1344         }
1345     }
1346 
1347     void paintIcon(SynthContext context, Graphics g,
1348             Method paintMethod, int x, int y, int w, int h, Object direction) {
1349         int state = context.getComponentState();
1350         synchronized (UNIXToolkit.GTK_LOCK) {
1351             if (! ENGINE.paintCachedImage(g,
1352                     x, y, w, h, state, paintMethod, direction)) {
1353                 ENGINE.startPainting(g,
1354                         x, y, w, h, state, paintMethod, direction);
1355                 try {
1356                     paintMethod.invoke(this, context,
1357                             g, state, x, y, w, h, direction);
1358                 } catch (IllegalAccessException iae) {
1359                     assert false;
1360                 } catch (InvocationTargetException ite) {
1361                     assert false;
1362                 }
1363                 ENGINE.finishPainting();
1364             }
1365         }
1366     }
1367 
1368     // All icon painting methods are called from under GTK_LOCK
1369 
1370     public void paintTreeExpandedIcon(SynthContext context,
1371             Graphics g, int state, int x, int y, int w, int h) {
1372         ENGINE.paintExpander(g, context, Region.TREE,
1373                 GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
1374                 ExpanderStyle.EXPANDED, "expander", x, y, w, h);
1375     }
1376 
1377     public void paintTreeCollapsedIcon(SynthContext context,
1378             Graphics g, int state, int x, int y, int w, int h) {
1379         ENGINE.paintExpander(g, context, Region.TREE,
1380                 GTKLookAndFeel.synthStateToGTKState(context.getRegion(), state),
1381                 ExpanderStyle.COLLAPSED, "expander", x, y, w, h);
1382     }
1383 
1384     public void paintCheckBoxIcon(SynthContext context,
1385             Graphics g, int state, int x, int y, int w, int h) {
1386         GTKStyle style = (GTKStyle)context.getStyle();
1387         int size = style.getClassSpecificIntValue(context,
1388                         "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
1389         int offset = style.getClassSpecificIntValue(context,
1390                         "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
1391 
1392         ENGINE.paintCheck(g, context, Region.CHECK_BOX, "checkbutton",
1393                 x+offset, y+offset, size, size);
1394     }
1395 
1396     public void paintRadioButtonIcon(SynthContext context,
1397             Graphics g, int state, int x, int y, int w, int h) {
1398         GTKStyle style = (GTKStyle)context.getStyle();
1399         int size = style.getClassSpecificIntValue(context,
1400                         "indicator-size", GTKIconFactory.DEFAULT_ICON_SIZE);
1401         int offset = style.getClassSpecificIntValue(context,
1402                         "indicator-spacing", GTKIconFactory.DEFAULT_ICON_SPACING);
1403 
1404         ENGINE.paintOption(g, context, Region.RADIO_BUTTON, "radiobutton",
1405                 x+offset, y+offset, size, size);
1406     }
1407 
1408     public void paintMenuArrowIcon(SynthContext context, Graphics g,
1409             int state, int x, int y, int w, int h, ArrowType dir) {
1410         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1411                 context.getRegion(), state);
1412         ShadowType shadow = ShadowType.OUT;
1413         if (gtkState == SynthConstants.MOUSE_OVER) {
1414             shadow = ShadowType.IN;
1415         }
1416         if (!GTKLookAndFeel.is3()) {
1417             x += 3;
1418             y += 3;
1419             w = h = 7;
1420         }
1421         ENGINE.paintArrow(g, context, Region.MENU_ITEM, gtkState, shadow,
1422                 dir, "menuitem", x, y, w, h);
1423     }
1424 
1425     public void paintCheckBoxMenuItemCheckIcon(SynthContext context,
1426             Graphics g, int state, int x, int y, int w, int h) {
1427 
1428         GTKStyle style = (GTKStyle)context.getStyle();
1429         int size = style.getClassSpecificIntValue(context,"indicator-size",
1430                 GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
1431 
1432         ENGINE.paintCheck(g, context, Region.CHECK_BOX_MENU_ITEM, "check",
1433                 x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1434                 y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1435                 size, size);
1436     }
1437 
1438     public void paintRadioButtonMenuItemCheckIcon(SynthContext context,
1439             Graphics g, int state, int x, int y, int w, int h) {
1440 
1441         GTKStyle style = (GTKStyle)context.getStyle();
1442         int size = style.getClassSpecificIntValue(context,"indicator-size",
1443                 GTKIconFactory.DEFAULT_TOGGLE_MENU_ITEM_SIZE);
1444 
1445         ENGINE.paintOption(g, context, Region.RADIO_BUTTON_MENU_ITEM, "option",
1446                 x + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1447                 y + GTKIconFactory.CHECK_ICON_EXTRA_INSET,
1448                 size, size);
1449     }
1450 
1451     public void paintToolBarHandleIcon(SynthContext context, Graphics g,
1452             int state, int x, int y, int w, int h, Orientation orientation) {
1453         int gtkState = GTKLookAndFeel.synthStateToGTKState(
1454                 context.getRegion(), state);
1455 
1456         // The orientation parameter passed down by Synth refers to the
1457         // orientation of the toolbar, but the one we pass to GTK refers
1458         // to the orientation of the handle.  Therefore, we need to swap
1459         // the value here: horizontal toolbars have vertical handles, and
1460         // vice versa.
1461         orientation = (orientation == Orientation.HORIZONTAL) ?
1462             Orientation.VERTICAL : Orientation.HORIZONTAL;
1463 
1464         ENGINE.paintHandle(g, context, Region.TOOL_BAR, gtkState,
1465                 ShadowType.OUT, "handlebox", x, y, w, h, orientation);
1466     }
1467 
1468     public void paintAscendingSortIcon(SynthContext context,
1469             Graphics g, int state, int x, int y, int w, int h) {
1470         ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
1471                 ShadowType.IN, ArrowType.UP, "arrow", x, y, w, h);
1472     }
1473 
1474     public void paintDescendingSortIcon(SynthContext context,
1475             Graphics g, int state, int x, int y, int w, int h) {
1476         ENGINE.paintArrow(g, context, Region.TABLE, SynthConstants.ENABLED,
1477                 ShadowType.IN, ArrowType.DOWN, "arrow", x, y, w, h);
1478     }
1479 
1480     /*
1481      * Fill an area with color determined from this context's Style using the
1482      * specified GTKColorType
1483      */
1484     private void fillArea(SynthContext context, Graphics g,
1485                           int x, int y, int w, int h, ColorType colorType) {
1486         if (context.getComponent().isOpaque()) {
1487             Region id = context.getRegion();
1488             int gtkState = GTKLookAndFeel.synthStateToGTKState(id,
1489                     context.getComponentState());
1490             GTKStyle style = (GTKStyle)context.getStyle();
1491 
1492             g.setColor(style.getGTKColor(context, gtkState, colorType));
1493             g.fillRect(x, y, w, h);
1494         }
1495     }
1496 
1497     // Refer to GTKLookAndFeel for details on this.
1498     @SuppressWarnings("serial") // Superclass is not serializable across versions
1499     static class ListTableFocusBorder extends AbstractBorder implements
1500                           UIResource {
1501 
1502         private boolean selectedCell;
1503         private boolean focusedCell;
1504 
1505         public static ListTableFocusBorder getSelectedCellBorder() {
1506             return new ListTableFocusBorder(true, true);
1507         }
1508 
1509         public static ListTableFocusBorder getUnselectedCellBorder() {
1510             return new ListTableFocusBorder(false, true);
1511         }
1512 
1513         public static ListTableFocusBorder getNoFocusCellBorder() {
1514             return new ListTableFocusBorder(false, false);
1515         }
1516 
1517         public ListTableFocusBorder(boolean selectedCell, boolean focusedCell) {
1518             this.selectedCell = selectedCell;
1519             this.focusedCell = focusedCell;
1520         }
1521 
1522         private SynthContext getContext(Component c) {
1523             SynthContext context = null;
1524 
1525             ComponentUI ui = null;
1526             if (c instanceof JLabel) {
1527                 ui = ((JLabel)c).getUI();
1528             }
1529 
1530             if (ui instanceof SynthUI) {
1531                 context = ((SynthUI)ui).getContext((JComponent)c);
1532             }
1533 
1534             return context;
1535         }
1536 
1537         public void paintBorder(Component c, Graphics g, int x, int y,
1538                                 int w, int h) {
1539             if (focusedCell) {
1540                 SynthContext context = getContext(c);
1541                 int state = (selectedCell? SynthConstants.SELECTED:
1542                              SynthConstants.FOCUSED | SynthConstants.ENABLED);
1543 
1544                 if (context != null) {
1545                     GTKPainter.INSTANCE.paintFocus(context, g,
1546                             Region.TABLE, state, "", x, y, w, h);
1547                 }
1548             }
1549         }
1550 
1551         public Insets getBorderInsets(Component c, Insets i) {
1552             SynthContext context = getContext(c);
1553 
1554             if (context != null) {
1555                 i = context.getStyle().getInsets(context, i);
1556             }
1557 
1558             return i;
1559         }
1560 
1561         public boolean isBorderOpaque() {
1562             return true;
1563         }
1564     }
1565 
1566     // TitledBorder implementation for GTK L&F
1567     @SuppressWarnings("serial") // Superclass is not serializable across versions
1568     static class TitledBorder extends AbstractBorder implements UIResource {
1569 
1570         public void paintBorder(Component c, Graphics g, int x, int y,
1571                                 int w, int h) {
1572             SynthContext context = getContext((JComponent)c);
1573             Region id = context.getRegion();
1574             int state = context.getComponentState();
1575             int gtkState = GTKLookAndFeel.synthStateToGTKState(id, state);
1576 
1577             synchronized (UNIXToolkit.GTK_LOCK) {
1578                 if (! ENGINE.paintCachedImage(g, x, y, w, h, id)) {
1579                     ENGINE.startPainting(g, x, y, w, h, id);
1580                     ENGINE.paintShadow(g, context, id, gtkState, ShadowType.ETCHED_IN,
1581                                       "frame", x, y, w, h);
1582                     ENGINE.finishPainting();
1583                 }
1584             }
1585         }
1586 
1587         public Insets getBorderInsets(Component c, Insets i) {
1588             SynthContext context = getContext((JComponent)c);
1589             return context.getStyle().getInsets(context, i);
1590         }
1591 
1592         public boolean isBorderOpaque() {
1593             return true;
1594         }
1595 
1596         private SynthStyle getStyle(JComponent c) {
1597             return SynthLookAndFeel.getStyle(c, GTKEngine.CustomRegion.TITLED_BORDER);
1598         }
1599 
1600         private SynthContext getContext(JComponent c) {
1601             int state = SynthConstants.DEFAULT;
1602             return new SynthContext(c, GTKEngine.CustomRegion.TITLED_BORDER,
1603                                     getStyle(c), state);
1604         }
1605     }
1606 }