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;
|