1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.scene.control.behavior; 27 28 import javafx.scene.Node; 29 import javafx.scene.control.*; 30 import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel; 31 import javafx.scene.input.MouseButton; 32 import javafx.scene.input.MouseEvent; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 38 public class TreeTableRowBehavior<T> extends CellBehaviorBase<TreeTableRow<T>> { 39 40 /*************************************************************************** 41 * * 42 * Private fields * 43 * * 44 **************************************************************************/ 45 46 // To support touch devices, we have to slightly modify this behavior, such 47 // that selection only happens on mouse release, if only minimal dragging 48 // has occurred. 49 private boolean latePress = false; 50 51 52 53 /*************************************************************************** 54 * * 55 * Constructors * 56 * * 57 **************************************************************************/ 58 59 public TreeTableRowBehavior(TreeTableRow<T> control) { 60 super(control, Collections.EMPTY_LIST); 61 } 62 63 /*************************************************************************** 64 * * 65 * Public API * 66 * * 67 **************************************************************************/ 68 69 @Override public void mousePressed(MouseEvent event) { 70 // we only care about clicks to the right of the right-most column 71 if (! isClickOutsideCellBounds(event.getX())) return; 72 73 if (event.isSynthesized()) { 74 latePress = true; 75 } else { 76 latePress = getControl().isSelected(); 77 if (!latePress) { 78 doSelect(event); 79 } 80 } 81 } 82 83 @Override public void mouseReleased(MouseEvent event) { 84 if (latePress) { 85 latePress = false; 86 doSelect(event); 87 } 88 } 89 90 @Override public void mouseDragged(MouseEvent event) { 91 latePress = false; 92 } 93 94 95 96 97 /*************************************************************************** 98 * * 99 * Private implementation * 100 * * 101 **************************************************************************/ 102 103 private void doSelect(MouseEvent e) { 104 super.mouseReleased(e); 105 106 if (e.getButton() != MouseButton.PRIMARY) return; 107 108 TreeTableRow<T> treeTableRow = getControl(); 109 TreeItem<T> treeItem = treeTableRow.getTreeItem(); 110 if (treeItem == null) return; 111 112 // if the user has clicked on the disclosure node, we do nothing other 113 // than expand/collapse the tree item (if applicable). We do not do editing! 114 Node disclosureNode = treeTableRow.getDisclosureNode(); 115 if (disclosureNode != null) { 116 if (disclosureNode.getBoundsInParent().contains(e.getX(), e.getY())) { 117 treeItem.setExpanded(! treeItem.isExpanded()); 118 return; 119 } 120 } 121 122 TreeTableView<T> table = treeTableRow.getTreeTableView(); 123 if (table == null) return; 124 final TreeTableViewSelectionModel<T> sm = table.getSelectionModel(); 125 if (sm == null || sm.isCellSelectionEnabled()) return; 126 127 final int index = getControl().getIndex(); 128 final boolean isAlreadySelected = sm.isSelected(index); 129 int clickCount = e.getClickCount(); 130 if (clickCount == 1) { 131 // get width of all visible columns (we only care about clicks to the 132 // right of the right-most column) 133 List<TreeTableColumn<T, ?>> columns = getControl().getTreeTableView().getVisibleLeafColumns(); 134 double width = 0.0; 135 for (int i = 0; i < columns.size(); i++) { 136 width += columns.get(i).getWidth(); 137 } 138 139 if (e.getX() < width) return; 140 141 // In the case of clicking to the right of the rightmost 142 // TreeTableCell, we should still support selection, so that 143 // is what we are doing here. 144 if (isAlreadySelected && e.isShortcutDown()) { 145 sm.clearSelection(index); 146 } else { 147 if (e.isShortcutDown()) { 148 sm.select(treeTableRow.getIndex()); 149 } else if (e.isShiftDown()) { 150 // we add all rows between the current focus and 151 // this row (inclusive) to the current selection. 152 TablePositionBase anchor = TreeTableCellBehavior.getAnchor(table, table.getFocusModel().getFocusedCell()); 153 final int anchorRow = anchor.getRow(); 154 final boolean asc = anchorRow < index; 155 156 // and then determine all row and columns which must be selected 157 int minRow = Math.min(anchor.getRow(), index); 158 int maxRow = Math.max(anchor.getRow(), index); 159 160 // To prevent RT-32119, we make a copy of the selected indices 161 // list first, so that we are not iterating and modifying it 162 // concurrently. 163 List<Integer> selectedIndices = new ArrayList<>(sm.getSelectedIndices()); 164 for (int i = 0, max = selectedIndices.size(); i < max; i++) { 165 int selectedIndex = selectedIndices.get(i); 166 if (selectedIndex < minRow || selectedIndex > maxRow) { 167 sm.clearSelection(selectedIndex); 168 } 169 } 170 171 if (minRow == maxRow) { 172 // RT-32560: This prevents the anchor 'sticking' in 173 // the wrong place when a range is selected and then 174 // selection goes back to the anchor position. 175 // (Refer to the video in RT-32560 for more detail). 176 sm.select(minRow); 177 } else { 178 // RT-21444: We need to put the range in the correct 179 // order or else the last selected row will not be the 180 // last item in the selectedItems list of the selection 181 // model, 182 if (asc) { 183 sm.selectRange(minRow, maxRow + 1); 184 } else { 185 sm.selectRange(maxRow, minRow - 1); 186 } 187 } 188 } else { 189 sm.clearAndSelect(treeTableRow.getIndex()); 190 } 191 } 192 } 193 } 194 195 private boolean isClickOutsideCellBounds(final double x) { 196 // get width of all visible columns (we only care about clicks to the 197 // right of the right-most column) 198 final TreeTableRow<T> tableRow = getControl(); 199 final TreeTableView<T> table = tableRow.getTreeTableView(); 200 if (table == null) return false; 201 List<TreeTableColumn<T, ?>> columns = table.getVisibleLeafColumns(); 202 double width = 0.0; 203 for (int i = 0; i < columns.size(); i++) { 204 width += columns.get(i).getWidth(); 205 } 206 207 return x > width; 208 } 209 }