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