1 /*
   2  * Copyright (c) 2010, 2019, 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 javafx.scene.control;
  27 
  28 import com.sun.javafx.scene.control.Properties;
  29 import com.sun.javafx.util.Utils;
  30 import javafx.css.converter.EnumConverter;
  31 import javafx.css.converter.SizeConverter;
  32 import javafx.scene.control.skin.ScrollBarSkin;
  33 
  34 import javafx.beans.property.DoubleProperty;
  35 import javafx.beans.property.ObjectProperty;
  36 import javafx.beans.property.SimpleDoubleProperty;
  37 import javafx.beans.value.WritableValue;
  38 import javafx.css.CssMetaData;
  39 import javafx.css.PseudoClass;
  40 import javafx.css.Styleable;
  41 import javafx.css.StyleableDoubleProperty;
  42 import javafx.css.StyleableObjectProperty;
  43 import javafx.css.StyleableProperty;
  44 import javafx.geometry.Orientation;
  45 import javafx.scene.AccessibleAction;
  46 import javafx.scene.AccessibleAttribute;
  47 import javafx.scene.AccessibleRole;
  48 
  49 import java.util.ArrayList;
  50 import java.util.Collections;
  51 import java.util.List;
  52 
  53 /**
  54  * Either a horizontal or vertical bar with increment and decrement buttons and
  55  * a "thumb" with which the user can interact. Typically not used alone but used
  56  * for building up more complicated controls such as the ScrollPane and ListView.
  57  * <p>
  58  * ScrollBar sets focusTraversable to false.
  59  * </p>
  60  *
  61  * <p>
  62  * This example creates a vertical ScrollBar:
  63  * <pre><code> ScrollBar s1 = new ScrollBar();
  64  * s1.setOrientation(Orientation.VERTICAL);</code></pre>
  65  *
  66  * <img src="doc-files/ScrollBar.png" alt="Image of the ScrollBar control">
  67  *
  68  * @since JavaFX 2.0
  69  */
  70 public class ScrollBar extends Control {
  71 
  72     /***************************************************************************
  73      *                                                                         *
  74      * Constructors                                                            *
  75      *                                                                         *
  76      **************************************************************************/
  77 
  78     /**
  79      * Creates a new horizontal ScrollBar (ie getOrientation() == Orientation.HORIZONTAL).
  80      *
  81      *
  82      */
  83     public ScrollBar() {
  84         // TODO : we need to ensure we have a width and height
  85         setWidth(Properties.DEFAULT_WIDTH);
  86         setHeight(Properties.DEFAULT_LENGTH);
  87         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
  88         setAccessibleRole(AccessibleRole.SCROLL_BAR);
  89         // focusTraversable is styleable through css. Calling setFocusTraversable
  90         // makes it look to css like the user set the value and css will not
  91         // override. Initializing focusTraversable by calling applyStyle with null
  92         // for StyleOrigin ensures that css will be able to override the value.
  93         ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null,Boolean.FALSE);
  94 
  95         // set pseudo-class state to horizontal
  96         pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, true);
  97 
  98     }
  99     /***************************************************************************
 100      *                                                                         *
 101      * Properties                                                              *
 102      *                                                                         *
 103      **************************************************************************/
 104     /**
 105      * The minimum value represented by this {@code ScrollBar}. This should be a
 106      * value less than or equal to {@link #maxProperty max}. Default value is 0.
 107      */
 108     private DoubleProperty min;
 109     public final void setMin(double value) {
 110         minProperty().set(value);
 111     }
 112 
 113     public final double getMin() {
 114         return min == null ? 0 : min.get();
 115     }
 116 
 117     public final DoubleProperty minProperty() {
 118         if (min == null) {
 119             min = new SimpleDoubleProperty(this, "min");
 120         }
 121         return min;
 122     }
 123     /**
 124      * The maximum value represented by this {@code ScrollBar}. This should be a
 125      * value greater than or equal to {@link #minProperty min}. Default value is 100.
 126      */
 127     private DoubleProperty max;
 128     public final void setMax(double value) {
 129         maxProperty().set(value);
 130     }
 131 
 132     public final double getMax() {
 133         return max == null ? 100 : max.get();
 134     }
 135 
 136     public final DoubleProperty maxProperty() {
 137         if (max == null) {
 138             max = new SimpleDoubleProperty(this, "max", 100);
 139         }
 140         return max;
 141     }
 142     /**
 143      * The current value represented by this {@code ScrollBar}. This value should
 144      * be between {@link #minProperty min} and {@link #maxProperty max}, inclusive.
 145      */
 146     private DoubleProperty value;
 147     public final void setValue(double value) {
 148         valueProperty().set(value);
 149     }
 150 
 151     public final double getValue() {
 152         return value == null ? 0 : value.get();
 153     }
 154 
 155     public final DoubleProperty valueProperty() {
 156         if (value == null) {
 157             value = new SimpleDoubleProperty(this, "value");
 158         }
 159         return value;
 160     }
 161     /**
 162      * The orientation of the {@code ScrollBar} can either be {@link javafx.geometry.Orientation#HORIZONTAL HORIZONTAL}
 163      * or {@link javafx.geometry.Orientation#VERTICAL VERTICAL}.
 164      */
 165     private ObjectProperty<Orientation> orientation;
 166     public final void setOrientation(Orientation value) {
 167         orientationProperty().set(value);
 168     }
 169 
 170     public final Orientation getOrientation() {
 171         return orientation == null ? Orientation.HORIZONTAL : orientation.get();
 172     }
 173 
 174     public final ObjectProperty<Orientation> orientationProperty() {
 175         if (orientation == null) {
 176             orientation = new StyleableObjectProperty<Orientation>(Orientation.HORIZONTAL) {
 177                 @Override protected void invalidated() {
 178                     final boolean vertical = (get() == Orientation.VERTICAL);
 179                     pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE,    vertical);
 180                     pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, !vertical);
 181                 }
 182 
 183                 @Override
 184                 public CssMetaData<ScrollBar,Orientation> getCssMetaData() {
 185                     return StyleableProperties.ORIENTATION;
 186                 }
 187 
 188                 @Override
 189                 public Object getBean() {
 190                     return ScrollBar.this;
 191                 }
 192 
 193                 @Override
 194                 public String getName() {
 195                     return "orientation";
 196                 }
 197             };
 198         }
 199         return orientation;
 200     }
 201 
 202     /**
 203      * The amount by which to adjust the ScrollBar when the {@link #increment() increment} or
 204      * {@link #decrement() decrement} methods are called.
 205      */
 206     private DoubleProperty unitIncrement;
 207     public final void setUnitIncrement(double value) {
 208         unitIncrementProperty().set(value);
 209     }
 210 
 211     public final double getUnitIncrement() {
 212         return unitIncrement == null ? 1 : unitIncrement.get();
 213     }
 214 
 215     public final DoubleProperty unitIncrementProperty() {
 216         if (unitIncrement == null) {
 217             unitIncrement = new StyleableDoubleProperty(1) {
 218 
 219                 @Override
 220                 public CssMetaData<ScrollBar,Number> getCssMetaData() {
 221                     return StyleableProperties.UNIT_INCREMENT;
 222                 }
 223 
 224                 @Override
 225                 public Object getBean() {
 226                     return ScrollBar.this;
 227                 }
 228 
 229                 @Override
 230                 public String getName() {
 231                     return "unitIncrement";
 232                 }
 233             };
 234         }
 235         return unitIncrement;
 236     }
 237     /**
 238      * The amount by which to adjust the scrollbar if the track of the bar is
 239      * clicked.
 240      */
 241     private DoubleProperty blockIncrement;
 242     public final void setBlockIncrement(double value) {
 243         blockIncrementProperty().set(value);
 244     }
 245 
 246     public final double getBlockIncrement() {
 247         return blockIncrement == null ? 10 : blockIncrement.get();
 248     }
 249 
 250     public final DoubleProperty blockIncrementProperty() {
 251         if (blockIncrement == null) {
 252             blockIncrement = new StyleableDoubleProperty(10) {
 253 
 254                 @Override
 255                 public CssMetaData<ScrollBar,Number> getCssMetaData() {
 256                     return StyleableProperties.BLOCK_INCREMENT;
 257                 }
 258 
 259                 @Override
 260                 public Object getBean() {
 261                     return ScrollBar.this;
 262                 }
 263 
 264                 @Override
 265                 public String getName() {
 266                     return "blockIncrement";
 267                 }
 268             };
 269         }
 270         return blockIncrement;
 271     }
 272     /**
 273      * Visible amount of the scrollbar's range, typically represented by
 274      * the size of the scroll bar's thumb.
 275      */
 276     private DoubleProperty visibleAmount;
 277 
 278     public final void setVisibleAmount(double value) {
 279         visibleAmountProperty().set(value);
 280     }
 281 
 282     public final double getVisibleAmount() {
 283         return visibleAmount == null ? 15 : visibleAmount.get();
 284     }
 285 
 286     public final DoubleProperty visibleAmountProperty() {
 287         if (visibleAmount == null) {
 288             visibleAmount = new SimpleDoubleProperty(this, "visibleAmount");
 289         }
 290         return visibleAmount;
 291     }
 292 
 293     /***************************************************************************
 294      *                                                                         *
 295      * Methods                                                                 *
 296      *                                                                         *
 297      **************************************************************************/
 298 
 299     /**
 300      * Adjusts the {@link #valueProperty() value} property by
 301      * {@link #blockIncrementProperty() blockIncrement}. The {@code position} is the fractional amount
 302      * between the {@link #minProperty min} and {@link #maxProperty max}. For
 303      * example, it might be 50%. If {@code #minProperty min} were 0 and {@code #maxProperty max}
 304      * were 100 and {@link #valueProperty() value} were 25, then a position of .5 would indicate
 305      * that we should increment {@link #valueProperty() value} by
 306      * {@code blockIncrement}. If {@link #valueProperty() value} were 75, then a
 307      * position of .5 would indicate that we
 308      * should decrement {@link #valueProperty() value} by {@link #blockIncrementProperty blockIncrement}.
 309      *
 310      * Note: This function is intended to be used by experts, primarily
 311      *       by those implementing new Skins or Behaviors. It is not common
 312      *       for developers or designers to access this function directly.
 313      * @param position the position
 314      */
 315     public void adjustValue(double position) {
 316         // figure out the "value" associated with the specified position
 317         double posValue = ((getMax() - getMin()) * Utils.clamp(0, position, 1))+getMin();
 318         double newValue;
 319         if (Double.compare(posValue, getValue()) != 0) {
 320             if (posValue > getValue()) {
 321                 newValue = getValue() + getBlockIncrement();
 322             }
 323             else {
 324                 newValue = getValue() - getBlockIncrement();
 325             }
 326 
 327             boolean incrementing = position > ((getValue() - getMin())/(getMax() - getMin()));
 328             if (incrementing && newValue > posValue) newValue = posValue;
 329             if (! incrementing && newValue < posValue) newValue = posValue;
 330             setValue(Utils.clamp(getMin(), newValue, getMax()));
 331         }
 332     }
 333 
 334     /**
 335      * Increments the value of the {@code ScrollBar} by the
 336      * {@link #unitIncrementProperty unitIncrement}
 337      */
 338     public void increment() {
 339         setValue(Utils.clamp(getMin(), getValue() + getUnitIncrement(), getMax()));
 340     }
 341 
 342     /**
 343      * Decrements the value of the {@code ScrollBar} by the
 344      * {@link #unitIncrementProperty unitIncrement}
 345      */
 346     public void decrement() {
 347         setValue(Utils.clamp(getMin(), getValue() - getUnitIncrement(), getMax()));
 348     }
 349 
 350     private void blockIncrement() {
 351         adjustValue(getValue() + getBlockIncrement());
 352     }
 353 
 354     private void blockDecrement() {
 355         adjustValue(getValue() - getBlockIncrement());
 356     }
 357 
 358     /** {@inheritDoc} */
 359     @Override protected Skin<?> createDefaultSkin() {
 360         return new ScrollBarSkin(this);
 361     }
 362 
 363     /***************************************************************************
 364      *                                                                         *
 365      * Stylesheet Handling                                                     *
 366      *                                                                         *
 367      **************************************************************************/
 368 
 369     /**
 370      * Initialize the style class to 'scroll-bar'.
 371      *
 372      * This is the selector class from which CSS can be used to style
 373      * this control.
 374      */
 375     private static final String DEFAULT_STYLE_CLASS = "scroll-bar";
 376 
 377     private static class StyleableProperties {
 378         private static final CssMetaData<ScrollBar,Orientation> ORIENTATION =
 379             new CssMetaData<ScrollBar,Orientation>("-fx-orientation",
 380                 new EnumConverter<Orientation>(Orientation.class),
 381                 Orientation.HORIZONTAL) {
 382 
 383             @Override
 384             public Orientation getInitialValue(ScrollBar node) {
 385                 // A vertical ScrollBar should remain vertical
 386                 return node.getOrientation();
 387             }
 388 
 389             @Override
 390             public boolean isSettable(ScrollBar n) {
 391                 return n.orientation == null || !n.orientation.isBound();
 392             }
 393 
 394             @Override
 395             public StyleableProperty<Orientation> getStyleableProperty(ScrollBar n) {
 396                 return (StyleableProperty<Orientation>)(WritableValue<Orientation>)n.orientationProperty();
 397             }
 398         };
 399 
 400         private static final CssMetaData<ScrollBar,Number> UNIT_INCREMENT =
 401             new CssMetaData<ScrollBar,Number>("-fx-unit-increment",
 402                 SizeConverter.getInstance(), 1.0) {
 403 
 404             @Override
 405             public boolean isSettable(ScrollBar n) {
 406                 return n.unitIncrement == null || !n.unitIncrement.isBound();
 407             }
 408 
 409             @Override
 410             public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
 411                 return (StyleableProperty<Number>)(WritableValue<Number>)n.unitIncrementProperty();
 412             }
 413 
 414         };
 415 
 416         private static final CssMetaData<ScrollBar,Number> BLOCK_INCREMENT =
 417             new CssMetaData<ScrollBar,Number>("-fx-block-increment",
 418                 SizeConverter.getInstance(), 10.0) {
 419 
 420             @Override
 421             public boolean isSettable(ScrollBar n) {
 422                 return n.blockIncrement == null || !n.blockIncrement.isBound();
 423             }
 424 
 425             @Override
 426             public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
 427                 return (StyleableProperty<Number>)(WritableValue<Number>)n.blockIncrementProperty();
 428             }
 429 
 430         };
 431 
 432         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 433         static {
 434             final List<CssMetaData<? extends Styleable, ?>> styleables =
 435                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
 436             styleables.add(ORIENTATION);
 437             styleables.add(UNIT_INCREMENT);
 438             styleables.add(BLOCK_INCREMENT);
 439             STYLEABLES = Collections.unmodifiableList(styleables);
 440         }
 441     }
 442 
 443     /**
 444      * @return The CssMetaData associated with this class, which may include the
 445      * CssMetaData of its superclasses.
 446      * @since JavaFX 8.0
 447      */
 448     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 449         return StyleableProperties.STYLEABLES;
 450     }
 451 
 452     /**
 453      * {@inheritDoc}
 454      * @since JavaFX 8.0
 455      */
 456     @Override
 457     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
 458         return getClassCssMetaData();
 459     }
 460 
 461     /**
 462      * Pseud-class indicating this is a vertical ScrollBar.
 463      */
 464     private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE =
 465             PseudoClass.getPseudoClass("vertical");
 466 
 467     /**
 468      * Pseudo-class indicating this is a horizontal ScrollBar.
 469      */
 470     private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE =
 471             PseudoClass.getPseudoClass("horizontal");
 472 
 473     /**
 474      * Returns the initial focus traversable state of this control, for use
 475      * by the JavaFX CSS engine to correctly set its initial value. This method
 476      * is overridden as by default UI controls have focus traversable set to true,
 477      * but that is not appropriate for this control.
 478      *
 479      * @since 9
 480      */
 481     @Override protected Boolean getInitialFocusTraversable() {
 482         return Boolean.FALSE;
 483     }
 484 
 485 
 486 
 487     /***************************************************************************
 488      *                                                                         *
 489      * Accessibility handling                                                  *
 490      *                                                                         *
 491      **************************************************************************/
 492 
 493     /** {@inheritDoc} */
 494     @Override
 495     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 496         switch (attribute) {
 497             case VALUE: return getValue();
 498             case MAX_VALUE: return getMax();
 499             case MIN_VALUE: return getMin();
 500             case ORIENTATION: return getOrientation();
 501             default: return super.queryAccessibleAttribute(attribute, parameters);
 502         }
 503     }
 504 
 505     /** {@inheritDoc} */
 506     @Override
 507     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 508         switch (action) {
 509             case INCREMENT: increment(); break;
 510             case DECREMENT: decrement(); break;
 511             case BLOCK_INCREMENT: blockIncrement(); break;
 512             case BLOCK_DECREMENT: blockDecrement(); break;
 513             case SET_VALUE: {
 514                 Double value = (Double) parameters[0];
 515                 if (value != null) setValue(value);
 516                 break;
 517             }
 518             default: super.executeAccessibleAction(action, parameters);
 519         }
 520     }
 521 }