1 /*
   2  * Copyright (c) 1997, 2014, 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 
  26 package com.sun.java.swing.plaf.windows;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.image.*;
  31 import java.lang.ref.*;
  32 import java.util.*;
  33 import javax.swing.plaf.basic.*;
  34 import javax.swing.*;
  35 import javax.swing.plaf.ComponentUI;
  36 
  37 import static com.sun.java.swing.plaf.windows.TMSchema.*;
  38 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
  39 
  40 
  41 /**
  42  * Windows rendition of the component.
  43  * <p>
  44  * <strong>Warning:</strong>
  45  * Serialized objects of this class will not be compatible with
  46  * future Swing releases.  The current serialization support is appropriate
  47  * for short term storage or RMI between applications running the same
  48  * version of Swing.  A future release of Swing will provide support for
  49  * long term persistence.
  50  */
  51 public class WindowsScrollBarUI extends BasicScrollBarUI {
  52     private Grid thumbGrid;
  53     private Grid highlightGrid;
  54 
  55     /**
  56      * Creates a UI for a JScrollBar.
  57      *
  58      * @param c the text field
  59      * @return the UI
  60      */
  61     public static ComponentUI createUI(JComponent c) {
  62         return new WindowsScrollBarUI();
  63     }
  64 
  65     protected void installDefaults() {
  66         super.installDefaults();
  67 
  68         if (XPStyle.getXP() != null) {
  69             scrollbar.setBorder(null);
  70         }
  71     }
  72 
  73     public void uninstallUI(JComponent c) {
  74         super.uninstallUI(c);
  75         thumbGrid = highlightGrid = null;
  76     }
  77 
  78     protected void configureScrollBarColors() {
  79         super.configureScrollBarColors();
  80         Color color = UIManager.getColor("ScrollBar.trackForeground");
  81         if (color != null && trackColor != null) {
  82             thumbGrid = Grid.getGrid(color, trackColor);
  83         }
  84 
  85         color = UIManager.getColor("ScrollBar.trackHighlightForeground");
  86         if (color != null && trackHighlightColor != null) {
  87             highlightGrid = Grid.getGrid(color, trackHighlightColor);
  88         }
  89     }
  90 
  91     protected JButton createDecreaseButton(int orientation)  {
  92         return new WindowsArrowButton(orientation,
  93                                     UIManager.getColor("ScrollBar.thumb"),
  94                                     UIManager.getColor("ScrollBar.thumbShadow"),
  95                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
  96                                     UIManager.getColor("ScrollBar.thumbHighlight"));
  97     }
  98 
  99     protected JButton createIncreaseButton(int orientation)  {
 100         return new WindowsArrowButton(orientation,
 101                                     UIManager.getColor("ScrollBar.thumb"),
 102                                     UIManager.getColor("ScrollBar.thumbShadow"),
 103                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
 104                                     UIManager.getColor("ScrollBar.thumbHighlight"));
 105     }
 106 
 107     /**
 108      * {@inheritDoc}
 109      * @since 1.6
 110      */
 111     @Override
 112     protected ArrowButtonListener createArrowButtonListener(){
 113         // we need to repaint the entire scrollbar because state change for each
 114         // button causes a state change for the thumb and other button on Vista
 115         if(XPStyle.isVista()) {
 116             return new ArrowButtonListener() {
 117                 public void mouseEntered(MouseEvent evt) {
 118                     repaint();
 119                     super.mouseEntered(evt);
 120                 }
 121                 public void mouseExited(MouseEvent evt) {
 122                     repaint();
 123                     super.mouseExited(evt);
 124                 }
 125                 private void repaint() {
 126                     scrollbar.repaint();
 127                 }
 128             };
 129         } else {
 130             return super.createArrowButtonListener();
 131         }
 132     }
 133 
 134     protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds){
 135         boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);
 136 
 137         XPStyle xp = XPStyle.getXP();
 138         if (xp != null) {
 139             JScrollBar sb = (JScrollBar)c;
 140             State state = State.NORMAL;
 141             // Pending: Implement rollover (hot) and pressed
 142             if (!sb.isEnabled()) {
 143                 state = State.DISABLED;
 144             }
 145             Part part = v ? Part.SBP_LOWERTRACKVERT : Part.SBP_LOWERTRACKHORZ;
 146             xp.getSkin(sb, part).paintSkin(g, trackBounds, state);
 147         } else if (thumbGrid == null) {
 148             super.paintTrack(g, c, trackBounds);
 149         }
 150         else {
 151             thumbGrid.paint(g, trackBounds.x, trackBounds.y, trackBounds.width,
 152                             trackBounds.height);
 153             if (trackHighlight == DECREASE_HIGHLIGHT) {
 154                 paintDecreaseHighlight(g);
 155             }
 156             else if (trackHighlight == INCREASE_HIGHLIGHT) {
 157                 paintIncreaseHighlight(g);
 158             }
 159         }
 160     }
 161 
 162     protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
 163         boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);
 164 
 165         XPStyle xp = XPStyle.getXP();
 166         if (xp != null) {
 167             JScrollBar sb = (JScrollBar)c;
 168             State state = State.NORMAL;
 169             if (!sb.isEnabled()) {
 170                 state = State.DISABLED;
 171             } else if (isDragging) {
 172                 state = State.PRESSED;
 173             } else if (isThumbRollover()) {
 174                 state = State.HOT;
 175             } else if (XPStyle.isVista()) {
 176                 if ((incrButton != null && incrButton.getModel().isRollover()) ||
 177                     (decrButton != null && decrButton.getModel().isRollover())) {
 178                     state = State.HOVER;
 179                 }
 180             }
 181             // Paint thumb
 182             Part thumbPart = v ? Part.SBP_THUMBBTNVERT : Part.SBP_THUMBBTNHORZ;
 183             xp.getSkin(sb, thumbPart).paintSkin(g, thumbBounds, state);
 184             // Paint gripper
 185             Part gripperPart = v ? Part.SBP_GRIPPERVERT : Part.SBP_GRIPPERHORZ;
 186             Skin skin = xp.getSkin(sb, gripperPart);
 187             Insets gripperInsets = xp.getMargin(c, thumbPart, null, Prop.CONTENTMARGINS);
 188             if (gripperInsets == null ||
 189                 (v && (thumbBounds.height - gripperInsets.top -
 190                        gripperInsets.bottom >= skin.getHeight())) ||
 191                 (!v && (thumbBounds.width - gripperInsets.left -
 192                         gripperInsets.right >= skin.getWidth()))) {
 193                 skin.paintSkin(g,
 194                                thumbBounds.x + (thumbBounds.width  - skin.getWidth()) / 2,
 195                                thumbBounds.y + (thumbBounds.height - skin.getHeight()) / 2,
 196                                skin.getWidth(), skin.getHeight(), state);
 197             }
 198         } else {
 199             super.paintThumb(g, c, thumbBounds);
 200         }
 201     }
 202 
 203 
 204     protected void paintDecreaseHighlight(Graphics g) {
 205         if (highlightGrid == null) {
 206             super.paintDecreaseHighlight(g);
 207         }
 208         else {
 209             Insets insets = scrollbar.getInsets();
 210             Rectangle thumbR = getThumbBounds();
 211             int x, y, w, h;
 212 
 213             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 214                 x = insets.left;
 215                 y = decrButton.getY() + decrButton.getHeight();
 216                 w = scrollbar.getWidth() - (insets.left + insets.right);
 217                 h = thumbR.y - y;
 218             }
 219             else {
 220                 x = decrButton.getX() + decrButton.getHeight();
 221                 y = insets.top;
 222                 w = thumbR.x - x;
 223                 h = scrollbar.getHeight() - (insets.top + insets.bottom);
 224             }
 225             highlightGrid.paint(g, x, y, w, h);
 226         }
 227     }
 228 
 229 
 230     protected void paintIncreaseHighlight(Graphics g) {
 231         if (highlightGrid == null) {
 232             super.paintDecreaseHighlight(g);
 233         }
 234         else {
 235             Insets insets = scrollbar.getInsets();
 236             Rectangle thumbR = getThumbBounds();
 237             int x, y, w, h;
 238 
 239             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 240                 x = insets.left;
 241                 y = thumbR.y + thumbR.height;
 242                 w = scrollbar.getWidth() - (insets.left + insets.right);
 243                 h = incrButton.getY() - y;
 244             }
 245             else {
 246                 x = thumbR.x + thumbR.width;
 247                 y = insets.top;
 248                 w = incrButton.getX() - x;
 249                 h = scrollbar.getHeight() - (insets.top + insets.bottom);
 250             }
 251             highlightGrid.paint(g, x, y, w, h);
 252         }
 253     }
 254 
 255 
 256     /**
 257      * {@inheritDoc}
 258      * @since 1.6
 259      */
 260     @Override
 261     protected void setThumbRollover(boolean active) {
 262         boolean old = isThumbRollover();
 263         super.setThumbRollover(active);
 264         // we need to repaint the entire scrollbar because state change for thumb
 265         // causes state change for incr and decr buttons on Vista
 266         if(XPStyle.isVista() && active != old) {
 267             scrollbar.repaint();
 268         }
 269     }
 270 
 271     /**
 272      * WindowsArrowButton is used for the buttons to position the
 273      * document up/down. It differs from BasicArrowButton in that the
 274      * preferred size is always a square.
 275      */
 276     @SuppressWarnings("serial") // Superclass is not serializable across versions
 277     private class WindowsArrowButton extends BasicArrowButton {
 278 
 279         public WindowsArrowButton(int direction, Color background, Color shadow,
 280                          Color darkShadow, Color highlight) {
 281             super(direction, background, shadow, darkShadow, highlight);
 282         }
 283 
 284         public WindowsArrowButton(int direction) {
 285             super(direction);
 286         }
 287 
 288         public void paint(Graphics g) {
 289             XPStyle xp = XPStyle.getXP();
 290             if (xp != null) {
 291                 ButtonModel model = getModel();
 292                 Skin skin = xp.getSkin(this, Part.SBP_ARROWBTN);
 293                 State state = null;
 294 
 295                 boolean jointRollover = XPStyle.isVista() && (isThumbRollover() ||
 296                     (this == incrButton && decrButton.getModel().isRollover()) ||
 297                     (this == decrButton && incrButton.getModel().isRollover()));
 298 
 299                 // normal, rollover, pressed, disabled
 300                 if (model.isArmed() && model.isPressed()) {
 301                     switch (direction) {
 302                         case NORTH: state = State.UPPRESSED;    break;
 303                         case SOUTH: state = State.DOWNPRESSED;  break;
 304                         case WEST:  state = State.LEFTPRESSED;  break;
 305                         case EAST:  state = State.RIGHTPRESSED; break;
 306                     }
 307                 } else if (!model.isEnabled()) {
 308                     switch (direction) {
 309                         case NORTH: state = State.UPDISABLED;    break;
 310                         case SOUTH: state = State.DOWNDISABLED;  break;
 311                         case WEST:  state = State.LEFTDISABLED;  break;
 312                         case EAST:  state = State.RIGHTDISABLED; break;
 313                     }
 314                 } else if (model.isRollover() || model.isPressed()) {
 315                     switch (direction) {
 316                         case NORTH: state = State.UPHOT;    break;
 317                         case SOUTH: state = State.DOWNHOT;  break;
 318                         case WEST:  state = State.LEFTHOT;  break;
 319                         case EAST:  state = State.RIGHTHOT; break;
 320                     }
 321                 } else if (jointRollover) {
 322                     switch (direction) {
 323                         case NORTH: state = State.UPHOVER;    break;
 324                         case SOUTH: state = State.DOWNHOVER;  break;
 325                         case WEST:  state = State.LEFTHOVER;  break;
 326                         case EAST:  state = State.RIGHTHOVER; break;
 327                     }
 328                 } else {
 329                     switch (direction) {
 330                         case NORTH: state = State.UPNORMAL;    break;
 331                         case SOUTH: state = State.DOWNNORMAL;  break;
 332                         case WEST:  state = State.LEFTNORMAL;  break;
 333                         case EAST:  state = State.RIGHTNORMAL; break;
 334                     }
 335                 }
 336 
 337                 skin.paintSkin(g, 0, 0, getWidth(), getHeight(), state);
 338             } else {
 339                 super.paint(g);
 340             }
 341         }
 342 
 343         public Dimension getPreferredSize() {
 344             int size = 16;
 345             if (scrollbar != null) {
 346                 switch (scrollbar.getOrientation()) {
 347                 case JScrollBar.VERTICAL:
 348                     size = scrollbar.getWidth();
 349                     break;
 350                 case JScrollBar.HORIZONTAL:
 351                     size = scrollbar.getHeight();
 352                     break;
 353                 }
 354                 size = Math.max(size, 5);
 355             }
 356             return new Dimension(size, size);
 357         }
 358     }
 359 
 360 
 361     /**
 362      * This should be pulled out into its own class if more classes need to
 363      * use it.
 364      * <p>
 365      * Grid is used to draw the track for windows scrollbars. Grids
 366      * are cached in a HashMap, with the key being the rgb components
 367      * of the foreground/background colors. Further the Grid is held through
 368      * a WeakRef so that it can be freed when no longer needed. As the
 369      * Grid is rather expensive to draw, it is drawn in a BufferedImage.
 370      */
 371     private static class Grid {
 372         private static final int BUFFER_SIZE = 64;
 373         private static HashMap<String, WeakReference<Grid>> map;
 374 
 375         private BufferedImage image;
 376 
 377         static {
 378             map = new HashMap<String, WeakReference<Grid>>();
 379         }
 380 
 381         public static Grid getGrid(Color fg, Color bg) {
 382             String key = fg.getRGB() + " " + bg.getRGB();
 383             WeakReference<Grid> ref = map.get(key);
 384             Grid grid = (ref == null) ? null : ref.get();
 385             if (grid == null) {
 386                 grid = new Grid(fg, bg);
 387                 map.put(key, new WeakReference<Grid>(grid));
 388             }
 389             return grid;
 390         }
 391 
 392         public Grid(Color fg, Color bg) {
 393             int cmap[] = { fg.getRGB(), bg.getRGB() };
 394             IndexColorModel icm = new IndexColorModel(8, 2, cmap, 0, false, -1,
 395                                                       DataBuffer.TYPE_BYTE);
 396             image = new BufferedImage(BUFFER_SIZE, BUFFER_SIZE,
 397                                       BufferedImage.TYPE_BYTE_INDEXED, icm);
 398             Graphics g = image.getGraphics();
 399             try {
 400                 g.setClip(0, 0, BUFFER_SIZE, BUFFER_SIZE);
 401                 paintGrid(g, fg, bg);
 402             }
 403             finally {
 404                 g.dispose();
 405             }
 406         }
 407 
 408         /**
 409          * Paints the grid into the specified Graphics at the specified
 410          * location.
 411          */
 412         public void paint(Graphics g, int x, int y, int w, int h) {
 413             Rectangle clipRect = g.getClipBounds();
 414             int minX = Math.max(x, clipRect.x);
 415             int minY = Math.max(y, clipRect.y);
 416             int maxX = Math.min(clipRect.x + clipRect.width, x + w);
 417             int maxY = Math.min(clipRect.y + clipRect.height, y + h);
 418 
 419             if (maxX <= minX || maxY <= minY) {
 420                 return;
 421             }
 422             int xOffset = (minX - x) % 2;
 423             for (int xCounter = minX; xCounter < maxX;
 424                  xCounter += BUFFER_SIZE) {
 425                 int yOffset = (minY - y) % 2;
 426                 int width = Math.min(BUFFER_SIZE - xOffset,
 427                                      maxX - xCounter);
 428 
 429                 for (int yCounter = minY; yCounter < maxY;
 430                      yCounter += BUFFER_SIZE) {
 431                     int height = Math.min(BUFFER_SIZE - yOffset,
 432                                           maxY - yCounter);
 433 
 434                     g.drawImage(image, xCounter, yCounter,
 435                                 xCounter + width, yCounter + height,
 436                                 xOffset, yOffset,
 437                                 xOffset + width, yOffset + height, null);
 438                     if (yOffset != 0) {
 439                         yCounter -= yOffset;
 440                         yOffset = 0;
 441                     }
 442                 }
 443                 if (xOffset != 0) {
 444                     xCounter -= xOffset;
 445                     xOffset = 0;
 446                 }
 447             }
 448         }
 449 
 450         /**
 451          * Actually renders the grid into the Graphics <code>g</code>.
 452          */
 453         private void paintGrid(Graphics g, Color fg, Color bg) {
 454             Rectangle clipRect = g.getClipBounds();
 455             g.setColor(bg);
 456             g.fillRect(clipRect.x, clipRect.y, clipRect.width,
 457                        clipRect.height);
 458             g.setColor(fg);
 459             g.translate(clipRect.x, clipRect.y);
 460             int width = clipRect.width;
 461             int height = clipRect.height;
 462             int xCounter = clipRect.x % 2;
 463             for (int end = width - height; xCounter < end; xCounter += 2) {
 464                 g.drawLine(xCounter, 0, xCounter + height, height);
 465             }
 466             for (int end = width; xCounter < end; xCounter += 2) {
 467                 g.drawLine(xCounter, 0, width, width - xCounter);
 468             }
 469 
 470             int yCounter = ((clipRect.x % 2) == 0) ? 2 : 1;
 471             for (int end = height - width; yCounter < end; yCounter += 2) {
 472                 g.drawLine(0, yCounter, width, yCounter + width);
 473             }
 474             for (int end = height; yCounter < end; yCounter += 2) {
 475                 g.drawLine(0, yCounter, height - yCounter, height);
 476             }
 477             g.translate(-clipRect.x, -clipRect.y);
 478         }
 479     }
 480 }