modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/CellBehaviorBase.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


  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 com.sun.javafx.scene.control.behavior;
  27 
  28 import javafx.scene.control.Cell;
  29 import javafx.scene.control.Control;
  30 import javafx.scene.control.FocusModel;
  31 import javafx.scene.control.IndexedCell;
  32 import javafx.scene.control.MultipleSelectionModel;
  33 import javafx.scene.control.SelectionMode;
  34 import javafx.scene.input.ContextMenuEvent;
  35 import javafx.scene.input.MouseButton;
  36 import javafx.scene.input.MouseEvent;
  37 
  38 import java.util.ArrayList;
  39 import java.util.List;
  40 
  41 /**
  42  * Behaviors for standard cells types. Simply defines methods that subclasses
  43  * implement so that CellSkinBase has API to call.
  44  */
  45 public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {
  46 
  47 
  48     /***************************************************************************
  49      *                                                                         *
  50      * Private static implementation                                           *
  51      *                                                                         *
  52      **************************************************************************/
  53 
  54     private static final String ANCHOR_PROPERTY_KEY = "anchor";


  84         return isDefaultAnchor != null && isDefaultAnchor == true && hasAnchor(control);
  85     }
  86 
  87     private static boolean hasAnchor(Control control) {
  88         return control.getProperties().get(ANCHOR_PROPERTY_KEY) != null;
  89     }
  90 
  91     public static void removeAnchor(Control control) {
  92         control.getProperties().remove(ANCHOR_PROPERTY_KEY);
  93         control.getProperties().remove(IS_DEFAULT_ANCHOR_KEY);
  94     }
  95 
  96 
  97 
  98     /***************************************************************************
  99      *                                                                         *
 100      * Private fields                                                          *
 101      *                                                                         *
 102      **************************************************************************/
 103 


 104     // To support touch devices, we have to slightly modify this behavior, such
 105     // that selection only happens on mouse release, if only minimal dragging
 106     // has occurred.
 107     private boolean latePress = false;
 108 
 109 
 110 
 111     /***************************************************************************
 112      *                                                                         *
 113      * Constructors                                                            *
 114      *                                                                         *
 115      **************************************************************************/
 116 
 117     public CellBehaviorBase(T control, List<KeyBinding> bindings) {
 118         super(control, bindings);













 119     }
 120 
 121 
 122     protected abstract Control getCellContainer(); // e.g. ListView
 123     protected abstract MultipleSelectionModel<?> getSelectionModel();
 124     protected abstract FocusModel<?> getFocusModel();
 125     protected abstract void edit(T cell);
 126     protected boolean handleDisclosureNode(double x, double y) {
 127         return false;
 128     }
 129     protected boolean isClickPositionValid(final double x, final double y) {
 130         return true;
 131     }
 132 
 133 

 134     /***************************************************************************
 135      *                                                                         *
 136      * Public API                                                              *
 137      *                                                                         *
 138      **************************************************************************/
 139 





 140     protected int getIndex() {
 141         return getControl() instanceof IndexedCell ? ((IndexedCell<?>)getControl()).getIndex() : -1;
 142     }
 143 
 144     @Override public void mousePressed(MouseEvent e) {
 145         if (e.isSynthesized()) {
 146             latePress = true;
 147         } else {
 148             latePress  = isSelected();
 149             if (!latePress) {
 150                 doSelect(e.getX(), e.getY(), e.getButton(), e.getClickCount(),
 151                         e.isShiftDown(), e.isShortcutDown());
 152             }
 153         }
 154     }
 155 
 156     @Override public void mouseReleased(MouseEvent e) {
 157         if (latePress) {
 158             latePress = false;
 159             doSelect(e.getX(), e.getY(), e.getButton(), e.getClickCount(),
 160                     e.isShiftDown(), e.isShortcutDown());
 161         }
 162     }
 163 
 164     @Override public void mouseDragged(MouseEvent e) {
 165         latePress = false;
 166     }
 167 
 168 
 169 
 170     /***************************************************************************
 171      *                                                                         *
 172      * Private implementation                                                  *
 173      *                                                                         *
 174      **************************************************************************/
 175 
 176     protected void doSelect(final double x, final double y, final MouseButton button,
 177                             final int clickCount, final boolean shiftDown, final boolean shortcutDown) {
 178         // we update the cell to point to the new tree node
 179         final T cell = getControl();
 180 
 181         final Control cellContainer = getCellContainer();
 182 
 183         // If the mouse event is not contained within this TreeCell, then
 184         // we don't want to react to it.
 185         if (cell.isEmpty() || ! cell.contains(x, y)) {
 186             return;
 187         }
 188 
 189         final int index = getIndex();
 190         boolean selected = cell.isSelected();
 191         MultipleSelectionModel<?> sm = getSelectionModel();
 192         if (sm == null) return;
 193 
 194         FocusModel<?> fm = getFocusModel();
 195         if (fm == null) return;
 196 
 197         // if the user has clicked on the disclosure node, we do nothing other
 198         // than expand/collapse the tree item (if applicable). We do not do editing!
 199         if (handleDisclosureNode(x,y)) {


 246     protected void simpleSelect(MouseButton button, int clickCount, boolean shortcutDown) {
 247         final int index = getIndex();
 248         MultipleSelectionModel<?> sm = getSelectionModel();
 249         boolean isAlreadySelected = sm.isSelected(index);
 250 
 251         if (isAlreadySelected && shortcutDown) {
 252             sm.clearSelection(index);
 253             getFocusModel().focus(index);
 254             isAlreadySelected = false;
 255         } else {
 256             sm.clearAndSelect(index);
 257         }
 258 
 259         handleClicks(button, clickCount, isAlreadySelected);
 260     }
 261 
 262     protected void handleClicks(MouseButton button, int clickCount, boolean isAlreadySelected) {
 263         // handle editing, which only occurs with the primary mouse button
 264         if (button == MouseButton.PRIMARY) {
 265             if (clickCount == 1 && isAlreadySelected) {
 266                 edit(getControl());
 267             } else if (clickCount == 1) {
 268                 // cancel editing
 269                 edit(null);
 270             } else if (clickCount == 2 && getControl().isEditable()) {
 271                 edit(getControl());
 272             }
 273         }
 274     }
 275 
 276     void selectRows(int focusedIndex, int index) {
 277         final boolean asc = focusedIndex < index;
 278 
 279         // and then determine all row and columns which must be selected
 280         int minRow = Math.min(focusedIndex, index);
 281         int maxRow = Math.max(focusedIndex, index);
 282 
 283         // To prevent RT-32119, we make a copy of the selected indices
 284         // list first, so that we are not iterating and modifying it
 285         // concurrently.
 286         List<Integer> selectedIndices = new ArrayList<>(getSelectionModel().getSelectedIndices());
 287         for (int i = 0, max = selectedIndices.size(); i < max; i++) {
 288             int selectedIndex = selectedIndices.get(i);
 289             if (selectedIndex < minRow || selectedIndex > maxRow) {
 290                 getSelectionModel().clearSelection(selectedIndex);
 291             }


 294         if (minRow == maxRow) {
 295             // RT-32560: This prevents the anchor 'sticking' in
 296             // the wrong place when a range is selected and then
 297             // selection goes back to the anchor position.
 298             // (Refer to the video in RT-32560 for more detail).
 299             getSelectionModel().select(minRow);
 300         } else {
 301             // RT-21444: We need to put the range in the correct
 302             // order or else the last selected row will not be the
 303             // last item in the selectedItems list of the selection
 304             // model,
 305             if (asc) {
 306                 getSelectionModel().selectRange(minRow, maxRow + 1);
 307             } else {
 308                 getSelectionModel().selectRange(maxRow, minRow - 1);
 309             }
 310         }
 311     }
 312 
 313     protected boolean isSelected() {
 314         return getControl().isSelected();
 315     }
 316 }


  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 com.sun.javafx.scene.control.behavior;
  27 
  28 import javafx.scene.control.Cell;
  29 import javafx.scene.control.Control;
  30 import javafx.scene.control.FocusModel;
  31 import javafx.scene.control.IndexedCell;
  32 import javafx.scene.control.MultipleSelectionModel;
  33 import javafx.scene.control.SelectionMode;
  34 import com.sun.javafx.scene.control.inputmap.InputMap;
  35 import javafx.scene.input.MouseButton;
  36 import javafx.scene.input.MouseEvent;
  37 
  38 import java.util.ArrayList;
  39 import java.util.List;
  40 
  41 /**
  42  * Behaviors for standard cells types. Simply defines methods that subclasses
  43  * implement so that CellSkinBase has API to call.
  44  */
  45 public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {
  46 
  47 
  48     /***************************************************************************
  49      *                                                                         *
  50      * Private static implementation                                           *
  51      *                                                                         *
  52      **************************************************************************/
  53 
  54     private static final String ANCHOR_PROPERTY_KEY = "anchor";


  84         return isDefaultAnchor != null && isDefaultAnchor == true && hasAnchor(control);
  85     }
  86 
  87     private static boolean hasAnchor(Control control) {
  88         return control.getProperties().get(ANCHOR_PROPERTY_KEY) != null;
  89     }
  90 
  91     public static void removeAnchor(Control control) {
  92         control.getProperties().remove(ANCHOR_PROPERTY_KEY);
  93         control.getProperties().remove(IS_DEFAULT_ANCHOR_KEY);
  94     }
  95 
  96 
  97 
  98     /***************************************************************************
  99      *                                                                         *
 100      * Private fields                                                          *
 101      *                                                                         *
 102      **************************************************************************/
 103 
 104     private final InputMap<T> cellInputMap;
 105 
 106     // To support touch devices, we have to slightly modify this behavior, such
 107     // that selection only happens on mouse release, if only minimal dragging
 108     // has occurred.
 109     private boolean latePress = false;
 110 
 111 
 112 
 113     /***************************************************************************
 114      *                                                                         *
 115      * Constructors                                                            *
 116      *                                                                         *
 117      **************************************************************************/
 118 
 119     public CellBehaviorBase(T control) {
 120         super(control);
 121 
 122         // create a map for cell-specific mappings (this reuses the default
 123         // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
 124         cellInputMap = createInputMap();
 125 
 126         // TODO add focus traversal mappings (?)
 127         // addDefaultMapping(cellInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
 128 
 129         addDefaultMapping(
 130             new InputMap.MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
 131             new InputMap.MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
 132             new InputMap.MouseMapping(MouseEvent.MOUSE_DRAGGED, this::mouseDragged)
 133         );
 134     }
 135 
 136 
 137     protected abstract Control getCellContainer(); // e.g. ListView
 138     protected abstract MultipleSelectionModel<?> getSelectionModel();
 139     protected abstract FocusModel<?> getFocusModel();
 140     protected abstract void edit(T cell);
 141     protected boolean handleDisclosureNode(double x, double y) {
 142         return false;
 143     }
 144     protected boolean isClickPositionValid(final double x, final double y) {
 145         return true;
 146     }
 147 
 148 
 149 
 150     /***************************************************************************
 151      *                                                                         *
 152      * Public API                                                              *
 153      *                                                                         *
 154      **************************************************************************/
 155 
 156     /** {@inheritDoc} */
 157     @Override public InputMap<T> getInputMap() {
 158         return cellInputMap;
 159     }
 160 
 161     protected int getIndex() {
 162         return getNode() instanceof IndexedCell ? ((IndexedCell<?>)getNode()).getIndex() : -1;
 163     }
 164 
 165     public void mousePressed(MouseEvent e) {
 166         if (e.isSynthesized()) {
 167             latePress = true;
 168         } else {
 169             latePress  = isSelected();
 170             if (!latePress) {
 171                 doSelect(e.getX(), e.getY(), e.getButton(), e.getClickCount(),
 172                         e.isShiftDown(), e.isShortcutDown());
 173             }
 174         }
 175     }
 176 
 177     public void mouseReleased(MouseEvent e) {
 178         if (latePress) {
 179             latePress = false;
 180             doSelect(e.getX(), e.getY(), e.getButton(), e.getClickCount(),
 181                     e.isShiftDown(), e.isShortcutDown());
 182         }
 183     }
 184 
 185     public void mouseDragged(MouseEvent e) {
 186         latePress = false;
 187     }
 188 
 189 
 190 
 191     /***************************************************************************
 192      *                                                                         *
 193      * Private implementation                                                  *
 194      *                                                                         *
 195      **************************************************************************/
 196 
 197     protected void doSelect(final double x, final double y, final MouseButton button,
 198                             final int clickCount, final boolean shiftDown, final boolean shortcutDown) {
 199         // we update the cell to point to the new tree node
 200         final T cell = getNode();
 201 
 202         final Control cellContainer = getCellContainer();
 203 
 204         // If the mouse event is not contained within this TreeCell, then
 205         // we don't want to react to it.
 206         if (cell.isEmpty() || ! cell.contains(x, y)) {
 207             return;
 208         }
 209 
 210         final int index = getIndex();
 211         boolean selected = cell.isSelected();
 212         MultipleSelectionModel<?> sm = getSelectionModel();
 213         if (sm == null) return;
 214 
 215         FocusModel<?> fm = getFocusModel();
 216         if (fm == null) return;
 217 
 218         // if the user has clicked on the disclosure node, we do nothing other
 219         // than expand/collapse the tree item (if applicable). We do not do editing!
 220         if (handleDisclosureNode(x,y)) {


 267     protected void simpleSelect(MouseButton button, int clickCount, boolean shortcutDown) {
 268         final int index = getIndex();
 269         MultipleSelectionModel<?> sm = getSelectionModel();
 270         boolean isAlreadySelected = sm.isSelected(index);
 271 
 272         if (isAlreadySelected && shortcutDown) {
 273             sm.clearSelection(index);
 274             getFocusModel().focus(index);
 275             isAlreadySelected = false;
 276         } else {
 277             sm.clearAndSelect(index);
 278         }
 279 
 280         handleClicks(button, clickCount, isAlreadySelected);
 281     }
 282 
 283     protected void handleClicks(MouseButton button, int clickCount, boolean isAlreadySelected) {
 284         // handle editing, which only occurs with the primary mouse button
 285         if (button == MouseButton.PRIMARY) {
 286             if (clickCount == 1 && isAlreadySelected) {
 287                 edit(getNode());
 288             } else if (clickCount == 1) {
 289                 // cancel editing
 290                 edit(null);
 291             } else if (clickCount == 2 && getNode().isEditable()) {
 292                 edit(getNode());
 293             }
 294         }
 295     }
 296 
 297     void selectRows(int focusedIndex, int index) {
 298         final boolean asc = focusedIndex < index;
 299 
 300         // and then determine all row and columns which must be selected
 301         int minRow = Math.min(focusedIndex, index);
 302         int maxRow = Math.max(focusedIndex, index);
 303 
 304         // To prevent RT-32119, we make a copy of the selected indices
 305         // list first, so that we are not iterating and modifying it
 306         // concurrently.
 307         List<Integer> selectedIndices = new ArrayList<>(getSelectionModel().getSelectedIndices());
 308         for (int i = 0, max = selectedIndices.size(); i < max; i++) {
 309             int selectedIndex = selectedIndices.get(i);
 310             if (selectedIndex < minRow || selectedIndex > maxRow) {
 311                 getSelectionModel().clearSelection(selectedIndex);
 312             }


 315         if (minRow == maxRow) {
 316             // RT-32560: This prevents the anchor 'sticking' in
 317             // the wrong place when a range is selected and then
 318             // selection goes back to the anchor position.
 319             // (Refer to the video in RT-32560 for more detail).
 320             getSelectionModel().select(minRow);
 321         } else {
 322             // RT-21444: We need to put the range in the correct
 323             // order or else the last selected row will not be the
 324             // last item in the selectedItems list of the selection
 325             // model,
 326             if (asc) {
 327                 getSelectionModel().selectRange(minRow, maxRow + 1);
 328             } else {
 329                 getSelectionModel().selectRange(maxRow, minRow - 1);
 330             }
 331         }
 332     }
 333 
 334     protected boolean isSelected() {
 335         return getNode().isSelected();
 336     }
 337 }