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