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