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