001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.component.ui; 020 021import icy.gui.component.RangeSlider; 022 023import java.awt.Graphics; 024import java.awt.Graphics2D; 025import java.awt.Insets; 026import java.awt.Rectangle; 027import java.awt.Shape; 028import java.awt.event.MouseEvent; 029import java.awt.geom.AffineTransform; 030import java.awt.geom.GeneralPath; 031import java.awt.geom.Point2D; 032import java.awt.image.BufferedImage; 033import java.beans.PropertyChangeEvent; 034import java.beans.PropertyChangeListener; 035import java.util.Map; 036 037import javax.swing.ButtonModel; 038import javax.swing.DefaultButtonModel; 039import javax.swing.Icon; 040import javax.swing.JComponent; 041import javax.swing.JSlider; 042import javax.swing.SwingConstants; 043import javax.swing.event.ChangeEvent; 044import javax.swing.event.ChangeListener; 045 046import org.pushingpixels.lafwidget.LafWidgetUtilities; 047import org.pushingpixels.substance.api.ColorSchemeAssociationKind; 048import org.pushingpixels.substance.api.ComponentState; 049import org.pushingpixels.substance.api.SubstanceColorScheme; 050import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter; 051import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter; 052import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter; 053import org.pushingpixels.substance.internal.animation.StateTransitionTracker; 054import org.pushingpixels.substance.internal.animation.TransitionAwareUI; 055import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils; 056import org.pushingpixels.substance.internal.ui.SubstanceSliderUI; 057import org.pushingpixels.substance.internal.utils.HashMapKey; 058import org.pushingpixels.substance.internal.utils.RolloverControlListener; 059import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities; 060import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; 061import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities; 062import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils; 063 064/** 065 * UI delegate for the RangeSlider component with Substance AndFeel. 066 * RangeSliderUI paints two thumbs, one for the lower value and one for the upper value. 067 * 068 * @author Stephane Dallongeville 069 */ 070public class RangeSliderUI extends SubstanceSliderUI 071{ 072 /** Location and size of thumb for upper value. */ 073 Rectangle upperThumbRect; 074 /** Indicator that determines whether upper thumb is selected. */ 075 boolean upperThumbSelected; 076 077 /** Indicator that determines whether lower thumb is being dragged. */ 078 transient boolean lowerDragging; 079 /** Indicator that determines whether upper thumb is being dragged. */ 080 transient boolean upperDragging; 081 082 /** 083 * Surrogate button model for tracking the general slider transitions. 084 */ 085 ButtonModel sliderModel; 086 /** 087 * Surrogate button model for tracking the upper thumb transitions. 088 */ 089 ButtonModel upperThumbModel; 090 /** 091 * General slider transition tracker. 092 */ 093 protected StateTransitionTracker sliderStateTransitionTracker; 094 /** 095 * Upper thumb transition tracker. 096 */ 097 protected StateTransitionTracker upperThumbStateTransitionTracker; 098 099 /** 100 * Listener for general slider transition animations. 101 */ 102 protected RolloverControlListener sliderRolloverListener; 103 /** 104 * Listener for upper thumb transition animations. 105 */ 106 protected RolloverControlListener upperThumbRolloverListener; 107 108 /** 109 * Listener on property change events. 110 */ 111 protected PropertyChangeListener sliderPropertyChangeListener; 112 113 /** 114 * Needed to return correct transition tracker on thumb paint. 115 */ 116 private boolean paintingLowerThumb; 117 private boolean paintingUpperThumb; 118 119 /** 120 * Constructs a RangeSliderUI for the specified slider component. 121 * 122 * @param rangeSlider 123 * RangeSlider 124 */ 125 public RangeSliderUI(RangeSlider rangeSlider) 126 { 127 super(rangeSlider); 128 129 sliderModel = new DefaultButtonModel(); 130 sliderModel.setArmed(false); 131 sliderModel.setSelected(false); 132 sliderModel.setPressed(false); 133 sliderModel.setRollover(false); 134 sliderModel.setEnabled(rangeSlider.isEnabled()); 135 136 upperThumbModel = new DefaultButtonModel(); 137 upperThumbModel.setArmed(false); 138 upperThumbModel.setSelected(false); 139 upperThumbModel.setPressed(false); 140 upperThumbModel.setRollover(false); 141 upperThumbModel.setEnabled(rangeSlider.isEnabled()); 142 143 sliderStateTransitionTracker = new StateTransitionTracker(rangeSlider, sliderModel); 144 upperThumbStateTransitionTracker = new StateTransitionTracker(rangeSlider, upperThumbModel); 145 146 paintingLowerThumb = false; 147 paintingUpperThumb = false; 148 } 149 150 /** 151 * Installs this UI delegate on the specified component. 152 */ 153 @Override 154 public void installUI(JComponent c) 155 { 156 upperThumbRect = new Rectangle(); 157 super.installUI(c); 158 } 159 160 @Override 161 protected void installListeners(JSlider slider) 162 { 163 super.installListeners(slider); 164 165 sliderRolloverListener = new RolloverControlListener(new TransitionAwareUI() 166 { 167 @Override 168 public boolean isInside(MouseEvent me) 169 { 170 final double x = me.getX(); 171 final double y = me.getY(); 172 return isInsideLowerThumbInternal(x, y) || isInsideUpperThumbInternal(x, y); 173 } 174 175 @Override 176 public StateTransitionTracker getTransitionTracker() 177 { 178 return sliderStateTransitionTracker; 179 } 180 }, sliderModel); 181 182 slider.addMouseListener(sliderRolloverListener); 183 slider.addMouseMotionListener(sliderRolloverListener); 184 185 upperThumbRolloverListener = new RolloverControlListener(new TransitionAwareUI() 186 { 187 @Override 188 public boolean isInside(MouseEvent me) 189 { 190 return isInsideUpperThumb(me.getX(), me.getY()); 191 } 192 193 @Override 194 public StateTransitionTracker getTransitionTracker() 195 { 196 return upperThumbStateTransitionTracker; 197 } 198 }, upperThumbModel); 199 200 slider.addMouseListener(upperThumbRolloverListener); 201 slider.addMouseMotionListener(upperThumbRolloverListener); 202 203 sliderPropertyChangeListener = new PropertyChangeListener() 204 { 205 @Override 206 public void propertyChange(PropertyChangeEvent evt) 207 { 208 if ("enabled".equals(evt.getPropertyName())) 209 { 210 final boolean enabled = RangeSliderUI.this.slider.isEnabled(); 211 212 sliderModel.setEnabled(enabled); 213 upperThumbModel.setEnabled(enabled); 214 } 215 } 216 }; 217 slider.addPropertyChangeListener(sliderPropertyChangeListener); 218 219 sliderStateTransitionTracker.registerModelListeners(); 220 sliderStateTransitionTracker.registerFocusListeners(); 221 upperThumbStateTransitionTracker.registerModelListeners(); 222 upperThumbStateTransitionTracker.registerFocusListeners(); 223 } 224 225 @Override 226 protected void uninstallListeners(JSlider slider) 227 { 228 super.uninstallListeners(slider); 229 230 slider.removeMouseListener(sliderRolloverListener); 231 slider.removeMouseMotionListener(sliderRolloverListener); 232 sliderRolloverListener = null; 233 slider.removeMouseListener(upperThumbRolloverListener); 234 slider.removeMouseMotionListener(upperThumbRolloverListener); 235 upperThumbRolloverListener = null; 236 237 slider.removePropertyChangeListener(sliderPropertyChangeListener); 238 sliderPropertyChangeListener = null; 239 240 sliderStateTransitionTracker.unregisterModelListeners(); 241 sliderStateTransitionTracker.unregisterFocusListeners(); 242 upperThumbStateTransitionTracker.unregisterModelListeners(); 243 upperThumbStateTransitionTracker.unregisterFocusListeners(); 244 } 245 246 /** 247 * Creates a listener to handle track events in the specified slider. 248 */ 249 @Override 250 protected TrackListener createTrackListener(JSlider slider) 251 { 252 return new RangeTrackListener(); 253 } 254 255 /** 256 * Creates a listener to handle change events in the specified slider. 257 */ 258 @Override 259 protected ChangeListener createChangeListener(JSlider slider) 260 { 261 return new ChangeHandler(); 262 } 263 264 /** 265 * Updates the dimensions for both thumbs. 266 */ 267 @Override 268 protected void calculateThumbSize() 269 { 270 // Call superclass method for lower thumb size. 271 super.calculateThumbSize(); 272 273 // Set upper thumb size. 274 upperThumbRect.setSize(thumbRect.width, thumbRect.height); 275 } 276 277 /** 278 * Updates the locations for both thumbs. 279 */ 280 @Override 281 protected void calculateThumbLocation() 282 { 283 // Call superclass method for lower thumb location. 284 super.calculateThumbLocation(); 285 286 // Adjust upper value to snap to ticks if necessary. 287 if (slider.getSnapToTicks()) 288 { 289 int upperValue = slider.getValue() + slider.getExtent(); 290 int snappedValue = upperValue; 291 int majorTickSpacing = slider.getMajorTickSpacing(); 292 int minorTickSpacing = slider.getMinorTickSpacing(); 293 int tickSpacing = 0; 294 295 if (minorTickSpacing > 0) 296 { 297 tickSpacing = minorTickSpacing; 298 } 299 else if (majorTickSpacing > 0) 300 { 301 tickSpacing = majorTickSpacing; 302 } 303 304 if (tickSpacing != 0) 305 { 306 // If it's not on a tick, change the value 307 if ((upperValue - slider.getMinimum()) % tickSpacing != 0) 308 { 309 float temp = (float) (upperValue - slider.getMinimum()) / (float) tickSpacing; 310 int whichTick = Math.round(temp); 311 snappedValue = slider.getMinimum() + (whichTick * tickSpacing); 312 } 313 314 if (snappedValue != upperValue) 315 { 316 slider.setExtent(snappedValue - slider.getValue()); 317 } 318 } 319 } 320 321 Rectangle trackRect = this.getPaintTrackRect(); 322 323 if (slider.getOrientation() == SwingConstants.HORIZONTAL) 324 { 325 int valuePosition = xPositionForValue(slider.getValue() + slider.getExtent()); 326 327 double centerY = trackRect.y + trackRect.height / 2.0; 328 upperThumbRect.y = (int) (centerY - upperThumbRect.height / 2.0) + 1; 329 upperThumbRect.x = valuePosition - upperThumbRect.width / 2; 330 } 331 else 332 { 333 int valuePosition = yPositionForValue(slider.getValue() + slider.getExtent()); 334 335 double centerX = trackRect.x + trackRect.width / 2.0; 336 upperThumbRect.x = (int) (centerX - upperThumbRect.width / 2.0) + 1; 337 upperThumbRect.y = valuePosition - (upperThumbRect.height / 2); 338 } 339 } 340 341 @Override 342 public boolean isInside(MouseEvent me) 343 { 344 return isInsideLowerThumb(me.getX(), me.getY()); 345 } 346 347 public boolean isInsideLowerThumbInternal(double x, double y) 348 { 349 final Rectangle thumbB = this.thumbRect; 350 return thumbB != null && thumbB.contains(x, y); 351 } 352 353 public boolean isInsideLowerThumb(double x, double y) 354 { 355 // inside lower ? 356 if (isInsideLowerThumbInternal(x, y)) 357 { 358 // also inside upper ? 359 if (isInsideUpperThumbInternal(x, y)) 360 { 361 final double dl = Point2D.distance(thumbRect.getCenterX(), thumbRect.getCenterY(), x, y); 362 final double du = Point2D.distance(upperThumbRect.getCenterX(), upperThumbRect.getCenterY(), x, y); 363 364 return (dl < du); 365 } 366 367 return true; 368 } 369 370 return false; 371 } 372 373 public boolean isInsideUpperThumbInternal(double x, double y) 374 { 375 final Rectangle upperThumbR = upperThumbRect; 376 return (upperThumbR != null) && upperThumbR.contains(x, y); 377 } 378 379 public boolean isInsideUpperThumb(double x, double y) 380 { 381 // inside lower ? 382 if (isInsideUpperThumbInternal(x, y)) 383 { 384 // also inside upper ? 385 if (isInsideLowerThumbInternal(x, y)) 386 { 387 // find closest one 388 final double dl = Point2D.distance(thumbRect.getCenterX(), thumbRect.getCenterY(), x, y); 389 final double du = Point2D.distance(upperThumbRect.getCenterX(), upperThumbRect.getCenterY(), x, y); 390 391 return (du <= dl); 392 } 393 394 return true; 395 } 396 397 return false; 398 } 399 400 @Override 401 public StateTransitionTracker getTransitionTracker() 402 { 403 if (paintingLowerThumb) 404 return super.getTransitionTracker(); 405 if (paintingUpperThumb) 406 return upperThumbStateTransitionTracker; 407 408 return sliderStateTransitionTracker; 409 } 410 411 /** 412 * Returns the rectangle of track for painting. 413 * 414 * @return The rectangle of track for painting. 415 */ 416 private Rectangle getPaintTrackRect() 417 { 418 int trackLeft = 0; 419 int trackRight; 420 int trackTop = 0; 421 int trackBottom; 422 int trackWidth = this.getTrackWidth(); 423 424 if (this.slider.getOrientation() == SwingConstants.HORIZONTAL) 425 { 426 trackTop = 3 + this.insetCache.top + 2 * this.focusInsets.top; 427 trackBottom = trackTop + trackWidth - 1; 428 trackRight = this.trackRect.width; 429 return new Rectangle(this.trackRect.x + trackLeft, trackTop, trackRight - trackLeft, trackBottom - trackTop); 430 } 431 432 if (this.slider.getPaintLabels() || this.slider.getPaintTicks()) 433 { 434 if (this.slider.getComponentOrientation().isLeftToRight()) 435 { 436 trackLeft = trackRect.x + this.insetCache.left + this.focusInsets.left; 437 trackRight = trackLeft + trackWidth - 1; 438 } 439 else 440 { 441 trackRight = trackRect.x + trackRect.width - this.insetCache.right - this.focusInsets.right; 442 trackLeft = trackRight - trackWidth - 1; 443 } 444 } 445 else 446 { 447 // horizontally center the track 448 if (this.slider.getComponentOrientation().isLeftToRight()) 449 { 450 trackLeft = (this.insetCache.left + this.focusInsets.left + this.slider.getWidth() 451 - this.insetCache.right - this.focusInsets.right) 452 / 2 - trackWidth / 2; 453 trackRight = trackLeft + trackWidth - 1; 454 } 455 else 456 { 457 trackRight = (this.insetCache.left + this.focusInsets.left + this.slider.getWidth() 458 - this.insetCache.right - this.focusInsets.right) 459 / 2 + trackWidth / 2; 460 trackLeft = trackRight - trackWidth - 1; 461 } 462 } 463 464 trackBottom = this.trackRect.height - 1; 465 return new Rectangle(trackLeft, this.trackRect.y + trackTop, trackRight - trackLeft, trackBottom - trackTop); 466 } 467 468 @Override 469 public void paint(Graphics g, final JComponent c) 470 { 471 Graphics2D graphics = (Graphics2D) g.create(); 472 473 ComponentState currState = ComponentState.getState(sliderModel, slider); 474 float alpha = SubstanceColorSchemeUtilities.getAlpha(slider, currState); 475 476 BackgroundPaintingUtils.updateIfOpaque(graphics, c); 477 478 recalculateIfInsetsChanged(); 479 recalculateIfOrientationChanged(); 480 final Rectangle clip = graphics.getClipBounds(); 481 482 if (!clip.intersects(trackRect) && slider.getPaintTrack()) 483 calculateGeometry(); 484 485 graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.slider, alpha, g)); 486 if (slider.getPaintTrack() && clip.intersects(trackRect)) 487 { 488 paintTrack(graphics); 489 } 490 if (slider.getPaintTicks() && clip.intersects(tickRect)) 491 { 492 paintTicks(graphics); 493 } 494 // don't paint focus as component is not focusable 495 // paintFocus(graphics); 496 if (clip.intersects(thumbRect)) 497 { 498 paintLowerThumb(graphics); 499 } 500 if (clip.intersects(upperThumbRect)) 501 { 502 paintUpperThumb(graphics); 503 } 504 graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.slider, 1.0f, g)); 505 if (slider.getPaintLabels() && clip.intersects(labelRect)) 506 { 507 paintLabels(graphics); 508 } 509 510 graphics.dispose(); 511 } 512 513 public void paintLowerThumb(Graphics g) 514 { 515 paintingLowerThumb = true; 516 517 // default implementation 518 paintThumb(g); 519 520 paintingLowerThumb = false; 521 } 522 523 public void paintUpperThumb(Graphics g) 524 { 525 paintingUpperThumb = true; 526 527 final Graphics2D graphics = (Graphics2D) g.create(); 528 final Rectangle knobBounds = upperThumbRect; 529 530 graphics.translate(knobBounds.x, knobBounds.y); 531 532 final Icon icon = getIcon(); 533 534 if (slider.getOrientation() == SwingConstants.HORIZONTAL) 535 { 536 if (icon != null) 537 icon.paintIcon(this.slider, graphics, -1, 0); 538 } 539 else 540 { 541 if (slider.getComponentOrientation().isLeftToRight()) 542 { 543 if (icon != null) 544 icon.paintIcon(this.slider, graphics, 0, -1); 545 } 546 else 547 { 548 if (icon != null) 549 icon.paintIcon(this.slider, graphics, 0, 1); 550 } 551 } 552 553 graphics.dispose(); 554 555 paintingUpperThumb = false; 556 } 557 558 @Override 559 public void paintTrack(Graphics g) 560 { 561 Graphics2D graphics = (Graphics2D) g.create(); 562 563 boolean drawInverted = drawInverted(); 564 565 Rectangle paintRect = getPaintTrackRect(); 566 567 // Width and height of the painting rectangle. 568 int width = paintRect.width; 569 int height = paintRect.height; 570 571 if (this.slider.getOrientation() == SwingConstants.VERTICAL) 572 { 573 // apply rotation / translate transformation on vertical 574 // slider tracks 575 int temp = width; 576 // noinspection SuspiciousNameCombination 577 width = height; 578 height = temp; 579 AffineTransform at = graphics.getTransform(); 580 at.translate(paintRect.x, width + paintRect.y); 581 at.rotate(-Math.PI / 2); 582 graphics.setTransform(at); 583 } 584 else 585 { 586 graphics.translate(paintRect.x, paintRect.y); 587 } 588 589 StateTransitionTracker.ModelStateInfo modelStateInfo = sliderStateTransitionTracker.getModelStateInfo(); 590 591 SubstanceColorScheme trackSchemeUnselected = SubstanceColorSchemeUtilities.getColorScheme(this.slider, 592 slider.isEnabled() ? ComponentState.ENABLED : ComponentState.DISABLED_UNSELECTED); 593 SubstanceColorScheme trackBorderSchemeUnselected = SubstanceColorSchemeUtilities.getColorScheme(this.slider, 594 ColorSchemeAssociationKind.BORDER, this.slider.isEnabled() ? ComponentState.ENABLED 595 : ComponentState.DISABLED_UNSELECTED); 596 paintSliderTrack(graphics, drawInverted, trackSchemeUnselected, trackBorderSchemeUnselected, width, height); 597 598 Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo 599 .getStateContributionMap(); 600 for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates 601 .entrySet()) 602 { 603 ComponentState activeState = activeEntry.getKey(); 604 if (!activeState.isActive()) 605 continue; 606 607 float contribution = activeEntry.getValue().getContribution(); 608 if (contribution == 0.0f) 609 continue; 610 611 graphics.setComposite(LafWidgetUtilities.getAlphaComposite(slider, contribution, g)); 612 613 SubstanceColorScheme activeFillScheme = SubstanceColorSchemeUtilities.getColorScheme(this.slider, 614 activeState); 615 SubstanceColorScheme activeBorderScheme = SubstanceColorSchemeUtilities.getColorScheme(this.slider, 616 ColorSchemeAssociationKind.BORDER, activeState); 617 paintSliderTrackSelected(graphics, paintRect, activeFillScheme, activeBorderScheme, width, height); 618 } 619 620 graphics.dispose(); 621 } 622 623 /** 624 * Paints the slider track. 625 * 626 * @param graphics 627 * Graphics. 628 * @param drawInverted 629 * Indicates whether the value-range shown for the slider is 630 * reversed. 631 * @param fillColorScheme 632 * Fill color scheme. 633 * @param borderScheme 634 * Border color scheme. 635 * @param width 636 * Track width. 637 * @param height 638 * Track height. 639 */ 640 private void paintSliderTrack(Graphics2D graphics, boolean drawInverted, SubstanceColorScheme fillColorScheme, 641 SubstanceColorScheme borderScheme, int width, int height) 642 { 643 Graphics2D g2d = (Graphics2D) graphics.create(); 644 645 SubstanceFillPainter fillPainter = ClassicFillPainter.INSTANCE; 646 SubstanceBorderPainter borderPainter = SubstanceCoreUtilities.getBorderPainter(this.slider); 647 648 int componentFontSize = SubstanceSizeUtils.getComponentFontSize(this.slider); 649 int borderDelta = (int) Math.floor(SubstanceSizeUtils.getBorderStrokeWidth(componentFontSize) / 2.0); 650 float radius = SubstanceSizeUtils.getClassicButtonCornerRadius(componentFontSize) / 2.0f; 651 int borderThickness = (int) SubstanceSizeUtils.getBorderStrokeWidth(componentFontSize); 652 653 HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height, radius, borderDelta, borderThickness, 654 fillColorScheme.getDisplayName(), borderScheme.getDisplayName()); 655 656 BufferedImage trackImage = trackCache.get(key); 657 if (trackImage == null) 658 { 659 trackImage = SubstanceCoreUtilities.getBlankImage(width + 1, height + 1); 660 Graphics2D cacheGraphics = trackImage.createGraphics(); 661 662 Shape contour = SubstanceOutlineUtilities.getBaseOutline(width + 1, height + 1, radius, null, borderDelta); 663 664 fillPainter.paintContourBackground(cacheGraphics, slider, width, height, contour, false, fillColorScheme, 665 false); 666 667 GeneralPath contourInner = SubstanceOutlineUtilities.getBaseOutline(width + 1, height + 1, radius 668 - borderThickness, null, borderThickness + borderDelta); 669 borderPainter 670 .paintBorder(cacheGraphics, slider, width + 1, height + 1, contour, contourInner, borderScheme); 671 672 trackCache.put(key, trackImage); 673 cacheGraphics.dispose(); 674 } 675 676 g2d.drawImage(trackImage, 0, 0, null); 677 678 g2d.dispose(); 679 } 680 681 /** 682 * Paints the selected part of the slider track. 683 * 684 * @param graphics 685 * Graphics. 686 * @param drawInverted 687 * Indicates whether the value-range shown for the slider is 688 * reversed. 689 * @param paintRect 690 * Selected portion. 691 * @param fillScheme 692 * Fill color scheme. 693 * @param borderScheme 694 * Border color scheme. 695 * @param width 696 * Track width. 697 * @param height 698 * Track height. 699 */ 700 private void paintSliderTrackSelected(Graphics2D graphics, Rectangle paintRect, SubstanceColorScheme fillScheme, 701 SubstanceColorScheme borderScheme, int width, int height) 702 { 703 Graphics2D g2d = (Graphics2D) graphics.create(); 704 Insets insets = this.slider.getInsets(); 705 insets.top /= 2; 706 insets.left /= 2; 707 insets.bottom /= 2; 708 insets.right /= 2; 709 710 SubstanceFillPainter fillPainter = SubstanceCoreUtilities.getFillPainter(this.slider); 711 SubstanceBorderPainter borderPainter = SubstanceCoreUtilities.getBorderPainter(this.slider); 712 float radius = SubstanceSizeUtils.getClassicButtonCornerRadius(SubstanceSizeUtils.getComponentFontSize(slider)) / 2.0f; 713 int borderDelta = (int) Math.floor(SubstanceSizeUtils.getBorderStrokeWidth(SubstanceSizeUtils 714 .getComponentFontSize(slider)) / 2.0); 715 716 // fill selected portion 717 if (this.slider.isEnabled()) 718 { 719 if (this.slider.getOrientation() == SwingConstants.HORIZONTAL) 720 { 721 int ltPos = thumbRect.x + (this.thumbRect.width / 2) - paintRect.x; 722 int utPos = upperThumbRect.x + (this.upperThumbRect.width / 2) - paintRect.x; 723 724 int fillMinX; 725 int fillMaxX; 726 727 if (ltPos < utPos) 728 { 729 fillMinX = ltPos; 730 fillMaxX = utPos; 731 } 732 else 733 { 734 fillMinX = utPos; 735 fillMaxX = ltPos; 736 } 737 738 int fillWidth = fillMaxX - fillMinX; 739 int fillHeight = height + 1; 740 if ((fillWidth > 0) && (fillHeight > 0)) 741 { 742 Shape contour = SubstanceOutlineUtilities.getBaseOutline(fillWidth, fillHeight, radius, null, 743 borderDelta); 744 g2d.translate(fillMinX, 0); 745 fillPainter.paintContourBackground(g2d, this.slider, fillWidth, fillHeight, contour, false, 746 fillScheme, false); 747 borderPainter.paintBorder(g2d, this.slider, fillWidth, fillHeight, contour, null, borderScheme); 748 } 749 } 750 else 751 { 752 int ltPos = thumbRect.y + (this.thumbRect.height / 2) - paintRect.y; 753 int utPos = upperThumbRect.y + (this.upperThumbRect.height / 2) - paintRect.y; 754 int fillMin; 755 int fillMax; 756 757 if (ltPos < utPos) 758 { 759 fillMin = ltPos; 760 fillMax = utPos; 761 } 762 else 763 { 764 fillMin = utPos; 765 fillMax = ltPos; 766 } 767 768 // if (this.drawInverted()) 769 // { 770 // fillMin = 0; 771 // fillMax = middleOfThumb; 772 // // fix for issue 368 - inverted vertical sliders 773 // g2d.translate(width + 2 - middleOfThumb, 0); 774 // } 775 // else 776 // { 777 // fillMin = middleOfThumb; 778 // fillMax = width + 1; 779 // } 780 781 int fillWidth = fillMax - fillMin; 782 int fillHeight = height + 1; 783 if ((fillWidth > 0) && (fillHeight > 0)) 784 { 785 Shape contour = SubstanceOutlineUtilities.getBaseOutline(fillWidth, fillHeight, radius, null, 786 borderDelta); 787 g2d.translate(paintRect.height - fillMax, 0); 788 fillPainter.paintContourBackground(g2d, this.slider, fillWidth, fillHeight, contour, false, 789 fillScheme, false); 790 borderPainter.paintBorder(g2d, this.slider, fillWidth, fillHeight, contour, null, borderScheme); 791 } 792 } 793 } 794 g2d.dispose(); 795 } 796 797 /** 798 * Sets the location of the upper thumb, and repaints the slider. This is 799 * called when the upper thumb is dragged to repaint the slider. The 800 * <code>setThumbLocation()</code> method performs the same task for the 801 * lower thumb. 802 */ 803 void setUpperThumbLocation(int x, int y) 804 { 805 upperThumbRect.setLocation(x, y); 806 slider.repaint(); 807 } 808 809 /** 810 * Moves the selected thumb in the specified direction by a block increment. 811 * This method is called when the user presses the Page Up or Down keys. 812 */ 813 @Override 814 public void scrollByBlock(int direction) 815 { 816 synchronized (slider) 817 { 818 int blockIncrement = (slider.getMaximum() - slider.getMinimum()) / 10; 819 if (blockIncrement <= 0 && slider.getMaximum() > slider.getMinimum()) 820 { 821 blockIncrement = 1; 822 } 823 int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); 824 825 if (upperThumbSelected) 826 { 827 int oldValue = ((RangeSlider) slider).getUpperValue(); 828 ((RangeSlider) slider).setUpperValue(oldValue + delta); 829 } 830 else 831 { 832 int oldValue = slider.getValue(); 833 slider.setValue(oldValue + delta); 834 } 835 } 836 } 837 838 /** 839 * Moves the selected thumb in the specified direction by a unit increment. 840 * This method is called when the user presses one of the arrow keys. 841 */ 842 @Override 843 public void scrollByUnit(int direction) 844 { 845 synchronized (slider) 846 { 847 int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); 848 849 if (upperThumbSelected) 850 { 851 int oldValue = ((RangeSlider) slider).getUpperValue(); 852 ((RangeSlider) slider).setUpperValue(oldValue + delta); 853 } 854 else 855 { 856 int oldValue = slider.getValue(); 857 slider.setValue(oldValue + delta); 858 } 859 } 860 } 861 862 /** 863 * Listener to handle model change events. This calculates the thumb 864 * locations and repaints the slider if the value change is not caused by 865 * dragging a thumb. 866 */ 867 public class ChangeHandler implements ChangeListener 868 { 869 @Override 870 public void stateChanged(ChangeEvent arg0) 871 { 872 if (!lowerDragging && !upperDragging) 873 { 874 calculateThumbLocation(); 875 slider.repaint(); 876 } 877 } 878 } 879 880 /** 881 * Listener to handle mouse movements in the slider track. 882 */ 883 public class RangeTrackListener extends TrackListener 884 { 885 @Override 886 public void mousePressed(MouseEvent e) 887 { 888 if (!slider.isEnabled()) 889 return; 890 891 currentMouseX = e.getX(); 892 currentMouseY = e.getY(); 893 894 if (slider.isRequestFocusEnabled()) 895 slider.requestFocus(); 896 897 // Determine which thumb is pressed. If the upper thumb is 898 // selected (last one dragged), then check its position first; 899 // otherwise check the position of the lower thumb first. 900 boolean lowerPressed = false; 901 boolean upperPressed = false; 902 if (isInsideLowerThumb(currentMouseX, currentMouseY)) 903 lowerPressed = true; 904 else if (isInsideUpperThumb(currentMouseX, currentMouseY)) 905 upperPressed = true; 906 907 // Handle lower thumb pressed. 908 if (lowerPressed) 909 { 910 switch (slider.getOrientation()) 911 { 912 case SwingConstants.VERTICAL: 913 offset = currentMouseY - thumbRect.y; 914 break; 915 case SwingConstants.HORIZONTAL: 916 offset = currentMouseX - thumbRect.x; 917 break; 918 } 919 upperThumbSelected = false; 920 lowerDragging = true; 921 return; 922 } 923 lowerDragging = false; 924 925 // Handle upper thumb pressed. 926 if (upperPressed) 927 { 928 switch (slider.getOrientation()) 929 { 930 case SwingConstants.VERTICAL: 931 offset = currentMouseY - upperThumbRect.y; 932 break; 933 case SwingConstants.HORIZONTAL: 934 offset = currentMouseX - upperThumbRect.x; 935 break; 936 } 937 upperThumbSelected = true; 938 upperDragging = true; 939 return; 940 } 941 upperDragging = false; 942 } 943 944 @Override 945 public void mouseReleased(MouseEvent e) 946 { 947 lowerDragging = false; 948 upperDragging = false; 949 slider.setValueIsAdjusting(false); 950 super.mouseReleased(e); 951 } 952 953 @Override 954 public void mouseDragged(MouseEvent e) 955 { 956 if (!slider.isEnabled()) 957 { 958 return; 959 } 960 961 currentMouseX = e.getX(); 962 currentMouseY = e.getY(); 963 964 if (lowerDragging) 965 { 966 slider.setValueIsAdjusting(true); 967 moveLowerThumb(); 968 969 } 970 else if (upperDragging) 971 { 972 slider.setValueIsAdjusting(true); 973 moveUpperThumb(); 974 } 975 } 976 977 @Override 978 public boolean shouldScroll(int direction) 979 { 980 return false; 981 } 982 983 /** 984 * Moves the location of the lower thumb, and sets its corresponding 985 * value in the slider. 986 */ 987 private void moveLowerThumb() 988 { 989 int thumbMiddle = 0; 990 991 switch (slider.getOrientation()) 992 { 993 case SwingConstants.VERTICAL: 994 int halfThumbHeight = thumbRect.height / 2; 995 int thumbTop = currentMouseY - offset; 996 int trackTop = trackRect.y; 997 int trackBottom = trackRect.y + (trackRect.height - 1); 998 int vMax = yPositionForValue(slider.getValue() + slider.getExtent()); 999 1000 // Apply bounds to thumb position. 1001 if (drawInverted()) 1002 { 1003 trackBottom = vMax; 1004 } 1005 else 1006 { 1007 trackTop = vMax; 1008 } 1009 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight); 1010 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight); 1011 1012 setThumbLocation(thumbRect.x, thumbTop); 1013 1014 // Update slider value. 1015 thumbMiddle = thumbTop + halfThumbHeight; 1016 slider.setValue(valueForYPosition(thumbMiddle)); 1017 break; 1018 1019 case SwingConstants.HORIZONTAL: 1020 int halfThumbWidth = thumbRect.width / 2; 1021 int thumbLeft = currentMouseX - offset; 1022 int trackLeft = trackRect.x; 1023 int trackRight = trackRect.x + (trackRect.width - 1); 1024 int hMax = xPositionForValue(slider.getValue() + slider.getExtent()); 1025 1026 // Apply bounds to thumb position. 1027 if (drawInverted()) 1028 { 1029 trackLeft = hMax; 1030 } 1031 else 1032 { 1033 trackRight = hMax; 1034 } 1035 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth); 1036 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth); 1037 1038 setThumbLocation(thumbLeft, thumbRect.y); 1039 1040 // Update slider value. 1041 thumbMiddle = thumbLeft + halfThumbWidth; 1042 slider.setValue(valueForXPosition(thumbMiddle)); 1043 break; 1044 1045 default: 1046 return; 1047 } 1048 } 1049 1050 /** 1051 * Moves the location of the upper thumb, and sets its corresponding 1052 * value in the slider. 1053 */ 1054 private void moveUpperThumb() 1055 { 1056 int thumbMiddle = 0; 1057 1058 switch (slider.getOrientation()) 1059 { 1060 case SwingConstants.VERTICAL: 1061 int halfThumbHeight = thumbRect.height / 2; 1062 int thumbTop = currentMouseY - offset; 1063 int trackTop = trackRect.y; 1064 int trackBottom = trackRect.y + (trackRect.height - 1); 1065 int vMin = yPositionForValue(slider.getValue()); 1066 1067 // Apply bounds to thumb position. 1068 if (drawInverted()) 1069 { 1070 trackTop = vMin; 1071 } 1072 else 1073 { 1074 trackBottom = vMin; 1075 } 1076 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight); 1077 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight); 1078 1079 setUpperThumbLocation(thumbRect.x, thumbTop); 1080 1081 // Update slider extent. 1082 thumbMiddle = thumbTop + halfThumbHeight; 1083 slider.setExtent(valueForYPosition(thumbMiddle) - slider.getValue()); 1084 break; 1085 1086 case SwingConstants.HORIZONTAL: 1087 int halfThumbWidth = thumbRect.width / 2; 1088 int thumbLeft = currentMouseX - offset; 1089 int trackLeft = trackRect.x; 1090 int trackRight = trackRect.x + (trackRect.width - 1); 1091 int hMin = xPositionForValue(slider.getValue()); 1092 1093 // Apply bounds to thumb position. 1094 if (drawInverted()) 1095 { 1096 trackRight = hMin; 1097 } 1098 else 1099 { 1100 trackLeft = hMin; 1101 } 1102 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth); 1103 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth); 1104 1105 setUpperThumbLocation(thumbLeft, thumbRect.y); 1106 1107 // Update slider extent. 1108 thumbMiddle = thumbLeft + halfThumbWidth; 1109 slider.setExtent(valueForXPosition(thumbMiddle) - slider.getValue()); 1110 break; 1111 1112 default: 1113 return; 1114 } 1115 } 1116 } 1117}