1 /*
   2  * Copyright (c) 1997, 2006, 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     private class WindowsArrowButton extends BasicArrowButton {
 277 
 278         public WindowsArrowButton(int direction, Color background, Color shadow,
 279                          Color darkShadow, Color highlight) {
 280             super(direction, background, shadow, darkShadow, highlight);
 281         }
 282 
 283         public WindowsArrowButton(int direction) {
 284             super(direction);
 285         }
 286 
 287         public void paint(Graphics g) {
 288             XPStyle xp = XPStyle.getXP();
 289             if (xp != null) {
 290                 ButtonModel model = getModel();
 291                 Skin skin = xp.getSkin(this, Part.SBP_ARROWBTN);
 292                 State state = null;
 293 
 294                 boolean jointRollover = XPStyle.isVista() && (isThumbRollover() ||
 295                     (this == incrButton && decrButton.getModel().isRollover()) ||
 296                     (this == decrButton && incrButton.getModel().isRollover()));
 297 
 298                 // normal, rollover, pressed, disabled
 299                 if (model.isArmed() && model.isPressed()) {
 300                     switch (direction) {
 301                         case NORTH: state = State.UPPRESSED;    break;
 302                         case SOUTH: state = State.DOWNPRESSED;  break;
 303                         case WEST:  state = State.LEFTPRESSED;  break;
 304                         case EAST:  state = State.RIGHTPRESSED; break;
 305                     }
 306                 } else if (!model.isEnabled()) {
 307                     switch (direction) {
 308                         case NORTH: state = State.UPDISABLED;    break;
 309                         case SOUTH: state = State.DOWNDISABLED;  break;
 310                         case WEST:  state = State.LEFTDISABLED;  break;
 311                         case EAST:  state = State.RIGHTDISABLED; break;
 312                     }
 313                 } else if (model.isRollover() || model.isPressed()) {
 314                     switch (direction) {
 315                         case NORTH: state = State.UPHOT;    break;
 316                         case SOUTH: state = State.DOWNHOT;  break;
 317                         case WEST:  state = State.LEFTHOT;  break;
 318                         case EAST:  state = State.RIGHTHOT; break;
 319                     }
 320                 } else if (jointRollover) {
 321                     switch (direction) {
 322                         case NORTH: state = State.UPHOVER;    break;
 323                         case SOUTH: state = State.DOWNHOVER;  break;
 324                         case WEST:  state = State.LEFTHOVER;  break;
 325                         case EAST:  state = State.RIGHTHOVER; break;
 326                     }
 327                 } else {
 328                     switch (direction) {
 329                         case NORTH: state = State.UPNORMAL;    break;
 330                         case SOUTH: state = State.DOWNNORMAL;  break;
 331                         case WEST:  state = State.LEFTNORMAL;  break;
 332                         case EAST:  state = State.RIGHTNORMAL; break;
 333                     }
 334                 }
 335 
 336                 skin.paintSkin(g, 0, 0, getWidth(), getHeight(), state);
 337             } else {
 338                 super.paint(g);
 339             }
 340         }
 341 
 342         public Dimension getPreferredSize() {
 343             int size = 16;
 344             if (scrollbar != null) {
 345                 switch (scrollbar.getOrientation()) {
 346                 case JScrollBar.VERTICAL:
 347                     size = scrollbar.getWidth();
 348                     break;
 349                 case JScrollBar.HORIZONTAL:
 350                     size = scrollbar.getHeight();
 351                     break;
 352                 }
 353                 size = Math.max(size, 5);
 354             }
 355             return new Dimension(size, size);
 356         }
 357     }
 358 
 359 
 360     /**
 361      * This should be pulled out into its own class if more classes need to
 362      * use it.
 363      * <p>
 364      * Grid is used to draw the track for windows scrollbars. Grids
 365      * are cached in a HashMap, with the key being the rgb components
 366      * of the foreground/background colors. Further the Grid is held through
 367      * a WeakRef so that it can be freed when no longer needed. As the
 368      * Grid is rather expensive to draw, it is drawn in a BufferedImage.
 369      */
 370     private static class Grid {
 371         private static final int BUFFER_SIZE = 64;
 372         private static HashMap<String, WeakReference<Grid>> map;
 373 
 374         private BufferedImage image;
 375 
 376         static {
 377             map = new HashMap<String, WeakReference<Grid>>();
 378         }
 379 
 380         public static Grid getGrid(Color fg, Color bg) {
 381             String key = fg.getRGB() + " " + bg.getRGB();
 382             WeakReference<Grid> ref = map.get(key);
 383             Grid grid = (ref == null) ? null : ref.get();
 384             if (grid == null) {
 385                 grid = new Grid(fg, bg);
 386                 map.put(key, new WeakReference<Grid>(grid));
 387             }
 388             return grid;
 389         }
 390 
 391         public Grid(Color fg, Color bg) {
 392             int cmap[] = { fg.getRGB(), bg.getRGB() };
 393             IndexColorModel icm = new IndexColorModel(8, 2, cmap, 0, false, -1,
 394                                                       DataBuffer.TYPE_BYTE);
 395             image = new BufferedImage(BUFFER_SIZE, BUFFER_SIZE,
 396                                       BufferedImage.TYPE_BYTE_INDEXED, icm);
 397             Graphics g = image.getGraphics();
 398             try {
 399                 g.setClip(0, 0, BUFFER_SIZE, BUFFER_SIZE);
 400                 paintGrid(g, fg, bg);
 401             }
 402             finally {
 403                 g.dispose();
 404             }
 405         }
 406 
 407         /**
 408          * Paints the grid into the specified Graphics at the specified
 409          * location.
 410          */
 411         public void paint(Graphics g, int x, int y, int w, int h) {
 412             Rectangle clipRect = g.getClipBounds();
 413             int minX = Math.max(x, clipRect.x);
 414             int minY = Math.max(y, clipRect.y);
 415             int maxX = Math.min(clipRect.x + clipRect.width, x + w);
 416             int maxY = Math.min(clipRect.y + clipRect.height, y + h);
 417 
 418             if (maxX <= minX || maxY <= minY) {
 419                 return;
 420             }
 421             int xOffset = (minX - x) % 2;
 422             for (int xCounter = minX; xCounter < maxX;
 423                  xCounter += BUFFER_SIZE) {
 424                 int yOffset = (minY - y) % 2;
 425                 int width = Math.min(BUFFER_SIZE - xOffset,
 426                                      maxX - xCounter);
 427 
 428                 for (int yCounter = minY; yCounter < maxY;
 429                      yCounter += BUFFER_SIZE) {
 430                     int height = Math.min(BUFFER_SIZE - yOffset,
 431                                           maxY - yCounter);
 432 
 433                     g.drawImage(image, xCounter, yCounter,
 434                                 xCounter + width, yCounter + height,
 435                                 xOffset, yOffset,
 436                                 xOffset + width, yOffset + height, null);
 437                     if (yOffset != 0) {
 438                         yCounter -= yOffset;
 439                         yOffset = 0;
 440                     }
 441                 }
 442                 if (xOffset != 0) {
 443                     xCounter -= xOffset;
 444                     xOffset = 0;
 445                 }
 446             }
 447         }
 448 
 449         /**
 450          * Actually renders the grid into the Graphics <code>g</code>.
 451          */
 452         private void paintGrid(Graphics g, Color fg, Color bg) {
 453             Rectangle clipRect = g.getClipBounds();
 454             g.setColor(bg);
 455             g.fillRect(clipRect.x, clipRect.y, clipRect.width,
 456                        clipRect.height);
 457             g.setColor(fg);
 458             g.translate(clipRect.x, clipRect.y);
 459             int width = clipRect.width;
 460             int height = clipRect.height;
 461             int xCounter = clipRect.x % 2;
 462             for (int end = width - height; xCounter < end; xCounter += 2) {
 463                 g.drawLine(xCounter, 0, xCounter + height, height);
 464             }
 465             for (int end = width; xCounter < end; xCounter += 2) {
 466                 g.drawLine(xCounter, 0, width, width - xCounter);
 467             }
 468 
 469             int yCounter = ((clipRect.x % 2) == 0) ? 2 : 1;
 470             for (int end = height - width; yCounter < end; yCounter += 2) {
 471                 g.drawLine(0, yCounter, width, yCounter + width);
 472             }
 473             for (int end = height; yCounter < end; yCounter += 2) {
 474                 g.drawLine(0, yCounter, height - yCounter, height);
 475             }
 476             g.translate(-clipRect.x, -clipRect.y);
 477         }
 478     }
 479 }