1 /*
   2  * Copyright (c) 2010, 2014, 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 javafx.beans.property.ReadOnlyIntegerProperty;
  29 import javafx.beans.property.ReadOnlyIntegerWrapper;
  30 import javafx.beans.property.ReadOnlyObjectProperty;
  31 import javafx.beans.property.ReadOnlyObjectWrapper;
  32 
  33 /**
  34  * The abstract base class for FocusModel implementations.
  35  * @since JavaFX 2.0
  36  */
  37 public abstract class FocusModel<T> {
  38 
  39     /***********************************************************************
  40      *                                                                     *
  41      * Constructors                                                        *
  42      *                                                                     *
  43      **********************************************************************/
  44 
  45     /**
  46      * Creates a default FocusModel instance.
  47      */
  48     public FocusModel() {
  49         focusedIndexProperty().addListener(valueModel -> {
  50             // we used to lazily retrieve the focused item, but now we just
  51             // do it when the focused index changes.
  52             setFocusedItem(getModelItem(getFocusedIndex()));
  53         });
  54     }
  55 
  56 
  57 
  58     /***************************************************************************
  59      *                                                                         *
  60      * Focus Properties                                                        *
  61      *                                                                         *
  62      **************************************************************************/
  63 
  64     /**
  65      * The index of the current item in the FocusModel which has the focus. It
  66      * is possible that this will be -1, but only if the control is empty.
  67      * If the control is not itself focused, this property will still
  68      * reference the row index that would receive the keyboard focus if the control
  69      * itself were focused.
  70      */
  71     private ReadOnlyIntegerWrapper focusedIndex = new ReadOnlyIntegerWrapper(this, "focusedIndex", -1);
  72     public final ReadOnlyIntegerProperty focusedIndexProperty() { return focusedIndex.getReadOnlyProperty();  }
  73     public final int getFocusedIndex() { return focusedIndex.get(); }
  74     final void setFocusedIndex(int value) { focusedIndex.set(value); }
  75 
  76 
  77 
  78     /**
  79      * The current item in the FocusModel which has the focus. It
  80      * is possible that this will be null, but only if the control is empty.
  81      * If the control is not itself focused, this property will still
  82      * reference the item that would receive the keyboard focus if the control
  83      * itself were focused.
  84      */
  85     private ReadOnlyObjectWrapper<T> focusedItem = new ReadOnlyObjectWrapper<T>(this, "focusedItem");
  86     public final ReadOnlyObjectProperty<T> focusedItemProperty() { return focusedItem.getReadOnlyProperty(); }
  87     public final T getFocusedItem() { return focusedItemProperty().get(); }
  88     final void setFocusedItem(T value) { focusedItem.set(value); }
  89 
  90 
  91 
  92     /***********************************************************************
  93      *                                                                     *
  94      * Public Focus API                                                    *
  95      *                                                                     *
  96      **********************************************************************/
  97 
  98 
  99     /**
 100      * Returns the number of items in the data model that underpins the control.
 101      * An example would be that a ListView focus model would likely return
 102      * <code>listView.getItems().size()</code>. The valid range of focusable
 103      * indices is between 0 and whatever is returned by this method.
 104      * @return the number of items in the data model that underpins the control
 105      */
 106     protected abstract int getItemCount();
 107 
 108     /**
 109      * Returns the item at the given index. An example using ListView would be
 110      * <code>listView.getItems().get(index)</code>.
 111      *
 112      * @param index The index of the item that is requested from the underlying
 113      *      data model.
 114      * @return Returns null if the index is out of bounds, or an element of type
 115      *      T that is related to the given index.
 116      */
 117     protected abstract T getModelItem(int index);
 118 
 119     /**
 120      * <p>Convenience method to inform if the given index is currently focused
 121      * in this SelectionModel. Is functionally equivalent to calling
 122      * <pre><code>getFocusedIndex() == index</code></pre>.
 123      *
 124      * @param index The index to check as to whether it is currently focused
 125      *      or not.
 126      * @return True if the given index is focused, false otherwise.
 127      */
 128     public boolean isFocused(int index) {
 129         if (index < 0 || index >= getItemCount()) return false;
 130 
 131         return getFocusedIndex() == index;
 132     }
 133 
 134     /**
 135      * Causes the item at the given index to receive the focus. This does not
 136      * cause the current selection to change. Updates the focusedItem and
 137      * focusedIndex properties such that <code>focusedIndex = -1</code> unless
 138      * <code>0 &lt;= index &lt; model size</code>.
 139      *
 140      * @param index The index of the item to get focus.
 141      */
 142     public void focus(int index) {
 143         if (index < 0 || index >= getItemCount()) {
 144             setFocusedIndex(-1);
 145         } else {
 146             int oldFocusIndex = getFocusedIndex();
 147             setFocusedIndex(index);
 148 
 149             if (oldFocusIndex == index) {
 150                 // manually update the focus item to ensure consistency
 151                 setFocusedItem(getModelItem(index));
 152             }
 153         }
 154     }
 155 
 156     /**
 157      * Attempts to give focus to the row previous to the currently focused row.
 158      * If the current focus owner is the first row, or is -1 (representing that
 159      * there is no current focus owner), calling this method will have no result.
 160      */
 161     public void focusPrevious() {
 162         if (getFocusedIndex() == -1) {
 163             focus(0);
 164         } else if (getFocusedIndex() > 0) {
 165             focus(getFocusedIndex() - 1);
 166         }
 167     }
 168 
 169     /**
 170      * Attempts to give focus to the row after to the currently focused row.
 171      * If the current focus owner is the last row, calling this method will have
 172      * no result.
 173      */
 174     public void focusNext() {
 175         if (getFocusedIndex() == -1) {
 176             focus(0);
 177         } else if (getFocusedIndex() != getItemCount() -1) {
 178             focus(getFocusedIndex() + 1);
 179         }
 180     }
 181 }