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.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      * @expert 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      */
 319     public void adjustValue(double position) {
 320         // figure out the "value" associated with the specified position
 321         double posValue = ((getMax() - getMin()) * Utils.clamp(0, position, 1))+getMin();
 322         double newValue;
 323         if (Double.compare(posValue, getValue()) != 0) {
 324             if (posValue > getValue()) {
 325                 newValue = getValue() + getBlockIncrement();
 326             }
 327             else {
 328                 newValue = getValue() - getBlockIncrement();
 329             }
 330             
 331             boolean incrementing = position > ((getValue() - getMin())/(getMax() - getMin()));
 332             if (incrementing && newValue > posValue) newValue = posValue;
 333             if (! incrementing && newValue < posValue) newValue = posValue;
 334             setValue(Utils.clamp(getMin(), newValue, getMax()));
 335         }
 336     }
 337 
 338     /**
 339      * Increments the value of the {@code ScrollBar} by the
 340      * {@link #unitIncrementProperty unitIncrement}
 341      */
 342     public void increment() {
 343         setValue(Utils.clamp(getMin(), getValue() + getUnitIncrement(), getMax()));
 344     }
 345 
 346     /**
 347      * Decrements the value of the {@code ScrollBar} by the
 348      * {@link #unitIncrementProperty unitIncrement}
 349      */
 350     public void decrement() {
 351         setValue(Utils.clamp(getMin(), getValue() - getUnitIncrement(), getMax()));
 352     }
 353 
 354     private void blockIncrement() {
 355         adjustValue(getValue() + getBlockIncrement());
 356     }
 357 
 358     private void blockDecrement() {
 359         adjustValue(getValue() - getBlockIncrement());
 360     }
 361 
 362     /** {@inheritDoc} */
 363     @Override protected Skin<?> createDefaultSkin() {
 364         return new ScrollBarSkin(this);
 365     }
 366 
 367     /***************************************************************************
 368      *                                                                         *
 369      * Stylesheet Handling                                                     *
 370      *                                                                         *
 371      **************************************************************************/
 372 
 373     /**
 374      * Initialize the style class to 'scroll-bar'.
 375      *
 376      * This is the selector class from which CSS can be used to style
 377      * this control.
 378      */
 379     private static final String DEFAULT_STYLE_CLASS = "scroll-bar";
 380 
 381     private static class StyleableProperties {
 382         private static final CssMetaData<ScrollBar,Orientation> ORIENTATION = 
 383             new CssMetaData<ScrollBar,Orientation>("-fx-orientation",
 384                 new EnumConverter<Orientation>(Orientation.class),
 385                 Orientation.HORIZONTAL) {
 386 
 387             @Override
 388             public Orientation getInitialValue(ScrollBar node) {
 389                 // A vertical ScrollBar should remain vertical 
 390                 return node.getOrientation();
 391             }
 392                     
 393             @Override
 394             public boolean isSettable(ScrollBar n) {
 395                 return n.orientation == null || !n.orientation.isBound();
 396             }
 397 
 398             @Override
 399             public StyleableProperty<Orientation> getStyleableProperty(ScrollBar n) {
 400                 return (StyleableProperty<Orientation>)(WritableValue<Orientation>)n.orientationProperty();
 401             }
 402         };
 403         
 404         private static final CssMetaData<ScrollBar,Number> UNIT_INCREMENT = 
 405             new CssMetaData<ScrollBar,Number>("-fx-unit-increment",
 406                 SizeConverter.getInstance(), 1.0) {
 407 
 408             @Override
 409             public boolean isSettable(ScrollBar n) {
 410                 return n.unitIncrement == null || !n.unitIncrement.isBound();
 411             }
 412 
 413             @Override
 414             public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
 415                 return (StyleableProperty<Number>)(WritableValue<Number>)n.unitIncrementProperty();
 416             }
 417                     
 418         };
 419         
 420         private static final CssMetaData<ScrollBar,Number> BLOCK_INCREMENT = 
 421             new CssMetaData<ScrollBar,Number>("-fx-block-increment",
 422                 SizeConverter.getInstance(), 10.0) {
 423 
 424             @Override
 425             public boolean isSettable(ScrollBar n) {
 426                 return n.blockIncrement == null || !n.blockIncrement.isBound();
 427             }
 428 
 429             @Override
 430             public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
 431                 return (StyleableProperty<Number>)(WritableValue<Number>)n.blockIncrementProperty();
 432             }
 433                     
 434         };
 435         
 436         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 437         static {
 438             final List<CssMetaData<? extends Styleable, ?>> styleables = 
 439                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
 440             styleables.add(ORIENTATION);
 441             styleables.add(UNIT_INCREMENT);
 442             styleables.add(BLOCK_INCREMENT);
 443             STYLEABLES = Collections.unmodifiableList(styleables);
 444         }
 445     }
 446 
 447     /**
 448      * @return The CssMetaData associated with this class, which may include the
 449      * CssMetaData of its super classes.
 450      * @since JavaFX 8.0
 451      */
 452     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 453         return StyleableProperties.STYLEABLES;
 454     }
 455 
 456     /**
 457      * {@inheritDoc}
 458      * @since JavaFX 8.0
 459      */
 460     @Override
 461     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
 462         return getClassCssMetaData();
 463     }
 464 
 465     /**
 466      * Pseud-class indicating this is a vertical ScrollBar.
 467      */
 468     private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE =
 469             PseudoClass.getPseudoClass("vertical");
 470 
 471     /**
 472      * Pseudo-class indicating this is a horizontal ScrollBar.
 473      */
 474     private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE =
 475             PseudoClass.getPseudoClass("horizontal");
 476     
 477     /**
 478       * Most Controls return true for focusTraversable, so Control overrides
 479       * this method to return true, but ScrollBar returns false for
 480       * focusTraversable's initial value; hence the override of the override. 
 481       * This method is called from CSS code to get the correct initial value.
 482       * @treatAsPrivate implementation detail
 483       */
 484     @Deprecated @Override
 485     protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
 486         return Boolean.FALSE;
 487     }
 488 
 489 
 490 
 491     /***************************************************************************
 492      *                                                                         *
 493      * Accessibility handling                                                  *
 494      *                                                                         *
 495      **************************************************************************/
 496 
 497     @Override
 498     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 499         switch (attribute) {
 500             case VALUE: return getValue();
 501             case MAX_VALUE: return getMax();
 502             case MIN_VALUE: return getMin();
 503             case ORIENTATION: return getOrientation();
 504             default: return super.queryAccessibleAttribute(attribute, parameters);
 505         }
 506     }
 507 
 508     @Override
 509     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 510         switch (action) {
 511             case INCREMENT: increment(); break;
 512             case DECREMENT: decrement(); break;
 513             case BLOCK_INCREMENT: blockIncrement(); break;
 514             case BLOCK_DECREMENT: blockDecrement(); break;
 515             case SET_VALUE: {
 516                 Double value = (Double) parameters[0];
 517                 if (value != null) setValue(value);
 518                 break;
 519             }
 520             default: super.executeAccessibleAction(action, parameters);
 521         }
 522     }
 523 }