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