1 /* 2 * Copyright (c) 2013, 2016, 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 javafx.scene.control; 27 28 import com.sun.javafx.scene.control.skin.Utils; 29 import javafx.collections.ListChangeListener; 30 import javafx.collections.ObservableList; 31 import javafx.event.Event; 32 import javafx.scene.Node; 33 import javafx.scene.Parent; 34 import javafx.scene.Scene; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.stream.Collectors; 39 40 class ControlUtils { 41 private ControlUtils() { } 42 43 public static void scrollToIndex(final Control control, int index) { 44 Utils.executeOnceWhenPropertyIsNonNull(control.skinProperty(), (Skin<?> skin) -> { 45 Event.fireEvent(control, new ScrollToEvent<>(control, control, ScrollToEvent.scrollToTopIndex(), index)); 46 }); 47 } 48 49 public static void scrollToColumn(final Control control, final TableColumnBase<?, ?> column) { 50 Utils.executeOnceWhenPropertyIsNonNull(control.skinProperty(), (Skin<?> skin) -> { 51 control.fireEvent(new ScrollToEvent<TableColumnBase<?, ?>>(control, control, ScrollToEvent.scrollToColumn(), column)); 52 }); 53 } 54 55 static void requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(Control c) { 56 Scene scene = c.getScene(); 57 final Node focusOwner = scene == null ? null : scene.getFocusOwner(); 58 if (focusOwner == null) { 59 c.requestFocus(); 60 } else if (! c.equals(focusOwner)) { 61 Parent p = focusOwner.getParent(); 62 while (p != null) { 63 if (c.equals(p)) { 64 c.requestFocus(); 65 break; 66 } 67 p = p.getParent(); 68 } 69 } 70 } 71 72 static <T> ListChangeListener.Change<T> buildClearAndSelectChange( 73 ObservableList<T> list, List<T> removed, int retainedRow) { 74 return new ListChangeListener.Change<T>(list) { 75 private final int[] EMPTY_PERM = new int[0]; 76 77 private final int removedSize = removed.size(); 78 79 private final List<T> firstRemovedRange; 80 private final List<T> secondRemovedRange; 81 82 private boolean invalid = true; 83 private boolean atFirstRange = true; 84 85 private int from = -1; 86 87 { 88 int midIndex = retainedRow >= removedSize ? removedSize : 89 retainedRow < 0 ? 0 : 90 retainedRow; 91 firstRemovedRange = removed.subList(0, midIndex); 92 secondRemovedRange = removed.subList(midIndex, removedSize); 93 } 94 95 @Override public int getFrom() { 96 checkState(); 97 return from; 98 } 99 100 @Override public int getTo() { 101 return getFrom(); 102 } 103 104 @Override public List<T> getRemoved() { 105 checkState(); 106 return atFirstRange ? firstRemovedRange : secondRemovedRange; 107 } 108 109 @Override public int getRemovedSize() { 110 return atFirstRange ? firstRemovedRange.size() : secondRemovedRange.size(); 111 } 112 113 @Override protected int[] getPermutation() { 114 checkState(); 115 return EMPTY_PERM; 116 } 117 118 @Override public boolean next() { 119 if (invalid && atFirstRange) { 120 invalid = false; 121 122 // point 'from' to the first position, relative to 123 // the underlying selectedCells index. 124 from = 0; 125 return true; 126 } 127 128 if (atFirstRange && !secondRemovedRange.isEmpty()) { 129 atFirstRange = false; 130 131 // point 'from' to the second position, relative to 132 // the underlying selectedCells index. 133 from = 1; 134 return true; 135 } 136 137 return false; 138 } 139 140 @Override public void reset() { 141 invalid = true; 142 atFirstRange = true; 143 } 144 145 private void checkState() { 146 if (invalid) { 147 throw new IllegalStateException("Invalid Change state: next() must be called before inspecting the Change."); 148 } 149 } 150 }; 151 } 152 153 public static <S> void updateSelectedIndices(MultipleSelectionModelBase<S> sm, ListChangeListener.Change<? extends TablePositionBase<?>> c) { 154 sm.selectedIndices._beginChange(); 155 156 while (c.next()) { 157 // it may look like all we are doing here is collecting the removed elements (and 158 // counting the added elements), but the call to 'peek' is also crucial - it is 159 // ensuring that the selectedIndices bitset is correctly updated. 160 161 sm.startAtomic(); 162 final List<Integer> removed = c.getRemoved().stream() 163 .map(TablePositionBase::getRow) 164 .distinct() 165 .peek(sm.selectedIndices::clear) 166 .collect(Collectors.toList()); 167 168 final int addedSize = (int)c.getAddedSubList().stream() 169 .map(TablePositionBase::getRow) 170 .distinct() 171 .peek(sm.selectedIndices::set) 172 .count(); 173 sm.stopAtomic(); 174 175 final int to = c.getFrom() + addedSize; 176 177 if (c.wasReplaced()) { 178 sm.selectedIndices._nextReplace(c.getFrom(), to, removed); 179 } else if (c.wasRemoved()) { 180 sm.selectedIndices._nextRemove(c.getFrom(), removed); 181 } else if (c.wasAdded()) { 182 sm.selectedIndices._nextAdd(c.getFrom(), to); 183 } 184 } 185 c.reset(); 186 sm.selectedIndices.reset(); 187 188 if (sm.isAtomic()) { 189 return; 190 } 191 192 // Fix for RT-31577 - the selectedItems list was going to 193 // empty, but the selectedItem property was staying non-null. 194 // There is a unit test for this, so if a more elegant solution 195 // can be found in the future and this code removed, the unit 196 // test will fail if it isn't fixed elsewhere. 197 // makeAtomic toggle added to resolve RT-32618 198 if (sm.getSelectedItems().isEmpty() && sm.getSelectedItem() != null) { 199 sm.setSelectedItem(null); 200 } 201 202 sm.selectedIndices._endChange(); 203 } 204 205 // Given a listen of removed elements, we create the minimal number of changes by coalescing elements that are 206 // adjacent 207 static void reducingChange(MultipleSelectionModelBase<?>.SelectedIndicesList selectedIndices, List<Integer> removed) { 208 if (removed.isEmpty()) return; 209 210 int startPos = 0; 211 int endPos = 1; 212 boolean firedOnce = false; 213 while (endPos < removed.size()) { 214 if (removed.get(startPos) == removed.get(endPos) - 1) { 215 endPos++; 216 continue; 217 } 218 selectedIndices._nextRemove(selectedIndices.indexOf(removed.get(startPos)), removed.subList(startPos, endPos)); 219 startPos = endPos; 220 endPos = startPos + 1; 221 firedOnce = true; 222 } 223 224 if (!firedOnce) { 225 selectedIndices._nextRemove(selectedIndices.indexOf(removed.get(0)), removed); 226 } 227 } 228 }