1 /* 2 * Copyright (c) 1998, 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 javax.swing.plaf.metal; 27 28 import javax.swing.plaf.basic.BasicSliderUI; 29 30 import java.awt.Graphics; 31 import java.awt.Dimension; 32 import java.awt.Rectangle; 33 import java.awt.Color; 34 import java.beans.*; 35 36 import javax.swing.*; 37 import javax.swing.plaf.*; 38 39 /** 40 * A Java L&F implementation of SliderUI. 41 * <p> 42 * <strong>Warning:</strong> 43 * Serialized objects of this class will not be compatible with 44 * future Swing releases. The current serialization support is 45 * appropriate for short term storage or RMI between applications running 46 * the same version of Swing. As of 1.4, support for long term storage 47 * of all JavaBeans™ 48 * has been added to the <code>java.beans</code> package. 49 * Please see {@link java.beans.XMLEncoder}. 50 * 51 * @author Tom Santos 52 */ 53 @SuppressWarnings("serial") // Same-version serialization only 54 public class MetalSliderUI extends BasicSliderUI { 55 56 protected final int TICK_BUFFER = 4; 57 protected boolean filledSlider = false; 58 // NOTE: these next five variables are currently unused. 59 protected static Color thumbColor; 60 protected static Color highlightColor; 61 protected static Color darkShadowColor; 62 protected static int trackWidth; 63 protected static int tickLength; 64 private int safeLength; 65 66 /** 67 * A default horizontal thumb <code>Icon</code>. This field might not be 68 * used. To change the <code>Icon</code> used by this delegate directly set it 69 * using the <code>Slider.horizontalThumbIcon</code> UIManager property. 70 */ 71 protected static Icon horizThumbIcon; 72 73 /** 74 * A default vertical thumb <code>Icon</code>. This field might not be 75 * used. To change the <code>Icon</code> used by this delegate directly set it 76 * using the <code>Slider.verticalThumbIcon</code> UIManager property. 77 */ 78 protected static Icon vertThumbIcon; 79 80 private static Icon SAFE_HORIZ_THUMB_ICON; 81 private static Icon SAFE_VERT_THUMB_ICON; 82 83 84 protected final String SLIDER_FILL = "JSlider.isFilled"; 85 86 public static ComponentUI createUI(JComponent c) { 87 return new MetalSliderUI(); 88 } 89 90 public MetalSliderUI() { 91 super( null ); 92 } 93 94 private static Icon getHorizThumbIcon() { 95 if (System.getSecurityManager() != null) { 96 return SAFE_HORIZ_THUMB_ICON; 97 } else { 98 return horizThumbIcon; 99 } 100 } 101 102 private static Icon getVertThumbIcon() { 103 if (System.getSecurityManager() != null) { 104 return SAFE_VERT_THUMB_ICON; 105 } else { 106 return vertThumbIcon; 107 } 108 } 109 110 public void installUI( JComponent c ) { 111 trackWidth = ((Integer)UIManager.get( "Slider.trackWidth" )).intValue(); 112 tickLength = safeLength = ((Integer)UIManager.get( "Slider.majorTickLength" )).intValue(); 113 horizThumbIcon = SAFE_HORIZ_THUMB_ICON = 114 UIManager.getIcon( "Slider.horizontalThumbIcon" ); 115 vertThumbIcon = SAFE_VERT_THUMB_ICON = 116 UIManager.getIcon( "Slider.verticalThumbIcon" ); 117 118 super.installUI( c ); 119 120 thumbColor = UIManager.getColor("Slider.thumb"); 121 highlightColor = UIManager.getColor("Slider.highlight"); 122 darkShadowColor = UIManager.getColor("Slider.darkShadow"); 123 124 scrollListener.setScrollByBlock( false ); 125 126 prepareFilledSliderField(); 127 } 128 129 protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) { 130 return new MetalPropertyListener(); 131 } 132 133 protected class MetalPropertyListener extends BasicSliderUI.PropertyChangeHandler { 134 public void propertyChange( PropertyChangeEvent e ) { // listen for slider fill 135 super.propertyChange( e ); 136 137 if (e.getPropertyName().equals(SLIDER_FILL)) { 138 prepareFilledSliderField(); 139 } 140 } 141 } 142 143 private void prepareFilledSliderField() { 144 // Use true for Ocean theme 145 filledSlider = MetalLookAndFeel.usingOcean(); 146 147 Object sliderFillProp = slider.getClientProperty(SLIDER_FILL); 148 149 if (sliderFillProp != null) { 150 filledSlider = ((Boolean) sliderFillProp).booleanValue(); 151 } 152 } 153 154 public void paintThumb(Graphics g) { 155 Rectangle knobBounds = thumbRect; 156 157 g.translate( knobBounds.x, knobBounds.y ); 158 159 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 160 getHorizThumbIcon().paintIcon( slider, g, 0, 0 ); 161 } 162 else { 163 getVertThumbIcon().paintIcon( slider, g, 0, 0 ); 164 } 165 166 g.translate( -knobBounds.x, -knobBounds.y ); 167 } 168 169 /** 170 * Returns a rectangle enclosing the track that will be painted. 171 */ 172 private Rectangle getPaintTrackRect() { 173 int trackLeft = 0, trackRight, trackTop = 0, trackBottom; 174 if (slider.getOrientation() == JSlider.HORIZONTAL) { 175 trackBottom = (trackRect.height - 1) - getThumbOverhang(); 176 trackTop = trackBottom - (getTrackWidth() - 1); 177 trackRight = trackRect.width - 1; 178 } 179 else { 180 if (MetalUtils.isLeftToRight(slider)) { 181 trackLeft = (trackRect.width - getThumbOverhang()) - 182 getTrackWidth(); 183 trackRight = (trackRect.width - getThumbOverhang()) - 1; 184 } 185 else { 186 trackLeft = getThumbOverhang(); 187 trackRight = getThumbOverhang() + getTrackWidth() - 1; 188 } 189 trackBottom = trackRect.height - 1; 190 } 191 return new Rectangle(trackRect.x + trackLeft, trackRect.y + trackTop, 192 trackRight - trackLeft, trackBottom - trackTop); 193 } 194 195 public void paintTrack(Graphics g) { 196 if (MetalLookAndFeel.usingOcean()) { 197 oceanPaintTrack(g); 198 return; 199 } 200 Color trackColor = !slider.isEnabled() ? MetalLookAndFeel.getControlShadow() : 201 slider.getForeground(); 202 203 boolean leftToRight = MetalUtils.isLeftToRight(slider); 204 205 g.translate( trackRect.x, trackRect.y ); 206 207 int trackLeft = 0; 208 int trackTop = 0; 209 int trackRight; 210 int trackBottom; 211 212 // Draw the track 213 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 214 trackBottom = (trackRect.height - 1) - getThumbOverhang(); 215 trackTop = trackBottom - (getTrackWidth() - 1); 216 trackRight = trackRect.width - 1; 217 } 218 else { 219 if (leftToRight) { 220 trackLeft = (trackRect.width - getThumbOverhang()) - 221 getTrackWidth(); 222 trackRight = (trackRect.width - getThumbOverhang()) - 1; 223 } 224 else { 225 trackLeft = getThumbOverhang(); 226 trackRight = getThumbOverhang() + getTrackWidth() - 1; 227 } 228 trackBottom = trackRect.height - 1; 229 } 230 231 if ( slider.isEnabled() ) { 232 g.setColor( MetalLookAndFeel.getControlDarkShadow() ); 233 g.drawRect( trackLeft, trackTop, 234 (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 ); 235 236 g.setColor( MetalLookAndFeel.getControlHighlight() ); 237 g.drawLine( trackLeft + 1, trackBottom, trackRight, trackBottom ); 238 g.drawLine( trackRight, trackTop + 1, trackRight, trackBottom ); 239 240 g.setColor( MetalLookAndFeel.getControlShadow() ); 241 g.drawLine( trackLeft + 1, trackTop + 1, trackRight - 2, trackTop + 1 ); 242 g.drawLine( trackLeft + 1, trackTop + 1, trackLeft + 1, trackBottom - 2 ); 243 } 244 else { 245 g.setColor( MetalLookAndFeel.getControlShadow() ); 246 g.drawRect( trackLeft, trackTop, 247 (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 ); 248 } 249 250 // Draw the fill 251 if ( filledSlider ) { 252 int middleOfThumb; 253 int fillTop; 254 int fillLeft; 255 int fillBottom; 256 int fillRight; 257 258 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 259 middleOfThumb = thumbRect.x + (thumbRect.width / 2); 260 middleOfThumb -= trackRect.x; // To compensate for the g.translate() 261 fillTop = !slider.isEnabled() ? trackTop : trackTop + 1; 262 fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2; 263 264 if ( !drawInverted() ) { 265 fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1; 266 fillRight = middleOfThumb; 267 } 268 else { 269 fillLeft = middleOfThumb; 270 fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2; 271 } 272 } 273 else { 274 middleOfThumb = thumbRect.y + (thumbRect.height / 2); 275 middleOfThumb -= trackRect.y; // To compensate for the g.translate() 276 fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1; 277 fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2; 278 279 if ( !drawInverted() ) { 280 fillTop = middleOfThumb; 281 fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2; 282 } 283 else { 284 fillTop = !slider.isEnabled() ? trackTop : trackTop + 1; 285 fillBottom = middleOfThumb; 286 } 287 } 288 289 if ( slider.isEnabled() ) { 290 g.setColor( slider.getBackground() ); 291 g.drawLine( fillLeft, fillTop, fillRight, fillTop ); 292 g.drawLine( fillLeft, fillTop, fillLeft, fillBottom ); 293 294 g.setColor( MetalLookAndFeel.getControlShadow() ); 295 g.fillRect( fillLeft + 1, fillTop + 1, 296 fillRight - fillLeft, fillBottom - fillTop ); 297 } 298 else { 299 g.setColor( MetalLookAndFeel.getControlShadow() ); 300 g.fillRect(fillLeft, fillTop, fillRight - fillLeft, fillBottom - fillTop); 301 } 302 } 303 304 g.translate( -trackRect.x, -trackRect.y ); 305 } 306 307 private void oceanPaintTrack(Graphics g) { 308 boolean leftToRight = MetalUtils.isLeftToRight(slider); 309 boolean drawInverted = drawInverted(); 310 Color sliderAltTrackColor = (Color)UIManager.get( 311 "Slider.altTrackColor"); 312 313 // Translate to the origin of the painting rectangle 314 Rectangle paintRect = getPaintTrackRect(); 315 g.translate(paintRect.x, paintRect.y); 316 317 // Width and height of the painting rectangle. 318 int w = paintRect.width; 319 int h = paintRect.height; 320 321 if (slider.getOrientation() == JSlider.HORIZONTAL) { 322 int middleOfThumb = thumbRect.x + thumbRect.width / 2 - paintRect.x; 323 324 if (slider.isEnabled()) { 325 int fillMinX; 326 int fillMaxX; 327 328 if (middleOfThumb > 0) { 329 g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() : 330 MetalLookAndFeel.getPrimaryControlDarkShadow()); 331 332 g.drawRect(0, 0, middleOfThumb - 1, h - 1); 333 } 334 335 if (middleOfThumb < w) { 336 g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() : 337 MetalLookAndFeel.getControlDarkShadow()); 338 339 g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1); 340 } 341 342 if (filledSlider) { 343 g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); 344 if (drawInverted) { 345 fillMinX = middleOfThumb; 346 fillMaxX = w - 2; 347 g.drawLine(1, 1, middleOfThumb, 1); 348 } else { 349 fillMinX = 1; 350 fillMaxX = middleOfThumb; 351 g.drawLine(middleOfThumb, 1, w - 1, 1); 352 } 353 if (h == 6) { 354 g.setColor(MetalLookAndFeel.getWhite()); 355 g.drawLine(fillMinX, 1, fillMaxX, 1); 356 g.setColor(sliderAltTrackColor); 357 g.drawLine(fillMinX, 2, fillMaxX, 2); 358 g.setColor(MetalLookAndFeel.getControlShadow()); 359 g.drawLine(fillMinX, 3, fillMaxX, 3); 360 g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); 361 g.drawLine(fillMinX, 4, fillMaxX, 4); 362 } 363 } 364 } else { 365 g.setColor(MetalLookAndFeel.getControlShadow()); 366 367 if (middleOfThumb > 0) { 368 if (!drawInverted && filledSlider) { 369 g.fillRect(0, 0, middleOfThumb - 1, h - 1); 370 } else { 371 g.drawRect(0, 0, middleOfThumb - 1, h - 1); 372 } 373 } 374 375 if (middleOfThumb < w) { 376 if (drawInverted && filledSlider) { 377 g.fillRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1); 378 } else { 379 g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1); 380 } 381 } 382 } 383 } else { 384 int middleOfThumb = thumbRect.y + (thumbRect.height / 2) - paintRect.y; 385 386 if (slider.isEnabled()) { 387 int fillMinY; 388 int fillMaxY; 389 390 if (middleOfThumb > 0) { 391 g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() : 392 MetalLookAndFeel.getControlDarkShadow()); 393 394 g.drawRect(0, 0, w - 1, middleOfThumb - 1); 395 } 396 397 if (middleOfThumb < h) { 398 g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() : 399 MetalLookAndFeel.getPrimaryControlDarkShadow()); 400 401 g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1); 402 } 403 404 if (filledSlider) { 405 g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); 406 if (drawInverted()) { 407 fillMinY = 1; 408 fillMaxY = middleOfThumb; 409 if (leftToRight) { 410 g.drawLine(1, middleOfThumb, 1, h - 1); 411 } else { 412 g.drawLine(w - 2, middleOfThumb, w - 2, h - 1); 413 } 414 } else { 415 fillMinY = middleOfThumb; 416 fillMaxY = h - 2; 417 if (leftToRight) { 418 g.drawLine(1, 1, 1, middleOfThumb); 419 } else { 420 g.drawLine(w - 2, 1, w - 2, middleOfThumb); 421 } 422 } 423 if (w == 6) { 424 g.setColor(leftToRight ? MetalLookAndFeel.getWhite() : MetalLookAndFeel.getPrimaryControlShadow()); 425 g.drawLine(1, fillMinY, 1, fillMaxY); 426 g.setColor(leftToRight ? sliderAltTrackColor : MetalLookAndFeel.getControlShadow()); 427 g.drawLine(2, fillMinY, 2, fillMaxY); 428 g.setColor(leftToRight ? MetalLookAndFeel.getControlShadow() : sliderAltTrackColor); 429 g.drawLine(3, fillMinY, 3, fillMaxY); 430 g.setColor(leftToRight ? MetalLookAndFeel.getPrimaryControlShadow() : MetalLookAndFeel.getWhite()); 431 g.drawLine(4, fillMinY, 4, fillMaxY); 432 } 433 } 434 } else { 435 g.setColor(MetalLookAndFeel.getControlShadow()); 436 437 if (middleOfThumb > 0) { 438 if (drawInverted && filledSlider) { 439 g.fillRect(0, 0, w - 1, middleOfThumb - 1); 440 } else { 441 g.drawRect(0, 0, w - 1, middleOfThumb - 1); 442 } 443 } 444 445 if (middleOfThumb < h) { 446 if (!drawInverted && filledSlider) { 447 g.fillRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1); 448 } else { 449 g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1); 450 } 451 } 452 } 453 } 454 455 g.translate(-paintRect.x, -paintRect.y); 456 } 457 458 public void paintFocus(Graphics g) { 459 } 460 461 protected Dimension getThumbSize() { 462 Dimension size = new Dimension(); 463 464 if ( slider.getOrientation() == JSlider.VERTICAL ) { 465 size.width = getVertThumbIcon().getIconWidth(); 466 size.height = getVertThumbIcon().getIconHeight(); 467 } 468 else { 469 size.width = getHorizThumbIcon().getIconWidth(); 470 size.height = getHorizThumbIcon().getIconHeight(); 471 } 472 473 return size; 474 } 475 476 /** 477 * Gets the height of the tick area for horizontal sliders and the width of the 478 * tick area for vertical sliders. BasicSliderUI uses the returned value to 479 * determine the tick area rectangle. 480 */ 481 public int getTickLength() { 482 return slider.getOrientation() == JSlider.HORIZONTAL ? safeLength + TICK_BUFFER + 1 : 483 safeLength + TICK_BUFFER + 3; 484 } 485 486 /** 487 * Returns the shorter dimension of the track. 488 */ 489 protected int getTrackWidth() { 490 // This strange calculation is here to keep the 491 // track in proportion to the thumb. 492 final double kIdealTrackWidth = 7.0; 493 final double kIdealThumbHeight = 16.0; 494 final double kWidthScalar = kIdealTrackWidth / kIdealThumbHeight; 495 496 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 497 return (int)(kWidthScalar * thumbRect.height); 498 } 499 else { 500 return (int)(kWidthScalar * thumbRect.width); 501 } 502 } 503 504 /** 505 * Returns the longer dimension of the slide bar. (The slide bar is only the 506 * part that runs directly under the thumb) 507 */ 508 protected int getTrackLength() { 509 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 510 return trackRect.width; 511 } 512 return trackRect.height; 513 } 514 515 /** 516 * Returns the amount that the thumb goes past the slide bar. 517 */ 518 protected int getThumbOverhang() { 519 return (int)(getThumbSize().getHeight()-getTrackWidth())/2; 520 } 521 522 protected void scrollDueToClickInTrack( int dir ) { 523 scrollByUnit( dir ); 524 } 525 526 protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { 527 g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() ); 528 g.drawLine( x, TICK_BUFFER, x, TICK_BUFFER + (safeLength / 2) ); 529 } 530 531 protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { 532 g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() ); 533 g.drawLine( x, TICK_BUFFER , x, TICK_BUFFER + (safeLength - 1) ); 534 } 535 536 protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { 537 g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() ); 538 539 if (MetalUtils.isLeftToRight(slider)) { 540 g.drawLine( TICK_BUFFER, y, TICK_BUFFER + (safeLength / 2), y ); 541 } 542 else { 543 g.drawLine( 0, y, safeLength/2, y ); 544 } 545 } 546 547 protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { 548 g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() ); 549 550 if (MetalUtils.isLeftToRight(slider)) { 551 g.drawLine( TICK_BUFFER, y, TICK_BUFFER + safeLength, y ); 552 } 553 else { 554 g.drawLine( 0, y, safeLength, y ); 555 } 556 } 557 }