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