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

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


  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 com.sun.javafx.scene.control.behavior;
  27 
  28 import com.sun.javafx.scene.control.SizeLimitedList;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.collections.ObservableList;
  31 import javafx.collections.WeakListChangeListener;

  32 import javafx.geometry.NodeOrientation;
  33 import javafx.scene.control.*;


  34 import javafx.scene.input.KeyEvent;
  35 import javafx.scene.input.MouseEvent;
  36 import javafx.util.Callback;
  37 import java.util.ArrayList;
  38 import java.util.List;
  39 import com.sun.javafx.PlatformUtil;
  40 import static javafx.scene.input.KeyCode.A;
  41 import static javafx.scene.input.KeyCode.DOWN;
  42 import static javafx.scene.input.KeyCode.END;
  43 import static javafx.scene.input.KeyCode.ENTER;
  44 import static javafx.scene.input.KeyCode.ESCAPE;
  45 import static javafx.scene.input.KeyCode.F2;
  46 import static javafx.scene.input.KeyCode.HOME;
  47 import static javafx.scene.input.KeyCode.KP_DOWN;
  48 import static javafx.scene.input.KeyCode.KP_LEFT;
  49 import static javafx.scene.input.KeyCode.KP_RIGHT;
  50 import static javafx.scene.input.KeyCode.KP_UP;
  51 import static javafx.scene.input.KeyCode.LEFT;
  52 import static javafx.scene.input.KeyCode.PAGE_DOWN;
  53 import static javafx.scene.input.KeyCode.PAGE_UP;
  54 import static javafx.scene.input.KeyCode.RIGHT;
  55 import static javafx.scene.input.KeyCode.SPACE;
  56 import static javafx.scene.input.KeyCode.TAB;
  57 import static javafx.scene.input.KeyCode.UP;
  58 
  59 public abstract class TableViewBehaviorBase<C extends Control, T, TC extends TableColumnBase<T,?>> extends BehaviorBase<C> {
  60 
  61     /**************************************************************************
  62      *                                                                        *
  63      * Setup key bindings                                                     *
  64      *                                                                        *  
  65      *************************************************************************/
  66     protected static final List<KeyBinding> TABLE_VIEW_BINDINGS = new ArrayList<KeyBinding>();
  67 
  68     static {
  69         TABLE_VIEW_BINDINGS.add(new KeyBinding(TAB, "TraverseNext"));
  70         TABLE_VIEW_BINDINGS.add(new KeyBinding(TAB, "TraversePrevious").shift());
  71 
  72         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectFirstRow"));
  73         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectLastRow"));
  74         
  75         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "ScrollUp"));
  76         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "ScrollDown"));
  77 
  78         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "SelectLeftCell"));
  79         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "SelectLeftCell"));
  80         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "SelectRightCell"));
  81         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "SelectRightCell"));
  82 
  83         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "SelectPreviousRow"));
  84         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "SelectPreviousRow"));
  85         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "SelectNextRow"));
  86         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "SelectNextRow"));
  87 
  88         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "TraverseLeft"));
  89         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "TraverseLeft"));
  90         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "SelectNextRow"));
  91         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "SelectNextRow"));
  92         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "TraverseUp"));
  93         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "TraverseUp"));
  94         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "TraverseDown"));
  95         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "TraverseDown"));
  96 
  97         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectAllToFirstRow").shift());
  98         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectAllToLastRow").shift());
  99         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "SelectAllPageUp").shift());
 100         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "SelectAllPageDown").shift());
 101 
 102         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPrevious").shift());
 103         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPrevious").shift());
 104         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNext").shift());
 105         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNext").shift());
 106         
 107         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocus").shift());
 108         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocusAndSetAnchor").shortcut().shift());
 109 
 110 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPreviousCell").shift());
 111 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPreviousCell").shift());
 112 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNextCell").shift());
 113 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNextCell").shift());
 114         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "AlsoSelectLeftCell").shift());
 115         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "AlsoSelectLeftCell").shift());
 116         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "AlsoSelectRightCell").shift());
 117         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "AlsoSelectRightCell").shift());
 118 
 119         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "FocusPreviousRow").shortcut());
 120         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "FocusNextRow").shortcut());
 121         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "FocusRightCell").shortcut());
 122         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "FocusRightCell").shortcut());
 123         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "FocusLeftCell").shortcut());
 124         TABLE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "FocusLeftCell").shortcut());
 125         TABLE_VIEW_BINDINGS.add(new KeyBinding(A, "SelectAll").shortcut());
 126         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "FocusFirstRow").shortcut());
 127         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "FocusLastRow").shortcut());
 128         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "FocusPageUp").shortcut());
 129         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "FocusPageDown").shortcut());
 130 
 131         TABLE_VIEW_BINDINGS.add(new KeyBinding(UP, "DiscontinuousSelectPreviousRow").shortcut().shift());
 132         TABLE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "DiscontinuousSelectNextRow").shortcut().shift());
 133         TABLE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "DiscontinuousSelectPreviousColumn").shortcut().shift());
 134         TABLE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "DiscontinuousSelectNextColumn").shortcut().shift());
 135         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "DiscontinuousSelectPageUp").shortcut().shift());
 136         TABLE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "DiscontinuousSelectPageDown").shortcut().shift());
 137         TABLE_VIEW_BINDINGS.add(new KeyBinding(HOME, "DiscontinuousSelectAllToFirstRow").shortcut().shift());
 138         TABLE_VIEW_BINDINGS.add(new KeyBinding(END, "DiscontinuousSelectAllToLastRow").shortcut().shift());
 139         
 140         if (PlatformUtil.isMac()) {
 141             TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl().shortcut());
 142         } else {
 143             TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl());
 144         }
 145 
 146         TABLE_VIEW_BINDINGS.add(new KeyBinding(ENTER, "Activate"));
 147         TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "Activate"));
 148         TABLE_VIEW_BINDINGS.add(new KeyBinding(F2, "Activate"));
 149 //        TABLE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "Activate").ctrl());
 150         
 151         TABLE_VIEW_BINDINGS.add(new KeyBinding(ESCAPE, "CancelEdit"));
 152     }
 153 
 154     @Override protected void callAction(String name) {
 155         boolean rtl = (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 156 
 157         if ("SelectPreviousRow".equals(name)) selectPreviousRow();
 158         else if ("SelectNextRow".equals(name)) selectNextRow();
 159         else if ("SelectLeftCell".equals(name)) { if (rtl) selectRightCell(); else selectLeftCell(); }
 160         else if ("SelectRightCell".equals(name)) { if (rtl) selectLeftCell(); else selectRightCell(); }
 161         else if ("SelectFirstRow".equals(name)) selectFirstRow();
 162         else if ("SelectLastRow".equals(name)) selectLastRow();
 163         else if ("SelectAll".equals(name)) selectAll();
 164         else if ("SelectAllPageUp".equals(name)) selectAllPageUp();
 165         else if ("SelectAllPageDown".equals(name)) selectAllPageDown();
 166         else if ("SelectAllToFirstRow".equals(name)) selectAllToFirstRow();
 167         else if ("SelectAllToLastRow".equals(name)) selectAllToLastRow();
 168         else if ("AlsoSelectNext".equals(name)) alsoSelectNext();
 169         else if ("AlsoSelectPrevious".equals(name)) alsoSelectPrevious();
 170         else if ("AlsoSelectLeftCell".equals(name)) { if (rtl) alsoSelectRightCell(); else alsoSelectLeftCell(); }
 171         else if ("AlsoSelectRightCell".equals(name)) { if (rtl) alsoSelectLeftCell(); else alsoSelectRightCell(); }
 172         else if ("ClearSelection".equals(name)) clearSelection();
 173         else if ("ScrollUp".equals(name)) scrollUp();
 174         else if ("ScrollDown".equals(name)) scrollDown();
 175         else if ("FocusPreviousRow".equals(name)) focusPreviousRow();
 176         else if ("FocusNextRow".equals(name)) focusNextRow();
 177         else if ("FocusLeftCell".equals(name)) { if (rtl) focusRightCell(); else focusLeftCell(); }
 178         else if ("FocusRightCell".equals(name)) { if (rtl) focusLeftCell(); else focusRightCell(); }
 179         else if ("Activate".equals(name)) activate();
 180         else if ("CancelEdit".equals(name)) cancelEdit();
 181         else if ("FocusFirstRow".equals(name)) focusFirstRow();
 182         else if ("FocusLastRow".equals(name)) focusLastRow();
 183         else if ("toggleFocusOwnerSelection".equals(name)) toggleFocusOwnerSelection();
 184 
 185         else if ("SelectAllToFocus".equals(name)) selectAllToFocus(false);
 186         else if ("SelectAllToFocusAndSetAnchor".equals(name)) selectAllToFocus(true);
 187 
 188         else if ("FocusPageUp".equals(name)) focusPageUp();
 189         else if ("FocusPageDown".equals(name)) focusPageDown();
 190         else if ("DiscontinuousSelectNextRow".equals(name)) discontinuousSelectNextRow();
 191         else if ("DiscontinuousSelectPreviousRow".equals(name)) discontinuousSelectPreviousRow();
 192         else if ("DiscontinuousSelectNextColumn".equals(name)) { if (rtl) discontinuousSelectPreviousColumn(); else discontinuousSelectNextColumn(); }
 193         else if ("DiscontinuousSelectPreviousColumn".equals(name)) { if (rtl) discontinuousSelectNextColumn(); else discontinuousSelectPreviousColumn(); }
 194         else if ("DiscontinuousSelectPageUp".equals(name)) discontinuousSelectPageUp();
 195         else if ("DiscontinuousSelectPageDown".equals(name)) discontinuousSelectPageDown();
 196         else if ("DiscontinuousSelectAllToLastRow".equals(name)) discontinuousSelectAllToLastRow();
 197         else if ("DiscontinuousSelectAllToFirstRow".equals(name)) discontinuousSelectAllToFirstRow();
 198         else super.callAction(name);
 199     }
 200 
 201     @Override protected void callActionForEvent(KeyEvent e) {

 202         // RT-12751: we want to keep an eye on the user holding down the shift key, 
 203         // so that we know when they enter/leave multiple selection mode. This
 204         // changes what happens when certain key combinations are pressed.
 205         isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
 206         isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
 207         
 208         super.callActionForEvent(e);
 209     }
 210     
 211     
 212     
 213     /**************************************************************************
 214      *                                                                        *
 215      * Internal fields                                                        *
 216      *                                                                        *  
 217      *************************************************************************/
 218     
 219     protected boolean isShortcutDown = false;
 220     protected boolean isShiftDown = false;
 221     private boolean selectionPathDeviated = false;
 222     protected boolean selectionChanging = false;
 223 
 224     private final SizeLimitedList<TablePositionBase> selectionHistory = new SizeLimitedList<>(10);
 225 
 226     protected final ListChangeListener<TablePositionBase> selectedCellsListener = c -> {
 227         while (c.next()) {
 228             if (c.wasReplaced()) {
 229                 if (TreeTableCellBehavior.hasDefaultAnchor(getControl())) {
 230                     TreeTableCellBehavior.removeAnchor(getControl());
 231                 }
 232             }
 233 
 234             if (! c.wasAdded()) {
 235                 continue;
 236             }
 237 
 238             TableSelectionModel sm = getSelectionModel();
 239             if (sm == null) return;
 240 
 241             TablePositionBase anchor = getAnchor();
 242             boolean cellSelectionEnabled = sm.isCellSelectionEnabled();
 243 
 244             int addedSize = c.getAddedSize();
 245             List<TablePositionBase> addedSubList = (List<TablePositionBase>) c.getAddedSubList();
 246 
 247             for (TablePositionBase tpb : addedSubList) {
 248                 if (! selectionHistory.contains(tpb)) {
 249                     selectionHistory.add(tpb);
 250                 }


 265                         setSelectionPathDeviated(true);
 266                         break;
 267                     }
 268                 }
 269             }
 270         }
 271     };
 272     
 273     protected final WeakListChangeListener<TablePositionBase> weakSelectedCellsListener = 
 274             new WeakListChangeListener<TablePositionBase>(selectedCellsListener);
 275     
 276     
 277 
 278     /**************************************************************************
 279      *                                                                        *
 280      * Constructors                                                           *
 281      *                                                                        *  
 282      *************************************************************************/
 283 
 284     public TableViewBehaviorBase(C control) {
 285         this(control, null);
 286     }



 287 
 288     public TableViewBehaviorBase(C control, List<KeyBinding> bindings) {
 289         super(control, bindings == null ? TABLE_VIEW_BINDINGS : bindings);




























































































 290     }
 291 
 292     
 293     
 294     /**************************************************************************
 295      *                                                                        *
 296      * Abstract API                                                           *
 297      *                                                                        *  
 298      *************************************************************************/    
 299     





 300     /**
 301      * Call to record the current anchor position
 302      */
 303     protected void setAnchor(TablePositionBase tp) {
 304         TableCellBehaviorBase.setAnchor(getControl(), tp, false);
 305         setSelectionPathDeviated(false);
 306     }
 307     
 308     /**
 309      * Will return the current anchor position.
 310      */
 311     protected TablePositionBase getAnchor() {
 312         return TableCellBehaviorBase.getAnchor(getControl(), getFocusedCell());
 313     }
 314     
 315     /**
 316      * Returns true if there is an anchor set, and false if not anchor is set.
 317      */
 318     protected boolean hasAnchor() {
 319         return TableCellBehaviorBase.hasNonDefaultAnchor(getControl());
 320     }
 321     
 322     /**
 323      * Returns the number of items in the underlying data model.
 324      */
 325     protected abstract int getItemCount();
 326 
 327     /**
 328      * Returns the focus model for the underlying UI control (which must extend
 329      * from TableFocusModel).
 330      */
 331     protected abstract TableFocusModel getFocusModel();
 332     
 333     /**
 334      * Returns the selection model for the underlying UI control (which must extend
 335      * from TableSelectionModel).
 336      */
 337     protected abstract TableSelectionModel<T> getSelectionModel();
 338     
 339     /**


 426     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
 427 
 428     private Runnable onSelectPreviousRow;
 429     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
 430 
 431     private Runnable onSelectNextRow;
 432     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
 433 
 434     private Runnable onMoveToFirstCell;
 435     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
 436 
 437     private Runnable onMoveToLastCell;
 438     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
 439 
 440     private Runnable onSelectRightCell;
 441     public void setOnSelectRightCell(Runnable r) { onSelectRightCell = r; }
 442 
 443     private Runnable onSelectLeftCell;
 444     public void setOnSelectLeftCell(Runnable r) { onSelectLeftCell = r; }
 445     
 446     @Override public void mousePressed(MouseEvent e) {
 447         super.mousePressed(e);
 448         
 449 //        // FIXME can't assume (yet) cells.get(0) is necessarily the lead cell
 450 //        ObservableList<? extends TablePositionBase> cells = getSelectedCells();
 451 //        setAnchor(cells.isEmpty() ? null : cells.get(0));
 452         
 453         if (!getControl().isFocused() && getControl().isFocusTraversable()) {
 454             getControl().requestFocus();
 455         }
 456     }
 457 
 458     protected boolean isRTL() {
 459         return (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 460     }
 461     
 462     
 463     /**************************************************************************
 464      *                                                                        *
 465      * Private implementation                                                 *
 466      *                                                                        *  
 467      *************************************************************************/
 468 
 469     private void setSelectionPathDeviated(boolean selectionPathDeviated) {
 470         this.selectionPathDeviated = selectionPathDeviated;
 471     }
 472     
 473     protected void scrollUp() {
 474         TableSelectionModel<T> sm = getSelectionModel();
 475         if (sm == null || getSelectedCells().isEmpty()) return;
 476         
 477         TablePositionBase<TC> selectedCell = getSelectedCells().get(0);
 478         
 479         int newSelectedIndex = -1;


1134         setAnchor(focusedCell.getRow(), focusedCell.getTableColumn());
1135     }
1136     
1137     // This functionality was added, but then removed when it was realised by 
1138     // UX that TableView should not include 'spreadsheet-like' functionality.
1139     // When / if we ever introduce this kind of control, this functionality can
1140     // be re-enabled then.
1141     /*
1142     protected void moveToLeftMostColumn() {
1143         // Functionality as described in RT-12752
1144         if (onMoveToLeftMostColumn != null) onMoveToLeftMostColumn.run();
1145         
1146         TableSelectionModel sm = getSelectionModel();
1147         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1148         
1149         TableFocusModel fm = getFocusModel();
1150         if (fm == null) return;
1151 
1152         TablePosition focusedCell = fm.getFocusedCell();
1153         
1154         TableColumn endColumn = getControl().getVisibleLeafColumn(0);
1155         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1156     }
1157     
1158     protected void moveToRightMostColumn() {
1159         // Functionality as described in RT-12752
1160         if (onMoveToRightMostColumn != null) onMoveToRightMostColumn.run();
1161         
1162         TableSelectionModel sm = getSelectionModel();
1163         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1164         
1165         TableFocusModel fm = getFocusModel();
1166         if (fm == null) return;
1167 
1168         TablePosition focusedCell = fm.getFocusedCell();
1169         
1170         TableColumn endColumn = getControl().getVisibleLeafColumn(getControl().getVisibleLeafColumns().size() - 1);
1171         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1172     }
1173      */
1174     
1175     
1176     /**************************************************************************
1177      * Discontinuous Selection                                                *
1178      *************************************************************************/
1179     
1180     protected void discontinuousSelectPreviousRow() {
1181         TableSelectionModel sm = getSelectionModel();
1182         if (sm == null) return;
1183 
1184         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
1185             selectPreviousRow();
1186             return;
1187         }
1188         
1189         TableFocusModel fm = getFocusModel();
1190         if (fm == null) return;




  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 com.sun.javafx.scene.control.behavior;
  27 
  28 import com.sun.javafx.scene.control.SizeLimitedList;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.collections.ObservableList;
  31 import javafx.collections.WeakListChangeListener;
  32 import javafx.event.EventHandler;
  33 import javafx.geometry.NodeOrientation;
  34 import javafx.scene.control.*;
  35 import com.sun.javafx.scene.control.inputmap.InputMap;
  36 import com.sun.javafx.scene.control.inputmap.KeyBinding;
  37 import javafx.scene.input.KeyEvent;
  38 import javafx.scene.input.MouseEvent;
  39 import javafx.util.Callback;
  40 import java.util.ArrayList;
  41 import java.util.List;
  42 import com.sun.javafx.PlatformUtil;
  43 import static javafx.scene.input.KeyCode.*;
  44 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
















  45 
  46 public abstract class TableViewBehaviorBase<C extends Control, T, TC extends TableColumnBase<T,?>> extends BehaviorBase<C> {
  47 
  48     /**************************************************************************
  49      *                                                                        *
  50      * Internal fields                                                        *
  51      *                                                                        *  
  52      *************************************************************************/

  53 
  54     private final InputMap<C> tableViewInputMap;
  55     
  56     protected boolean isShortcutDown = false;
  57     protected boolean isShiftDown = false;
  58     private boolean selectionPathDeviated = false;
  59     protected boolean selectionChanging = false;






























































































































  60 
  61     private final EventHandler<KeyEvent> keyEventListener = e -> {
  62         if (!e.isConsumed()) {
  63             // RT-12751: we want to keep an eye on the user holding down the shift key,
  64             // so that we know when they enter/leave multiple selection mode. This
  65             // changes what happens when certain key combinations are pressed.
  66             isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
  67             isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();


  68         }
  69     };












  70 
  71     private final SizeLimitedList<TablePositionBase> selectionHistory = new SizeLimitedList<>(10);
  72 
  73     protected final ListChangeListener<TablePositionBase> selectedCellsListener = c -> {
  74         while (c.next()) {
  75             if (c.wasReplaced()) {
  76                 if (TreeTableCellBehavior.hasDefaultAnchor(getNode())) {
  77                     TreeTableCellBehavior.removeAnchor(getNode());
  78                 }
  79             }
  80 
  81             if (! c.wasAdded()) {
  82                 continue;
  83             }
  84 
  85             TableSelectionModel sm = getSelectionModel();
  86             if (sm == null) return;
  87 
  88             TablePositionBase anchor = getAnchor();
  89             boolean cellSelectionEnabled = sm.isCellSelectionEnabled();
  90 
  91             int addedSize = c.getAddedSize();
  92             List<TablePositionBase> addedSubList = (List<TablePositionBase>) c.getAddedSubList();
  93 
  94             for (TablePositionBase tpb : addedSubList) {
  95                 if (! selectionHistory.contains(tpb)) {
  96                     selectionHistory.add(tpb);
  97                 }


 112                         setSelectionPathDeviated(true);
 113                         break;
 114                     }
 115                 }
 116             }
 117         }
 118     };
 119     
 120     protected final WeakListChangeListener<TablePositionBase> weakSelectedCellsListener = 
 121             new WeakListChangeListener<TablePositionBase>(selectedCellsListener);
 122     
 123     
 124 
 125     /**************************************************************************
 126      *                                                                        *
 127      * Constructors                                                           *
 128      *                                                                        *  
 129      *************************************************************************/
 130 
 131     public TableViewBehaviorBase(C control) {
 132         super(control);
 133 
 134 
 135         // create a map for TableView(Base)-specific mappings
 136         tableViewInputMap = createInputMap();
 137 
 138         addDefaultMapping(tableViewInputMap,
 139                 new KeyMapping(TAB, FocusTraversalInputMap::traverseNext),
 140                 new KeyMapping(new KeyBinding(TAB).shift(), FocusTraversalInputMap::traversePrevious),
 141 
 142                 new KeyMapping(HOME, e -> selectFirstRow()),
 143                 new KeyMapping(END, e -> selectLastRow()),
 144 
 145                 new KeyMapping(PAGE_UP, e -> scrollUp()),
 146                 new KeyMapping(PAGE_DOWN, e -> scrollDown()),
 147 
 148                 new KeyMapping(LEFT, e -> selectLeftCell()),
 149                 new KeyMapping(KP_LEFT, e -> selectLeftCell()),
 150                 new KeyMapping(RIGHT, e -> selectRightCell()),
 151                 new KeyMapping(KP_RIGHT, e -> selectRightCell()),
 152 
 153                 new KeyMapping(UP, e -> selectPreviousRow()),
 154                 new KeyMapping(KP_UP, e -> selectPreviousRow()),
 155                 new KeyMapping(DOWN, e -> selectNextRow()),
 156                 new KeyMapping(KP_DOWN, e -> selectNextRow()),
 157 
 158                 new KeyMapping(LEFT, FocusTraversalInputMap::traverseLeft),
 159                 new KeyMapping(KP_LEFT, FocusTraversalInputMap::traverseLeft),
 160                 new KeyMapping(RIGHT, FocusTraversalInputMap::traverseRight),
 161                 new KeyMapping(KP_RIGHT, FocusTraversalInputMap::traverseRight),
 162                 new KeyMapping(UP, FocusTraversalInputMap::traverseUp),
 163                 new KeyMapping(KP_UP, FocusTraversalInputMap::traverseUp),
 164                 new KeyMapping(DOWN, FocusTraversalInputMap::traverseDown),
 165                 new KeyMapping(KP_DOWN, FocusTraversalInputMap::traverseDown),
 166 
 167                 new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()),
 168                 new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()),
 169                 new KeyMapping(new KeyBinding(PAGE_UP).shift(), e -> selectAllPageUp()),
 170                 new KeyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> selectAllPageDown()),
 171 
 172                 new KeyMapping(new KeyBinding(UP).shift(), e -> alsoSelectPrevious()),
 173                 new KeyMapping(new KeyBinding(KP_UP).shift(), e -> alsoSelectPrevious()),
 174                 new KeyMapping(new KeyBinding(DOWN).shift(), e -> alsoSelectNext()),
 175                 new KeyMapping(new KeyBinding(KP_DOWN).shift(), e -> alsoSelectNext()),
 176 
 177                 new KeyMapping(new KeyBinding(SPACE).shift(), e -> selectAllToFocus(false)),
 178                 new KeyMapping(new KeyBinding(SPACE).shortcut().shift(), e -> selectAllToFocus(true)),
 179 
 180                 new KeyMapping(new KeyBinding(LEFT).shift(), e -> alsoSelectLeftCell()),
 181                 new KeyMapping(new KeyBinding(KP_LEFT).shift(), e -> alsoSelectLeftCell()),
 182                 new KeyMapping(new KeyBinding(RIGHT).shift(), e -> alsoSelectRightCell()),
 183                 new KeyMapping(new KeyBinding(KP_RIGHT).shift(), e -> alsoSelectRightCell()),
 184 
 185                 new KeyMapping(new KeyBinding(UP).shortcut(), e -> focusPreviousRow()),
 186                 new KeyMapping(new KeyBinding(DOWN).shortcut(), e -> focusNextRow()),
 187                 new KeyMapping(new KeyBinding(RIGHT).shortcut(), e -> focusRightCell()),
 188                 new KeyMapping(new KeyBinding(KP_RIGHT).shortcut(), e -> focusRightCell()),
 189                 new KeyMapping(new KeyBinding(LEFT).shortcut(), e -> focusLeftCell()),
 190                 new KeyMapping(new KeyBinding(KP_LEFT).shortcut(), e -> focusLeftCell()),
 191 
 192                 new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()),
 193                 new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()),
 194                 new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow()),
 195                 new KeyMapping(new KeyBinding(PAGE_UP).shortcut(), e -> focusPageUp()),
 196                 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut(), e -> focusPageDown()),
 197 
 198                 new KeyMapping(new KeyBinding(UP).shortcut().shift(), e -> discontinuousSelectPreviousRow()),
 199                 new KeyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> discontinuousSelectNextRow()),
 200                 new KeyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> discontinuousSelectPreviousColumn()),
 201                 new KeyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> discontinuousSelectNextColumn()),
 202                 new KeyMapping(new KeyBinding(PAGE_UP).shortcut().shift(), e -> discontinuousSelectPageUp()),
 203                 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut().shift(), e -> discontinuousSelectPageDown()),
 204                 new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()),
 205                 new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow()),
 206 
 207                 new KeyMapping(ENTER, e -> activate()),
 208                 new KeyMapping(SPACE, e -> activate()),
 209                 new KeyMapping(F2, e -> activate()),
 210                 new KeyMapping(ESCAPE, e -> cancelEdit()),
 211 
 212                 new InputMap.MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed)
 213         );
 214 
 215         // create OS-specific child mappings
 216         // --- mac OS
 217         InputMap<C> macInputMap = new InputMap<>(control);
 218         macInputMap.setInterceptor(event -> !PlatformUtil.isMac());
 219         addDefaultMapping(macInputMap, new KeyMapping(new KeyBinding(SPACE).shortcut().ctrl(), e -> toggleFocusOwnerSelection()));
 220         addDefaultChildMap(tableViewInputMap, macInputMap);
 221 
 222         // --- all other platforms
 223         InputMap<C> otherOsInputMap = new InputMap<>(control);
 224         otherOsInputMap.setInterceptor(event -> PlatformUtil.isMac());
 225         addDefaultMapping(otherOsInputMap, new KeyMapping(new KeyBinding(SPACE).ctrl(), e -> toggleFocusOwnerSelection()));
 226         addDefaultChildMap(tableViewInputMap, otherOsInputMap);
 227 
 228         // set up other listeners
 229         // We make this an event _filter_ so that we can determine the state
 230         // of the shift key before the event handlers get a shot at the event.
 231         control.addEventFilter(KeyEvent.ANY, keyEventListener);
 232     }
 233 
 234     
 235     
 236     /**************************************************************************
 237      *                                                                        *
 238      * Abstract API                                                           *
 239      *                                                                        *  
 240      *************************************************************************/
 241 
 242     /** {@inheritDoc} */
 243     @Override public InputMap<C> getInputMap() {
 244         return tableViewInputMap;
 245     }
 246 
 247     /**
 248      * Call to record the current anchor position
 249      */
 250     protected void setAnchor(TablePositionBase tp) {
 251         TableCellBehaviorBase.setAnchor(getNode(), tp, false);
 252         setSelectionPathDeviated(false);
 253     }
 254     
 255     /**
 256      * Will return the current anchor position.
 257      */
 258     protected TablePositionBase getAnchor() {
 259         return TableCellBehaviorBase.getAnchor(getNode(), getFocusedCell());
 260     }
 261     
 262     /**
 263      * Returns true if there is an anchor set, and false if not anchor is set.
 264      */
 265     protected boolean hasAnchor() {
 266         return TableCellBehaviorBase.hasNonDefaultAnchor(getNode());
 267     }
 268     
 269     /**
 270      * Returns the number of items in the underlying data model.
 271      */
 272     protected abstract int getItemCount();
 273 
 274     /**
 275      * Returns the focus model for the underlying UI control (which must extend
 276      * from TableFocusModel).
 277      */
 278     protected abstract TableFocusModel getFocusModel();
 279     
 280     /**
 281      * Returns the selection model for the underlying UI control (which must extend
 282      * from TableSelectionModel).
 283      */
 284     protected abstract TableSelectionModel<T> getSelectionModel();
 285     
 286     /**


 373     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
 374 
 375     private Runnable onSelectPreviousRow;
 376     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
 377 
 378     private Runnable onSelectNextRow;
 379     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
 380 
 381     private Runnable onMoveToFirstCell;
 382     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
 383 
 384     private Runnable onMoveToLastCell;
 385     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
 386 
 387     private Runnable onSelectRightCell;
 388     public void setOnSelectRightCell(Runnable r) { onSelectRightCell = r; }
 389 
 390     private Runnable onSelectLeftCell;
 391     public void setOnSelectLeftCell(Runnable r) { onSelectLeftCell = r; }
 392     
 393     public void mousePressed(MouseEvent e) {


 394 //        // FIXME can't assume (yet) cells.get(0) is necessarily the lead cell
 395 //        ObservableList<? extends TablePositionBase> cells = getSelectedCells();
 396 //        setAnchor(cells.isEmpty() ? null : cells.get(0));
 397         
 398         if (!getNode().isFocused() && getNode().isFocusTraversable()) {
 399             getNode().requestFocus();
 400         }
 401     }
 402 
 403     protected boolean isRTL() {
 404         return (getNode().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 405     }
 406     
 407     
 408     /**************************************************************************
 409      *                                                                        *
 410      * Private implementation                                                 *
 411      *                                                                        *  
 412      *************************************************************************/
 413 
 414     private void setSelectionPathDeviated(boolean selectionPathDeviated) {
 415         this.selectionPathDeviated = selectionPathDeviated;
 416     }
 417     
 418     protected void scrollUp() {
 419         TableSelectionModel<T> sm = getSelectionModel();
 420         if (sm == null || getSelectedCells().isEmpty()) return;
 421         
 422         TablePositionBase<TC> selectedCell = getSelectedCells().get(0);
 423         
 424         int newSelectedIndex = -1;


1079         setAnchor(focusedCell.getRow(), focusedCell.getTableColumn());
1080     }
1081     
1082     // This functionality was added, but then removed when it was realised by 
1083     // UX that TableView should not include 'spreadsheet-like' functionality.
1084     // When / if we ever introduce this kind of control, this functionality can
1085     // be re-enabled then.
1086     /*
1087     protected void moveToLeftMostColumn() {
1088         // Functionality as described in RT-12752
1089         if (onMoveToLeftMostColumn != null) onMoveToLeftMostColumn.run();
1090         
1091         TableSelectionModel sm = getSelectionModel();
1092         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1093         
1094         TableFocusModel fm = getFocusModel();
1095         if (fm == null) return;
1096 
1097         TablePosition focusedCell = fm.getFocusedCell();
1098         
1099         TableColumn endColumn = getNode().getVisibleLeafColumn(0);
1100         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1101     }
1102     
1103     protected void moveToRightMostColumn() {
1104         // Functionality as described in RT-12752
1105         if (onMoveToRightMostColumn != null) onMoveToRightMostColumn.run();
1106         
1107         TableSelectionModel sm = getSelectionModel();
1108         if (sm == null || ! sm.isCellSelectionEnabled()) return;
1109         
1110         TableFocusModel fm = getFocusModel();
1111         if (fm == null) return;
1112 
1113         TablePosition focusedCell = fm.getFocusedCell();
1114         
1115         TableColumn endColumn = getNode().getVisibleLeafColumn(getNode().getVisibleLeafColumns().size() - 1);
1116         sm.clearAndSelect(focusedCell.getRow(), endColumn);
1117     }
1118      */
1119     
1120     
1121     /**************************************************************************
1122      * Discontinuous Selection                                                *
1123      *************************************************************************/
1124     
1125     protected void discontinuousSelectPreviousRow() {
1126         TableSelectionModel sm = getSelectionModel();
1127         if (sm == null) return;
1128 
1129         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
1130             selectPreviousRow();
1131             return;
1132         }
1133         
1134         TableFocusModel fm = getFocusModel();
1135         if (fm == null) return;