1 /*
   2  * Copyright (c) 2012, 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 java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import javafx.beans.DefaultProperty;
  32 import javafx.beans.property.IntegerProperty;
  33 import javafx.beans.property.ObjectProperty;
  34 import javafx.beans.property.SimpleIntegerProperty;
  35 import javafx.beans.property.SimpleObjectProperty;
  36 import javafx.beans.value.ObservableValue;
  37 import javafx.beans.value.WritableValue;
  38 import javafx.css.CssMetaData;
  39 import javafx.css.StyleableIntegerProperty;
  40 import javafx.css.Styleable;
  41 import javafx.css.StyleableProperty;
  42 import javafx.scene.AccessibleRole;
  43 import javafx.scene.Node;
  44 import javafx.util.Callback;
  45 import javafx.css.converter.SizeConverter;
  46 import javafx.scene.control.skin.PaginationSkin;
  47 
  48 /**
  49  * <p>
  50  * A Pagination control is used for navigation between pages of a single content,
  51  * which has been divided into smaller parts.
  52  * </p>
  53  *
  54  * <h3>Styling the page indicators</h3>
  55  * <p>
  56  * The control can be customized to display numeric page indicators or bullet style indicators by
  57  * setting the style class {@link #STYLE_CLASS_BULLET}.  The
  58  * {@link #maxPageIndicatorCountProperty() maxPageIndicatorCountProperty} can be used to change
  59  * the maximum number of page indicators.  The property value can also be changed
  60  * via CSS using -fx-max-page-indicator-count.
  61  *</p>
  62  *
  63  * <h3>Page count</h3>
  64  * <p>
  65  * The {@link #pageCountProperty() pageCountProperty} controls the number of
  66  * pages this pagination control has.  If the page count is
  67  * not known {@link #INDETERMINATE} should be used as the page count.
  68  * </p>
  69  *
  70  * <h3>Page factory</h3>
  71  * <p>
  72  * The {@link #pageFactoryProperty() pageFactoryProperty} is a callback function
  73  * that is called when a page has been selected by the application or
  74  * the user.  The function is required for the functionality of the pagination
  75  * control.  The callback function should load and return the contents of the selected page.
  76  * Null should be returned if the selected page index does not exist.
  77  * </p>
  78  *
  79  * <h3>Creating a Pagination control:</h3>
  80  * <p>
  81  * A simple example of how to create a pagination control with ten pages and
  82  * each page containing ten hyperlinks.
  83  * </p>
  84  *
  85  * <pre>
  86  * {@code
  87  *   Pagination pagination = new Pagination(10, 0);
  88  *   pagination.setPageFactory(new Callback<Integer, Node>() {
  89  *       public Node call(Integer pageIndex) {
  90  *           VBox box = new VBox(5);
  91  *           for (int i = 0; i < pageIndex + 10; i++) {
  92  *               Hyperlink link = new Hyperlink(myurls[i]);
  93  *               box.getChildren().add(link);
  94  *           }
  95  *           return box;
  96  *       }
  97  *   });
  98  * }</pre>
  99  * @since JavaFX 2.2
 100  */
 101 @DefaultProperty("pages")
 102 public class Pagination extends Control {
 103 
 104     private static final int DEFAULT_MAX_PAGE_INDICATOR_COUNT = 10;
 105 
 106     /**
 107      * The style class to change the numeric page indicators to
 108      * bullet indicators.
 109      */
 110     public static final String STYLE_CLASS_BULLET = "bullet";
 111 
 112     /**
 113      * Value for indicating that the page count is indeterminate.
 114      *
 115      * @see #setPageCount
 116      */
 117     public static final int INDETERMINATE = Integer.MAX_VALUE;
 118 
 119     /**
 120      * Constructs a new Pagination control with the specified page count
 121      * and page index.
 122      *
 123      * @param pageCount the number of pages for the pagination control
 124      * @param pageIndex the index of the first page.
 125      *
 126      */
 127     public Pagination(int pageCount, int pageIndex) {
 128         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 129         setAccessibleRole(AccessibleRole.PAGINATION);
 130         setPageCount(pageCount);
 131         setCurrentPageIndex(pageIndex);
 132     }
 133 
 134     /**
 135      * Constructs a new Pagination control with the specified page count.
 136      *
 137      * @param pageCount the number of pages for the pagination control
 138      *
 139      */
 140     public Pagination(int pageCount) {
 141         this(pageCount, 0);
 142     }
 143 
 144     /**
 145      * Constructs a Pagination control with an {@link #INDETERMINATE} page count
 146      * and a page index equal to zero.
 147      */
 148     public Pagination() {
 149         this(INDETERMINATE, 0);
 150     }
 151 
 152     /***************************************************************************
 153      *                                                                         *
 154      * Properties                                                              *
 155      *                                                                         *
 156      **************************************************************************/
 157 
 158     private int oldMaxPageIndicatorCount = DEFAULT_MAX_PAGE_INDICATOR_COUNT;
 159     private IntegerProperty maxPageIndicatorCount;
 160 
 161     /**
 162      * Sets the maximum number of page indicators.
 163      *
 164      * @param value the number of page indicators.  The default is 10.
 165      */
 166     public final void setMaxPageIndicatorCount(int value) { maxPageIndicatorCountProperty().set(value); }
 167 
 168     /**
 169      * Returns the maximum number of page indicators.
 170      */
 171     public final int getMaxPageIndicatorCount() {
 172         return maxPageIndicatorCount == null ? DEFAULT_MAX_PAGE_INDICATOR_COUNT : maxPageIndicatorCount.get();
 173     }
 174 
 175     /**
 176      * The maximum number of page indicators to use for this pagination control.
 177      * The maximum number of pages indicators will remain unchanged if the value is less than 1
 178      * or greater than the {@link #pageCount}.  The number of page indicators will be
 179      * reduced to fit the control if the {@code maxPageIndicatorCount} cannot fit.
 180      *
 181      * The default is 10 page indicators.
 182      */
 183     public final IntegerProperty maxPageIndicatorCountProperty() {
 184         if (maxPageIndicatorCount == null) {
 185             maxPageIndicatorCount = new StyleableIntegerProperty(DEFAULT_MAX_PAGE_INDICATOR_COUNT) {
 186 
 187                 @Override protected void invalidated() {
 188                     if (!maxPageIndicatorCount.isBound()) {
 189                         if (getMaxPageIndicatorCount() < 1 || getMaxPageIndicatorCount() > getPageCount()) {
 190                             setMaxPageIndicatorCount(oldMaxPageIndicatorCount);
 191                         }
 192                         oldMaxPageIndicatorCount = getMaxPageIndicatorCount();
 193                     }
 194                 }
 195 
 196                 @Override
 197                 public CssMetaData<Pagination,Number> getCssMetaData() {
 198                     return StyleableProperties.MAX_PAGE_INDICATOR_COUNT;
 199                 }
 200 
 201                 @Override
 202                 public Object getBean() {
 203                     return Pagination.this;
 204                 }
 205 
 206                 @Override
 207                 public String getName() {
 208                     return "maxPageIndicatorCount";
 209                 }
 210             };
 211         }
 212         return maxPageIndicatorCount;
 213     }
 214 
 215     private int oldPageCount = INDETERMINATE;
 216     private IntegerProperty pageCount = new SimpleIntegerProperty(this, "pageCount", INDETERMINATE) {
 217         @Override protected void invalidated() {
 218             if (!pageCount.isBound()) {
 219                 if (getPageCount() < 1) {
 220                     setPageCount(oldPageCount);
 221                 }
 222                 oldPageCount = getPageCount();
 223             }
 224         }
 225     };
 226 
 227     /**
 228      * Sets the number of pages.
 229      *
 230      * @param value the number of pages
 231      */
 232     public final void setPageCount(int value) { pageCount.set(value); }
 233 
 234     /**
 235      * Returns the number of pages.
 236      */
 237     public final int getPageCount() { return pageCount.get(); }
 238 
 239     /**
 240      * The number of pages for this pagination control.  This
 241      * value must be greater than or equal to 1. {@link #INDETERMINATE}
 242      * should be used as the page count if the total number of pages is unknown.
 243      *
 244      * The default is an {@link #INDETERMINATE} number of pages.
 245      */
 246     public final IntegerProperty pageCountProperty() { return pageCount; }
 247 
 248     private final IntegerProperty currentPageIndex = new SimpleIntegerProperty(this, "currentPageIndex", 0) {
 249         @Override protected void invalidated() {
 250             if (!currentPageIndex.isBound()) {
 251                 if (getCurrentPageIndex() < 0) {
 252                     setCurrentPageIndex(0);
 253                 } else if (getCurrentPageIndex() > getPageCount() - 1) {
 254                     setCurrentPageIndex(getPageCount() - 1);
 255                 }
 256             }
 257         }
 258 
 259         @Override
 260         public void bind(ObservableValue<? extends Number> rawObservable) {
 261             throw new UnsupportedOperationException("currentPageIndex supports only bidirectional binding");
 262         }
 263     };
 264 
 265     /**
 266      * Sets the current page index.
 267      * @param value the current page index.
 268      */
 269     public final void setCurrentPageIndex(int value) { currentPageIndex.set(value); }
 270 
 271     /**
 272      * Returns the current page index.
 273      */
 274     public final int getCurrentPageIndex() { return currentPageIndex.get(); }
 275 
 276     /**
 277      * The current page index to display for this pagination control.  The first page will be
 278      * the current page if the value is less than 0.  Similarly the last page
 279      * will be the current page if the value is greater than the {@link #pageCount}
 280      *
 281      * The default is 0 for the first page.
 282      * <p>
 283      * Because the page indicators set the current page index, the currentPageIndex property permits only
 284      * bidirectional binding.
 285      * The {@link javafx.beans.property.IntegerProperty#bind(javafx.beans.value.ObservableValue) bind} method
 286      * throws an UnsupportedOperationException.
 287      * </p>
 288      */
 289     public final IntegerProperty currentPageIndexProperty() { return currentPageIndex; }
 290 
 291     private ObjectProperty<Callback<Integer, Node>> pageFactory =
 292             new SimpleObjectProperty<Callback<Integer, Node>>(this, "pageFactory");
 293 
 294     /**
 295      * Sets the page factory callback function.
 296      */
 297     public final void setPageFactory(Callback<Integer, Node> value) { pageFactory.set(value); }
 298 
 299     /**
 300      * Returns the page factory callback function.
 301      */
 302     public final Callback<Integer, Node> getPageFactory() {return pageFactory.get(); }
 303 
 304     /**
 305      * The pageFactory callback function that is called when a page has been
 306      * selected by the application or the user.
 307      *
 308      * This function is required for the functionality of the pagination
 309      * control.  The callback function should load and return the contents the page index.
 310      * Null should be returned if the page index does not exist.  The currentPageIndex
 311      * will not change when null is returned.
 312      *
 313      * The default is null if there is no page factory set.
 314      */
 315     public final ObjectProperty<Callback<Integer, Node>> pageFactoryProperty() { return pageFactory; }
 316 
 317 
 318     /***************************************************************************
 319      *                                                                         *
 320      * Methods                                                                 *
 321      *                                                                         *
 322      **************************************************************************/
 323 
 324     /** {@inheritDoc} */
 325     @Override protected Skin<?> createDefaultSkin() {
 326         return new PaginationSkin(this);
 327     }
 328 
 329     /***************************************************************************
 330      *                                                                         *
 331      *                         Stylesheet Handling                             *
 332      *                                                                         *
 333      **************************************************************************/
 334 
 335     private static final String DEFAULT_STYLE_CLASS = "pagination";
 336 
 337     private static class StyleableProperties {
 338         private static final CssMetaData<Pagination,Number> MAX_PAGE_INDICATOR_COUNT =
 339             new CssMetaData<Pagination,Number>("-fx-max-page-indicator-count",
 340                 SizeConverter.getInstance(), DEFAULT_MAX_PAGE_INDICATOR_COUNT) {
 341 
 342             @Override
 343             public boolean isSettable(Pagination n) {
 344                 return n.maxPageIndicatorCount == null || !n.maxPageIndicatorCount.isBound();
 345             }
 346 
 347             @Override
 348             public StyleableProperty<Number> getStyleableProperty(Pagination n) {
 349                 return (StyleableProperty<Number>)(WritableValue<Number>)n.maxPageIndicatorCountProperty();
 350             }
 351         };
 352         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 353         static {
 354             final List<CssMetaData<? extends Styleable, ?>> styleables =
 355                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
 356             styleables.add(MAX_PAGE_INDICATOR_COUNT);
 357             STYLEABLES = Collections.unmodifiableList(styleables);
 358         }
 359     }
 360 
 361     /**
 362      * @return The CssMetaData associated with this class, which may include the
 363      * CssMetaData of its superclasses.
 364      * @since JavaFX 8.0
 365      */
 366     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 367         return StyleableProperties.STYLEABLES;
 368     }
 369 
 370     /**
 371      * {@inheritDoc}
 372      * @since JavaFX 8.0
 373      */
 374     @Override
 375     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
 376         return getClassCssMetaData();
 377     }
 378 
 379 }