1 /*
   2  * Copyright (c) 2012, 2017, 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      * @return the maximum number of page indicators
 171      */
 172     public final int getMaxPageIndicatorCount() {
 173         return maxPageIndicatorCount == null ? DEFAULT_MAX_PAGE_INDICATOR_COUNT : maxPageIndicatorCount.get();
 174     }
 175 
 176     /**
 177      * The maximum number of page indicators to use for this pagination control.
 178      * The maximum number of pages indicators will remain unchanged if the value is less than 1
 179      * or greater than the {@link #pageCount}.  The number of page indicators will be
 180      * reduced to fit the control if the {@code maxPageIndicatorCount} cannot fit.
 181      *
 182      * The default is 10 page indicators.
 183      * @return the maximum number of page indicators to use for this pagination control
 184      */
 185     public final IntegerProperty maxPageIndicatorCountProperty() {
 186         if (maxPageIndicatorCount == null) {
 187             maxPageIndicatorCount = new StyleableIntegerProperty(DEFAULT_MAX_PAGE_INDICATOR_COUNT) {
 188 
 189                 @Override protected void invalidated() {
 190                     if (!maxPageIndicatorCount.isBound()) {
 191                         if (getMaxPageIndicatorCount() < 1 || getMaxPageIndicatorCount() > getPageCount()) {
 192                             setMaxPageIndicatorCount(oldMaxPageIndicatorCount);
 193                         }
 194                         oldMaxPageIndicatorCount = getMaxPageIndicatorCount();
 195                     }
 196                 }
 197 
 198                 @Override
 199                 public CssMetaData<Pagination,Number> getCssMetaData() {
 200                     return StyleableProperties.MAX_PAGE_INDICATOR_COUNT;
 201                 }
 202 
 203                 @Override
 204                 public Object getBean() {
 205                     return Pagination.this;
 206                 }
 207 
 208                 @Override
 209                 public String getName() {
 210                     return "maxPageIndicatorCount";
 211                 }
 212             };
 213         }
 214         return maxPageIndicatorCount;
 215     }
 216 
 217     private int oldPageCount = INDETERMINATE;
 218     private IntegerProperty pageCount = new SimpleIntegerProperty(this, "pageCount", INDETERMINATE) {
 219         @Override protected void invalidated() {
 220             if (!pageCount.isBound()) {
 221                 if (getPageCount() < 1) {
 222                     setPageCount(oldPageCount);
 223                 }
 224                 oldPageCount = getPageCount();
 225             }
 226         }
 227     };
 228 
 229     /**
 230      * Sets the number of pages.
 231      *
 232      * @param value the number of pages
 233      */
 234     public final void setPageCount(int value) { pageCount.set(value); }
 235 
 236     /**
 237      * Returns the number of pages.
 238      * @return the number of pages
 239      */
 240     public final int getPageCount() { return pageCount.get(); }
 241 
 242     /**
 243      * The number of pages for this pagination control.  This
 244      * value must be greater than or equal to 1. {@link #INDETERMINATE}
 245      * should be used as the page count if the total number of pages is unknown.
 246      *
 247      * The default is an {@link #INDETERMINATE} number of pages.
 248      * @return the number of pages for this pagination control
 249      */
 250     public final IntegerProperty pageCountProperty() { return pageCount; }
 251 
 252     private final IntegerProperty currentPageIndex = new SimpleIntegerProperty(this, "currentPageIndex", 0) {
 253         @Override protected void invalidated() {
 254             if (!currentPageIndex.isBound()) {
 255                 if (getCurrentPageIndex() < 0) {
 256                     setCurrentPageIndex(0);
 257                 } else if (getCurrentPageIndex() > getPageCount() - 1) {
 258                     setCurrentPageIndex(getPageCount() - 1);
 259                 }
 260             }
 261         }
 262 
 263         @Override
 264         public void bind(ObservableValue<? extends Number> rawObservable) {
 265             throw new UnsupportedOperationException("currentPageIndex supports only bidirectional binding");
 266         }
 267     };
 268 
 269     /**
 270      * Sets the current page index.
 271      * @param value the current page index.
 272      */
 273     public final void setCurrentPageIndex(int value) { currentPageIndex.set(value); }
 274 
 275     /**
 276      * Returns the current page index.
 277      * @return the current page index
 278      */
 279     public final int getCurrentPageIndex() { return currentPageIndex.get(); }
 280 
 281     /**
 282      * The current page index to display for this pagination control.  The first page will be
 283      * the current page if the value is less than 0.  Similarly the last page
 284      * will be the current page if the value is greater than the {@link #pageCount}
 285      *
 286      * The default is 0 for the first page.
 287      * <p>
 288      * Because the page indicators set the current page index, the currentPageIndex property permits only
 289      * bidirectional binding.
 290      * The {@link javafx.beans.property.IntegerProperty#bind(javafx.beans.value.ObservableValue) bind} method
 291      * throws an UnsupportedOperationException.
 292      * </p>
 293      * @return the current page index property
 294      */
 295     public final IntegerProperty currentPageIndexProperty() { return currentPageIndex; }
 296 
 297     private ObjectProperty<Callback<Integer, Node>> pageFactory =
 298             new SimpleObjectProperty<Callback<Integer, Node>>(this, "pageFactory");
 299 
 300     /**
 301      * Sets the page factory callback function.
 302      * @param value the page factory callback function
 303      */
 304     public final void setPageFactory(Callback<Integer, Node> value) { pageFactory.set(value); }
 305 
 306     /**
 307      * Returns the page factory callback function.
 308      * @return the page factory callback function
 309      */
 310     public final Callback<Integer, Node> getPageFactory() {return pageFactory.get(); }
 311 
 312     /**
 313      * The pageFactory callback function that is called when a page has been
 314      * selected by the application or the user.
 315      *
 316      * This function is required for the functionality of the pagination
 317      * control.  The callback function should load and return the contents the page index.
 318      * Null should be returned if the page index does not exist.  The currentPageIndex
 319      * will not change when null is returned.
 320      *
 321      * The default is null if there is no page factory set.
 322      * @return the page factory property
 323      */
 324     public final ObjectProperty<Callback<Integer, Node>> pageFactoryProperty() { return pageFactory; }
 325 
 326 
 327     /***************************************************************************
 328      *                                                                         *
 329      * Methods                                                                 *
 330      *                                                                         *
 331      **************************************************************************/
 332 
 333     /** {@inheritDoc} */
 334     @Override protected Skin<?> createDefaultSkin() {
 335         return new PaginationSkin(this);
 336     }
 337 
 338     /***************************************************************************
 339      *                                                                         *
 340      *                         Stylesheet Handling                             *
 341      *                                                                         *
 342      **************************************************************************/
 343 
 344     private static final String DEFAULT_STYLE_CLASS = "pagination";
 345 
 346     private static class StyleableProperties {
 347         private static final CssMetaData<Pagination,Number> MAX_PAGE_INDICATOR_COUNT =
 348             new CssMetaData<Pagination,Number>("-fx-max-page-indicator-count",
 349                 SizeConverter.getInstance(), DEFAULT_MAX_PAGE_INDICATOR_COUNT) {
 350 
 351             @Override
 352             public boolean isSettable(Pagination n) {
 353                 return n.maxPageIndicatorCount == null || !n.maxPageIndicatorCount.isBound();
 354             }
 355 
 356             @Override
 357             public StyleableProperty<Number> getStyleableProperty(Pagination n) {
 358                 return (StyleableProperty<Number>)(WritableValue<Number>)n.maxPageIndicatorCountProperty();
 359             }
 360         };
 361         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 362         static {
 363             final List<CssMetaData<? extends Styleable, ?>> styleables =
 364                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
 365             styleables.add(MAX_PAGE_INDICATOR_COUNT);
 366             STYLEABLES = Collections.unmodifiableList(styleables);
 367         }
 368     }
 369 
 370     /**
 371      * @return The CssMetaData associated with this class, which may include the
 372      * CssMetaData of its superclasses.
 373      * @since JavaFX 8.0
 374      */
 375     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 376         return StyleableProperties.STYLEABLES;
 377     }
 378 
 379     /**
 380      * {@inheritDoc}
 381      * @since JavaFX 8.0
 382      */
 383     @Override
 384     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
 385         return getClassCssMetaData();
 386     }
 387 
 388 }