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 }