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